diff --git a/block.go b/block.go index 4e8a03f9..d7da33f2 100644 --- a/block.go +++ b/block.go @@ -34,7 +34,7 @@ var ( // Parse block-level data. // Note: this function and many that it calls assume that // the input buffer ends with a newline. -func (p *Parser) block(data []byte) { +func (p *Markdown) block(data []byte) { // this is called recursively: enforce a maximum depth if p.nesting >= p.maxNesting { return @@ -71,7 +71,7 @@ func (p *Parser) block(data []byte) { // % stuff // % more stuff // % even more stuff - if p.flags&Titleblock != 0 { + if p.extensions&Titleblock != 0 { if data[0] == '%' { if i := p.titleBlock(data, true); i > 0 { data = data[i:] @@ -109,7 +109,7 @@ func (p *Parser) block(data []byte) { // return n * fact(n-1) // } // ``` - if p.flags&FencedCode != 0 { + if p.extensions&FencedCode != 0 { if i := p.fencedCodeBlock(data, true); i > 0 { data = data[i:] continue @@ -147,7 +147,7 @@ func (p *Parser) block(data []byte) { // ------|-----|--------- // Bob | 31 | 555-1234 // Alice | 27 | 555-4321 - if p.flags&Tables != 0 { + if p.extensions&Tables != 0 { if i := p.table(data); i > 0 { data = data[i:] continue @@ -182,7 +182,7 @@ func (p *Parser) block(data []byte) { // // Term 2 // : Definition c - if p.flags&DefinitionLists != 0 { + if p.extensions&DefinitionLists != 0 { if p.dliPrefix(data) > 0 { data = data[p.list(data, ListTypeDefinition):] continue @@ -197,19 +197,19 @@ func (p *Parser) block(data []byte) { p.nesting-- } -func (p *Parser) addBlock(typ NodeType, content []byte) *Node { +func (p *Markdown) addBlock(typ NodeType, content []byte) *Node { p.closeUnmatchedBlocks() container := p.addChild(typ, 0) container.content = content return container } -func (p *Parser) isPrefixHeading(data []byte) bool { +func (p *Markdown) isPrefixHeading(data []byte) bool { if data[0] != '#' { return false } - if p.flags&SpaceHeadings != 0 { + if p.extensions&SpaceHeadings != 0 { level := 0 for level < 6 && level < len(data) && data[level] == '#' { level++ @@ -221,7 +221,7 @@ func (p *Parser) isPrefixHeading(data []byte) bool { return true } -func (p *Parser) prefixHeading(data []byte) int { +func (p *Markdown) prefixHeading(data []byte) int { level := 0 for level < 6 && level < len(data) && data[level] == '#' { level++ @@ -230,7 +230,7 @@ func (p *Parser) prefixHeading(data []byte) int { end := skipUntilChar(data, i, '\n') skip := end id := "" - if p.flags&HeadingIDs != 0 { + if p.extensions&HeadingIDs != 0 { j, k := 0, 0 // find start/end of heading id for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { @@ -257,7 +257,7 @@ func (p *Parser) prefixHeading(data []byte) int { end-- } if end > i { - if id == "" && p.flags&AutoHeadingIDs != 0 { + if id == "" && p.extensions&AutoHeadingIDs != 0 { id = sanitized_anchor_name.Create(string(data[i:end])) } block := p.addBlock(Heading, data[i:end]) @@ -267,7 +267,7 @@ func (p *Parser) prefixHeading(data []byte) int { return skip } -func (p *Parser) isUnderlinedHeading(data []byte) int { +func (p *Markdown) isUnderlinedHeading(data []byte) int { // test of level 1 heading if data[0] == '=' { i := skipChar(data, 1, '=') @@ -291,7 +291,7 @@ func (p *Parser) isUnderlinedHeading(data []byte) int { return 0 } -func (p *Parser) titleBlock(data []byte, doRender bool) int { +func (p *Markdown) titleBlock(data []byte, doRender bool) int { if data[0] != '%' { return 0 } @@ -315,7 +315,7 @@ func (p *Parser) titleBlock(data []byte, doRender bool) int { return consumed } -func (p *Parser) html(data []byte, doRender bool) int { +func (p *Markdown) html(data []byte, doRender bool) int { var i, j int // identify the opening tag @@ -419,7 +419,7 @@ func finalizeHTMLBlock(block *Node) { } // HTML comment, lax form -func (p *Parser) htmlComment(data []byte, doRender bool) int { +func (p *Markdown) htmlComment(data []byte, doRender bool) int { i := p.inlineHTMLComment(data) // needs to end with a blank line if j := p.isEmpty(data[i:]); j > 0 { @@ -439,7 +439,7 @@ func (p *Parser) htmlComment(data []byte, doRender bool) int { } // HR, which is the only self-closing block tag considered -func (p *Parser) htmlHr(data []byte, doRender bool) int { +func (p *Markdown) htmlHr(data []byte, doRender bool) int { if len(data) < 4 { return 0 } @@ -472,7 +472,7 @@ func (p *Parser) htmlHr(data []byte, doRender bool) int { return 0 } -func (p *Parser) htmlFindTag(data []byte) (string, bool) { +func (p *Markdown) htmlFindTag(data []byte) (string, bool) { i := 0 for i < len(data) && isalnum(data[i]) { i++ @@ -484,7 +484,7 @@ func (p *Parser) htmlFindTag(data []byte) (string, bool) { return "", false } -func (p *Parser) htmlFindEnd(tag string, data []byte) int { +func (p *Markdown) htmlFindEnd(tag string, data []byte) int { // assume data[0] == '<' && data[1] == '/' already tested if tag == "hr" { return 2 @@ -508,7 +508,7 @@ func (p *Parser) htmlFindEnd(tag string, data []byte) int { return i } - if p.flags&LaxHTMLBlocks != 0 { + if p.extensions&LaxHTMLBlocks != 0 { return i } if skip = p.isEmpty(data[i:]); skip == 0 { @@ -519,7 +519,7 @@ func (p *Parser) htmlFindEnd(tag string, data []byte) int { return i + skip } -func (*Parser) isEmpty(data []byte) int { +func (*Markdown) isEmpty(data []byte) int { // it is okay to call isEmpty on an empty buffer if len(data) == 0 { return 0 @@ -537,7 +537,7 @@ func (*Parser) isEmpty(data []byte) int { return i } -func (*Parser) isHRule(data []byte) bool { +func (*Markdown) isHRule(data []byte) bool { i := 0 // skip up to three spaces @@ -667,7 +667,7 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker // fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, // or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. // If doRender is true, a final newline is mandatory to recognize the fenced code block. -func (p *Parser) fencedCodeBlock(data []byte, doRender bool) int { +func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { var syntax string beg, marker := isFenceLine(data, &syntax, "") if beg == 0 || beg >= len(data) { @@ -739,7 +739,7 @@ func finalizeCodeBlock(block *Node) { block.content = nil } -func (p *Parser) table(data []byte) int { +func (p *Markdown) table(data []byte) int { table := p.addBlock(Table, nil) i, columns := p.tableHeader(data) if i == 0 { @@ -782,7 +782,7 @@ func isBackslashEscaped(data []byte, i int) bool { return backslashes&1 == 1 } -func (p *Parser) tableHeader(data []byte) (size int, columns []CellAlignFlags) { +func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) { i := 0 colCount := 1 for i = 0; i < len(data) && data[i] != '\n'; i++ { @@ -895,7 +895,7 @@ func (p *Parser) tableHeader(data []byte) (size int, columns []CellAlignFlags) { return } -func (p *Parser) tableRow(data []byte, columns []CellAlignFlags, header bool) { +func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) { p.addBlock(TableRow, nil) i, col := 0, 0 @@ -939,7 +939,7 @@ func (p *Parser) tableRow(data []byte, columns []CellAlignFlags, header bool) { } // returns blockquote prefix length -func (p *Parser) quotePrefix(data []byte) int { +func (p *Markdown) quotePrefix(data []byte) int { i := 0 for i < 3 && i < len(data) && data[i] == ' ' { i++ @@ -955,7 +955,7 @@ func (p *Parser) quotePrefix(data []byte) int { // blockquote ends with at least one blank line // followed by something without a blockquote prefix -func (p *Parser) terminateBlockquote(data []byte, beg, end int) bool { +func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool { if p.isEmpty(data[beg:]) <= 0 { return false } @@ -966,7 +966,7 @@ func (p *Parser) terminateBlockquote(data []byte, beg, end int) bool { } // parse a blockquote fragment -func (p *Parser) quote(data []byte) int { +func (p *Markdown) quote(data []byte) int { block := p.addBlock(BlockQuote, nil) var raw bytes.Buffer beg, end := 0, 0 @@ -976,7 +976,7 @@ func (p *Parser) quote(data []byte) int { // fenced code and if one's found, incorporate it altogether, // irregardless of any contents inside it for end < len(data) && data[end] != '\n' { - if p.flags&FencedCode != 0 { + if p.extensions&FencedCode != 0 { if i := p.fencedCodeBlock(data[end:], false); i > 0 { // -1 to compensate for the extra end++ after the loop: end += i - 1 @@ -1004,7 +1004,7 @@ func (p *Parser) quote(data []byte) int { } // returns prefix length for block code -func (p *Parser) codePrefix(data []byte) int { +func (p *Markdown) codePrefix(data []byte) int { if len(data) >= 1 && data[0] == '\t' { return 1 } @@ -1014,7 +1014,7 @@ func (p *Parser) codePrefix(data []byte) int { return 0 } -func (p *Parser) code(data []byte) int { +func (p *Markdown) code(data []byte) int { var work bytes.Buffer i := 0 @@ -1064,7 +1064,7 @@ func (p *Parser) code(data []byte) int { } // returns unordered list item prefix -func (p *Parser) uliPrefix(data []byte) int { +func (p *Markdown) uliPrefix(data []byte) int { i := 0 // start with up to 3 spaces for i < len(data) && i < 3 && data[i] == ' ' { @@ -1082,7 +1082,7 @@ func (p *Parser) uliPrefix(data []byte) int { } // returns ordered list item prefix -func (p *Parser) oliPrefix(data []byte) int { +func (p *Markdown) oliPrefix(data []byte) int { i := 0 // start with up to 3 spaces @@ -1107,7 +1107,7 @@ func (p *Parser) oliPrefix(data []byte) int { } // returns definition list item prefix -func (p *Parser) dliPrefix(data []byte) int { +func (p *Markdown) dliPrefix(data []byte) int { if len(data) < 2 { return 0 } @@ -1123,7 +1123,7 @@ func (p *Parser) dliPrefix(data []byte) int { } // parse ordered or unordered list block -func (p *Parser) list(data []byte, flags ListType) int { +func (p *Markdown) list(data []byte, flags ListType) int { i := 0 flags |= ListItemBeginningOfList block := p.addBlock(List, nil) @@ -1191,7 +1191,7 @@ func finalizeList(block *Node) { // Parse a single list item. // Assumes initial prefix is already removed if this is a sublist. -func (p *Parser) listItem(data []byte, flags *ListType) int { +func (p *Markdown) listItem(data []byte, flags *ListType) int { // keep track of the indentation of the first line itemIndent := 0 if data[0] == '\t' { @@ -1383,7 +1383,7 @@ gatherlines: } // render a single paragraph that has already been parsed out -func (p *Parser) renderParagraph(data []byte) { +func (p *Markdown) renderParagraph(data []byte) { if len(data) == 0 { return } @@ -1408,13 +1408,13 @@ func (p *Parser) renderParagraph(data []byte) { p.addBlock(Paragraph, data[beg:end]) } -func (p *Parser) paragraph(data []byte) int { +func (p *Markdown) paragraph(data []byte) int { // prev: index of 1st char of previous line // line: index of 1st char of current line // i: index of cursor/end of current line var prev, line, i int tabSize := TabSizeDefault - if p.flags&TabSizeEight != 0 { + if p.extensions&TabSizeEight != 0 { tabSize = TabSizeDouble } // keep going until we find something to mark the end of the paragraph @@ -1435,7 +1435,7 @@ func (p *Parser) paragraph(data []byte) int { // 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&DefinitionLists != 0 { + if p.extensions&DefinitionLists != 0 { if i < len(data)-1 && data[i+1] == ':' { return p.list(data[prev:], ListTypeDefinition) } @@ -1461,7 +1461,7 @@ func (p *Parser) paragraph(data []byte) int { } id := "" - if p.flags&AutoHeadingIDs != 0 { + if p.extensions&AutoHeadingIDs != 0 { id = sanitized_anchor_name.Create(string(data[prev:eol])) } @@ -1478,7 +1478,7 @@ func (p *Parser) paragraph(data []byte) int { } // if the next line starts a block of HTML, then the paragraph ends here - if p.flags&LaxHTMLBlocks != 0 { + if p.extensions&LaxHTMLBlocks != 0 { if data[i] == '<' && p.html(current, false) > 0 { // rewind to before the HTML block p.renderParagraph(data[:i]) @@ -1493,7 +1493,7 @@ func (p *Parser) paragraph(data []byte) int { } // if there's a fenced code block, paragraph is over - if p.flags&FencedCode != 0 { + if p.extensions&FencedCode != 0 { if p.fencedCodeBlock(current, false) > 0 { p.renderParagraph(data[:i]) return i @@ -1501,7 +1501,7 @@ func (p *Parser) paragraph(data []byte) int { } // if there's a definition list item, prev line is a definition term - if p.flags&DefinitionLists != 0 { + if p.extensions&DefinitionLists != 0 { if p.dliPrefix(current) != 0 { ret := p.list(data[prev:], ListTypeDefinition) return ret @@ -1509,7 +1509,7 @@ func (p *Parser) paragraph(data []byte) int { } // if there's a list after this, paragraph is over - if p.flags&NoEmptyLineBeforeBlock != 0 { + if p.extensions&NoEmptyLineBeforeBlock != 0 { if p.uliPrefix(current) != 0 || p.oliPrefix(current) != 0 || p.quotePrefix(current) != 0 || diff --git a/doc.go b/doc.go index 321d6541..6cf29cee 100644 --- a/doc.go +++ b/doc.go @@ -4,14 +4,14 @@ // then be further processed to HTML (provided by Blackfriday itself) or other // formats (provided by the community). // -// The simplest way to invoke Blackfriday is to call one of Markdown* -// functions. It will take a text input and produce a text output in HTML (or -// other format). +// The simplest way to invoke Blackfriday is to call the Markdown function. It +// will take a text input and produce a text output in HTML (or other format). // -// A slightly more sophisticated way to use Blackfriday is to call Parse, which -// returns a syntax tree for the input document. You can use that to write your -// own renderer or, for example, to leverage Blackfriday's parsing for content -// extraction from markdown documents. +// A slightly more sophisticated way to use Blackfriday is to create a Markdown +// processor and to call Parse, which returns a syntax tree for the input +// document. You can leverage Blackfriday's parsing for content extraction from +// markdown documents. You can assign a custom renderer and set various options +// to the Markdown processor. // // If you're interested in calling Blackfriday from command line, see // https://github.com/russross/blackfriday-tool. diff --git a/helpers_test.go b/helpers_test.go index d2639d28..089c730e 100644 --- a/helpers_test.go +++ b/helpers_test.go @@ -46,7 +46,7 @@ func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, s func runMarkdown(input string, params TestParams) string { params.HTMLRendererParameters.Flags = params.HTMLFlags renderer := NewHTMLRenderer(params.HTMLRendererParameters) - return string(Markdown([]byte(input), WithRenderer(renderer), + return string(Run([]byte(input), WithRenderer(renderer), WithExtensions(params.extensions), WithRefOverride(params.referenceOverride))) } diff --git a/inline.go b/inline.go index 67ca2192..3d633106 100644 --- a/inline.go +++ b/inline.go @@ -32,7 +32,7 @@ var ( // data is the complete block being rendered // offset is the number of valid chars before the current cursor -func (p *Parser) inline(currBlock *Node, data []byte) { +func (p *Markdown) inline(currBlock *Node, data []byte) { // handlers might call us recursively: enforce a maximum depth if p.nesting >= p.maxNesting || len(data) == 0 { return @@ -69,7 +69,7 @@ func (p *Parser) inline(currBlock *Node, data []byte) { } // single and double emphasis parsing -func emphasis(p *Parser, data []byte, offset int) (int, *Node) { +func emphasis(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] c := data[0] @@ -114,7 +114,7 @@ func emphasis(p *Parser, data []byte, offset int) (int, *Node) { return 0, nil } -func codeSpan(p *Parser, data []byte, offset int) (int, *Node) { +func codeSpan(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] nb := 0 @@ -161,7 +161,7 @@ func codeSpan(p *Parser, data []byte, offset int) (int, *Node) { } // newline preceded by two spaces becomes
-func maybeLineBreak(p *Parser, data []byte, offset int) (int, *Node) { +func maybeLineBreak(p *Markdown, data []byte, offset int) (int, *Node) { origOffset := offset for offset < len(data) && data[offset] == ' ' { offset++ @@ -177,8 +177,8 @@ func maybeLineBreak(p *Parser, data []byte, offset int) (int, *Node) { } // newline without two spaces works when HardLineBreak is enabled -func lineBreak(p *Parser, data []byte, offset int) (int, *Node) { - if p.flags&HardLineBreak != 0 { +func lineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + if p.extensions&HardLineBreak != 0 { return 1, NewNode(Hardbreak) } return 0, nil @@ -200,14 +200,14 @@ func isReferenceStyleLink(data []byte, pos int, t linkType) bool { return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^' } -func maybeImage(p *Parser, data []byte, offset int) (int, *Node) { +func maybeImage(p *Markdown, data []byte, offset int) (int, *Node) { if offset < len(data)-1 && data[offset+1] == '[' { return link(p, data, offset) } return 0, nil } -func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, *Node) { +func maybeInlineFootnote(p *Markdown, data []byte, offset int) (int, *Node) { if offset < len(data)-1 && data[offset+1] == '[' { return link(p, data, offset) } @@ -215,7 +215,7 @@ func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, *Node) { } // '[': parse a link or an image or a footnote -func link(p *Parser, data []byte, offset int) (int, *Node) { +func link(p *Markdown, data []byte, offset int) (int, *Node) { // no links allowed inside regular links, footnote, and deferred footnotes if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') { return 0, nil @@ -225,7 +225,7 @@ func link(p *Parser, data []byte, offset int) (int, *Node) { switch { // special case: ![^text] == deferred footnote (that follows something with // an exclamation point) - case p.flags&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^': + case p.extensions&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^': t = linkDeferredFootnote // ![alt] == image case offset >= 0 && data[offset] == '!': @@ -233,7 +233,7 @@ func link(p *Parser, data []byte, offset int) (int, *Node) { offset++ // ^[text] == inline footnote // [^refId] == deferred footnote - case p.flags&Footnotes != 0: + case p.extensions&Footnotes != 0: if offset >= 0 && data[offset] == '^' { t = linkInlineFootnote offset++ @@ -573,7 +573,7 @@ func link(p *Parser, data []byte, offset int) (int, *Node) { return i, linkNode } -func (p *Parser) inlineHTMLComment(data []byte) int { +func (p *Markdown) inlineHTMLComment(data []byte) int { if len(data) < 5 { return 0 } @@ -613,7 +613,7 @@ const ( ) // '<' when tags or autolinks are allowed -func leftAngle(p *Parser, data []byte, offset int) (int, *Node) { +func leftAngle(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] altype, end := tagLength(data) if size := p.inlineHTMLComment(data); size > 0 { @@ -646,11 +646,11 @@ func leftAngle(p *Parser, data []byte, offset int) (int, *Node) { // '\\' backslash escape var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~") -func escape(p *Parser, data []byte, offset int) (int, *Node) { +func escape(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] if len(data) > 1 { - if p.flags&BackslashLineBreak != 0 && data[1] == '\n' { + if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' { return 2, NewNode(Hardbreak) } if bytes.IndexByte(escapeChars, data[1]) < 0 { @@ -686,7 +686,7 @@ func unescapeText(ob *bytes.Buffer, src []byte) { // '&' escaped when it doesn't belong to an entity // valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; -func entity(p *Parser, data []byte, offset int) (int, *Node) { +func entity(p *Markdown, data []byte, offset int) (int, *Node) { data = data[offset:] end := 1 @@ -748,7 +748,7 @@ var protocolPrefixes = [][]byte{ const shortestPrefix = 6 // len("ftp://"), the shortest of the above -func maybeAutoLink(p *Parser, data []byte, offset int) (int, *Node) { +func maybeAutoLink(p *Markdown, data []byte, offset int) (int, *Node) { // quick check to rule out most false hits if p.insideLink || len(data) < offset+shortestPrefix { return 0, nil @@ -765,7 +765,7 @@ func maybeAutoLink(p *Parser, data []byte, offset int) (int, *Node) { return 0, nil } -func autoLink(p *Parser, data []byte, offset int) (int, *Node) { +func autoLink(p *Markdown, data []byte, offset int) (int, *Node) { // Now a more expensive check to see if we're not inside an anchor element anchorStart := offset offsetFromAnchor := 0 @@ -1095,7 +1095,7 @@ func helperFindEmphChar(data []byte, c byte) int { return 0 } -func helperEmphasis(p *Parser, data []byte, c byte) (int, *Node) { +func helperEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { i := 0 // skip one symbol if coming from emph3 @@ -1120,7 +1120,7 @@ func helperEmphasis(p *Parser, data []byte, c byte) (int, *Node) { if data[i] == c && !isspace(data[i-1]) { - if p.flags&NoIntraEmphasis != 0 { + if p.extensions&NoIntraEmphasis != 0 { if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) { continue } @@ -1135,7 +1135,7 @@ func helperEmphasis(p *Parser, data []byte, c byte) (int, *Node) { return 0, nil } -func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *Node) { +func helperDoubleEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { i := 0 for i < len(data) { @@ -1159,7 +1159,7 @@ func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *Node) { return 0, nil } -func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, *Node) { +func helperTripleEmphasis(p *Markdown, data []byte, offset int, c byte) (int, *Node) { i := 0 origData := data data = data[offset:] diff --git a/markdown.go b/markdown.go index 9b7c8181..9c70e22e 100644 --- a/markdown.go +++ b/markdown.go @@ -154,18 +154,20 @@ type Renderer interface { // Callback functions for inline parsing. One such function is defined // for each character that triggers a response when parsing inline data. -type inlineParser func(p *Parser, data []byte, offset int) (int, *Node) - -// Parser holds runtime state used by the parser. -// This is constructed by the Markdown function. -type Parser struct { - refOverride ReferenceOverrideFunc - refs map[string]*reference - inlineCallback [256]inlineParser - flags Extensions - nesting int - maxNesting int - insideLink bool +type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node) + +// Markdown is a type that holds: +// - extensions and the runtime state used by Parse, +// - the renderer. +type Markdown struct { + renderer Renderer + referenceOverride ReferenceOverrideFunc + refs map[string]*reference + inlineCallback [256]inlineParser + extensions Extensions + nesting int + maxNesting int + insideLink bool // Footnotes need to be ordered as well as available to quickly check for // presence. If a ref is also a footnote, it's stored both in refs and here @@ -179,9 +181,9 @@ type Parser struct { allClosed bool } -func (p *Parser) getRef(refid string) (ref *reference, found bool) { - if p.refOverride != nil { - r, overridden := p.refOverride(refid) +func (p *Markdown) getRef(refid string) (ref *reference, found bool) { + if p.referenceOverride != nil { + r, overridden := p.referenceOverride(refid) if overridden { if r == nil { return nil, false @@ -199,17 +201,17 @@ func (p *Parser) getRef(refid string) (ref *reference, found bool) { return ref, found } -func (p *Parser) finalize(block *Node) { +func (p *Markdown) finalize(block *Node) { above := block.Parent block.open = false p.tip = above } -func (p *Parser) addChild(node NodeType, offset uint32) *Node { +func (p *Markdown) addChild(node NodeType, offset uint32) *Node { return p.addExistingChild(NewNode(node), offset) } -func (p *Parser) addExistingChild(node *Node, offset uint32) *Node { +func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node { for !p.tip.canContain(node.Type) { p.finalize(p.tip) } @@ -218,7 +220,7 @@ func (p *Parser) addExistingChild(node *Node, offset uint32) *Node { return node } -func (p *Parser) closeUnmatchedBlocks() { +func (p *Markdown) closeUnmatchedBlocks() { if !p.allClosed { for p.oldTip != p.lastMatchedContainer { parent := p.oldTip.Parent @@ -253,32 +255,13 @@ type Reference struct { // See the documentation in Options for more details on use-case. type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) -// Processor contains all the state necessary for Blackfriday to operate. -type Processor struct { - r Renderer - extensions Extensions - referenceOverride ReferenceOverrideFunc -} - -// DefaultProcessor creates the processor tuned to the most common behavior. -func DefaultProcessor() *Processor { - return &Processor{ - r: NewHTMLRenderer(HTMLRendererParameters{ - Flags: CommonHTMLFlags, - }), - extensions: CommonExtensions, - } -} - -// NewParser constructs a Parser. You can use the same With* functions as for -// Markdown() to customize parser's behavior. -func (proc *Processor) NewParser(opts ...Option) *Parser { +// New constructs a Markdown processor. You can use the same With* functions as +// for Run() to customize parser's behavior and the renderer. +func New(opts ...Option) *Markdown { + var p Markdown for _, opt := range opts { - opt(proc) + opt(&p) } - var p Parser - p.flags = proc.extensions - p.refOverride = proc.referenceOverride p.refs = make(map[string]*reference) p.maxNesting = 16 p.insideLink = false @@ -292,7 +275,7 @@ func (proc *Processor) NewParser(opts ...Option) *Parser { p.inlineCallback[' '] = maybeLineBreak p.inlineCallback['*'] = emphasis p.inlineCallback['_'] = emphasis - if proc.extensions&Strikethrough != 0 { + if p.extensions&Strikethrough != 0 { p.inlineCallback['~'] = emphasis } p.inlineCallback['`'] = codeSpan @@ -303,7 +286,7 @@ func (proc *Processor) NewParser(opts ...Option) *Parser { p.inlineCallback['&'] = entity p.inlineCallback['!'] = maybeImage p.inlineCallback['^'] = maybeInlineFootnote - if proc.extensions&Autolink != 0 { + if p.extensions&Autolink != 0 { p.inlineCallback['h'] = maybeAutoLink p.inlineCallback['m'] = maybeAutoLink p.inlineCallback['f'] = maybeAutoLink @@ -311,35 +294,35 @@ func (proc *Processor) NewParser(opts ...Option) *Parser { p.inlineCallback['M'] = maybeAutoLink p.inlineCallback['F'] = maybeAutoLink } - if proc.extensions&Footnotes != 0 { + if p.extensions&Footnotes != 0 { p.notes = make([]*reference, 0) } return &p } -// Option customizes Processor's default behavior. -type Option func(*Processor) +// Option customizes the Markdown processor's default behavior. +type Option func(*Markdown) // WithRenderer allows you to override the default renderer. func WithRenderer(r Renderer) Option { - return func(p *Processor) { - p.r = r + return func(p *Markdown) { + p.renderer = r } } // WithExtensions allows you to pick some of the many extensions provided by // Blackfriday. You can bitwise OR them. func WithExtensions(e Extensions) Option { - return func(p *Processor) { + return func(p *Markdown) { p.extensions = e } } // WithNoExtensions turns off all extensions and custom behavior. func WithNoExtensions() Option { - return func(p *Processor) { + return func(p *Markdown) { p.extensions = NoExtensions - p.r = NewHTMLRenderer(HTMLRendererParameters{ + p.renderer = NewHTMLRenderer(HTMLRendererParameters{ Flags: HTMLFlagsNone, }) } @@ -360,41 +343,45 @@ func WithNoExtensions() Option { // the override function indicates an override did not occur, the refids at // the bottom will be used to fill in the link details. func WithRefOverride(o ReferenceOverrideFunc) Option { - return func(p *Processor) { + return func(p *Markdown) { p.referenceOverride = o } } -// Markdown is the main entry point to Blackfriday. It parses and renders a +// Run is the main entry point to Blackfriday. It parses and renders a // block of markdown-encoded text. // -// The simplest invocation of Markdown takes one argument, input: -// output := Markdown(input) +// The simplest invocation of Run takes one argument, input: +// output := Run(input) // This will parse the input with CommonExtensions enabled and render it with // the default HTMLRenderer (with CommonHTMLFlags). // -// Variadic arguments opts can customize the default behavior. Since Processor +// Variadic arguments opts can customize the default behavior. Since Markdown // type does not contain exported fields, you can not use it directly. Instead, // use the With* functions. For example, this will call the most basic // functionality, with no extensions: -// output := Markdown(input, WithNoExtensions()) +// output := Run(input, WithNoExtensions()) // // You can use any number of With* arguments, even contradicting ones. They // will be applied in order of appearance and the latter will override the // former: -// output := Markdown(input, WithNoExtensions(), WithExtensions(exts), +// output := Run(input, WithNoExtensions(), WithExtensions(exts), // WithRenderer(yourRenderer)) -func Markdown(input []byte, opts ...Option) []byte { - p := DefaultProcessor() - parser := p.NewParser(opts...) - return p.r.Render(parser.Parse(input)) +func Run(input []byte, opts ...Option) []byte { + r := NewHTMLRenderer(HTMLRendererParameters{ + Flags: CommonHTMLFlags, + }) + optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)} + optList = append(optList, opts...) + parser := New(optList...) + return parser.renderer.Render(parser.Parse(input)) } // Parse is an entry point to the parsing part of Blackfriday. It takes an // input markdown document and produces a syntax tree for its contents. This // tree can then be rendered with a default or custom renderer, or // analyzed/transformed by the caller to whatever non-standard needs they have. -func (p *Parser) Parse(input []byte) *Node { +func (p *Markdown) Parse(input []byte) *Node { p.block(input) // Walk the tree and finish up some of unfinished blocks for p.tip != nil { @@ -412,8 +399,8 @@ func (p *Parser) Parse(input []byte) *Node { return p.doc } -func (p *Parser) parseRefsToAST() { - if p.flags&Footnotes == 0 || len(p.notes) == 0 { +func (p *Markdown) parseRefsToAST() { + if p.extensions&Footnotes == 0 || len(p.notes) == 0 { return } p.tip = p.doc @@ -537,7 +524,7 @@ func (r *reference) String() string { // (in the render struct). // Returns the number of bytes to skip to move past it, // or zero if the first line is not a reference. -func isReference(p *Parser, data []byte, tabSize int) int { +func isReference(p *Markdown, data []byte, tabSize int) int { // up to 3 optional leading spaces if len(data) < 4 { return 0 @@ -554,7 +541,7 @@ func isReference(p *Parser, data []byte, tabSize int) int { return 0 } i++ - if p.flags&Footnotes != 0 { + if p.extensions&Footnotes != 0 { if i < len(data) && data[i] == '^' { // we can set it to anything here because the proper noteIds will // be assigned later during the second pass. It just has to be != 0 @@ -605,7 +592,7 @@ func isReference(p *Parser, data []byte, tabSize int) int { hasBlock bool ) - if p.flags&Footnotes != 0 && noteID != 0 { + if p.extensions&Footnotes != 0 && noteID != 0 { linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) lineEnd = linkEnd } else { @@ -640,7 +627,7 @@ func isReference(p *Parser, data []byte, tabSize int) int { return lineEnd } -func scanLinkRef(p *Parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { +func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { // link: whitespace-free sequence, optionally between angle brackets if data[i] == '<' { i++ @@ -714,7 +701,7 @@ func scanLinkRef(p *Parser, data []byte, i int) (linkOffset, linkEnd, titleOffse // blockEnd is the end of the section in the input buffer, and contents is the // extracted text that was shifted over one tab. It will need to be rendered at // the end of the document. -func scanFootnote(p *Parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { +func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { if i == 0 || len(data) == 0 { return }