Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(parser): support Markdown style quote blocks #563

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion make/go.mk
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ generate: install-pigeon
generate-optimized: install-pigeon
@echo "generating the parser (optimized)..."
@pigeon -optimize-parser \
-alternate-entrypoints AsciidocDocument,VerbatimDocument,TextDocument,DocumentBlock,FileLocation,IncludedFileLine,InlineLinks,LabeledListItemTerm,VerbatimContent,NormalBlockContent \
-alternate-entrypoints AsciidocDocument,VerbatimDocument,TextDocument,DocumentBlock,FileLocation,IncludedFileLine,InlineLinks,LabeledListItemTerm,NormalBlockContent,VerseBlockContent,MarkdownQuoteBlockAttribution \
-o ./pkg/parser/parser.go ./pkg/parser/parser.peg

.PHONY: build
Expand Down
185 changes: 185 additions & 0 deletions pkg/parser/delimited_block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,191 @@ foo
})
})

Context("markdown-style quote blocks", func() {

It("with single marker without author", func() {
source := `> some text
on *multiple lines*`

expected := types.DraftDocument{
Blocks: []interface{}{
types.DelimitedBlock{
Attributes: types.ElementAttributes{},
Kind: types.MarkdownQuote,
Elements: []interface{}{
types.Paragraph{
Attributes: types.ElementAttributes{},
Lines: [][]interface{}{
{
types.StringElement{
Content: "some text",
},
},
{
types.StringElement{
Content: "on ",
},
types.QuotedText{
Kind: types.Bold,
Elements: []interface{}{
types.StringElement{
Content: "multiple lines",
},
},
},
},
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(Equal(expected))
})

It("with marker on each line without author", func() {
source := `> some text
> on *multiple lines*`

expected := types.DraftDocument{
Blocks: []interface{}{
types.DelimitedBlock{
Attributes: types.ElementAttributes{},
Kind: types.MarkdownQuote,
Elements: []interface{}{
types.Paragraph{
Attributes: types.ElementAttributes{},
Lines: [][]interface{}{
{
types.StringElement{
Content: "some text",
},
},
{
types.StringElement{
Content: "on ",
},
types.QuotedText{
Kind: types.Bold,
Elements: []interface{}{
types.StringElement{
Content: "multiple lines",
},
},
},
},
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(Equal(expected))
})

It("with marker on each line with author", func() {
source := `> some text
> on *multiple lines*
> -- John Doe`
expected := types.DraftDocument{
Blocks: []interface{}{
types.DelimitedBlock{
Attributes: types.ElementAttributes{
types.AttrQuoteAuthor: "John Doe",
},
Kind: types.MarkdownQuote,
Elements: []interface{}{
types.Paragraph{
Attributes: types.ElementAttributes{},
Lines: [][]interface{}{
{
types.StringElement{
Content: "some text",
},
},
{
types.StringElement{
Content: "on ",
},
types.QuotedText{
Kind: types.Bold,
Elements: []interface{}{
types.StringElement{
Content: "multiple lines",
},
},
},
},
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(Equal(expected))
})

It("with marker on each line with author and title", func() {
source := `.title
> some text
> on *multiple lines*
> -- John Doe`
expected := types.DraftDocument{
Blocks: []interface{}{
types.DelimitedBlock{
Attributes: types.ElementAttributes{
types.AttrTitle: "title",
types.AttrQuoteAuthor: "John Doe",
},
Kind: types.MarkdownQuote,
Elements: []interface{}{
types.Paragraph{
Attributes: types.ElementAttributes{},
Lines: [][]interface{}{
{
types.StringElement{
Content: "some text",
},
},
{
types.StringElement{
Content: "on ",
},
types.QuotedText{
Kind: types.Bold,
Elements: []interface{}{
types.StringElement{
Content: "multiple lines",
},
},
},
},
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(Equal(expected))
})

It("with with author only", func() {
source := `> -- John Doe`
expected := types.DraftDocument{
Blocks: []interface{}{
types.DelimitedBlock{
Attributes: types.ElementAttributes{
types.AttrQuoteAuthor: "John Doe",
},
Kind: types.MarkdownQuote,
Elements: []interface{}{},
},
},
}
Expect(ParseDraftDocument(source)).To(Equal(expected))
})
})

Context("verse blocks", func() {

It("single line verse with author and title", func() {
Expand Down
51 changes: 40 additions & 11 deletions pkg/parser/document_preprocessing.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,11 @@ func processFileInclusions(elements []interface{}, attrs types.DocumentAttribute
}
}
// next, parse the elements with the grammar rule that corresponds to the delimited block substitutions (based on its type)
elmts, err = parseDelimitedBlockContent(config.Filename, e.Kind, elmts, options...)
extraAttrs, elmts, err := parseDelimitedBlockContent(config.Filename, e.Kind, elmts, options...)
if err != nil {
return nil, err
}
e.Attributes.AddAll(extraAttrs)
result = append(result, types.DelimitedBlock{
Attributes: e.Attributes,
Kind: e.Kind,
Expand Down Expand Up @@ -123,37 +124,65 @@ func processFileInclusions(elements []interface{}, attrs types.DocumentAttribute

// parseDelimitedBlockContent parses the given verbatim elements, depending on the given delimited block kind.
// May return the elements unchanged, or convert the elements to a source doc and parse with a custom entrypoint
func parseDelimitedBlockContent(filename string, kind types.BlockKind, elements []interface{}, options ...Option) ([]interface{}, error) {
func parseDelimitedBlockContent(filename string, kind types.BlockKind, elements []interface{}, options ...Option) (types.ElementAttributes, []interface{}, error) {
switch kind {
case types.Fenced, types.Listing, types.Literal, types.Source, types.Comment:
// return the verbatim elements
return elements, nil
return types.ElementAttributes{}, elements, nil
case types.Example, types.Quote, types.Sidebar:
return parseDelimitedBlockElements(filename, elements, append(options, Entrypoint("NormalBlockContent"))...)
case types.MarkdownQuote:
return parseMarkdownQuoteBlockElements(filename, elements, append(options, Entrypoint("NormalBlockContent"))...)
case types.Verse:
return parseDelimitedBlockElements(filename, elements, append(options, Entrypoint("VerseBlockContent"))...)
default:
return nil, fmt.Errorf("unexpected kind of delimited block: '%s'", kind)
return nil, nil, fmt.Errorf("unexpected kind of delimited block: '%s'", kind)
}
}

func parseDelimitedBlockElements(filename string, elements []interface{}, options ...Option) ([]interface{}, error) {
verbatim, err := marshal(elements)
func parseDelimitedBlockElements(filename string, elements []interface{}, options ...Option) (types.ElementAttributes, []interface{}, error) {
verbatim, err := serialize(elements)
if err != nil {
return nil, err
return nil, nil, err
}
e, err := ParseReader(filename, verbatim, options...)
if err != nil {
return nil, err
return nil, nil, err
}
if result, ok := e.([]interface{}); ok {
return result, nil
return types.ElementAttributes{}, result, nil
}
return nil, fmt.Errorf("unexpected type of element after parsing the content of a delimited block: '%T'", e)
return nil, nil, fmt.Errorf("unexpected type of element after parsing the content of a delimited block: '%T'", e)
}

func parseMarkdownQuoteBlockElements(filename string, elements []interface{}, options ...Option) (types.ElementAttributes, []interface{}, error) {
author := parseMarkdownQuoteBlockAttribution(filename, elements)
if author != "" {
elements = elements[:len(elements)-1]
}
attrs, lines, err := parseDelimitedBlockElements(filename, elements, options...)
attrs.AddNonEmpty(types.AttrQuoteAuthor, author)
return attrs, lines, err
}

func parseMarkdownQuoteBlockAttribution(filename string, elements []interface{}) string {
// first, check if last line is an attribution (author)
if lastLine, ok := elements[len(elements)-1].(types.VerbatimLine); ok {
buf := bytes.NewBuffer(nil)
buf.WriteString(lastLine.Content)
a, err := ParseReader(filename, buf, Entrypoint("MarkdownQuoteBlockAttribution"))
// assume that the last line is not an author attribution if an error occurred
if err != nil {
return ""
}
if a, ok := a.(string); ok {
return a
}
}
return ""
}

func marshal(elements []interface{}) (io.Reader, error) {
func serialize(elements []interface{}) (io.Reader, error) {
buf := bytes.NewBuffer(nil)
for _, e := range elements {
if r, ok := e.(types.VerbatimLine); ok {
Expand Down
Loading