Skip to content

Commit

Permalink
feat(parser/renderer): support admonitions (#70)
Browse files Browse the repository at this point in the history
support admonition paragraphs and blocks (ie,
"masquerade" on paragraph)

Fixes #67

Signed-off-by: Xavier Coulon <[email protected]>
  • Loading branch information
xcoulon committed Mar 11, 2018
1 parent 5d9e7d8 commit 6c221f1
Show file tree
Hide file tree
Showing 13 changed files with 2,865 additions and 2,278 deletions.
152 changes: 152 additions & 0 deletions parser/admonition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package parser_test

import (
"github.com/bytesparadise/libasciidoc/parser"
"github.com/bytesparadise/libasciidoc/types"
. "github.com/onsi/ginkgo"
)

var _ = Describe("admonitions", func() {

Context("admonition paragraphs", func() {
It("note admonition paragraph", func() {
actualContent := `NOTE: this is a note.`
expectedResult := &types.Admonition{
Kind: types.Note,
Content: &types.AdmonitionParagraph{
Lines: []*types.InlineContent{
{
Elements: []types.InlineElement{
&types.StringElement{
Content: "this is a note.",
},
},
},
},
},
}
verify(GinkgoT(), expectedResult, actualContent, parser.Entrypoint("BlockElement"))
})

It("warning admonition paragraph", func() {
actualContent := `WARNING: this is a multiline
warning!`
expectedResult := &types.Admonition{
Kind: types.Warning,
Content: &types.AdmonitionParagraph{
Lines: []*types.InlineContent{
{
Elements: []types.InlineElement{
&types.StringElement{
Content: "this is a multiline",
},
},
},
{
Elements: []types.InlineElement{
&types.StringElement{
Content: "warning!",
},
},
},
},
},
}
verify(GinkgoT(), expectedResult, actualContent, parser.Entrypoint("BlockElement"))
})

It("admonition note paragraph with id and title", func() {
actualContent := `[[foo]]
.bar
NOTE: this is a note.`
expectedResult := &types.Admonition{
ID: &types.ElementID{
Value: "foo",
},
Title: &types.ElementTitle{
Value: "bar",
},
Kind: types.Note,
Content: &types.AdmonitionParagraph{
Lines: []*types.InlineContent{
{
Elements: []types.InlineElement{
&types.StringElement{
Content: "this is a note.",
},
},
},
},
},
}
verify(GinkgoT(), expectedResult, actualContent, parser.Entrypoint("BlockElement"))
})
})

Context("admonition blocks", func() {
It("caution admonition block", func() {
actualContent := `[CAUTION]
this is a caution!`
expectedResult := &types.Admonition{
Kind: types.Caution,
Content: &types.AdmonitionParagraph{
Lines: []*types.InlineContent{
{
Elements: []types.InlineElement{
&types.StringElement{
Content: "this is a caution!",
},
},
},
},
},
}
verify(GinkgoT(), expectedResult, actualContent, parser.Entrypoint("BlockElement"))
})

It("multiline caution admonition block with title and id", func() {
actualContent := `[[foo]]
[CAUTION]
.bar
this is a
*caution*!`
expectedResult := &types.Admonition{
ID: &types.ElementID{
Value: "foo",
},
Title: &types.ElementTitle{
Value: "bar",
},
Kind: types.Caution,
Content: &types.AdmonitionParagraph{
Lines: []*types.InlineContent{
{
Elements: []types.InlineElement{
&types.StringElement{
Content: "this is a ",
},
},
},
{
Elements: []types.InlineElement{
&types.QuotedText{
Kind: types.Bold,
Elements: []types.InlineElement{
&types.StringElement{
Content: "caution",
},
},
},
&types.StringElement{
Content: "!",
},
},
},
},
},
}
verify(GinkgoT(), expectedResult, actualContent, parser.Entrypoint("BlockElement"))
})
})

})
144 changes: 62 additions & 82 deletions parser/asciidoc-grammar.peg
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ DocumentBlocks <- content:(Preamble Section+) / content:(BlockElement*) {
return content, nil
}

BlockElement <- DocumentAttributeDeclaration / DocumentAttributeReset / TableOfContentsMacro / BlockImage / List / LiteralBlock / DelimitedBlock / Paragraph / (ElementAttribute EOL) / BlankLine //TODO: should Paragraph be the last type ?
BlockElement <- DocumentAttributeDeclaration / DocumentAttributeReset / TableOfContentsMacro / BlockImage / List / LiteralBlock / DelimitedBlock / Admonition / Paragraph / (ElementAttribute EOL) / BlankLine //TODO: should Paragraph be the last type ?

Preamble <- elements:(BlockElement*) {
return types.NewPreamble(elements.([]interface{}))
Expand Down Expand Up @@ -110,7 +110,6 @@ DocumentAttributeResetWithTrailingBangSymbol <- ":" name:(AttributeName) "!:" WS
return types.NewDocumentAttributeReset(name.([]interface{}))
}


DocumentAttributeSubstitution <- "{" name:(AttributeName) "}" {
return types.NewDocumentAttributeSubstitution(name.([]interface{}))
}
Expand Down Expand Up @@ -245,14 +244,12 @@ UnorderedListItemContent <- elements:(ListParagraph+ ContinuedBlockElement*) { /
return types.NewListItemContent(elements.([]interface{}))
}



// ------------------------------------------
// Labeled Lists
// ------------------------------------------
LabeledListItem <- LabeledListItemWithDescription / LabeledListItemWithTermAlone

LabeledListItemWithTermAlone <- term:(LabeledListItemTerm) "::" WS* EOL { // here, WS is optional since there is no description afterwards
LabeledListItem <- term:(LabeledListItemTerm) LabeledListItemSeparator description:(LabeledListItemDescription) {
return types.NewLabeledListItem(term.([]interface{}), description.([]types.DocElement))
} / term:(LabeledListItemTerm) "::" WS* EOL { // here, WS is optional since there is no description afterwards
return types.NewLabeledListItem(term.([]interface{}), nil)
}

Expand All @@ -263,10 +260,6 @@ LabeledListItemTerm <- term:(!NEWLINE !"::" .)* {
// term separator: ('::') and at least one space or endline
LabeledListItemSeparator <- "::" (WS / NEWLINE)+

LabeledListItemWithDescription <- term:(LabeledListItemTerm) LabeledListItemSeparator description:(LabeledListItemDescription) {
return types.NewLabeledListItem(term.([]interface{}), description.([]types.DocElement))
}

LabeledListItemDescription <- elements:(ListParagraph / ContinuedBlockElement)* {
return types.NewListItemContent(elements.([]interface{}))
}
Expand All @@ -292,101 +285,93 @@ InlineContent <- !BlockDelimiter elements:(WS* !InlineElementID InlineElement)+

InlineElement <- (CrossReference / Passthrough / InlineImage / QuotedText / Link / DocumentAttributeSubstitution / Characters)

// ------------------------------------------
// Admonitions
// ------------------------------------------
// a paragraph is a group of line ending with a blank line (or end of file)
// a paragraph cannot start with the `section` sequence (`= `, `== `, etc.)
Admonition <- attributes:(ElementAttribute)* !("="+ WS+) t:(AdmonitionKind) ": " content:(AdmonitionParagraph) { // paragraph style
return types.NewAdmonition(t.(types.AdmonitionKind), content, attributes.([]interface{}))
} /
attributes:(ElementAttribute)* "[" t:(AdmonitionKind) "]" WS* NEWLINE otherAttributes:(ElementAttribute)* content:(AdmonitionParagraph) { // block style
return types.NewAdmonition(t.(types.AdmonitionKind), content, append(attributes.([]interface{}), otherAttributes.([]interface{})...))
}

AdmonitionParagraph <- lines:(InlineContentWithTrailingSpaces EOL)+ {
return types.NewAdmonitionParagraph(lines.([]interface{}))
}

AdmonitionKind <- "TIP" {
return types.Tip, nil
} / "NOTE" {
return types.Note, nil
} / "IMPORTANT" {
return types.Important, nil
} / "WARNING" {
return types.Warning, nil
} / "CAUTION" {
return types.Caution, nil
}

// ----------------------------------------------------------------------------
// Quoted Texts (bold, italic and monospace) including substitution prevention
// ----------------------------------------------------------------------------
QuotedText <- BoldText / ItalicText / MonospaceText /
EscapedBoldText / EscapedItalicText / EscapedMonospaceText

BoldText <- BoldTextDoublePunctuation / BoldTextUnbalancedPunctuation / BoldTextSimplePunctuation // double punctuation must be evaluated first

BoldTextSimplePunctuation <- !`\` "*" content:(QuotedTextContent) "*" {
BoldText <- !`\\` "**" content:(QuotedTextContent) "**" { // double punctuation must be evaluated first
return types.NewQuotedText(types.Bold, content.([]interface{}))
}

BoldTextDoublePunctuation <- !`\\` "**" content:(QuotedTextContent) "**" {
return types.NewQuotedText(types.Bold, content.([]interface{}))
}

BoldTextUnbalancedPunctuation <- !`\\` "**" content:(QuotedTextContent) "*" { // unbalanced `**` vs `*` punctuation
} / !`\\` "**" content:(QuotedTextContent) "*" { // unbalanced `**` vs `*` punctuation
result := append([]interface{}{"*"}, content.([]interface{}))
return types.NewQuotedText(types.Bold, result)
}

EscapedBoldText <- EscapedBoldTextDoublePunctuation / EscapedBoldTextUnbalancedPunctuation / EscapedBoldTextSimplePunctuation // double punctuation must be evaluated first

EscapedBoldTextSimplePunctuation <- backslashes:(`\` `\`*) "*" content:(QuotedTextContent) "*" {
return types.NewEscapedQuotedText(backslashes.([]interface{}), "*", content.([]interface{}))
}
} / !`\` "*" content:(QuotedTextContent) "*" { // single punctuation
return types.NewQuotedText(types.Bold, content.([]interface{}))
}

EscapedBoldTextDoublePunctuation <- backslashes:(`\\` `\`*) "**" content:(QuotedTextContent) "**" {
EscapedBoldText <- backslashes:(`\\` `\`*) "**" content:(QuotedTextContent) "**" { // double punctuation must be evaluated first
return types.NewEscapedQuotedText(backslashes.([]interface{}), "**", content.([]interface{}))
}

EscapedBoldTextUnbalancedPunctuation <- backslashes:(`\` `\`*) "**" content:(QuotedTextContent) "*" { // unbalanced `**` vs `*` punctuation
} / backslashes:(`\` `\`*) "**" content:(QuotedTextContent) "*" { // unbalanced `**` vs `*` punctuation
result := append([]interface{}{"*"}, content.([]interface{}))
return types.NewEscapedQuotedText(backslashes.([]interface{}), "*", result)
}

ItalicText <- ItalicTextDoublePunctuation / ItalicTextUnbalancedPunctuation / ItalicTextSimplePunctuation

ItalicTextSimplePunctuation <- !`\` "_" content:(QuotedTextContent) "_" {
return types.NewQuotedText(types.Italic, content.([]interface{}))
}
} / backslashes:(`\` `\`*) "*" content:(QuotedTextContent) "*" { // simple punctuation must be evaluated last
return types.NewEscapedQuotedText(backslashes.([]interface{}), "*", content.([]interface{}))
}

ItalicTextDoublePunctuation <- !`\\` "__" content:(QuotedTextContent) "__" {
ItalicText <- !`\\` "__" content:(QuotedTextContent) "__" {
return types.NewQuotedText(types.Italic, content.([]interface{}))
}

ItalicTextUnbalancedPunctuation <- !`\\` "__" content:(QuotedTextContent) "_" { // unbalanced `__` vs `_` punctuation
} / !`\\` "__" content:(QuotedTextContent) "_" { // unbalanced `__` vs `_` punctuation
result := append([]interface{}{"_"}, content.([]interface{}))
return types.NewQuotedText(types.Italic, result)
} / !`\` "_" content:(QuotedTextContent) "_" {
return types.NewQuotedText(types.Italic, content.([]interface{}))
}

EscapedItalicText <- EscapedItalicTextDoublePunctuation / EscapedItalicTextUnbalancedPunctuation / EscapedItalicTextSimplePunctuation // double punctuation must be evaluated first

EscapedItalicTextSimplePunctuation <- backslashes:(`\` `\`*) "_" content:(QuotedTextContent) "_" {
return types.NewEscapedQuotedText(backslashes.([]interface{}), "_", content.([]interface{}))
}

EscapedItalicTextDoublePunctuation <- backslashes:(`\\` `\`*) "__" content:(QuotedTextContent) "__" {
EscapedItalicText <- backslashes:(`\\` `\`*) "__" content:(QuotedTextContent) "__" { // double punctuation must be evaluated first
return types.NewEscapedQuotedText(backslashes.([]interface{}), "__", content.([]interface{}))
}

EscapedItalicTextUnbalancedPunctuation <- backslashes:(`\` `\`*) "__" content:(QuotedTextContent) "_" { // unbalanced `__` vs `_` punctuation
} / backslashes:(`\` `\`*) "__" content:(QuotedTextContent) "_" { // unbalanced `__` vs `_` punctuation
result := append([]interface{}{"_"}, content.([]interface{}))
return types.NewEscapedQuotedText(backslashes.([]interface{}), "_", result)
}

MonospaceText <- MonospaceTextDoublePunctuation / MonospaceTextUnbalancedPunctuation / MonospaceTextSimplePunctuation

MonospaceTextSimplePunctuation <- !`\` "`" content:(QuotedTextContent) "`" {
return types.NewQuotedText(types.Monospace, content.([]interface{}))
}
} / backslashes:(`\` `\`*) "_" content:(QuotedTextContent) "_" { // simple punctuation must be evaluated last
return types.NewEscapedQuotedText(backslashes.([]interface{}), "_", content.([]interface{}))
}

MonospaceTextDoublePunctuation <- !`\\` "``" content:(QuotedTextContent) "``" {
MonospaceText <- !`\\` "``" content:(QuotedTextContent) "``" { // double punctuation must be evaluated first
return types.NewQuotedText(types.Monospace, content.([]interface{}))
}

MonospaceTextUnbalancedPunctuation <- !`\\` "``" content:(QuotedTextContent) "`" { // unbalanced "``" vs "`" punctuation
} / !`\\` "``" content:(QuotedTextContent) "`" { // unbalanced "``" vs "`" punctuation
result := append([]interface{}{"`"}, content.([]interface{}))
return types.NewQuotedText(types.Monospace, result)
} / !`\` "`" content:(QuotedTextContent) "`" { // simple punctuation must be evaluated last
return types.NewQuotedText(types.Monospace, content.([]interface{}))
}

EscapedMonospaceText <- EscapedMonospaceTextDoublePunctuation / EscapedMonospaceTextUnbalancedPunctuation / EscapedMonospaceTextSimplePunctuation // double punctuation must be evaluated first

EscapedMonospaceTextSimplePunctuation <- backslashes:(`\` `\`*) "`" content:(QuotedTextContent) "`" {
return types.NewEscapedQuotedText(backslashes.([]interface{}), "`", content.([]interface{}))
}

EscapedMonospaceTextDoublePunctuation <- backslashes:(`\\` `\`*) "``" content:(QuotedTextContent) "``" {
EscapedMonospaceText <- backslashes:(`\\` `\`*) "``" content:(QuotedTextContent) "``" { // double punctuation must be evaluated first
return types.NewEscapedQuotedText(backslashes.([]interface{}), "``", content.([]interface{}))
}

EscapedMonospaceTextUnbalancedPunctuation <- backslashes:(`\` `\`*) "``" content:(QuotedTextContent) "`" { // unbalanced "``" vs "`" punctuation
} / backslashes:(`\` `\`*) "``" content:(QuotedTextContent) "`" { // unbalanced "``" vs "`" punctuation
result := append([]interface{}{"`"}, content.([]interface{}))
return types.NewEscapedQuotedText(backslashes.([]interface{}), "`", result)
}
} / backslashes:(`\` `\`*) "`" content:(QuotedTextContent) "`" { // simple punctuation must be evaluated last
return types.NewEscapedQuotedText(backslashes.([]interface{}), "`", content.([]interface{}))
}

QuotedTextContent <- QuotedTextContentElement (WS+ QuotedTextContentElement)*

Expand Down Expand Up @@ -414,13 +399,9 @@ TriplePlusPassthrough <- "+++" content:(!"+++" .)* "+++" {
return types.NewPassthrough(types.TriplePlusPassthrough, content.([]interface{}))
}

PassthroughMacro <- SimplePassthroughMacro / PassthroughWithQuotedText

SimplePassthroughMacro <- "pass:[" content:(PassthroughMacroCharacter)* "]" {
PassthroughMacro <- "pass:[" content:(PassthroughMacroCharacter)* "]" {
return types.NewPassthrough(types.PassthroughMacro, content.([]interface{}))
}

PassthroughWithQuotedText <- "pass:q[" content:(QuotedText / PassthroughMacroCharacter)* "]" {
} / "pass:q[" content:(QuotedText / PassthroughMacroCharacter)* "]" {
return types.NewPassthrough(types.PassthroughMacro, content.([]interface{}))
}

Expand Down Expand Up @@ -570,7 +551,6 @@ Characters <- (!NEWLINE !WS .)+ {
return string(c.text), nil
}


URL <- (!NEWLINE !WS !"[" !"]" .)+ {
return string(c.text), nil
}
Expand Down
Loading

0 comments on commit 6c221f1

Please sign in to comment.