Skip to content

Commit

Permalink
feat(parser/renderer): Support wanted for #mark# syntax
Browse files Browse the repository at this point in the history
Fixes bytesparadise#599.

This adds support for the #mark# style, including support for
using the role attribute to change <mark> into <span>.
  • Loading branch information
gdamore committed Jun 14, 2020
1 parent ba628cd commit eb71542
Show file tree
Hide file tree
Showing 10 changed files with 3,625 additions and 2,616 deletions.
6,028 changes: 3,419 additions & 2,609 deletions pkg/parser/parser.go

Large diffs are not rendered by default.

102 changes: 95 additions & 7 deletions pkg/parser/parser.peg
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,8 @@ ConstrainedQuotedTextMarker <- "*" !"*" / "_" !"_" / "`" !"`"
UnconstrainedQuotedTextPrefix <- "**" / "__" / "``" / "^" / "~"

ConstrainedQuotedText <- text:(SingleQuoteBoldText
/ SingleQuoteItalicText
/ SingleQuoteItalicText
/ SingleQuoteMarkedText
/ SingleQuoteMonospaceText
/ SubscriptText
/ SuperscriptText
Expand All @@ -911,10 +912,12 @@ ConstrainedQuotedText <- text:(SingleQuoteBoldText

UnconstrainedQuotedText <- DoubleQuoteBoldText
/ DoubleQuoteItalicText
/ DoubleQuoteMarkedText
/ DoubleQuoteMonospaceText

EscapedQuotedText <- EscapedBoldText
/ EscapedItalicText
/ EscapedItalicText
/ EscapedMarkedText
/ EscapedMonospaceText
/ EscapedSubscriptText
/ EscapedSuperscriptText
Expand Down Expand Up @@ -945,7 +948,8 @@ DoubleQuoteBoldTextElements <- DoubleQuoteBoldTextElement (!("**") (Space / Doub

DoubleQuoteBoldTextElement <- Word
/ SingleQuoteBoldText
/ ItalicText
/ ItalicText
/ MarkedText
/ MonospaceText
/ SubscriptText
/ SuperscriptText
Expand Down Expand Up @@ -978,7 +982,8 @@ SingleQuoteBoldTextElements <- !Space SingleQuoteBoldTextElement+
SingleQuoteBoldTextElement <- Word
/ DoubleQuoteBoldText
/ Space+ ('*' !'*')?
/ ItalicText
/ ItalicText
/ MarkedText
/ MonospaceText
/ SubscriptText
/ SuperscriptText
Expand Down Expand Up @@ -1023,7 +1028,8 @@ DoubleQuoteItalicTextElements <- DoubleQuoteItalicTextElement (!("__") (Space /

DoubleQuoteItalicTextElement <- Word
/ SingleQuoteItalicText
/ BoldText
/ BoldText
/ MarkedText
/ MonospaceText
/ SubscriptText
/ SuperscriptText
Expand Down Expand Up @@ -1054,7 +1060,8 @@ SingleQuoteItalicTextElements <- !Space SingleQuoteItalicTextElement+
SingleQuoteItalicTextElement <- Word
/ DoubleQuoteItalicText
/ Space+ ('_' !'_')?
/ BoldText
/ BoldText
/ MarkedText
/ MonospaceText
/ SubscriptText
/ SuperscriptText
Expand Down Expand Up @@ -1099,7 +1106,8 @@ DoubleQuoteMonospaceTextElements <- DoubleQuoteMonospaceTextElement (!("``") (Sp
DoubleQuoteMonospaceTextElement <- Word
/ SingleQuoteMonospaceText
/ BoldText
/ ItalicText
/ ItalicText
/ MarkedText
/ SubscriptText
/ SuperscriptText
/ InlineImage
Expand Down Expand Up @@ -1132,6 +1140,7 @@ SingleQuoteMonospaceTextElement <- Word
/ Newline // allows multiline
/ BoldText
/ ItalicText
/ MarkedText
/ SubscriptText
/ SuperscriptText
/ InlineImage
Expand Down Expand Up @@ -1161,6 +1170,85 @@ EscapedMonospaceText <-
return types.NewEscapedQuotedText(backslashes.(string), "`", elements.([]interface{}))
}

// -----------------
// Marked text
// -----------------

MarkedText <- DoubleQuoteMarkedText / SingleQuoteMarkedText

DoubleQuoteMarkedText <- attrs:(QuotedTextAttrs)? !`\\` "##" elements:(DoubleQuoteMarkedTextElements) "##" { // double punctuation must be evaluated first
return types.NewQuotedText(types.Marked, attrs, elements.([]interface{}))
}

DoubleQuoteMarkedTextElements <- DoubleQuoteMarkedTextElement (!("##") (Space / DoubleQuoteMarkedTextElement))* // may start and end with spaces

DoubleQuoteMarkedTextElement <- Word
/ SingleQuoteMarkedText
/ BoldText
/ ItalicText
/ MonospaceText
/ SubscriptText
/ SuperscriptText
/ InlineImage
/ Link
/ InlinePassthrough
/ DoubleQuoteMarkedTextStringElement
/ DoubleQuoteMarkedTextFallbackCharacter

DoubleQuoteMarkedTextStringElement <- (!"##" [^\r\n ^~{}])+ {
return types.NewStringElement(string(c.text))
}

DoubleQuoteMarkedTextFallbackCharacter <-
[^\r\n#] // anything except EOL and marked delimiter (fallback in case nothing else matched)
/ "##" Alphanums { // or a marked delimiter when immediately followed by an alphanum (ie, in the middle of some text)
return types.NewStringElement(string(c.text))
}

SingleQuoteMarkedText <- attrs:(QuotedTextAttrs)? (!`\` "#" !"#") elements:(SingleQuoteMarkedTextElements) "#" { // single punctuation cannot be followed by a character (needs '##' to emphazise a portion of a word)
return types.NewQuotedText(types.Marked, attrs, elements.([]interface{}))
} / attrs:(QuotedTextAttrs)? !`\\` "#" elements:("#" SingleQuoteMarkedTextElements) "#" { // unbalanced `##` vs `#` punctuation.
return types.NewQuotedText(types.Marked, attrs, elements.([]interface{})) // include the second heading `_` as a regular StringElement in the italic content
}

SingleQuoteMarkedTextElements <- !Space SingleQuoteMarkedTextElement+

SingleQuoteMarkedTextElement <- Word
/ DoubleQuoteMarkedText
/ Space+ ('#' !'#')?
/ BoldText
/ ItalicText
/ MonospaceText
/ SubscriptText
/ SuperscriptText
/ InlineImage
/ Link
/ InlinePassthrough
/ AttributeSubstitution
/ SingleQuoteMarkedTextStringElement
/ SingleQuoteMarkedTextFallbackCharacter

SingleQuoteMarkedTextStringElement <- [^\r\n{} #^~]+ { // anything except EOL, space, bold and subscript/superscript delimiters and brackets. Excludes curly brackets to match with AttributeSubstitution elements
return types.NewStringElement(string(c.text))
}

SingleQuoteMarkedTextFallbackCharacter <-
[^\r\n#] // anything except EOL and mark delimiter (fallback in case nothing else matched)
/ "#" Alphanums { // or a mark delimiter when immediately followed by an alphanum (ie, in the middle of some text)
return types.NewStringElement(string(c.text))
}

EscapedMarkedText <-
backslashes:(TwoOrMoreBackslashes) "##" elements:(DoubleQuoteMarkedTextElements) "##" { // double punctuation must be evaluated first
return types.NewEscapedQuotedText(backslashes.(string), "##", elements.([]interface{}))
} / backslashes:(OneOrMoreBackslashes) "##" elements:(SingleQuoteMarkedTextElements) "#" { // unbalanced `##` vs `#` punctuation
result := append([]interface{}{"#"}, elements.([]interface{}))
return types.NewEscapedQuotedText(backslashes.(string), "#", result)
} / backslashes:(OneOrMoreBackslashes) "#" elements:(SingleQuoteMarkedTextElements) "#" { // simple punctuation must be evaluated last
return types.NewEscapedQuotedText(backslashes.(string), "#", elements.([]interface{}))
}


SubscriptText <- attrs:(QuotedTextAttrs)? !`\` "~" element:(SubscriptTextElement) "~" { // wraps a single word
return types.NewQuotedText(types.Subscript, attrs, element)
}
Expand Down
69 changes: 69 additions & 0 deletions pkg/parser/quoted_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ var _ = Describe("quoted texts", func() {
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})
})

Context("attributes", func() {
It("simple role italics", func() {
source := "[myrole]_italics_"
Expand Down Expand Up @@ -1012,6 +1013,30 @@ var _ = Describe("quoted texts", func() {
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("marked short-hand role only", func() {
source := "[.bob]##the builder##"
expected := types.DraftDocument{
Blocks: []interface{}{
types.Paragraph{
Lines: [][]interface{}{
{
types.QuotedText{
Kind: types.Marked,
Elements: []interface{}{
types.StringElement{Content: "the builder"},
},
Attributes: types.Attributes{
types.AttrRole: []string{"bob"},
},
},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("short-hand multiple roles and id", func() {
source := "[.r1#anchor.r2.r3]**bold**[#here.second.class]_text_"
expected := types.DraftDocument{
Expand Down Expand Up @@ -1463,6 +1488,50 @@ var _ = Describe("quoted texts", func() {
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("quoted text within marked text", func() {
source := "some #marked and _italic_ and *bold* and `monospaced` content together#."
expected := types.DraftDocument{
Blocks: []interface{}{
types.Paragraph{
Lines: [][]interface{}{
{
types.StringElement{Content: "some "},
types.QuotedText{
Kind: types.Marked,
Elements: []interface{}{
types.StringElement{Content: "marked and "},
types.QuotedText{
Kind: types.Italic,
Elements: []interface{}{
types.StringElement{Content: "italic"},
},
},
types.StringElement{Content: " and "},
types.QuotedText{
Kind: types.Bold,
Elements: []interface{}{
types.StringElement{Content: "bold"},
},
},
types.StringElement{Content: " and "},
types.QuotedText{
Kind: types.Monospace,
Elements: []interface{}{
types.StringElement{Content: "monospaced"},
},
},
types.StringElement{Content: " content together"},
},
},
types.StringElement{Content: "."},
},
},
},
},
}
Expect(ParseDraftDocument(source)).To(MatchDraftDocument(expected))
})

It("unbalanced bold in monospace - case 1", func() {
source := "`*a`"
expected := types.DraftDocument{
Expand Down
1 change: 1 addition & 0 deletions pkg/renderer/sgml/html5/quoted_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ const (
monospaceTextTmpl = `<code{{ if .ID }} id="{{ .ID }}"{{ end }}{{ if .Role }} class="{{ .Role }}"{{ end }}>{{ .Content }}</code>`
subscriptTextTmpl = `<sub{{ if .ID }} id="{{ .ID }}"{{ end }}{{ if .Role }} class="{{ .Role }}"{{ end }}>{{ .Content }}</sub>`
superscriptTextTmpl = `<sup{{ if .ID }} id="{{ .ID }}"{{ end }}{{ if .Role }} class="{{ .Role }}"{{ end }}>{{ .Content }}</sup>`
markedTextTmpl = `{{ if .Role }}<span{{ else }}<mark{{ end }}{{ if .ID }} id="{{ .ID }}"{{ end }}{{ if .Role }} class="{{ .Role }}"{{ end }}>{{ .Content }}{{ if .Role }}</span>{{ else }}</mark>{{ end }}`
)
33 changes: 33 additions & 0 deletions pkg/renderer/sgml/html5/quoted_text_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,22 @@ var _ = Describe("quoted texts", func() {
Expect(RenderHTML(source)).To(MatchHTML(expected))
})

It("marked role (span) only", func() {
source := "[.bob]##bold##"
expected := `<div class="paragraph">
<p><span class="bob">bold</span></p>
</div>`
Expect(RenderHTML(source)).To(MatchHTML(expected))
})

It("marked role id only", func() {
source := "[#link]##content##"
expected := `<div class="paragraph">
<p><mark id="link">content</mark></p>
</div>`
Expect(RenderHTML(source)).To(MatchHTML(expected))
})

It("empty role", func() {
source := "[]**bold**"
expected := `<div class="paragraph">
Expand Down Expand Up @@ -227,6 +243,23 @@ var _ = Describe("quoted texts", func() {
</div>`
Expect(RenderHTML(source)).To(MatchHTML(expected))
})

It("marked content within bold quote in sentence", func() {
source := "some *bold and #marked content#* together."
expected := `<div class="paragraph">
<p>some <strong>bold and <mark>marked content</mark></strong> together.</p>
</div>`
Expect(RenderHTML(source)).To(MatchHTML(expected))
})

It("span content within italic quote in sentence", func() {
source := "some *bold and [strikeout]#span content#* together."
expected := `<div class="paragraph">
<p>some <strong>bold and <span class="strikeout">span content</span></strong> together.</p>
</div>`
Expect(RenderHTML(source)).To(MatchHTML(expected))
})

})

Context("invalid content", func() {
Expand Down
1 change: 1 addition & 0 deletions pkg/renderer/sgml/html5/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var templates = sgml.Templates{
LiteralBlock: literalBlockTmpl,
ManpageHeader: manpageHeaderTmpl,
ManpageNameParagraph: manpageNameParagraphTmpl,
MarkedText: markedTextTmpl,
MonospaceText: monospaceTextTmpl,
OrderedList: orderedListTmpl,
PassthroughBlock: pssThroughBlock,
Expand Down
2 changes: 2 additions & 0 deletions pkg/renderer/sgml/quoted_text.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ func (r *sgmlRenderer) renderQuotedText(ctx *renderer.Context, t types.QuotedTex
tmpl = r.boldText
case types.Italic:
tmpl = r.italicText
case types.Marked:
tmpl = r.markedText
case types.Monospace:
tmpl = r.monospaceText
case types.Subscript:
Expand Down
2 changes: 2 additions & 0 deletions pkg/renderer/sgml/sgml_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type sgmlRenderer struct {
literalBlock *textTemplate
manpageHeader *textTemplate
manpageNameParagraph *textTemplate
markedText *textTemplate
monospaceText *textTemplate
orderedList *textTemplate
paragraph *textTemplate
Expand Down Expand Up @@ -101,6 +102,7 @@ func (r *sgmlRenderer) prepareTemplates() error {
r.literalBlock, err = r.newTemplate("literal-block", tmpls.LiteralBlock, err)
r.manpageHeader, err = r.newTemplate("manpage-header", tmpls.ManpageHeader, err)
r.manpageNameParagraph, err = r.newTemplate("manpage-name-paragraph", tmpls.ManpageNameParagraph, err)
r.markedText, err = r.newTemplate("marked-text", tmpls.MarkedText, err)
r.monospaceText, err = r.newTemplate("monospace-text", tmpls.MonospaceText, err)
r.orderedList, err = r.newTemplate("ordered-list", tmpls.OrderedList, err)
r.paragraph, err = r.newTemplate("paragraph", tmpls.Paragraph, err)
Expand Down
1 change: 1 addition & 0 deletions pkg/renderer/sgml/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type Templates struct {
LiteralBlock string
ManpageHeader string
ManpageNameParagraph string
MarkedText string
MonospaceText string
OrderedList string
Paragraph string
Expand Down
2 changes: 2 additions & 0 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,8 @@ const (
Bold QuotedTextKind = iota
// Italic italic quoted text (wrapped with '_' or '__')
Italic
// Marked text (highlighter, wrapped with '#' or '##')
Marked
// Monospace monospace quoted text (wrapped with '`' or '``')
Monospace
// Subscript subscript quoted text (wrapped with '~' or '~~')
Expand Down

0 comments on commit eb71542

Please sign in to comment.