mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
Change the public interface to use functional options
Convert the most important Blackfriday's function, Markdown(), to accept functional options (as per this Dave Cheney's post: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)
This commit is contained in:
parent
d04a53c644
commit
e81d1d1138
58
block.go
58
block.go
|
@ -34,7 +34,7 @@ var (
|
||||||
// Parse block-level data.
|
// Parse block-level data.
|
||||||
// Note: this function and many that it calls assume that
|
// Note: this function and many that it calls assume that
|
||||||
// the input buffer ends with a newline.
|
// the input buffer ends with a newline.
|
||||||
func (p *parser) block(data []byte) {
|
func (p *Parser) block(data []byte) {
|
||||||
// this is called recursively: enforce a maximum depth
|
// this is called recursively: enforce a maximum depth
|
||||||
if p.nesting >= p.maxNesting {
|
if p.nesting >= p.maxNesting {
|
||||||
return
|
return
|
||||||
|
@ -197,14 +197,14 @@ func (p *parser) block(data []byte) {
|
||||||
p.nesting--
|
p.nesting--
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) addBlock(typ NodeType, content []byte) *Node {
|
func (p *Parser) addBlock(typ NodeType, content []byte) *Node {
|
||||||
p.closeUnmatchedBlocks()
|
p.closeUnmatchedBlocks()
|
||||||
container := p.addChild(typ, 0)
|
container := p.addChild(typ, 0)
|
||||||
container.content = content
|
container.content = content
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) isPrefixHeader(data []byte) bool {
|
func (p *Parser) isPrefixHeader(data []byte) bool {
|
||||||
if data[0] != '#' {
|
if data[0] != '#' {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ func (p *parser) isPrefixHeader(data []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) prefixHeader(data []byte) int {
|
func (p *Parser) prefixHeader(data []byte) int {
|
||||||
level := 0
|
level := 0
|
||||||
for level < 6 && level < len(data) && data[level] == '#' {
|
for level < 6 && level < len(data) && data[level] == '#' {
|
||||||
level++
|
level++
|
||||||
|
@ -267,7 +267,7 @@ func (p *parser) prefixHeader(data []byte) int {
|
||||||
return skip
|
return skip
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) isUnderlinedHeader(data []byte) int {
|
func (p *Parser) isUnderlinedHeader(data []byte) int {
|
||||||
// test of level 1 header
|
// test of level 1 header
|
||||||
if data[0] == '=' {
|
if data[0] == '=' {
|
||||||
i := skipChar(data, 1, '=')
|
i := skipChar(data, 1, '=')
|
||||||
|
@ -291,7 +291,7 @@ func (p *parser) isUnderlinedHeader(data []byte) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) titleBlock(data []byte, doRender bool) int {
|
func (p *Parser) titleBlock(data []byte, doRender bool) int {
|
||||||
if data[0] != '%' {
|
if data[0] != '%' {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -315,7 +315,7 @@ func (p *parser) titleBlock(data []byte, doRender bool) int {
|
||||||
return consumed
|
return consumed
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) html(data []byte, doRender bool) int {
|
func (p *Parser) html(data []byte, doRender bool) int {
|
||||||
var i, j int
|
var i, j int
|
||||||
|
|
||||||
// identify the opening tag
|
// identify the opening tag
|
||||||
|
@ -419,7 +419,7 @@ func finalizeHTMLBlock(block *Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTML comment, lax form
|
// HTML comment, lax form
|
||||||
func (p *parser) htmlComment(data []byte, doRender bool) int {
|
func (p *Parser) htmlComment(data []byte, doRender bool) int {
|
||||||
i := p.inlineHTMLComment(data)
|
i := p.inlineHTMLComment(data)
|
||||||
// needs to end with a blank line
|
// needs to end with a blank line
|
||||||
if j := p.isEmpty(data[i:]); j > 0 {
|
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
|
// HR, which is the only self-closing block tag considered
|
||||||
func (p *parser) htmlHr(data []byte, doRender bool) int {
|
func (p *Parser) htmlHr(data []byte, doRender bool) int {
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -472,7 +472,7 @@ func (p *parser) htmlHr(data []byte, doRender bool) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) htmlFindTag(data []byte) (string, bool) {
|
func (p *Parser) htmlFindTag(data []byte) (string, bool) {
|
||||||
i := 0
|
i := 0
|
||||||
for i < len(data) && isalnum(data[i]) {
|
for i < len(data) && isalnum(data[i]) {
|
||||||
i++
|
i++
|
||||||
|
@ -484,7 +484,7 @@ func (p *parser) htmlFindTag(data []byte) (string, bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) htmlFindEnd(tag string, data []byte) int {
|
func (p *Parser) htmlFindEnd(tag string, data []byte) int {
|
||||||
// assume data[0] == '<' && data[1] == '/' already tested
|
// assume data[0] == '<' && data[1] == '/' already tested
|
||||||
if tag == "hr" {
|
if tag == "hr" {
|
||||||
return 2
|
return 2
|
||||||
|
@ -519,7 +519,7 @@ func (p *parser) htmlFindEnd(tag string, data []byte) int {
|
||||||
return i + skip
|
return i + skip
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*parser) isEmpty(data []byte) int {
|
func (*Parser) isEmpty(data []byte) int {
|
||||||
// it is okay to call isEmpty on an empty buffer
|
// it is okay to call isEmpty on an empty buffer
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return 0
|
return 0
|
||||||
|
@ -537,7 +537,7 @@ func (*parser) isEmpty(data []byte) int {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*parser) isHRule(data []byte) bool {
|
func (*Parser) isHRule(data []byte) bool {
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
// skip up to three spaces
|
// 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,
|
// 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.
|
// 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.
|
// 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 *Parser) fencedCodeBlock(data []byte, doRender bool) int {
|
||||||
var syntax string
|
var syntax string
|
||||||
beg, marker := isFenceLine(data, &syntax, "")
|
beg, marker := isFenceLine(data, &syntax, "")
|
||||||
if beg == 0 || beg >= len(data) {
|
if beg == 0 || beg >= len(data) {
|
||||||
|
@ -739,7 +739,7 @@ func finalizeCodeBlock(block *Node) {
|
||||||
block.content = nil
|
block.content = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) table(data []byte) int {
|
func (p *Parser) table(data []byte) int {
|
||||||
table := p.addBlock(Table, nil)
|
table := p.addBlock(Table, nil)
|
||||||
i, columns := p.tableHeader(data)
|
i, columns := p.tableHeader(data)
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
@ -782,7 +782,7 @@ func isBackslashEscaped(data []byte, i int) bool {
|
||||||
return backslashes&1 == 1
|
return backslashes&1 == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
|
func (p *Parser) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
|
||||||
i := 0
|
i := 0
|
||||||
colCount := 1
|
colCount := 1
|
||||||
for i = 0; i < len(data) && data[i] != '\n'; i++ {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) tableRow(data []byte, columns []CellAlignFlags, header bool) {
|
func (p *Parser) tableRow(data []byte, columns []CellAlignFlags, header bool) {
|
||||||
p.addBlock(TableRow, nil)
|
p.addBlock(TableRow, nil)
|
||||||
i, col := 0, 0
|
i, col := 0, 0
|
||||||
|
|
||||||
|
@ -939,7 +939,7 @@ func (p *parser) tableRow(data []byte, columns []CellAlignFlags, header bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns blockquote prefix length
|
// returns blockquote prefix length
|
||||||
func (p *parser) quotePrefix(data []byte) int {
|
func (p *Parser) quotePrefix(data []byte) int {
|
||||||
i := 0
|
i := 0
|
||||||
for i < 3 && i < len(data) && data[i] == ' ' {
|
for i < 3 && i < len(data) && data[i] == ' ' {
|
||||||
i++
|
i++
|
||||||
|
@ -955,7 +955,7 @@ func (p *parser) quotePrefix(data []byte) int {
|
||||||
|
|
||||||
// blockquote ends with at least one blank line
|
// blockquote ends with at least one blank line
|
||||||
// followed by something without a blockquote prefix
|
// followed by something without a blockquote prefix
|
||||||
func (p *parser) terminateBlockquote(data []byte, beg, end int) bool {
|
func (p *Parser) terminateBlockquote(data []byte, beg, end int) bool {
|
||||||
if p.isEmpty(data[beg:]) <= 0 {
|
if p.isEmpty(data[beg:]) <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -966,7 +966,7 @@ func (p *parser) terminateBlockquote(data []byte, beg, end int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse a blockquote fragment
|
// parse a blockquote fragment
|
||||||
func (p *parser) quote(data []byte) int {
|
func (p *Parser) quote(data []byte) int {
|
||||||
block := p.addBlock(BlockQuote, nil)
|
block := p.addBlock(BlockQuote, nil)
|
||||||
var raw bytes.Buffer
|
var raw bytes.Buffer
|
||||||
beg, end := 0, 0
|
beg, end := 0, 0
|
||||||
|
@ -1004,7 +1004,7 @@ func (p *parser) quote(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns prefix length for block code
|
// returns prefix length for block code
|
||||||
func (p *parser) codePrefix(data []byte) int {
|
func (p *Parser) codePrefix(data []byte) int {
|
||||||
if len(data) >= 1 && data[0] == '\t' {
|
if len(data) >= 1 && data[0] == '\t' {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -1014,7 +1014,7 @@ func (p *parser) codePrefix(data []byte) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) code(data []byte) int {
|
func (p *Parser) code(data []byte) int {
|
||||||
var work bytes.Buffer
|
var work bytes.Buffer
|
||||||
|
|
||||||
i := 0
|
i := 0
|
||||||
|
@ -1064,7 +1064,7 @@ func (p *parser) code(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns unordered list item prefix
|
// returns unordered list item prefix
|
||||||
func (p *parser) uliPrefix(data []byte) int {
|
func (p *Parser) uliPrefix(data []byte) int {
|
||||||
i := 0
|
i := 0
|
||||||
// start with up to 3 spaces
|
// start with up to 3 spaces
|
||||||
for i < len(data) && i < 3 && data[i] == ' ' {
|
for i < len(data) && i < 3 && data[i] == ' ' {
|
||||||
|
@ -1082,7 +1082,7 @@ func (p *parser) uliPrefix(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns ordered list item prefix
|
// returns ordered list item prefix
|
||||||
func (p *parser) oliPrefix(data []byte) int {
|
func (p *Parser) oliPrefix(data []byte) int {
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
// start with up to 3 spaces
|
// start with up to 3 spaces
|
||||||
|
@ -1107,7 +1107,7 @@ func (p *parser) oliPrefix(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns definition list item prefix
|
// returns definition list item prefix
|
||||||
func (p *parser) dliPrefix(data []byte) int {
|
func (p *Parser) dliPrefix(data []byte) int {
|
||||||
if len(data) < 2 {
|
if len(data) < 2 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -1123,7 +1123,7 @@ func (p *parser) dliPrefix(data []byte) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse ordered or unordered list block
|
// parse ordered or unordered list block
|
||||||
func (p *parser) list(data []byte, flags ListType) int {
|
func (p *Parser) list(data []byte, flags ListType) int {
|
||||||
i := 0
|
i := 0
|
||||||
flags |= ListItemBeginningOfList
|
flags |= ListItemBeginningOfList
|
||||||
block := p.addBlock(List, nil)
|
block := p.addBlock(List, nil)
|
||||||
|
@ -1191,7 +1191,7 @@ func finalizeList(block *Node) {
|
||||||
|
|
||||||
// Parse a single list item.
|
// Parse a single list item.
|
||||||
// Assumes initial prefix is already removed if this is a sublist.
|
// Assumes initial prefix is already removed if this is a sublist.
|
||||||
func (p *parser) listItem(data []byte, flags *ListType) int {
|
func (p *Parser) listItem(data []byte, flags *ListType) int {
|
||||||
// keep track of the indentation of the first line
|
// keep track of the indentation of the first line
|
||||||
itemIndent := 0
|
itemIndent := 0
|
||||||
if data[0] == '\t' {
|
if data[0] == '\t' {
|
||||||
|
@ -1383,7 +1383,7 @@ gatherlines:
|
||||||
}
|
}
|
||||||
|
|
||||||
// render a single paragraph that has already been parsed out
|
// render a single paragraph that has already been parsed out
|
||||||
func (p *parser) renderParagraph(data []byte) {
|
func (p *Parser) renderParagraph(data []byte) {
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1408,7 +1408,7 @@ func (p *parser) renderParagraph(data []byte) {
|
||||||
p.addBlock(Paragraph, data[beg:end])
|
p.addBlock(Paragraph, data[beg:end])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) paragraph(data []byte) int {
|
func (p *Parser) paragraph(data []byte) int {
|
||||||
// prev: index of 1st char of previous line
|
// prev: index of 1st char of previous line
|
||||||
// line: index of 1st char of current line
|
// line: index of 1st char of current line
|
||||||
// i: index of cursor/end of current line
|
// i: index of cursor/end of current line
|
||||||
|
|
|
@ -253,7 +253,7 @@ func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsParam(t, tests, TestParams{
|
doTestsParam(t, tests, TestParams{
|
||||||
Options: Options{Extensions: HeaderIDs},
|
extensions: HeaderIDs,
|
||||||
HTMLFlags: UseXHTML,
|
HTMLFlags: UseXHTML,
|
||||||
HTMLRendererParameters: parameters,
|
HTMLRendererParameters: parameters,
|
||||||
})
|
})
|
||||||
|
@ -365,7 +365,7 @@ func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsParam(t, tests, TestParams{
|
doTestsParam(t, tests, TestParams{
|
||||||
Options: Options{Extensions: AutoHeaderIDs},
|
extensions: AutoHeaderIDs,
|
||||||
HTMLFlags: UseXHTML,
|
HTMLFlags: UseXHTML,
|
||||||
HTMLRendererParameters: parameters,
|
HTMLRendererParameters: parameters,
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,7 +21,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestParams struct {
|
type TestParams struct {
|
||||||
Options
|
extensions Extensions
|
||||||
|
referenceOverride ReferenceOverrideFunc
|
||||||
HTMLFlags
|
HTMLFlags
|
||||||
HTMLRendererParameters
|
HTMLRendererParameters
|
||||||
}
|
}
|
||||||
|
@ -45,13 +46,15 @@ func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, s
|
||||||
func runMarkdown(input string, params TestParams) string {
|
func runMarkdown(input string, params TestParams) string {
|
||||||
params.HTMLRendererParameters.Flags = params.HTMLFlags
|
params.HTMLRendererParameters.Flags = params.HTMLFlags
|
||||||
renderer := NewHTMLRenderer(params.HTMLRendererParameters)
|
renderer := NewHTMLRenderer(params.HTMLRendererParameters)
|
||||||
return string(Markdown([]byte(input), renderer, params.Options))
|
return string(Markdown([]byte(input), WithRenderer(renderer),
|
||||||
|
WithExtensions(params.extensions),
|
||||||
|
WithRefOverride(params.referenceOverride)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// doTests runs full document tests using MarkdownCommon configuration.
|
// doTests runs full document tests using MarkdownCommon configuration.
|
||||||
func doTests(t *testing.T, tests []string) {
|
func doTests(t *testing.T, tests []string) {
|
||||||
doTestsParam(t, tests, TestParams{
|
doTestsParam(t, tests, TestParams{
|
||||||
Options: DefaultOptions,
|
extensions: CommonExtensions,
|
||||||
HTMLRendererParameters: HTMLRendererParameters{
|
HTMLRendererParameters: HTMLRendererParameters{
|
||||||
Flags: CommonHTMLFlags,
|
Flags: CommonHTMLFlags,
|
||||||
},
|
},
|
||||||
|
@ -60,7 +63,7 @@ func doTests(t *testing.T, tests []string) {
|
||||||
|
|
||||||
func doTestsBlock(t *testing.T, tests []string, extensions Extensions) {
|
func doTestsBlock(t *testing.T, tests []string, extensions Extensions) {
|
||||||
doTestsParam(t, tests, TestParams{
|
doTestsParam(t, tests, TestParams{
|
||||||
Options: Options{Extensions: extensions},
|
extensions: extensions,
|
||||||
HTMLFlags: UseXHTML,
|
HTMLFlags: UseXHTML,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -124,8 +127,7 @@ func doSafeTestsInline(t *testing.T, tests []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTestsInlineParam(t *testing.T, tests []string, params TestParams) {
|
func doTestsInlineParam(t *testing.T, tests []string, params TestParams) {
|
||||||
params.Options.Extensions |= Autolink
|
params.extensions |= Autolink | Strikethrough
|
||||||
params.Options.Extensions |= Strikethrough
|
|
||||||
params.HTMLFlags |= UseXHTML
|
params.HTMLFlags |= UseXHTML
|
||||||
doTestsParam(t, tests, params)
|
doTestsParam(t, tests, params)
|
||||||
}
|
}
|
||||||
|
@ -145,7 +147,7 @@ func transformLinks(tests []string, prefix string) []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doTestsReference(t *testing.T, files []string, flag Extensions) {
|
func doTestsReference(t *testing.T, files []string, flag Extensions) {
|
||||||
params := TestParams{Options: Options{Extensions: flag}}
|
params := TestParams{extensions: flag}
|
||||||
execRecoverableTestSuite(t, files, params, func(candidate *string) {
|
execRecoverableTestSuite(t, files, params, func(candidate *string) {
|
||||||
for _, basename := range files {
|
for _, basename := range files {
|
||||||
filename := filepath.Join("testdata", basename+".text")
|
filename := filepath.Join("testdata", basename+".text")
|
||||||
|
|
34
inline.go
34
inline.go
|
@ -32,7 +32,7 @@ var (
|
||||||
// data is the complete block being rendered
|
// data is the complete block being rendered
|
||||||
// offset is the number of valid chars before the current cursor
|
// offset is the number of valid chars before the current cursor
|
||||||
|
|
||||||
func (p *parser) inline(currBlock *Node, data []byte) {
|
func (p *Parser) inline(currBlock *Node, data []byte) {
|
||||||
// handlers might call us recursively: enforce a maximum depth
|
// handlers might call us recursively: enforce a maximum depth
|
||||||
if p.nesting >= p.maxNesting || len(data) == 0 {
|
if p.nesting >= p.maxNesting || len(data) == 0 {
|
||||||
return
|
return
|
||||||
|
@ -69,7 +69,7 @@ func (p *parser) inline(currBlock *Node, data []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// single and double emphasis parsing
|
// single and double emphasis parsing
|
||||||
func emphasis(p *parser, data []byte, offset int) (int, *Node) {
|
func emphasis(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
data = data[offset:]
|
data = data[offset:]
|
||||||
c := data[0]
|
c := data[0]
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ func emphasis(p *parser, data []byte, offset int) (int, *Node) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func codeSpan(p *parser, data []byte, offset int) (int, *Node) {
|
func codeSpan(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
data = data[offset:]
|
data = data[offset:]
|
||||||
|
|
||||||
nb := 0
|
nb := 0
|
||||||
|
@ -161,7 +161,7 @@ func codeSpan(p *parser, data []byte, offset int) (int, *Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newline preceded by two spaces becomes <br>
|
// newline preceded by two spaces becomes <br>
|
||||||
func maybeLineBreak(p *parser, data []byte, offset int) (int, *Node) {
|
func maybeLineBreak(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
origOffset := offset
|
origOffset := offset
|
||||||
for offset < len(data) && data[offset] == ' ' {
|
for offset < len(data) && data[offset] == ' ' {
|
||||||
offset++
|
offset++
|
||||||
|
@ -177,7 +177,7 @@ func maybeLineBreak(p *parser, data []byte, offset int) (int, *Node) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// newline without two spaces works when HardLineBreak is enabled
|
// newline without two spaces works when HardLineBreak is enabled
|
||||||
func lineBreak(p *parser, data []byte, offset int) (int, *Node) {
|
func lineBreak(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
if p.flags&HardLineBreak != 0 {
|
if p.flags&HardLineBreak != 0 {
|
||||||
return 1, NewNode(Hardbreak)
|
return 1, NewNode(Hardbreak)
|
||||||
}
|
}
|
||||||
|
@ -200,14 +200,14 @@ func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
|
||||||
return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
|
return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeImage(p *parser, data []byte, offset int) (int, *Node) {
|
func maybeImage(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
if offset < len(data)-1 && data[offset+1] == '[' {
|
if offset < len(data)-1 && data[offset+1] == '[' {
|
||||||
return link(p, data, offset)
|
return link(p, data, offset)
|
||||||
}
|
}
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeInlineFootnote(p *parser, data []byte, offset int) (int, *Node) {
|
func maybeInlineFootnote(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
if offset < len(data)-1 && data[offset+1] == '[' {
|
if offset < len(data)-1 && data[offset+1] == '[' {
|
||||||
return link(p, data, offset)
|
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
|
// '[': parse a link or an image or a footnote
|
||||||
func link(p *parser, data []byte, offset int) (int, *Node) {
|
func link(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
// no links allowed inside regular links, footnote, and deferred footnotes
|
// 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] == '^') {
|
if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
@ -573,7 +573,7 @@ func link(p *parser, data []byte, offset int) (int, *Node) {
|
||||||
return i, linkNode
|
return i, linkNode
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) inlineHTMLComment(data []byte) int {
|
func (p *Parser) inlineHTMLComment(data []byte) int {
|
||||||
if len(data) < 5 {
|
if len(data) < 5 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
@ -613,7 +613,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// '<' when tags or autolinks are allowed
|
// '<' when tags or autolinks are allowed
|
||||||
func leftAngle(p *parser, data []byte, offset int) (int, *Node) {
|
func leftAngle(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
data = data[offset:]
|
data = data[offset:]
|
||||||
altype, end := tagLength(data)
|
altype, end := tagLength(data)
|
||||||
if size := p.inlineHTMLComment(data); size > 0 {
|
if size := p.inlineHTMLComment(data); size > 0 {
|
||||||
|
@ -646,7 +646,7 @@ func leftAngle(p *parser, data []byte, offset int) (int, *Node) {
|
||||||
// '\\' backslash escape
|
// '\\' backslash escape
|
||||||
var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
|
var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
|
||||||
|
|
||||||
func escape(p *parser, data []byte, offset int) (int, *Node) {
|
func escape(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
data = data[offset:]
|
data = data[offset:]
|
||||||
|
|
||||||
if len(data) > 1 {
|
if len(data) > 1 {
|
||||||
|
@ -686,7 +686,7 @@ func unescapeText(ob *bytes.Buffer, src []byte) {
|
||||||
|
|
||||||
// '&' escaped when it doesn't belong to an entity
|
// '&' escaped when it doesn't belong to an entity
|
||||||
// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
|
// 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 *Parser, data []byte, offset int) (int, *Node) {
|
||||||
data = data[offset:]
|
data = data[offset:]
|
||||||
|
|
||||||
end := 1
|
end := 1
|
||||||
|
@ -748,7 +748,7 @@ var protocolPrefixes = [][]byte{
|
||||||
|
|
||||||
const shortestPrefix = 6 // len("ftp://"), the shortest of the above
|
const shortestPrefix = 6 // len("ftp://"), the shortest of the above
|
||||||
|
|
||||||
func maybeAutoLink(p *parser, data []byte, offset int) (int, *Node) {
|
func maybeAutoLink(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
// quick check to rule out most false hits
|
// quick check to rule out most false hits
|
||||||
if p.insideLink || len(data) < offset+shortestPrefix {
|
if p.insideLink || len(data) < offset+shortestPrefix {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
|
@ -765,7 +765,7 @@ func maybeAutoLink(p *parser, data []byte, offset int) (int, *Node) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func autoLink(p *parser, data []byte, offset int) (int, *Node) {
|
func autoLink(p *Parser, data []byte, offset int) (int, *Node) {
|
||||||
// Now a more expensive check to see if we're not inside an anchor element
|
// Now a more expensive check to see if we're not inside an anchor element
|
||||||
anchorStart := offset
|
anchorStart := offset
|
||||||
offsetFromAnchor := 0
|
offsetFromAnchor := 0
|
||||||
|
@ -1095,7 +1095,7 @@ func helperFindEmphChar(data []byte, c byte) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func helperEmphasis(p *parser, data []byte, c byte) (int, *Node) {
|
func helperEmphasis(p *Parser, data []byte, c byte) (int, *Node) {
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
// skip one symbol if coming from emph3
|
// skip one symbol if coming from emph3
|
||||||
|
@ -1135,7 +1135,7 @@ func helperEmphasis(p *parser, data []byte, c byte) (int, *Node) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func helperDoubleEmphasis(p *parser, data []byte, c byte) (int, *Node) {
|
func helperDoubleEmphasis(p *Parser, data []byte, c byte) (int, *Node) {
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for i < len(data) {
|
for i < len(data) {
|
||||||
|
@ -1159,7 +1159,7 @@ func helperDoubleEmphasis(p *parser, data []byte, c byte) (int, *Node) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func helperTripleEmphasis(p *parser, data []byte, offset int, c byte) (int, *Node) {
|
func helperTripleEmphasis(p *Parser, data []byte, offset int, c byte) (int, *Node) {
|
||||||
i := 0
|
i := 0
|
||||||
origData := data
|
origData := data
|
||||||
data = data[offset:]
|
data = data[offset:]
|
||||||
|
|
|
@ -100,8 +100,7 @@ func TestReferenceOverride(t *testing.T) {
|
||||||
"<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n",
|
"<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, tests, TestParams{
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
Options: Options{
|
referenceOverride: func(reference string) (rv *Reference, overridden bool) {
|
||||||
ReferenceOverride: func(reference string) (rv *Reference, overridden bool) {
|
|
||||||
switch reference {
|
switch reference {
|
||||||
case "ref1":
|
case "ref1":
|
||||||
// just an overridden reference exists without definition
|
// just an overridden reference exists without definition
|
||||||
|
@ -132,7 +131,6 @@ func TestReferenceOverride(t *testing.T) {
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,8 +341,8 @@ func TestLineBreak(t *testing.T) {
|
||||||
"this has an \nextra space\n",
|
"this has an \nextra space\n",
|
||||||
"<p>this has an<br />\nextra space</p>\n",
|
"<p>this has an<br />\nextra space</p>\n",
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, tests, TestParams{Options: Options{
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
Extensions: BackslashLineBreak}})
|
extensions: BackslashLineBreak})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInlineLink(t *testing.T) {
|
func TestInlineLink(t *testing.T) {
|
||||||
|
@ -932,7 +930,7 @@ what happens here
|
||||||
|
|
||||||
func TestFootnotes(t *testing.T) {
|
func TestFootnotes(t *testing.T) {
|
||||||
doTestsInlineParam(t, footnoteTests, TestParams{
|
doTestsInlineParam(t, footnoteTests, TestParams{
|
||||||
Options: Options{Extensions: Footnotes},
|
extensions: Footnotes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,7 +957,7 @@ func TestFootnotesWithParameters(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsInlineParam(t, tests, TestParams{
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
Options: Options{Extensions: Footnotes},
|
extensions: Footnotes,
|
||||||
HTMLFlags: FootnoteReturnLinks,
|
HTMLFlags: FootnoteReturnLinks,
|
||||||
HTMLRendererParameters: params,
|
HTMLRendererParameters: params,
|
||||||
})
|
})
|
||||||
|
@ -989,7 +987,7 @@ func TestNestedFootnotes(t *testing.T) {
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, tests, TestParams{Options: Options{Extensions: Footnotes}})
|
doTestsInlineParam(t, tests, TestParams{extensions: Footnotes})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInlineComments(t *testing.T) {
|
func TestInlineComments(t *testing.T) {
|
||||||
|
|
248
markdown.go
248
markdown.go
|
@ -57,9 +57,9 @@ const (
|
||||||
|
|
||||||
// DefaultOptions is a convenience variable with all the options that are
|
// DefaultOptions is a convenience variable with all the options that are
|
||||||
// enabled by default.
|
// enabled by default.
|
||||||
var DefaultOptions = Options{
|
// var DefaultOptions = Options{
|
||||||
Extensions: CommonExtensions,
|
// Extensions: CommonExtensions,
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ListType contains bitwise or'ed flags for list and list item objects.
|
// ListType contains bitwise or'ed flags for list and list item objects.
|
||||||
type ListType int
|
type ListType int
|
||||||
|
@ -160,11 +160,11 @@ type Renderer interface {
|
||||||
|
|
||||||
// Callback functions for inline parsing. One such function is defined
|
// Callback functions for inline parsing. One such function is defined
|
||||||
// for each character that triggers a response when parsing inline data.
|
// for each character that triggers a response when parsing inline data.
|
||||||
type inlineParser func(p *parser, data []byte, offset int) (int, *Node)
|
type inlineParser func(p *Parser, data []byte, offset int) (int, *Node)
|
||||||
|
|
||||||
// Parser holds runtime state used by the parser.
|
// Parser holds runtime state used by the parser.
|
||||||
// This is constructed by the Markdown function.
|
// This is constructed by the Markdown function.
|
||||||
type parser struct {
|
type Parser struct {
|
||||||
refOverride ReferenceOverrideFunc
|
refOverride ReferenceOverrideFunc
|
||||||
refs map[string]*reference
|
refs map[string]*reference
|
||||||
inlineCallback [256]inlineParser
|
inlineCallback [256]inlineParser
|
||||||
|
@ -185,7 +185,7 @@ type parser struct {
|
||||||
allClosed bool
|
allClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
func (p *Parser) getRef(refid string) (ref *reference, found bool) {
|
||||||
if p.refOverride != nil {
|
if p.refOverride != nil {
|
||||||
r, overridden := p.refOverride(refid)
|
r, overridden := p.refOverride(refid)
|
||||||
if overridden {
|
if overridden {
|
||||||
|
@ -205,17 +205,17 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||||
return ref, found
|
return ref, found
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) finalize(block *Node) {
|
func (p *Parser) finalize(block *Node) {
|
||||||
above := block.Parent
|
above := block.Parent
|
||||||
block.open = false
|
block.open = false
|
||||||
p.tip = above
|
p.tip = above
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) addChild(node NodeType, offset uint32) *Node {
|
func (p *Parser) addChild(node NodeType, offset uint32) *Node {
|
||||||
return p.addExistingChild(NewNode(node), offset)
|
return p.addExistingChild(NewNode(node), offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) addExistingChild(node *Node, offset uint32) *Node {
|
func (p *Parser) addExistingChild(node *Node, offset uint32) *Node {
|
||||||
for !p.tip.canContain(node.Type) {
|
for !p.tip.canContain(node.Type) {
|
||||||
p.finalize(p.tip)
|
p.finalize(p.tip)
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ func (p *parser) addExistingChild(node *Node, offset uint32) *Node {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) closeUnmatchedBlocks() {
|
func (p *Parser) closeUnmatchedBlocks() {
|
||||||
if !p.allClosed {
|
if !p.allClosed {
|
||||||
for p.oldTip != p.lastMatchedContainer {
|
for p.oldTip != p.lastMatchedContainer {
|
||||||
parent := p.oldTip.Parent
|
parent := p.oldTip.Parent
|
||||||
|
@ -259,14 +259,99 @@ type Reference struct {
|
||||||
// See the documentation in Options for more details on use-case.
|
// See the documentation in Options for more details on use-case.
|
||||||
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
|
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
|
||||||
|
|
||||||
// Options represents configurable overrides and callbacks (in addition to the
|
// Processor contains all the state necessary for Blackfriday to operate.
|
||||||
// extension flag set) for configuring a Markdown parse.
|
type Processor struct {
|
||||||
type Options struct {
|
r Renderer
|
||||||
// Extensions is a flag set of bit-wise ORed extension bits. See the
|
extensions Extensions
|
||||||
// Extensions flags defined in this package.
|
referenceOverride ReferenceOverrideFunc
|
||||||
Extensions Extensions
|
}
|
||||||
|
|
||||||
// ReferenceOverride is an optional function callback that is called every
|
// 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 {
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(proc)
|
||||||
|
}
|
||||||
|
var p Parser
|
||||||
|
p.flags = proc.extensions
|
||||||
|
p.refOverride = proc.referenceOverride
|
||||||
|
p.refs = make(map[string]*reference)
|
||||||
|
p.maxNesting = 16
|
||||||
|
p.insideLink = false
|
||||||
|
docNode := NewNode(Document)
|
||||||
|
p.doc = docNode
|
||||||
|
p.tip = docNode
|
||||||
|
p.oldTip = docNode
|
||||||
|
p.lastMatchedContainer = docNode
|
||||||
|
p.allClosed = true
|
||||||
|
// register inline parsers
|
||||||
|
p.inlineCallback[' '] = maybeLineBreak
|
||||||
|
p.inlineCallback['*'] = emphasis
|
||||||
|
p.inlineCallback['_'] = emphasis
|
||||||
|
if proc.extensions&Strikethrough != 0 {
|
||||||
|
p.inlineCallback['~'] = emphasis
|
||||||
|
}
|
||||||
|
p.inlineCallback['`'] = codeSpan
|
||||||
|
p.inlineCallback['\n'] = lineBreak
|
||||||
|
p.inlineCallback['['] = link
|
||||||
|
p.inlineCallback['<'] = leftAngle
|
||||||
|
p.inlineCallback['\\'] = escape
|
||||||
|
p.inlineCallback['&'] = entity
|
||||||
|
p.inlineCallback['!'] = maybeImage
|
||||||
|
p.inlineCallback['^'] = maybeInlineFootnote
|
||||||
|
if proc.extensions&Autolink != 0 {
|
||||||
|
p.inlineCallback['h'] = maybeAutoLink
|
||||||
|
p.inlineCallback['m'] = maybeAutoLink
|
||||||
|
p.inlineCallback['f'] = maybeAutoLink
|
||||||
|
p.inlineCallback['H'] = maybeAutoLink
|
||||||
|
p.inlineCallback['M'] = maybeAutoLink
|
||||||
|
p.inlineCallback['F'] = maybeAutoLink
|
||||||
|
}
|
||||||
|
if proc.extensions&Footnotes != 0 {
|
||||||
|
p.notes = make([]*reference, 0)
|
||||||
|
}
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option customizes Processor's default behavior.
|
||||||
|
type Option func(*Processor)
|
||||||
|
|
||||||
|
// WithRenderer allows you to override the default renderer.
|
||||||
|
func WithRenderer(r Renderer) Option {
|
||||||
|
return func(p *Processor) {
|
||||||
|
p.r = 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) {
|
||||||
|
p.extensions = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithNoExtensions turns off all extensions and custom behavior.
|
||||||
|
func WithNoExtensions() Option {
|
||||||
|
return func(p *Processor) {
|
||||||
|
p.extensions = NoExtensions
|
||||||
|
p.r = NewHTMLRenderer(HTMLRendererParameters{
|
||||||
|
Flags: HTMLFlagsNone,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRefOverride sets an optional function callback that is called every
|
||||||
// time a reference is resolved.
|
// time a reference is resolved.
|
||||||
//
|
//
|
||||||
// In Markdown, the link reference syntax can be made to resolve a link to
|
// In Markdown, the link reference syntax can be made to resolve a link to
|
||||||
|
@ -280,111 +365,42 @@ type Options struct {
|
||||||
// function first, before consulting the defined refids at the bottom. If
|
// function first, before consulting the defined refids at the bottom. If
|
||||||
// the override function indicates an override did not occur, the refids at
|
// the override function indicates an override did not occur, the refids at
|
||||||
// the bottom will be used to fill in the link details.
|
// the bottom will be used to fill in the link details.
|
||||||
ReferenceOverride ReferenceOverrideFunc
|
func WithRefOverride(o ReferenceOverrideFunc) Option {
|
||||||
|
return func(p *Processor) {
|
||||||
|
p.referenceOverride = o
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkdownBasic is a convenience function for simple rendering.
|
// Markdown is the main entry point to Blackfriday. It parses and renders a
|
||||||
// It processes markdown input with no extensions enabled.
|
// block of markdown-encoded text.
|
||||||
func MarkdownBasic(input []byte) []byte {
|
|
||||||
// set up the HTML renderer
|
|
||||||
renderer := NewHTMLRenderer(HTMLRendererParameters{
|
|
||||||
Flags: UseXHTML,
|
|
||||||
})
|
|
||||||
|
|
||||||
// set up the parser
|
|
||||||
return Markdown(input, renderer, Options{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarkdownCommon is a convenience function for simple rendering. It calls
|
|
||||||
// Markdown with most useful extensions enabled, including:
|
|
||||||
//
|
//
|
||||||
// * Smartypants processing with smart fractions and LaTeX dashes
|
// The simplest invocation of Markdown takes one argument, input:
|
||||||
|
// output := Markdown(input)
|
||||||
|
// This will parse the input with CommonExtensions enabled and render it with
|
||||||
|
// the default HTMLRenderer (with CommonHTMLFlags).
|
||||||
//
|
//
|
||||||
// * Intra-word emphasis suppression
|
// Variadic arguments opts can customize the default behavior. Since Processor
|
||||||
|
// 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())
|
||||||
//
|
//
|
||||||
// * Tables
|
// 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
|
||||||
// * Fenced code blocks
|
// former:
|
||||||
//
|
// output := Markdown(input, WithNoExtensions(), WithExtensions(exts),
|
||||||
// * Autolinking
|
// WithRenderer(yourRenderer))
|
||||||
//
|
func Markdown(input []byte, opts ...Option) []byte {
|
||||||
// * Strikethrough support
|
p := DefaultProcessor()
|
||||||
//
|
parser := p.NewParser(opts...)
|
||||||
// * Strict header parsing
|
return p.r.Render(parser.Parse(input))
|
||||||
//
|
|
||||||
// * Custom Header IDs
|
|
||||||
func MarkdownCommon(input []byte) []byte {
|
|
||||||
// set up the HTML renderer
|
|
||||||
renderer := NewHTMLRenderer(HTMLRendererParameters{
|
|
||||||
Flags: CommonHTMLFlags,
|
|
||||||
})
|
|
||||||
return Markdown(input, renderer, DefaultOptions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Markdown is the main rendering function.
|
|
||||||
// It parses and renders a block of markdown-encoded text.
|
|
||||||
// The supplied Renderer is used to format the output, and extensions dictates
|
|
||||||
// which non-standard extensions are enabled.
|
|
||||||
//
|
|
||||||
// To use the supplied HTML renderer, see NewHTMLRenderer.
|
|
||||||
func Markdown(input []byte, renderer Renderer, options Options) []byte {
|
|
||||||
if renderer == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return renderer.Render(Parse(input, options))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse is an entry point to the parsing part of Blackfriday. It takes an
|
// 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
|
// input markdown document and produces a syntax tree for its contents. This
|
||||||
// tree can then be rendered with a default or custom renderer, or
|
// tree can then be rendered with a default or custom renderer, or
|
||||||
// analyzed/transformed by the caller to whatever non-standard needs they have.
|
// analyzed/transformed by the caller to whatever non-standard needs they have.
|
||||||
func Parse(input []byte, opts Options) *Node {
|
func (p *Parser) Parse(input []byte) *Node {
|
||||||
extensions := opts.Extensions
|
|
||||||
|
|
||||||
// fill in the render structure
|
|
||||||
p := new(parser)
|
|
||||||
p.flags = extensions
|
|
||||||
p.refOverride = opts.ReferenceOverride
|
|
||||||
p.refs = make(map[string]*reference)
|
|
||||||
p.maxNesting = 16
|
|
||||||
p.insideLink = false
|
|
||||||
|
|
||||||
docNode := NewNode(Document)
|
|
||||||
p.doc = docNode
|
|
||||||
p.tip = docNode
|
|
||||||
p.oldTip = docNode
|
|
||||||
p.lastMatchedContainer = docNode
|
|
||||||
p.allClosed = true
|
|
||||||
|
|
||||||
// register inline parsers
|
|
||||||
p.inlineCallback[' '] = maybeLineBreak
|
|
||||||
p.inlineCallback['*'] = emphasis
|
|
||||||
p.inlineCallback['_'] = emphasis
|
|
||||||
if extensions&Strikethrough != 0 {
|
|
||||||
p.inlineCallback['~'] = emphasis
|
|
||||||
}
|
|
||||||
p.inlineCallback['`'] = codeSpan
|
|
||||||
p.inlineCallback['\n'] = lineBreak
|
|
||||||
p.inlineCallback['['] = link
|
|
||||||
p.inlineCallback['<'] = leftAngle
|
|
||||||
p.inlineCallback['\\'] = escape
|
|
||||||
p.inlineCallback['&'] = entity
|
|
||||||
p.inlineCallback['!'] = maybeImage
|
|
||||||
p.inlineCallback['^'] = maybeInlineFootnote
|
|
||||||
|
|
||||||
if extensions&Autolink != 0 {
|
|
||||||
p.inlineCallback['h'] = maybeAutoLink
|
|
||||||
p.inlineCallback['m'] = maybeAutoLink
|
|
||||||
p.inlineCallback['f'] = maybeAutoLink
|
|
||||||
p.inlineCallback['H'] = maybeAutoLink
|
|
||||||
p.inlineCallback['M'] = maybeAutoLink
|
|
||||||
p.inlineCallback['F'] = maybeAutoLink
|
|
||||||
}
|
|
||||||
|
|
||||||
if extensions&Footnotes != 0 {
|
|
||||||
p.notes = make([]*reference, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.block(input)
|
p.block(input)
|
||||||
// Walk the tree and finish up some of unfinished blocks
|
// Walk the tree and finish up some of unfinished blocks
|
||||||
for p.tip != nil {
|
for p.tip != nil {
|
||||||
|
@ -402,7 +418,7 @@ func Parse(input []byte, opts Options) *Node {
|
||||||
return p.doc
|
return p.doc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) parseRefsToAST() {
|
func (p *Parser) parseRefsToAST() {
|
||||||
if p.flags&Footnotes == 0 || len(p.notes) == 0 {
|
if p.flags&Footnotes == 0 || len(p.notes) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -527,7 +543,7 @@ func (r *reference) String() string {
|
||||||
// (in the render struct).
|
// (in the render struct).
|
||||||
// Returns the number of bytes to skip to move past it,
|
// Returns the number of bytes to skip to move past it,
|
||||||
// or zero if the first line is not a reference.
|
// or zero if the first line is not a reference.
|
||||||
func isReference(p *parser, data []byte, tabSize int) int {
|
func isReference(p *Parser, data []byte, tabSize int) int {
|
||||||
// up to 3 optional leading spaces
|
// up to 3 optional leading spaces
|
||||||
if len(data) < 4 {
|
if len(data) < 4 {
|
||||||
return 0
|
return 0
|
||||||
|
@ -630,7 +646,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
return lineEnd
|
return lineEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
|
func scanLinkRef(p *Parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
|
||||||
// link: whitespace-free sequence, optionally between angle brackets
|
// link: whitespace-free sequence, optionally between angle brackets
|
||||||
if data[i] == '<' {
|
if data[i] == '<' {
|
||||||
i++
|
i++
|
||||||
|
@ -701,13 +717,13 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first bit of this logic is the same as (*parser).listItem, but the rest
|
// The first bit of this logic is the same as Parser.listItem, but the rest
|
||||||
// is much simpler. This function simply finds the entire block and shifts it
|
// is much simpler. This function simply finds the entire block and shifts it
|
||||||
// over by one tab if it is indeed a block (just returns the line if it's not).
|
// over by one tab if it is indeed a block (just returns the line if it's not).
|
||||||
// blockEnd is the end of the section in the input buffer, and contents is the
|
// 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
|
// extracted text that was shifted over one tab. It will need to be rendered at
|
||||||
// the end of the document.
|
// the end of the document.
|
||||||
func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
|
func scanFootnote(p *Parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
|
||||||
if i == 0 || len(data) == 0 {
|
if i == 0 || len(data) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,7 @@ func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
var benchResultAnchor string
|
var benchResultAnchor string
|
||||||
|
|
||||||
func BenchmarkReference(b *testing.B) {
|
func BenchmarkReference(b *testing.B) {
|
||||||
params := TestParams{Options: Options{Extensions: CommonExtensions}}
|
params := TestParams{extensions: CommonExtensions}
|
||||||
files := []string{
|
files := []string{
|
||||||
"Amps and angle encoding",
|
"Amps and angle encoding",
|
||||||
"Auto links",
|
"Auto links",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user