all repos — grayfriday @ 4bed88b4fd00fbb66b49b0f38ed3dd0b902ab515

blackfriday fork with a few changes

Merge branch 'jtolds-master'
Vytautas Ĺ altenis vytas@rtfb.lt
Wed, 06 May 2015 16:00:36 +0300
commit

4bed88b4fd00fbb66b49b0f38ed3dd0b902ab515

parent

c6be4fadb114a6855d6c6c00235f5b5dcd80d0e2

3 files changed, 177 insertions(+), 38 deletions(-)

jump to
M inline.goinline.go

@@ -216,10 +216,10 @@

data = data[offset:] var ( - i = 1 - noteId int - title, link []byte - textHasNl = false + i = 1 + noteId int + title, link, altContent []byte + textHasNl = false ) if t == linkDeferredFootnote {

@@ -355,6 +355,7 @@

// reference style link case i < len(data)-1 && data[i] == '[' && data[i+1] != '^': var id []byte + altContentConsidered := false // look for the id i++

@@ -384,14 +385,14 @@

id = b.Bytes() } else { id = data[1:txtE] + altContentConsidered = true } } else { id = data[linkB:linkE] } - // find the reference with matching id (ids are case-insensitive) - key := string(bytes.ToLower(id)) - lr, ok := p.refs[key] + // find the reference with matching id + lr, ok := p.getRef(string(id)) if !ok { return 0

@@ -400,6 +401,9 @@

// keep link and title from reference link = lr.link title = lr.title + if altContentConsidered { + altContent = lr.text + } i++ // shortcut reference style link or reference or inline footnote

@@ -428,7 +432,6 @@ id = data[1:txtE]

} } - key := string(bytes.ToLower(id)) if t == linkInlineFootnote { // create a new reference noteId = len(p.notes) + 1

@@ -458,7 +461,7 @@ link = ref.link

title = ref.title } else { // find the reference with matching id - lr, ok := p.refs[key] + lr, ok := p.getRef(string(id)) if !ok { return 0 }

@@ -510,7 +513,11 @@

// call the relevant rendering function switch t { case linkNormal: - p.r.Link(out, uLink, title, content.Bytes()) + if len(altContent) > 0 { + p.r.Link(out, uLink, title, altContent) + } else { + p.r.Link(out, uLink, title, content.Bytes()) + } case linkImg: outSize := out.Len()
M inline_test.goinline_test.go

@@ -20,19 +20,19 @@

"strings" ) -func runMarkdownInline(input string, extensions, htmlFlags int, params HtmlRendererParameters) string { - extensions |= EXTENSION_AUTOLINK - extensions |= EXTENSION_STRIKETHROUGH +func runMarkdownInline(input string, opts Options, htmlFlags int, params HtmlRendererParameters) string { + opts.Extensions |= EXTENSION_AUTOLINK + opts.Extensions |= EXTENSION_STRIKETHROUGH htmlFlags |= HTML_USE_XHTML renderer := HtmlRendererWithParameters(htmlFlags, "", "", params) - return string(Markdown([]byte(input), renderer, extensions)) + return string(MarkdownOptions([]byte(input), renderer, opts)) } func doTestsInline(t *testing.T, tests []string) { - doTestsInlineParam(t, tests, 0, 0, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, 0, HtmlRendererParameters{}) } func doLinkTestsInline(t *testing.T, tests []string) {

@@ -41,22 +41,22 @@

prefix := "http://localhost" params := HtmlRendererParameters{AbsolutePrefix: prefix} transformTests := transformLinks(tests, prefix) - doTestsInlineParam(t, transformTests, 0, 0, params) - doTestsInlineParam(t, transformTests, 0, commonHtmlFlags, params) + doTestsInlineParam(t, transformTests, Options{}, 0, params) + doTestsInlineParam(t, transformTests, Options{}, commonHtmlFlags, params) } func doSafeTestsInline(t *testing.T, tests []string) { - doTestsInlineParam(t, tests, 0, HTML_SAFELINK, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK, HtmlRendererParameters{}) // All the links in this test should not have the prefix appended, so // just rerun it with different parameters and the same expectations. prefix := "http://localhost" params := HtmlRendererParameters{AbsolutePrefix: prefix} transformTests := transformLinks(tests, prefix) - doTestsInlineParam(t, transformTests, 0, HTML_SAFELINK, params) + doTestsInlineParam(t, transformTests, Options{}, HTML_SAFELINK, params) } -func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int, +func doTestsInlineParam(t *testing.T, tests []string, opts Options, htmlFlags int, params HtmlRendererParameters) { // catch and report panics var candidate string

@@ -72,8 +72,7 @@ for i := 0; i+1 < len(tests); i += 2 {

input := tests[i] candidate = input expected := tests[i+1] - - actual := runMarkdownInline(candidate, extensions, htmlFlags, params) + actual := runMarkdownInline(candidate, opts, htmlFlags, params) if actual != expected { t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]", candidate, expected, actual)

@@ -84,7 +83,7 @@ if !testing.Short() {

for start := 0; start < len(input); start++ { for end := start + 1; end <= len(input); end++ { candidate = input[start:end] - _ = runMarkdownInline(candidate, extensions, htmlFlags, params) + _ = runMarkdownInline(candidate, opts, htmlFlags, params) } } }

@@ -158,6 +157,63 @@ }

doTestsInline(t, tests) } +func TestReferenceOverride(t *testing.T) { + var tests = []string{ + "test [ref1][]\n", + "<p>test <a href=\"http://www.ref1.com/\" title=\"Reference 1\">ref1</a></p>\n", + + "test [my ref][ref1]\n", + "<p>test <a href=\"http://www.ref1.com/\" title=\"Reference 1\">my ref</a></p>\n", + + "test [ref2][]\n\n[ref2]: http://www.leftalone.com/ (Ref left alone)\n", + "<p>test <a href=\"http://www.overridden.com/\" title=\"Reference Overridden\">ref2</a></p>\n", + + "test [ref3][]\n\n[ref3]: http://www.leftalone.com/ (Ref left alone)\n", + "<p>test <a href=\"http://www.leftalone.com/\" title=\"Ref left alone\">ref3</a></p>\n", + + "test [ref4][]\n\n[ref4]: http://zombo.com/ (You can do anything)\n", + "<p>test [ref4][]</p>\n", + + "test [!(*http.ServeMux).ServeHTTP][] complicated ref\n", + "<p>test <a href=\"http://localhost:6060/pkg/net/http/#ServeMux.ServeHTTP\" title=\"ServeHTTP docs\">!(*http.ServeMux).ServeHTTP</a> complicated ref</p>\n", + + "test [ref5][]\n", + "<p>test <a href=\"http://www.ref5.com/\" title=\"Reference 5\">Moo</a></p>\n", + } + doTestsInlineParam(t, tests, Options{ + ReferenceOverride: func(reference string) (rv *Reference, overridden bool) { + switch reference { + case "ref1": + // just an overriden reference exists without definition + return &Reference{ + Link: "http://www.ref1.com/", + Title: "Reference 1"}, true + case "ref2": + // overridden exists and reference defined + return &Reference{ + Link: "http://www.overridden.com/", + Title: "Reference Overridden"}, true + case "ref3": + // not overridden and reference defined + return nil, false + case "ref4": + // overridden missing and defined + return nil, true + case "!(*http.ServeMux).ServeHTTP": + return &Reference{ + Link: "http://localhost:6060/pkg/net/http/#ServeMux.ServeHTTP", + Title: "ServeHTTP docs"}, true + case "ref5": + return &Reference{ + Link: "http://www.ref5.com/", + Title: "Reference 5", + Text: "Moo", + }, true + } + return nil, false + }}, 0, HtmlRendererParameters{}) +} + func TestStrong(t *testing.T) { var tests = []string{ "nothing inline\n",

@@ -359,7 +415,9 @@

"this has an \nextra space\n", "<p>this has an<br />\nextra space</p>\n", } - doTestsInlineParam(t, tests, EXTENSION_BACKSLASH_LINE_BREAK, 0, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{ + Extensions: EXTENSION_BACKSLASH_LINE_BREAK}, + 0, HtmlRendererParameters{}) } func TestInlineLink(t *testing.T) {

@@ -499,7 +557,7 @@

"[foo](../bar)\n", "<p><a href=\"../bar\">foo</a></p>\n", } - doTestsInlineParam(t, nofollowTests, 0, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, + doTestsInlineParam(t, nofollowTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, HtmlRendererParameters{}) var noreferrerTests = []string{

@@ -509,7 +567,7 @@

"[foo](/bar/)\n", "<p><a href=\"/bar/\">foo</a></p>\n", } - doTestsInlineParam(t, noreferrerTests, 0, HTML_SAFELINK|HTML_NOREFERRER_LINKS, + doTestsInlineParam(t, noreferrerTests, Options{}, HTML_SAFELINK|HTML_NOREFERRER_LINKS, HtmlRendererParameters{}) var nofollownoreferrerTests = []string{

@@ -519,7 +577,7 @@

"[foo](/bar/)\n", "<p><a href=\"/bar/\">foo</a></p>\n", } - doTestsInlineParam(t, nofollownoreferrerTests, 0, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS, + doTestsInlineParam(t, nofollownoreferrerTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS, HtmlRendererParameters{}) }

@@ -547,7 +605,7 @@

"[foo](http://example.com)\n", "<p><a href=\"http://example.com\" target=\"_blank\">foo</a></p>\n", } - doTestsInlineParam(t, tests, 0, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{}) } func TestSafeInlineLink(t *testing.T) {

@@ -887,7 +945,7 @@ "<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup><sup class=\"footnote-ref\" id=\"fnref:note2\"><a rel=\"footnote\" href=\"#fn:note2\">2</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1\n</li>\n<li id=\"fn:note2\">fn2\n</li>\n</ol>\n</div>\n",

} func TestFootnotes(t *testing.T) { - doTestsInlineParam(t, footnoteTests, EXTENSION_FOOTNOTES, 0, HtmlRendererParameters{}) + doTestsInlineParam(t, footnoteTests, Options{Extensions: EXTENSION_FOOTNOTES}, 0, HtmlRendererParameters{}) } func TestFootnotesWithParameters(t *testing.T) {

@@ -912,7 +970,7 @@ FootnoteAnchorPrefix: prefix,

FootnoteReturnLinkContents: returnText, } - doTestsInlineParam(t, tests, EXTENSION_FOOTNOTES, HTML_FOOTNOTE_RETURN_LINKS, params) + doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, HTML_FOOTNOTE_RETURN_LINKS, params) } func TestSmartDoubleQuotes(t *testing.T) {

@@ -924,7 +982,7 @@ "<p>this &ldquo; single double</p>\n",

"two pair of \"some\" quoted \"text\".\n", "<p>two pair of &ldquo;some&rdquo; quoted &ldquo;text&rdquo;.</p>\n"} - doTestsInlineParam(t, tests, 0, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) } func TestSmartAngledDoubleQuotes(t *testing.T) {

@@ -936,7 +994,7 @@ "<p>this &laquo; single double</p>\n",

"two pair of \"some\" quoted \"text\".\n", "<p>two pair of &laquo;some&raquo; quoted &laquo;text&raquo;.</p>\n"} - doTestsInlineParam(t, tests, 0, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{}) } func TestSmartFractions(t *testing.T) {

@@ -946,7 +1004,7 @@ "<p>&frac12;, &frac14; and &frac34;; &frac14;th and &frac34;ths</p>\n",

"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n", "<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"} - doTestsInlineParam(t, tests, 0, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) tests = []string{ "1/2, 2/3, 81/100 and 1000000/1048576.\n",

@@ -954,5 +1012,5 @@ "<p><sup>1</sup>&frasl;<sub>2</sub>, <sup>2</sup>&frasl;<sub>3</sub>, <sup>81</sup>&frasl;<sub>100</sub> and <sup>1000000</sup>&frasl;<sub>1048576</sub>.</p>\n",

"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\n", "<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"} - doTestsInlineParam(t, tests, 0, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{}) }
M markdown.gomarkdown.go

@@ -20,6 +20,7 @@ package blackfriday

import ( "bytes" + "strings" "unicode/utf8" )

@@ -198,6 +199,7 @@ // Parser holds runtime state used by the parser.

// This is constructed by the Markdown function. type parser struct { r Renderer + refOverride ReferenceOverrideFunc refs map[string]*reference inlineCallback [256]inlineParser flags int

@@ -211,12 +213,74 @@ // in notes. Slice is nil if footnotes not enabled.

notes []*reference } +func (p *parser) getRef(refid string) (ref *reference, found bool) { + if p.refOverride != nil { + r, overridden := p.refOverride(refid) + if overridden { + if r == nil { + return nil, false + } + return &reference{ + link: []byte(r.Link), + title: []byte(r.Title), + noteId: 0, + hasBlock: false, + text: []byte(r.Text)}, true + } + } + // refs are case insensitive + ref, found = p.refs[strings.ToLower(refid)] + return ref, found +} + // // // Public interface // // +// Reference represents the details of a link. +// See the documentation in Options for more details on use-case. +type Reference struct { + // Link is usually the URL the reference points to. + Link string + // Title is the alternate text describing the link in more detail. + Title string + // Text is the optional text to override the ref with if the syntax used was + // [refid][] + Text string +} + +// ReferenceOverrideFunc is expected to be called with a reference string and +// return either a valid Reference type that the reference string maps to or +// nil. If overridden is false, the default reference logic will be executed. +// See the documentation in Options for more details on use-case. +type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) + +// Options represents configurable overrides and callbacks (in addition to the +// extension flag set) for configuring a Markdown parse. +type Options struct { + // Extensions is a flag set of bit-wise ORed extension bits. See the + // EXTENSION_* flags defined in this package. + Extensions int + + // ReferenceOverride is an optional function callback that is called every + // time a reference is resolved. + // + // In Markdown, the link reference syntax can be made to resolve a link to + // a reference instead of an inline URL, in one of the following ways: + // + // * [link text][refid] + // * [refid][] + // + // Usually, the refid is defined at the bottom of the Markdown document. If + // this override function is provided, the refid is passed to the override + // function first, before consulting the defined refids at the bottom. If + // the override function indicates an override did not occur, the refids at + // the bottom will be used to fill in the link details. + ReferenceOverride ReferenceOverrideFunc +} + // MarkdownBasic is a convenience function for simple rendering. // It processes markdown input with no extensions enabled. func MarkdownBasic(input []byte) []byte {

@@ -225,9 +289,7 @@ htmlFlags := HTML_USE_XHTML

renderer := HtmlRenderer(htmlFlags, "", "") // set up the parser - extensions := 0 - - return Markdown(input, renderer, extensions) + return MarkdownOptions(input, renderer, Options{Extensions: 0}) } // Call Markdown with most useful extensions enabled

@@ -252,7 +314,8 @@ // * Custom Header IDs

func MarkdownCommon(input []byte) []byte { // set up the HTML renderer renderer := HtmlRenderer(commonHtmlFlags, "", "") - return Markdown(input, renderer, commonExtensions) + return MarkdownOptions(input, renderer, Options{ + Extensions: commonExtensions}) } // Markdown is the main rendering function.

@@ -263,15 +326,25 @@ //

// To use the supplied Html or LaTeX renderers, see HtmlRenderer and // LatexRenderer, respectively. func Markdown(input []byte, renderer Renderer, extensions int) []byte { + return MarkdownOptions(input, renderer, Options{ + Extensions: extensions}) +} + +// MarkdownOptions is just like Markdown but takes additional options through +// the Options struct. +func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte { // no point in parsing if we can't render if renderer == nil { return nil } + extensions := opts.Extensions + // fill in the render structure p := new(parser) p.r = renderer p.flags = extensions + p.refOverride = opts.ReferenceOverride p.refs = make(map[string]*reference) p.maxNesting = 16 p.insideLink = false

@@ -438,6 +511,7 @@ link []byte

title []byte noteId int // 0 if not a footnote ref hasBlock bool + text []byte } // Check whether or not data starts with a reference link.