Skip to content

Commit

Permalink
+links substitution
Browse files Browse the repository at this point in the history
  • Loading branch information
g-pavlov committed Oct 9, 2020
1 parent 8ee5bf3 commit 6d47817
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 8 deletions.
6 changes: 4 additions & 2 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,9 @@ type LocalityDomainValue struct {
// This applies only to markdown for links and images.
// - A fixed string that will replace the whole original link
// destination.
LinkSubstitutes Substitutes
// The keys in the substitution map are matched against documents
// links as exact string matches.
LinkSubstitutes Substitutes `yaml:"linkSubstitutes,omitempty"`
// DownloadSubstitutes is an optional map of resource names in this
// locality domain and their substitutions. Use it to override the
// default downloads naming:
Expand All @@ -197,7 +199,7 @@ type LocalityDomainValue struct {
// - $path: the original path of the resource in this domain (may be empty)
// - $uuid: the identifier generated for the downloaded resource
// Example expression: $name-$uuid
DownloadSubstitutes Substitutes
DownloadSubstitutes Substitutes `yaml:"downloadSubstitutes,omitempty"`
}

// Substitutes is map of ...
Expand Down
2 changes: 1 addition & 1 deletion pkg/hugo/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (f *Processor) Process(documentBlob []byte, node *api.Node) ([]byte, error)
if err != nil {
return nil, err
}
if documentBlob, err = mdutil.TransformLinks(contentBytes, func(destination []byte) ([]byte, error) {
if documentBlob, err = mdutil.UpdateLinkRefDestinations(contentBytes, func(destination []byte) ([]byte, error) {
return f.rewriteDestination(destination, node.Name)
}); err != nil {
return nil, err
Expand Down
67 changes: 63 additions & 4 deletions pkg/markdown/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,68 @@ import (
)

// OnLink is a callback function invoked on each link
// by mardown#TransformLinks
// by mardown#UpdateLinkRefDestinations
type OnLink func(destination []byte) ([]byte, error)

const (
extensions = parser.CommonExtensions | parser.AutoHeadingIDs
)

//TransformLinks transforms document links destinations, delegating
// the transformation to a callback invoked on each link
func removeDestination(node ast.Node) {
children := node.GetParent().GetChildren()
idx := -1
for i, p := range children {
if p == node {
idx = i
break
}
}
if idx > -1 {
if link, ok := node.(*ast.Link); ok {
textNode := link.Children[0]
if textNode != nil && len(textNode.AsLeaf().Literal) > 0 {
// if prev sibling is text node, add this link text to it
if idx > 0 {
_n := children[idx-1]
if t, ok := _n.(*ast.Text); ok {
t.Literal = append(t.Literal, textNode.AsLeaf().Literal...)
children = removeNode(children, idx)
node.GetParent().SetChildren(children)
return
}
}
// if next sibling is text node, add this link text to it
if idx < len(children)-1 {
_n := children[idx+1]
if t, ok := _n.(*ast.Text); ok {
t.Literal = append(t.Literal, textNode.AsLeaf().Literal...)
children = removeNode(children, idx)
node.GetParent().SetChildren(children)
return
}
}
node.GetParent().AsContainer().Children[idx] = textNode
return
}
}
if _, ok := node.(*ast.Image); ok {
children = removeNode(children, idx)
node.GetParent().SetChildren(children)
return
}
}
}
func removeNode(n []ast.Node, i int) []ast.Node {
return append(n[:i], n[i+1:]...)
}

// UpdateLinkRefDestinations changes document links destinations, consulting
// with callback on the destination to use on each link or image in document.
// If a callback returns "" for a destination, this is interpreted as
// request to remove the link destination and leave only the link text or in
// case it's an image - to remvoe it completely.
// TODO: failfast vs fault tolerance support
func TransformLinks(documentBlob []byte, callback OnLink) ([]byte, error) {
func UpdateLinkRefDestinations(documentBlob []byte, callback OnLink) ([]byte, error) {
mdParser := parser.NewWithExtensions(extensions)
document := markdown.Parse(documentBlob, mdParser)
ast.WalkFunc(document, func(_node ast.Node, entering bool) ast.WalkStatus {
Expand All @@ -38,13 +89,21 @@ func TransformLinks(documentBlob []byte, callback OnLink) ([]byte, error) {
if destination, err = callback(l.Destination); err != nil {
return ast.Terminate
}
if destination == nil {
removeDestination(l)
return ast.GoToNext
}
l.Destination = destination
return ast.GoToNext
}
if l, ok := _node.(*ast.Image); ok {
if destination, err = callback(l.Destination); err != nil {
return ast.Terminate
}
if destination == nil {
removeDestination(l)
return ast.GoToNext
}
l.Destination = destination
return ast.GoToNext
}
Expand Down
54 changes: 54 additions & 0 deletions pkg/markdown/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ package markdown

import (
"testing"

"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/parser"
"github.com/stretchr/testify/assert"
)

func TestStripFrontMatter(t *testing.T) {
Expand Down Expand Up @@ -73,3 +78,52 @@ title: Core Components
})
}
}

func TestRemoveLink(t *testing.T) {
testCases := []struct {
in string
wantLinksCount int
wantImgsCount int
wantTexts []string
}{
{
`A [a0](b.md) [a1](b.md "c") ![](a.png) B`,
0, 0,
[]string{"A a0", " a1", " ", " B"},
},
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
mdParser := parser.NewWithExtensions(extensions)
document := markdown.Parse([]byte(tc.in), mdParser)
ast.WalkFunc(document, func(node ast.Node, entering bool) ast.WalkStatus {
if l, ok := node.(*ast.Link); ok {
removeDestination(l)
}
if l, ok := node.(*ast.Image); ok {
removeDestination(l)
}
return ast.GoToNext
})
var (
links, images int
texts = make([]string, 0)
)
ast.WalkFunc(document, func(node ast.Node, entering bool) ast.WalkStatus {
if _, ok := node.(*ast.Link); ok {
links++
}
if _, ok := node.(*ast.Image); ok {
images++
}
if t, ok := node.(*ast.Text); ok {
texts = append(texts, string(t.Literal))
}
return ast.GoToNext
})
assert.Equal(t, tc.wantLinksCount, links)
assert.Equal(t, tc.wantLinksCount, images)
assert.Equal(t, tc.wantTexts, texts)
})
}
}
9 changes: 8 additions & 1 deletion pkg/reactor/content_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (c *NodeContentProcessor) ReconcileLinks(ctx context.Context, node *api.Nod

func (c *NodeContentProcessor) reconcileMDLinks(ctx context.Context, docNode *api.Node, contentBytes []byte, contentSourcePath string) ([]byte, error) {
var errors *multierror.Error
contentBytes, _ = markdown.TransformLinks(contentBytes, func(destination []byte) ([]byte, error) {
contentBytes, _ = markdown.UpdateLinkRefDestinations(contentBytes, func(destination []byte) ([]byte, error) {
var (
_destination string
downloadLink string
Expand All @@ -111,6 +111,9 @@ func (c *NodeContentProcessor) reconcileMDLinks(ctx context.Context, docNode *ap
if len(downloadLink) > 0 {
c.schedule(ctx, downloadLink, resourceName, contentSourcePath)
}
if len(_destination) < 1 {
return nil, nil
}
return []byte(_destination), nil
})
if c.failFast && errors != nil && errors.Len() > 0 {
Expand Down Expand Up @@ -171,6 +174,10 @@ func (c *NodeContentProcessor) processLink(ctx context.Context, node *api.Node,
if node != nil {
ld = resolveLocalityDomain(node, c.localityDomain)
}
if absLink = ld.SubstituteLink(absLink); len(absLink) == 0 {
// substitution is a request to remove this link
return "", "", "", nil
}
absLink, inLD := ld.MatchPathInLocality(absLink, c.ResourceHandlers)
if _a != absLink {
klog.V(6).Infof("[%s] Link converted %s -> %s\n", contentSourcePath, _a, absLink)
Expand Down
15 changes: 15 additions & 0 deletions pkg/reactor/localitydomain.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,19 @@ func (ld localityDomain) PathInLocality(link string, rhs resourcehandlers.Regist
return false
}

func (ld localityDomain) SubstituteLink(link string) string {
if len(link) > 0 {
for _, d := range ld {
if len(d.LinkSubstitutes) > 0 {
if s, ok := d.LinkSubstitutes[link]; ok {
return s
}
}
}
}
return link
}

// setLocalityDomainForNode visits all content selectors in the node and its
// descendants to build a localityDomain
func localityDomainFromNode(node *api.Node, rhs resourcehandlers.Registry) (localityDomain, error) {
Expand Down Expand Up @@ -299,6 +312,7 @@ func merge(a, b *localityDomainValue) *localityDomainValue {
_e[k] = v
}
}
a.LinkSubstitutes = _e
}
if len(b.DownloadSubstitutes) > 0 {
_e := a.DownloadSubstitutes
Expand All @@ -309,6 +323,7 @@ func merge(a, b *localityDomainValue) *localityDomainValue {
_e[k] = v
}
}
a.DownloadSubstitutes = _e
}
return a
}
40 changes: 40 additions & 0 deletions pkg/reactor/localitydomain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/gardener/docforge/pkg/resourcehandlers"
"github.com/gardener/docforge/pkg/resourcehandlers/github"
"github.com/stretchr/testify/assert"

"github.com/gardener/docforge/pkg/api"
)
Expand Down Expand Up @@ -227,3 +228,42 @@ func Test_SetLocalityDomainForNode(t *testing.T) {
})
}
}

func Test_SubstituteLink(t *testing.T) {
testCases := []struct {
link string
substitutes map[string]string
want string
}{
{
"abc",
map[string]string{
"abc": "cda",
},
"cda",
},
{
"abc",
map[string]string{},
"abc",
},
{
"",
map[string]string{
"abc": "cda",
},
"",
},
}
for _, tc := range testCases {
t.Run("", func(t *testing.T) {
ld := localityDomain{
"": &localityDomainValue{
LinkSubstitutes: tc.substitutes,
},
}
got := ld.SubstituteLink(tc.link)
assert.Equal(t, tc.want, got)
})
}
}

0 comments on commit 6d47817

Please sign in to comment.