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.
This commit is contained in:
parent
48aaef5fbf
commit
5e8b222b69
|
@ -384,9 +384,8 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
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 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
}
|
||||
}
|
||||
|
||||
key := string(bytes.ToLower(id))
|
||||
if t == linkInlineFootnote {
|
||||
// create a new reference
|
||||
noteId = len(p.notes) + 1
|
||||
|
@ -453,7 +451,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
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 @@ import (
|
|||
"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 @@ func doLinkTestsInline(t *testing.T, tests []string) {
|
|||
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 @@ func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int,
|
|||
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 @@ func doTestsInlineParam(t *testing.T, tests []string, extensions, htmlFlags int,
|
|||
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 @@ func TestEmphasis(t *testing.T) {
|
|||
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 @@ func TestNofollowLink(t *testing.T) {
|
|||
"[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 @@ func TestHrefTargetBlank(t *testing.T) {
|
|||
"[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 @@ what happens here
|
|||
}
|
||||
|
||||
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 @@ func TestFootnotesWithParameters(t *testing.T) {
|
|||
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 @@ func TestSmartDoubleQuotes(t *testing.T) {
|
|||
"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 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
|
|||
"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{})
|
||||
}
|
||||
|
|
77
markdown.go
77
markdown.go
|
@ -20,6 +20,7 @@ package blackfriday
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
|
@ -196,6 +197,7 @@ type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) in
|
|||
// 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 @@ type parser struct {
|
|||
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 @@ func MarkdownBasic(input []byte) []byte {
|
|||
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 @@ func MarkdownBasic(input []byte) []byte {
|
|||
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 @@ func MarkdownCommon(input []byte) []byte {
|
|||
// 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
|
||||
|
|
Loading…
Reference in New Issue