all repos — grayfriday @ 5e8b222b6980d15fae51c859ca79b97cc679168d

blackfriday fork with a few changes

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.
JT Olds hello@jtolds.com
Tue, 16 Dec 2014 16:17:49 -0700
commit

5e8b222b6980d15fae51c859ca79b97cc679168d

parent

48aaef5fbf8eb2337daca96fcc89f973981b9137

3 files changed, 142 insertions(+), 27 deletions(-)

jump to
M inline.goinline.go

@@ -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 }
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,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 &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) {

@@ -820,5 +868,5 @@ "<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{}) }
M markdown.gomarkdown.go

@@ -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