Skip to content

Commit

Permalink
feat(parser): support attribute substitution in attribute declaration (
Browse files Browse the repository at this point in the history
…#801)

Fixes #800

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon authored Nov 11, 2020
1 parent 62ddfce commit b0e80f5
Show file tree
Hide file tree
Showing 13 changed files with 3,748 additions and 3,548 deletions.
75 changes: 75 additions & 0 deletions pkg/parser/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image with empty alt and extra whitespace", func() {
source := "image::foo.png[ ]"
expected := types.DraftDocument{
Expand Down Expand Up @@ -78,6 +79,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image with simple double quoted alt", func() {
source := "image::foo.png[\"Quoted, Here\"]"
expected := types.DraftDocument{
Expand All @@ -96,6 +98,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image with double quoted alt and embedded quotes", func() {
source := `image::foo.png[ "The Ascii\"Doctor\" Is In" ]`
expected := types.DraftDocument{
Expand All @@ -114,6 +117,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image with double quoted alt extra whitespace", func() {
source := `image::foo.png[ "This \Backslash 2Spaced End Space " ]`
expected := types.DraftDocument{
Expand All @@ -132,6 +136,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image with single quoted alt and embedded quotes", func() {
source := "image::foo.png[ 'It\\'s It!' ]"
expected := types.DraftDocument{
Expand All @@ -150,6 +155,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image with single quoted alt extra whitespace", func() {
source := "image::foo.png[ 'This \\Backslash 2Spaced End Space ' ]"
expected := types.DraftDocument{
Expand All @@ -170,6 +176,7 @@ var _ = Describe("attributes", func() {
Expect(err).NotTo(HaveOccurred())
Expect(result).To(MatchDraftDocument(expected))
})

It("block image alt and named pair", func() {
source := `image::foo.png["Quoted, Here", height=100]`
expected := types.DraftDocument{
Expand All @@ -191,6 +198,7 @@ var _ = Describe("attributes", func() {
Expect(err).NotTo(HaveOccurred())
Expect(result).To(MatchDraftDocument(expected))
})

It("block image alt, width, height, and named pair", func() {
source := "image::foo.png[\"Quoted, Here\", 1, 2, height=100]"
expected := types.DraftDocument{
Expand All @@ -211,6 +219,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image alt, width, height, and named pair (spacing)", func() {
source := "image::foo.png[\"Quoted, Here\", 1, 2, height=100, test1=123 ,test2 = second test ]"
expected := types.DraftDocument{
Expand All @@ -233,6 +242,7 @@ var _ = Describe("attributes", func() {
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("block image alt, width, height, and named pair embedded quote", func() {
source := "image::foo.png[\"Quoted, Here\", 1, 2, height=100, test1=123 ,test2 = second \"test\" ]"
expected := types.DraftDocument{
Expand All @@ -256,4 +266,69 @@ var _ = Describe("attributes", func() {
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})
})

Context("recursive attributes", func() {

It("should substitute an attribute in another attribute", func() {
source := `:def: foo
:abc: {def}bar
{abc}`
expected := types.DraftDocument{
Attributes: types.Attributes{
"def": "foo",
"abc": "foobar", // resolved
},
Elements: []interface{}{
types.AttributeDeclaration{
Name: "def",
Value: "foo",
},
types.AttributeDeclaration{
Name: "abc",
Value: "foobar", // resolved
},
types.BlankLine{},
types.Paragraph{
Lines: [][]interface{}{
{
types.StringElement{
Content: "foobar",
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("should not substitute an attribute in another attribute when not defined", func() {
source := `:abc: {def}bar
{abc}`
expected := types.DraftDocument{
Attributes: types.Attributes{
"abc": "{def}bar", // unresolved
},
Elements: []interface{}{
types.AttributeDeclaration{
Name: "abc",
Value: "{def}bar", // unresolved
},
types.BlankLine{},
types.Paragraph{
Lines: [][]interface{}{
{
types.StringElement{
Content: "{def}bar",
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})
})
})
16 changes: 8 additions & 8 deletions pkg/parser/document_attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ v1.0:`
:Auth0r: Xavier`
expected := types.Document{
Attributes: types.Attributes{
"a": "",
"a": nil,
"author": "Xavier",
"_author": "Xavier",
"Author": "Xavier",
Expand All @@ -1065,10 +1065,10 @@ v1.0:`
a paragraph`
expected := types.Document{
Attributes: types.Attributes{
"toc": "",
"toc": nil,
"date": "2017-01-01",
"author": "Xavier",
types.DocumentAttrHardBreaks: "",
types.DocumentAttrHardBreaks: nil,
},
Elements: []interface{}{
types.TableOfContentsPlaceHolder{},
Expand All @@ -1090,7 +1090,7 @@ a paragraph`
a paragraph`
expected := types.Document{
Attributes: types.Attributes{
"toc": "",
"toc": nil,
"date": "2017-01-01",
"author": "Xavier",
},
Expand All @@ -1117,10 +1117,10 @@ a paragraph`
a paragraph`
expected := types.Document{
Attributes: types.Attributes{
"toc": "",
"toc": nil,
"date": "2017-01-01",
"author": "Xavier",
"hardbreaks": "",
"hardbreaks": nil,
},
Elements: []interface{}{
types.TableOfContentsPlaceHolder{},
Expand All @@ -1142,7 +1142,7 @@ a paragraph`
:author: Xavier`
expected := types.Document{
Attributes: types.Attributes{
"toc": "",
"toc": nil,
"date": "2017-01-01",
"author": "Xavier",
},
Expand Down Expand Up @@ -1216,7 +1216,7 @@ This journey continues`
}
expected := types.Document{
Attributes: types.Attributes{
"toc": "",
"toc": nil,
"keywords": "documentation, team, obstacles, journey, victory",
types.AttrAuthors: []types.DocumentAuthor{
{
Expand Down
37 changes: 20 additions & 17 deletions pkg/parser/document_processing_apply_substitutions.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,42 +607,45 @@ func applyAttributeSubstitutionsOnLines(lines [][]interface{}, attrs types.Attri
func applyAttributeSubstitutionsOnElement(element interface{}, attrs types.AttributesWithOverrides) (interface{}, error) {
var err error
switch e := element.(type) {
case types.AttributeDeclaration:
attrs.Set(e.Name, e.Value)
return e, nil
case types.AttributeReset:
attrs.Set(e.Name, nil) // This allows us to test for a reset vs. undefined.
return e, nil
case types.AttributeSubstitution:
if value, ok := attrs.GetAsString(e.Name); ok {
return types.StringElement{
element = types.StringElement{
Content: value,
}, nil
}
break
}
log.Warnf("unable to find attribute '%s'", e.Name)
return types.StringElement{
element = types.StringElement{
Content: "{" + e.Name + "}",
}, nil
}
case types.CounterSubstitution:
return applyCounterSubstitution(e, attrs)
if element, err = applyCounterSubstitution(e, attrs); err != nil {
return nil, err
}
case types.WithElementsToSubstitute:
elmts, err := applyAttributeSubstitutionsOnElements(e.ElementsToSubstitute(), attrs)
if err != nil {
return e, err
return nil, err
}
return e.ReplaceElements(elmts), nil
element = e.ReplaceElements(elmts)
case types.WithLineSubstitution:
lines, err := applyAttributeSubstitutionsOnLines(e.LinesToSubstitute(), attrs)
if err != nil {
return e, err
return nil, err
}
return e.SubstituteLines(lines), nil
element = e.SubstituteLines(lines)
case types.ContinuedListItemElement:
e.Element, err = applyAttributeSubstitutionsOnElement(e.Element, attrs)
return e, err
default:
return e, nil
if e.Element, err = applyAttributeSubstitutionsOnElement(e.Element, attrs); err != nil {
return nil, err
}
}
// also, retain the attribute declaration value (if applicable)
if e, ok := element.(types.AttributeDeclaration); ok {
attrs.Set(e.Name, e.Value)
}
return element, nil
}

// applyCounterSubstitutions is called by applyAttributeSubstitutionsOnElement. Unless there is an error with
Expand Down
58 changes: 58 additions & 0 deletions pkg/parser/document_processing_apply_substitutions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,64 @@ var _ = Describe("document substitutions", func() {
}))
})
})

Context("recursive attributes", func() {

It("should substitute an attribute in another attribute", func() {
elements := []interface{}{
types.AttributeDeclaration{
Name: "def",
Value: "foo",
},
types.AttributeDeclaration{
Name: "abc",
Value: []interface{}{
types.AttributeSubstitution{
Name: "def",
},
types.StringElement{
Content: "bar",
},
},
},
types.Paragraph{
Lines: [][]interface{}{
{
types.AttributeSubstitution{
Name: "abc",
},
},
},
},
}
result, err := applyAttributeSubstitutionsOnElements(elements, types.AttributesWithOverrides{
Content: map[string]interface{}{},
Overrides: map[string]string{},
Counters: map[string]interface{}{},
})
Expect(err).To(Not(HaveOccurred()))
Expect(result).To(Equal([]interface{}{ // at this stage, AttributeDeclaration and AttributeReset are still present
types.AttributeDeclaration{
Name: "def",
Value: "foo",
},
types.AttributeDeclaration{
Name: "abc",
Value: "foobar",
},
types.Paragraph{
Lines: [][]interface{}{
{
types.StringElement{
Content: "foobar",
},
},
},
},
}))
})

})
})

var _ = Describe("substitution funcs", func() {
Expand Down
8 changes: 4 additions & 4 deletions pkg/parser/document_processing_include_toc_placeholder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,21 @@ import (
// IncludeTableOfContentsPlaceHolder includes a `TableOfContentsPlaceHolder` block in the document
// if the `toc` attribute is present
func includeTableOfContentsPlaceHolder(doc types.Document) types.Document {
if t, found := doc.Attributes.GetAsString(types.AttrTableOfContents); found {
if t, found := doc.Attributes[types.AttrTableOfContents]; found {
doc = doInsertTableOfContentsPlaceHolder(doc, t)
}
return doc
}

func doInsertTableOfContentsPlaceHolder(doc types.Document, location string) types.Document {
func doInsertTableOfContentsPlaceHolder(doc types.Document, location interface{}) types.Document {
log.Debugf("inserting a table of contents at location `%s`", location)
// insert a TableOfContentsPlaceHolder element if `toc` value is:
// - "auto" (or empty)
// - "auto" (or `nil`)
// - "preamble"
log.Debugf("inserting ToC macro with placement: '%s'", location)
toc := types.TableOfContentsPlaceHolder{}
switch location {
case "", "auto":
case "auto", nil:
// insert TableOfContentsPlaceHolder at first position (in section '0' if it exists)
if header, ok := doc.Header(); ok {
header.Elements = append([]interface{}{toc}, header.Elements...)
Expand Down
Loading

0 comments on commit b0e80f5

Please sign in to comment.