Add programmable reference overrides If a user provides a ReferenceOverride function, then reference ids will be passed to the given ReferenceOverride function first, before consulting the generated reference table. The goal here is to enable programmable support for "WikiWords"-style identifiers or other application-specific user-generated keywords. Example, writing documentation: The [Frobnosticator][] is a very important class in our codebase. While it is used to frobnosticate widgets in general, it can also be passed to the [WeeDoodler][] to interesting effect. This might be solveable with the HTML Renderer relative prefix, but I didn't see a good way of making a short link to 'Frobnosticator' relatively without having to write it twice. Maybe '<Frobnosticator>' should work? Should Autolinks work for relative links? In addition, I wanted a little more richness. I plan to support Godoc links by prefixing references with a '!', like so: Check out the [Frobnosticator][] helper function [!util.Frobnosticate()][] The first link links to the Frobnosticator architectural overview documentation, whereas the second links to Godoc. Better advice on how to implement this sort of think with Blackfriday is highly desired.
@@ -384,9 +384,8 @@ } 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@@ -423,7 +422,6 @@ id = data[1:txtE]
} } - key := string(bytes.ToLower(id)) if t == linkInlineFootnote { // create a new reference noteId = len(p.notes) + 1@@ -453,7 +451,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 }
@@ -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,7 +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)@@ -83,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) } } }@@ -157,6 +157,54 @@ }
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", + } + 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 + } + return nil, false + }}, 0, HtmlRendererParameters{}) +} + func TestStrong(t *testing.T) { var tests = []string{ "nothing inline\n",@@ -436,7 +484,7 @@
"[foo](/bar/)\n", "<p><a href=\"/bar/\">foo</a></p>\n", } - doTestsInlineParam(t, tests, 0, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, + doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS, HtmlRendererParameters{}) }@@ -449,7 +497,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) {@@ -771,7 +819,7 @@ "<p>empty footnote<sup class=\"footnote-ref\" id=\"fnref:\"><a rel=\"footnote\" href=\"#fn:\">1</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:\">fn text\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) {@@ -796,7 +844,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) {@@ -808,7 +856,7 @@ "<p>this “ single double</p>\n",
"two pair of \"some\" quoted \"text\".\n", "<p>two pair of “some” quoted “text”.</p>\n"} - doTestsInlineParam(t, tests, 0, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) + doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{}) } func TestSmartAngledDoubleQuotes(t *testing.T) {@@ -820,5 +868,5 @@ "<p>this « single double</p>\n",
"two pair of \"some\" quoted \"text\".\n", "<p>two pair of «some» quoted «text».</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{}) }
@@ -20,6 +20,7 @@ package blackfriday
import ( "bytes" + "strings" "unicode/utf8" )@@ -196,6 +197,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@@ -209,12 +211,70 @@ // 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}, 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 +} + +// 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 {@@ -223,9 +283,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@@ -250,7 +308,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.@@ -261,15 +320,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