all repos — grayfriday @ c4825a719d3ad79436b3961e52b4fad8428c8bf0

blackfriday fork with a few changes

Add definition lists extension support
Vincent Batoufflet vincent@batoufflet.info
Fri, 29 May 2015 13:30:49 +0200
commit

c4825a719d3ad79436b3961e52b4fad8428c8bf0

parent

93ae1e873a88e44ac18bb7d9d9bd69ef77802471

4 files changed, 182 insertions(+), 13 deletions(-)

jump to
M block.goblock.go

@@ -166,6 +166,21 @@ data = data[p.list(out, data, LIST_TYPE_ORDERED):]

continue } + // definition lists: + // + // Term 1 + // : Definition a + // : Definition b + // + // Term 2 + // : Definition c + if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.dliPrefix(data) > 0 { + data = data[p.list(out, data, LIST_TYPE_DEFINITION):] + continue + } + } + // anything else must look like a normal paragraph // note: this finds underlined headers, too data = data[p.paragraph(out, data):]

@@ -1018,6 +1033,20 @@ }

return i + 2 } +// returns definition list item prefix +func (p *parser) dliPrefix(data []byte) int { + i := 0 + + // need a : followed by a spaces + if data[i] != ':' || data[i+1] != ' ' { + return 0 + } + for data[i] == ' ' { + i++ + } + return i + 2 +} + // parse ordered or unordered list block func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { i := 0

@@ -1053,7 +1082,19 @@ if i == 0 {

i = p.oliPrefix(data) } if i == 0 { - return 0 + i = p.dliPrefix(data) + // reset definition term flag + if i > 0 { + *flags &= ^LIST_TYPE_TERM + } + } + if i == 0 { + // if in defnition list, set term flag and continue + if *flags&LIST_TYPE_DEFINITION != 0 { + *flags |= LIST_TYPE_TERM + } else { + return 0 + } } // skip leading whitespace on first line

@@ -1063,7 +1104,7 @@ }

// find the end of the line line := i - for data[i-1] != '\n' { + for i > 0 && data[i-1] != '\n' { i++ }

@@ -1107,7 +1148,8 @@ // evaluate how this line fits in

switch { // is this a nested list item? case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || - p.oliPrefix(chunk) > 0: + p.oliPrefix(chunk) > 0 || + p.dliPrefix(chunk) > 0: if containsBlankLine { *flags |= LIST_ITEM_CONTAINS_BLOCK

@@ -1138,7 +1180,18 @@ // anything following an empty line is only part

// of this item if it is indented 4 spaces // (regardless of the indentation of the beginning of the item) case containsBlankLine && indent < 4: - *flags |= LIST_ITEM_END_OF_LIST + if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 { + // is the next item still a part of this list? + next := i + for data[next] != '\n' { + next++ + } + if next < len(data)-2 && data[next+1] != ':' { + *flags |= LIST_ITEM_END_OF_LIST + } + } else { + *flags |= LIST_ITEM_END_OF_LIST + } break gatherlines // a blank line means this should be parsed as a block

@@ -1152,6 +1205,7 @@ // re-introduce the blank into the buffer

if containsBlankLine { containsBlankLine = false raw.WriteByte('\n') + } // add the line into the working buffer without prefix

@@ -1164,8 +1218,8 @@ rawBytes := raw.Bytes()

// render the contents of the list item var cooked bytes.Buffer - if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 { - // intermediate render of block li + if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 { + // intermediate render of block item, except for definition term if sublist > 0 { p.block(&cooked, rawBytes[:sublist]) p.block(&cooked, rawBytes[sublist:])

@@ -1173,7 +1227,7 @@ } else {

p.block(&cooked, rawBytes) } } else { - // intermediate render of inline li + // intermediate render of inline item if sublist > 0 { p.inline(&cooked, rawBytes[:sublist]) p.block(&cooked, rawBytes[sublist:])

@@ -1237,6 +1291,13 @@ line = i

// did we find a blank line marking the end of the paragraph? if n := p.isEmpty(current); n > 0 { + // did this blank line followed by a definition list item? + if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if i < len(data)-1 && data[i+1] == ':' { + return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + } + } + p.renderParagraph(out, data[:i]) return i + n }

@@ -1293,6 +1354,13 @@ // if there's a prefixed header or a horizontal rule after this, paragraph is over

if p.isPrefixHeader(current) || p.isHRule(current) { p.renderParagraph(out, data[:i]) return i + } + + // if there's a definition list item, prev line is a definition term + if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.dliPrefix(current) != 0 { + return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + } } // if there's a list after this, paragraph is over
M block_test.goblock_test.go

@@ -801,6 +801,86 @@ }

doTestsBlock(t, tests, 0) } +func TestDefinitionList(t *testing.T) { + var tests = []string{ + "Term 1\n: Definition a\n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n", + + "Term 1\n: Definition a \n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n", + + "Term 1\n: Definition a\n: Definition b\n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n<dd>Definition b</dd>\n</dl>\n", + + "Term 1\n: Definition a\n\nTerm 2\n: Definition b\n", + "<dl>\n" + + "<dt>Term 1</dt>\n" + + "<dd>Definition a</dd>\n" + + "<dt>Term 2</dt>\n" + + "<dd>Definition b</dd>\n" + + "</dl>\n", + + "Term 1\n: Definition a\n: Definition b\n\nTerm 2\n: Definition c\n", + "<dl>\n" + + "<dt>Term 1</dt>\n" + + "<dd>Definition a</dd>\n" + + "<dd>Definition b</dd>\n" + + "<dt>Term 2</dt>\n" + + "<dd>Definition c</dd>\n" + + "</dl>\n", + + "Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n", + "<dl>\n" + + "<dt>Term 1</dt>\n" + + "<dd><p>Definition a</p></dd>\n" + + "<dt>Term 2</dt>\n" + + "<dd><p>Definition b</p></dd>\n" + + "</dl>\n", + + "Term 1\n\n: Definition a\n\n: Definition b\n\nTerm 2\n\n: Definition c\n", + "<dl>\n" + + "<dt>Term 1</dt>\n" + + "<dd><p>Definition a</p></dd>\n" + + "<dd><p>Definition b</p></dd>\n" + + "<dt>Term 2</dt>\n" + + "<dd><p>Definition c</p></dd>\n" + + "</dl>\n", + + "Term 1\n: Definition a\nNext line\n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n", + + "Term 1\n: Definition a\n Next line\n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n", + + "Term 1\n: Definition a \n Next line \n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a\nNext line</dd>\n</dl>\n", + + "Term 1\n: Definition a\nNext line\n\nTerm 2\n: Definition b", + "<dl>\n" + + "<dt>Term 1</dt>\n" + + "<dd>Definition a\nNext line</dd>\n" + + "<dt>Term 2</dt>\n" + + "<dd>Definition b</dd>\n" + + "</dl>\n", + + "Term 1\n: Definition a\n", + "<dl>\n<dt>Term 1</dt>\n<dd>Definition a</dd>\n</dl>\n", + + "Term 1\n:Definition a\n", + "<p>Term 1\n:Definition a</p>\n", + + "Term 1\n\n: Definition a\n\nTerm 2\n\n: Definition b\n\nText 1", + "<dl>\n" + + "<dt>Term 1</dt>\n" + + "<dd><p>Definition a</p></dd>\n" + + "<dt>Term 2</dt>\n" + + "<dd><p>Definition b</p></dd>\n" + + "</dl>\n" + + "\n<p>Text 1</p>\n", + } + doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS) +} + func TestPreformattedHtml(t *testing.T) { var tests = []string{ "<div></div>\n",
M html.gohtml.go

@@ -375,7 +375,9 @@ func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {

marker := out.Len() doubleSpace(out) - if flags&LIST_TYPE_ORDERED != 0 { + if flags&LIST_TYPE_DEFINITION != 0 { + out.WriteString("<dl>") + } else if flags&LIST_TYPE_ORDERED != 0 { out.WriteString("<ol>") } else { out.WriteString("<ul>")

@@ -384,7 +386,9 @@ if !text() {

out.Truncate(marker) return } - if flags&LIST_TYPE_ORDERED != 0 { + if flags&LIST_TYPE_DEFINITION != 0 { + out.WriteString("</dl>\n") + } else if flags&LIST_TYPE_ORDERED != 0 { out.WriteString("</ol>\n") } else { out.WriteString("</ul>\n")

@@ -392,12 +396,25 @@ }

} func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) { - if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { + if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) || + flags&LIST_ITEM_BEGINNING_OF_LIST != 0 { doubleSpace(out) } - out.WriteString("<li>") + if flags&LIST_TYPE_TERM != 0 { + out.WriteString("<dt>") + } else if flags&LIST_TYPE_DEFINITION != 0 { + out.WriteString("<dd>") + } else { + out.WriteString("<li>") + } out.Write(text) - out.WriteString("</li>\n") + if flags&LIST_TYPE_TERM != 0 { + out.WriteString("</dt>\n") + } else if flags&LIST_TYPE_DEFINITION != 0 { + out.WriteString("</dd>\n") + } else { + out.WriteString("</li>\n") + } } func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
M markdown.gomarkdown.go

@@ -44,6 +44,7 @@ EXTENSION_HEADER_IDS // specify header IDs with {#id}

EXTENSION_TITLEBLOCK // Titleblock ala pandoc EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks + EXTENSION_DEFINITION_LISTS // render definition lists commonHtmlFlags = 0 | HTML_USE_XHTML |

@@ -59,7 +60,8 @@ EXTENSION_AUTOLINK |

EXTENSION_STRIKETHROUGH | EXTENSION_SPACE_HEADERS | EXTENSION_HEADER_IDS | - EXTENSION_BACKSLASH_LINE_BREAK + EXTENSION_BACKSLASH_LINE_BREAK | + EXTENSION_DEFINITION_LISTS ) // These are the possible flag values for the link renderer.

@@ -76,6 +78,8 @@ // Multiple flag values may be ORed together.

// These are mostly of interest if you are writing a new output format. const ( LIST_TYPE_ORDERED = 1 << iota + LIST_TYPE_DEFINITION + LIST_TYPE_TERM LIST_ITEM_CONTAINS_BLOCK LIST_ITEM_BEGINNING_OF_LIST LIST_ITEM_END_OF_LIST