diff --git a/pkg/parser/document_processing_apply_custom_substitutions_test.go b/pkg/parser/document_processing_apply_custom_substitutions_test.go index 685265a6..955a788b 100644 --- a/pkg/parser/document_processing_apply_custom_substitutions_test.go +++ b/pkg/parser/document_processing_apply_custom_substitutions_test.go @@ -14,19 +14,17 @@ var _ = Describe("custom substitutions", func() { Context("example blocks", func() { - Context("delimited blocks", func() { - - // testing custom substitutions on example blocks only, as - // other verbatim blocks (fenced, literal, source, passthrough) - // share the same implementation + // testing custom substitutions on example blocks only, as + // other verbatim blocks (fenced, literal, source, passthrough) + // share the same implementation - // also, see https://asciidoctor.org/docs/user-manual/#incremental-substitutions - // "When you set the subs attribute on a block, you automatically remove all of its default substitutions. - // For example, if you set subs on a literal block, and assign it a value of attributes, - // only attributes are substituted." + // also, see https://asciidoctor.org/docs/user-manual/#incremental-substitutions + // "When you set the subs attribute on a block, you automatically remove all of its default substitutions. + // For example, if you set subs on a literal block, and assign it a value of attributes, + // only attributes are substituted." - source := `:github-url: https://github.com - + source := `:github-url: https://github.com + [subs="$SUBS"] ==== a link to https://example.com[] <1> @@ -38,6 +36,7 @@ and on the + <1> a callout ` + Context("explicit substitutions", func() { It("should apply the default substitution", func() { s := strings.ReplaceAll(source, "[subs=\"$SUBS\"]\n", "") @@ -1173,7 +1172,6 @@ and on the + <1> a callout ` - It("should apply the default substitution", func() { s := strings.ReplaceAll(source, "[subs=\"$SUBS\"]\n", "") // remove the 'subs' attribute expected := types.DraftDocument{ @@ -2111,16 +2109,184 @@ _foo_ Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected)) }) + + It("should apply the '+quotes' substitution", func() { + s := strings.ReplaceAll(source, "$SUBS", "+quotes") // default + quotes + expected := types.DraftDocument{ + Attributes: types.Attributes{ + "github-url": "https://github.com", + }, + Elements: []interface{}{ + types.AttributeDeclaration{ + Name: "github-url", + Value: "https://github.com", + }, + types.BlankLine{}, + types.ListingBlock{ + Attributes: types.Attributes{ + types.AttrSubstitutions: "+quotes", + }, + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a link to https://example.com[] ", + }, + types.Callout{ + Ref: 1, + }, + }, + { + types.StringElement{ + Content: "and ", + }, + types.SpecialCharacter{ + Name: "<", + }, + types.StringElement{ + Content: "more text", + }, + types.SpecialCharacter{ + Name: ">", + }, + types.StringElement{ + Content: " on the +", + }, + }, + { + types.QuotedText{ + Kind: types.Bold, + Elements: []interface{}{ + types.StringElement{ + Content: "next", + }, + }, + }, + types.StringElement{ + Content: " lines with a link to {github-url}[]", + }, + }, + {}, + { + types.StringElement{ + Content: "* not a list item", + }, + }, + }, + }, + types.BlankLine{}, + types.CalloutListItem{ + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a callout", + }, + }, + }, + }, + }, + }, + }, + } + Expect(ParseDraftDocument(s)).To(MatchDraftDocument(expected)) + }) }) Context("paragraph blocks", func() { + source := `:github-url: https://github.com + +[listing] +[subs="$SUBS"] +a link to https://example.com[] <1> +and on the + +*next* lines with a link to {github-url}[] + +<1> a callout` + + It("should apply the default substitution", func() { + s := strings.ReplaceAll(source, "[subs=\"$SUBS\"]\n", "") + expected := types.DraftDocument{ + Attributes: types.Attributes{ + "github-url": "https://github.com", + }, + Elements: []interface{}{ + types.AttributeDeclaration{ + Name: "github-url", + Value: "https://github.com", + }, + types.BlankLine{}, + types.Paragraph{ + Attributes: types.Attributes{ + types.AttrBlockKind: types.Listing, + }, + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a link to https://example.com[] ", + }, + types.Callout{ + Ref: 1, + }, + }, + { + types.StringElement{ + Content: "and ", + }, + types.SpecialCharacter{ + Name: "<", + }, + types.StringElement{ + Content: "more text", + }, + types.SpecialCharacter{ + Name: ">", + }, + types.StringElement{ + Content: " on the +", + }, + }, + { + types.StringElement{ + Content: "*next* lines with a link to {github-url}[]", + }, + }, + }, + }, + types.BlankLine{}, + types.CalloutListItem{ + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a callout", + }, + }, + }, + }, + }, + }, + }, + } + Expect(ParseDraftDocument(s)).To(MatchDraftDocument(expected)) + }) + It("should apply the 'quotes' substitution", func() { - source := `[listing] -[subs=quotes] -some *listing* content` + s := strings.ReplaceAll(source, "$SUBS", "quotes") expected := types.DraftDocument{ + Attributes: types.Attributes{ + "github-url": "https://github.com", + }, Elements: []interface{}{ + types.AttributeDeclaration{ + Name: "github-url", + Value: "https://github.com", + }, + types.BlankLine{}, types.Paragraph{ Attributes: types.Attributes{ types.AttrBlockKind: types.Listing, @@ -2129,27 +2295,195 @@ some *listing* content` Lines: [][]interface{}{ { types.StringElement{ - Content: "some ", + Content: "a link to https://example.com[] <1>", }, - types.QuotedText{ // quote substitution applied + }, + { + types.StringElement{ + Content: "and on the +", + }, + }, + { + types.QuotedText{ Kind: types.Bold, Elements: []interface{}{ types.StringElement{ - Content: "listing", // + Content: "next", }, }, }, types.StringElement{ - Content: " content", + Content: " lines with a link to {github-url}[]", + }, + }, + }, + }, + types.BlankLine{}, + types.CalloutListItem{ + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a callout", + }, + }, }, }, }, }, }, } + Expect(ParseDraftDocument(s)).To(MatchDraftDocument(expected)) + }) - Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected)) + It("should apply the '+quotes' substitution", func() { + s := strings.ReplaceAll(source, "$SUBS", "+quotes") // ie, default + quotes + expected := types.DraftDocument{ + Attributes: types.Attributes{ + "github-url": "https://github.com", + }, + Elements: []interface{}{ + types.AttributeDeclaration{ + Name: "github-url", + Value: "https://github.com", + }, + types.BlankLine{}, + types.Paragraph{ + Attributes: types.Attributes{ + types.AttrBlockKind: types.Listing, + types.AttrSubstitutions: "+quotes", + }, + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a link to https://example.com[] ", + }, + types.Callout{ + Ref: 1, + }, + }, + { + types.StringElement{ + Content: "and ", + }, + types.SpecialCharacter{ + Name: "<", + }, + types.StringElement{ + Content: "more text", + }, + types.SpecialCharacter{ + Name: ">", + }, + types.StringElement{ + Content: " on the +", + }, + }, + { + types.QuotedText{ + Kind: types.Bold, + Elements: []interface{}{ + types.StringElement{ + Content: "next", + }, + }, + }, + types.StringElement{ + Content: " lines with a link to {github-url}[]", + }, + }, + }, + }, + types.BlankLine{}, + types.CalloutListItem{ + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a callout", + }, + }, + }, + }, + }, + }, + }, + } + Expect(ParseDraftDocument(s)).To(MatchDraftDocument(expected)) + }) + + It("should apply the 'macros,+quotes,-quotes' substitution", func() { + s := strings.ReplaceAll(source, "$SUBS", "macros,+quotes,-quotes") + expected := types.DraftDocument{ + Attributes: types.Attributes{ + "github-url": "https://github.com", + }, + Elements: []interface{}{ + types.AttributeDeclaration{ + Name: "github-url", + Value: "https://github.com", + }, + types.BlankLine{}, + types.Paragraph{ + Attributes: types.Attributes{ + types.AttrBlockKind: types.Listing, + types.AttrSubstitutions: "macros,+quotes,-quotes", // ie, "macros" only + }, + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a link to ", + }, + types.InlineLink{ + Location: types.Location{ + Scheme: "https://", + Path: []interface{}{ + types.StringElement{ + Content: "example.com", + }, + }, + }, + }, + types.StringElement{ + Content: " <1>", + }, + }, + { + types.StringElement{ + Content: "and on the +", + }, + }, + { + types.StringElement{ + Content: "*next* lines with a link to {github-url}[]", + }, + }, + }, + }, + types.BlankLine{}, + types.CalloutListItem{ + Ref: 1, + Elements: []interface{}{ + types.Paragraph{ + Lines: [][]interface{}{ + { + types.StringElement{ + Content: "a callout", + }, + }, + }, + }, + }, + }, + }, + } + Expect(ParseDraftDocument(s)).To(MatchDraftDocument(expected)) }) }) + }) }) diff --git a/pkg/parser/document_processing_apply_substitutions.go b/pkg/parser/document_processing_apply_substitutions.go index 590a9c3a..249c0d65 100644 --- a/pkg/parser/document_processing_apply_substitutions.go +++ b/pkg/parser/document_processing_apply_substitutions.go @@ -116,15 +116,28 @@ func applySubstitutions(elements []interface{}, attrs types.AttributesWithOverri // Delimited Block substitutions // ---------------------------------------------------------------------------- +var substitutions = map[string]elementsSubstitution{ + "inline_passthrough": substituteInlinePassthrough, + "callouts": substituteCallouts, + "specialcharacters": substituteSpecialCharacters, + "specialchars": substituteSpecialCharacters, + "quotes": substituteQuotedTexts, + "attributes": substituteAttributes, + "replacements": substituteReplacements, + "macros": substituteInlineMacros, + "post_replacements": substitutePostReplacements, + "none": substituteNone, +} + // blocks of blocks -var defaultSubstitutionsForBlockElements = []elementsSubstitution{ - substituteInlinePassthrough, - substituteSpecialCharacters, - substituteQuotedTexts, - substituteAttributes, - substituteReplacements, - substituteInlineMacros, - substitutePostReplacements, +var defaultSubstitutionsForBlockElements = []string{ + "inline_passthrough", + "specialcharacters", + "quotes", + "attributes", + "replacements", + "macros", + "post_replacements", } var defaultExampleBlockSubstitutions = defaultSubstitutionsForBlockElements var defaultQuoteBlockSubstitutions = defaultSubstitutionsForBlockElements @@ -133,17 +146,17 @@ var defaultVerseBlockSubstitutions = defaultSubstitutionsForBlockElements // eve var defaultParagraphSubstitutions = defaultSubstitutionsForBlockElements // even though it's a block of lines, not a block of blocks // blocks of lines -var defaultSubstitutionsForBlockLines = []elementsSubstitution{ - substituteCallouts, - substituteSpecialCharacters, +var defaultSubstitutionsForBlockLines = []string{ + "callouts", // must be executed before "specialcharacters" + "specialcharacters", } var defaultFencedBlockSubstitutions = defaultSubstitutionsForBlockLines var defaultListingBlockSubstitutions = defaultSubstitutionsForBlockLines var defaultLiteralBlockSubstitutions = defaultSubstitutionsForBlockLines // other blocks -var defaultPassthroughBlockSubstitutions = []elementsSubstitution{} -var defaultCommentBlockSubstitutions = []elementsSubstitution{substituteNone} +var defaultPassthroughBlockSubstitutions = []string{} +var defaultCommentBlockSubstitutions = []string{"none"} func applySubstitutionsOnMarkdownQuoteBlock(b types.MarkdownQuoteBlock, attrs types.AttributesWithOverrides) (types.MarkdownQuoteBlock, error) { funcs := []elementsSubstitution{ @@ -197,74 +210,107 @@ func extractMarkdownQuoteAttribution(lines [][]interface{}) ([][]interface{}, st return lines, "" } +type funcs []string + +func (f funcs) append(others ...string) funcs { + return append(f, others...) +} + +func (f funcs) prepend(other string) funcs { + return append(funcs{other}, f...) +} + +func (f funcs) remove(other string) funcs { + for i, s := range f { + if s == other { + return append(f[:i], f[i+1:]...) + } + } + // unchanged + return f +} + func substitutionsFor(block types.BlockWithSubstitution) ([]elementsSubstitution, error) { - funcs := []elementsSubstitution{} + subs := funcs{} for _, s := range strings.Split(block.SubstitutionsToApply(), ",") { switch s { case "": - defaultSubs, err := defaultSubstitutionsFor(block) - if err != nil { - return nil, err - } - funcs = append(funcs, defaultSubs...) + subs = subs.append(defaultSubstitutionsFor(block)...) case "normal": - funcs = append(funcs, - substituteInlinePassthrough, - substituteSpecialCharacters, - substituteQuotedTexts, - substituteAttributes, - substituteReplacements, - substituteInlineMacros, - substitutePostReplacements, + subs = subs.append( + "inline_passthrough", + "specialcharacters", + "quotes", + "attributes", + "replacements", + "macros", + "post_replacements", ) - case "callouts": - funcs = append(funcs, substituteCallouts) - case "specialcharacters", "specialchars": - funcs = append(funcs, substituteSpecialCharacters) - case "quotes": - funcs = append(funcs, substituteQuotedTexts) - case "attributes": - funcs = append(funcs, substituteAttributes) - case "macros": - funcs = append(funcs, substituteInlineMacros) - case "replacements": - funcs = append(funcs, substituteReplacements) - case "post_replacements": - funcs = append(funcs, substitutePostReplacements) - case "none": - funcs = append(funcs, substituteNone) + case "callouts", "specialcharacters", "specialchars", "quotes", "attributes", "macros", "replacements", "post_replacements", "none": + subs = subs.append(s) + case "+callouts", "+specialcharacters", "+specialchars", "+quotes", "+attributes", "+macros", "+replacements", "+post_replacements", "+none": + if len(subs) == 0 { + subs = subs.append(defaultSubstitutionsFor(block)...) + } + subs = subs.append(strings.ReplaceAll(s, "+", "")) + case "callouts+", "specialcharacters+", "specialchars+", "quotes+", "attributes+", "macros+", "replacements+", "post_replacements+", "none+": + if len(subs) == 0 { + subs = subs.append(defaultSubstitutionsFor(block)...) + } + subs = subs.prepend(strings.ReplaceAll(s, "+", "")) + case "-callouts", "-specialcharacters", "-specialchars", "-quotes", "-attributes", "-macros", "-replacements", "-post_replacements", "-none": + if len(subs) == 0 { + subs = subs.append(defaultSubstitutionsFor(block)...) + } + subs = subs.remove(strings.ReplaceAll(s, "-", "")) default: return nil, fmt.Errorf("unsupported substitution: '%s", s) } } - funcs = append(funcs, splitLines) - return funcs, nil + result := make([]elementsSubstitution, 0, len(subs)+1) + // result = append(result, substituteInlinePassthrough) + for _, s := range subs { + if f, exists := substitutions[s]; exists { + result = append(result, f) + } + } + result = append(result, splitLines) + return result, nil } -func defaultSubstitutionsFor(block interface{}) ([]elementsSubstitution, error) { - switch block.(type) { +func defaultSubstitutionsFor(block interface{}) []string { + switch b := block.(type) { case types.ExampleBlock: - return defaultExampleBlockSubstitutions, nil + return defaultExampleBlockSubstitutions case types.QuoteBlock: - return defaultQuoteBlockSubstitutions, nil + return defaultQuoteBlockSubstitutions case types.SidebarBlock: - return defaultSidebarBlockSubstitutions, nil + return defaultSidebarBlockSubstitutions case types.FencedBlock: - return defaultFencedBlockSubstitutions, nil + return defaultFencedBlockSubstitutions case types.ListingBlock: - return defaultListingBlockSubstitutions, nil + return defaultListingBlockSubstitutions case types.VerseBlock: - return defaultVerseBlockSubstitutions, nil + return defaultVerseBlockSubstitutions case types.LiteralBlock: - return defaultLiteralBlockSubstitutions, nil + return defaultLiteralBlockSubstitutions case types.PassthroughBlock: - return defaultPassthroughBlockSubstitutions, nil + return defaultPassthroughBlockSubstitutions case types.CommentBlock: - return defaultCommentBlockSubstitutions, nil + return defaultCommentBlockSubstitutions case types.Paragraph: - return defaultParagraphSubstitutions, nil + // support for masquerading + // treat 'Listing' paragraphs as verbatim blocks + if k, exists := b.Attributes[types.AttrBlockKind]; exists { + switch k { + case types.Listing: + return defaultListingBlockSubstitutions + } + } + return defaultParagraphSubstitutions default: - return nil, fmt.Errorf("unsupported type of block: '%T'", block) + log.Warnf("unsupported substitutions on block of type: '%T'", block) + return nil } } diff --git a/pkg/parser/document_processing_apply_attribute_substitutions_test.go b/pkg/parser/document_processing_apply_substitutions_test.go similarity index 89% rename from pkg/parser/document_processing_apply_attribute_substitutions_test.go rename to pkg/parser/document_processing_apply_substitutions_test.go index 98571e44..0817a056 100644 --- a/pkg/parser/document_processing_apply_attribute_substitutions_test.go +++ b/pkg/parser/document_processing_apply_substitutions_test.go @@ -7,7 +7,7 @@ import ( . "github.com/onsi/gomega" //nolint golint ) -var _ = Describe("document attribute subsititutions", func() { +var _ = Describe("document attribute substitutions", func() { It("should replace with new StringElement on first position", func() { // given @@ -632,3 +632,69 @@ var _ = Describe("document attribute subsititutions", func() { }) }) }) + +var _ = Describe("substitution funcs", func() { + + It("should append sub", func() { + // given" + f := funcs{"attributes", "quotes"} + // when + f = f.append("macros") + // then + Expect(f).To(Equal(funcs{"attributes", "quotes", "macros"})) + }) + + It("should append subs", func() { + // given" + f := funcs{"attributes"} + // when + f = f.append("quotes", "macros") + // then + Expect(f).To(Equal(funcs{"attributes", "quotes", "macros"})) + }) + + It("should prepend sub", func() { + // given" + f := funcs{"attributes", "quotes"} + // when + f = f.prepend("macros") + // then + Expect(f).To(Equal(funcs{"macros", "attributes", "quotes"})) + }) + + It("should remove first sub", func() { + // given" + f := funcs{"attributes", "quotes", "macros"} + // when + f = f.remove("attributes") + // then + Expect(f).To(Equal(funcs{"quotes", "macros"})) + }) + + It("should remove middle sub", func() { + // given" + f := funcs{"attributes", "quotes", "macros"} + // when + f = f.remove("quotes") + // then + Expect(f).To(Equal(funcs{"attributes", "macros"})) + }) + + It("should remove last sub", func() { + // given" + f := funcs{"attributes", "quotes", "macros"} + // when + f = f.remove("macros") + // then + Expect(f).To(Equal(funcs{"attributes", "quotes"})) + }) + + It("should remove non existinge", func() { + // given" + f := funcs{"attributes", "quotes", "macros"} + // when + f = f.remove("other") + // then + Expect(f).To(Equal(funcs{"attributes", "quotes", "macros"})) + }) +}) diff --git a/pkg/types/types.go b/pkg/types/types.go index 7d5975b9..6eddb714 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -1178,13 +1178,6 @@ func (p Paragraph) SubstitutionsToApply() string { if subs, exists := p.Attributes.GetAsString(AttrSubstitutions); exists { return subs } - // treat 'Listing' paragraphs as verbatim blocks - if k, exists := p.Attributes[AttrBlockKind]; exists { - switch k { - case Listing: - return "callouts,specialcharacters" - } - } return "" }