mirror of
https://github.com/documize/community.git
synced 2025-07-19 05:09:42 +02:00
Upgrade to latest Markdown processing engine
This commit is contained in:
parent
1e2c468dc4
commit
0262763c95
22 changed files with 3560 additions and 2394 deletions
|
@ -24,5 +24,5 @@ import (
|
||||||
// PagesHTML set to the given (*api.DocumentConversionRequest).Filedata converted by the blackfriday lib.
|
// PagesHTML set to the given (*api.DocumentConversionRequest).Filedata converted by the blackfriday lib.
|
||||||
func Convert(ctx context.Context, in interface{}) (interface{}, error) {
|
func Convert(ctx context.Context, in interface{}) (interface{}, error) {
|
||||||
return &api.DocumentConversionResponse{
|
return &api.DocumentConversionResponse{
|
||||||
PagesHTML: blackfriday.MarkdownCommon(in.(*api.DocumentConversionRequest).Filedata)}, nil
|
PagesHTML: blackfriday.Run(in.(*api.DocumentConversionRequest).Filedata)}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R
|
||||||
|
|
||||||
// Render converts markdown data into HTML suitable for browser rendering.
|
// Render converts markdown data into HTML suitable for browser rendering.
|
||||||
func (*Provider) Render(ctx *provider.Context, config, data string) string {
|
func (*Provider) Render(ctx *provider.Context, config, data string) string {
|
||||||
result := blackfriday.MarkdownCommon([]byte(data))
|
result := blackfriday.Run([]byte(data))
|
||||||
|
|
||||||
return string(result)
|
return string(result)
|
||||||
}
|
}
|
||||||
|
|
125
vendor/github.com/documize/blackfriday/README.md
generated
vendored
125
vendor/github.com/documize/blackfriday/README.md
generated
vendored
|
@ -8,7 +8,7 @@ punctuation substitutions, etc.), and it is safe for all utf-8
|
||||||
(unicode) input.
|
(unicode) input.
|
||||||
|
|
||||||
HTML output is currently supported, along with Smartypants
|
HTML output is currently supported, along with Smartypants
|
||||||
extensions. An experimental LaTeX output engine is also included.
|
extensions.
|
||||||
|
|
||||||
It started as a translation from C of [Sundown][3].
|
It started as a translation from C of [Sundown][3].
|
||||||
|
|
||||||
|
@ -16,63 +16,87 @@ It started as a translation from C of [Sundown][3].
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Blackfriday is compatible with Go 1. If you are using an older
|
Blackfriday is compatible with any modern Go release. With Go 1.7 and git
|
||||||
release of Go, consider using v1.1 of blackfriday, which was based
|
installed:
|
||||||
on the last stable release of Go prior to Go 1. You can find it as a
|
|
||||||
tagged commit on github.
|
|
||||||
|
|
||||||
With Go 1 and git installed:
|
go get gopkg.in/russross/blackfriday.v2
|
||||||
|
|
||||||
go get github.com/russross/blackfriday
|
|
||||||
|
|
||||||
will download, compile, and install the package into your `$GOPATH`
|
will download, compile, and install the package into your `$GOPATH`
|
||||||
directory hierarchy. Alternatively, you can achieve the same if you
|
directory hierarchy. Alternatively, you can achieve the same if you
|
||||||
import it into a project:
|
import it into a project:
|
||||||
|
|
||||||
import "github.com/russross/blackfriday"
|
import "gopkg.in/russross/blackfriday.v2"
|
||||||
|
|
||||||
and `go get` without parameters.
|
and `go get` without parameters.
|
||||||
|
|
||||||
|
|
||||||
|
Versions
|
||||||
|
--------
|
||||||
|
|
||||||
|
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||||
|
developed on its own branch: https://github.com/russross/blackfriday/v2. You
|
||||||
|
should install and import it via [gopkg.in][6] at
|
||||||
|
`gopkg.in/russross/blackfriday.v2`.
|
||||||
|
|
||||||
|
Version 2 offers a number of improvements over v1:
|
||||||
|
|
||||||
|
* Cleaned up API
|
||||||
|
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
|
||||||
|
the document
|
||||||
|
* Latest bug fixes
|
||||||
|
* Flexibility to easily add your own rendering extensions
|
||||||
|
|
||||||
|
Potential drawbacks:
|
||||||
|
|
||||||
|
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
|
||||||
|
ballpark of around 15%.
|
||||||
|
* API breakage. If you can't afford modifying your code to adhere to the new API
|
||||||
|
and don't care too much about the new features, v2 is probably not for you.
|
||||||
|
* Several bug fixes are trailing behind and still need to be forward-ported to
|
||||||
|
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
|
||||||
|
tracking.
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
For basic usage, it is as simple as getting your input into a byte
|
For the most sensible markdown processing, it is as simple as getting your input
|
||||||
slice and calling:
|
into a byte slice and calling:
|
||||||
|
|
||||||
output := blackfriday.MarkdownBasic(input)
|
```go
|
||||||
|
output := blackfriday.Run(input)
|
||||||
|
```
|
||||||
|
|
||||||
This renders it with no extensions enabled. To get a more useful
|
Your input will be parsed and the output rendered with a set of most popular
|
||||||
feature set, use this instead:
|
extensions enabled. If you want the most basic feature set, corresponding with
|
||||||
|
the bare Markdown specification, use:
|
||||||
|
|
||||||
output := blackfriday.MarkdownCommon(input)
|
```go
|
||||||
|
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
|
||||||
|
```
|
||||||
|
|
||||||
### Sanitize untrusted content
|
### Sanitize untrusted content
|
||||||
|
|
||||||
Blackfriday itself does nothing to protect against malicious content. If you are
|
Blackfriday itself does nothing to protect against malicious content. If you are
|
||||||
dealing with user-supplied markdown, we recommend running blackfriday's output
|
dealing with user-supplied markdown, we recommend running Blackfriday's output
|
||||||
through HTML sanitizer such as
|
through HTML sanitizer such as [Bluemonday][5].
|
||||||
[Bluemonday](https://github.com/microcosm-cc/bluemonday).
|
|
||||||
|
|
||||||
Here's an example of simple usage of blackfriday together with bluemonday:
|
Here's an example of simple usage of Blackfriday together with Bluemonday:
|
||||||
|
|
||||||
``` go
|
```go
|
||||||
import (
|
import (
|
||||||
"github.com/microcosm-cc/bluemonday"
|
"github.com/microcosm-cc/bluemonday"
|
||||||
"github.com/russross/blackfriday"
|
"github.com/russross/blackfriday"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
unsafe := blackfriday.MarkdownCommon(input)
|
unsafe := blackfriday.Run(input)
|
||||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom options
|
### Custom options
|
||||||
|
|
||||||
If you want to customize the set of options, first get a renderer
|
If you want to customize the set of options, use `blackfriday.WithExtensions`,
|
||||||
(currently either the HTML or LaTeX output engines), then use it to
|
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
|
||||||
call the more general `Markdown` function. For examples, see the
|
|
||||||
implementations of `MarkdownBasic` and `MarkdownCommon` in
|
|
||||||
`markdown.go`.
|
|
||||||
|
|
||||||
You can also check out `blackfriday-tool` for a more complete example
|
You can also check out `blackfriday-tool` for a more complete example
|
||||||
of how to use it. Download and install it using:
|
of how to use it. Download and install it using:
|
||||||
|
@ -114,7 +138,7 @@ All features of Sundown are supported, including:
|
||||||
know and send me the input that does it.
|
know and send me the input that does it.
|
||||||
|
|
||||||
NOTE: "safety" in this context means *runtime safety only*. In order to
|
NOTE: "safety" in this context means *runtime safety only*. In order to
|
||||||
protect yourself agains JavaScript injection in untrusted content, see
|
protect yourself against JavaScript injection in untrusted content, see
|
||||||
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
|
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
|
||||||
|
|
||||||
* **Fast processing**. It is fast enough to render on-demand in
|
* **Fast processing**. It is fast enough to render on-demand in
|
||||||
|
@ -160,7 +184,7 @@ implements the following extensions:
|
||||||
and supply a language (to make syntax highlighting simple). Just
|
and supply a language (to make syntax highlighting simple). Just
|
||||||
mark it like this:
|
mark it like this:
|
||||||
|
|
||||||
``` go
|
```go
|
||||||
func getTrue() bool {
|
func getTrue() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -169,16 +193,33 @@ implements the following extensions:
|
||||||
You can use 3 or more backticks to mark the beginning of the
|
You can use 3 or more backticks to mark the beginning of the
|
||||||
block, and the same number to mark the end of the block.
|
block, and the same number to mark the end of the block.
|
||||||
|
|
||||||
|
* **Definition lists**. A simple definition list is made of a single-line
|
||||||
|
term followed by a colon and the definition for that term.
|
||||||
|
|
||||||
|
Cat
|
||||||
|
: Fluffy animal everyone likes
|
||||||
|
|
||||||
|
Internet
|
||||||
|
: Vector of transmission for pictures of cats
|
||||||
|
|
||||||
|
Terms must be separated from the previous definition by a blank line.
|
||||||
|
|
||||||
|
* **Footnotes**. A marker in the text that will become a superscript number;
|
||||||
|
a footnote definition that will be placed in a list of footnotes at the
|
||||||
|
end of the document. A footnote looks like this:
|
||||||
|
|
||||||
|
This is a footnote.[^1]
|
||||||
|
|
||||||
|
[^1]: the footnote text.
|
||||||
|
|
||||||
* **Autolinking**. Blackfriday can find URLs that have not been
|
* **Autolinking**. Blackfriday can find URLs that have not been
|
||||||
explicitly marked as links and turn them into links.
|
explicitly marked as links and turn them into links.
|
||||||
|
|
||||||
* **Strikethrough**. Use two tildes (`~~`) to mark text that
|
* **Strikethrough**. Use two tildes (`~~`) to mark text that
|
||||||
should be crossed out.
|
should be crossed out.
|
||||||
|
|
||||||
* **Hard line breaks**. With this extension enabled (it is off by
|
* **Hard line breaks**. With this extension enabled newlines in the input
|
||||||
default in the `MarkdownBasic` and `MarkdownCommon` convenience
|
translate into line breaks in the output. This extension is off by default.
|
||||||
functions), newlines in the input translate into line breaks in
|
|
||||||
the output.
|
|
||||||
|
|
||||||
* **Smart quotes**. Smartypants-style punctuation substitution is
|
* **Smart quotes**. Smartypants-style punctuation substitution is
|
||||||
supported, turning normal double- and single-quote marks into
|
supported, turning normal double- and single-quote marks into
|
||||||
|
@ -205,7 +246,7 @@ are a few of note:
|
||||||
|
|
||||||
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||||
highlighting, clickable header anchor links.
|
highlighting, clickable heading anchor links.
|
||||||
|
|
||||||
It's not customizable, and its goal is to produce HTML output
|
It's not customizable, and its goal is to produce HTML output
|
||||||
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
|
||||||
|
@ -214,15 +255,8 @@ are a few of note:
|
||||||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||||
but for markdown.
|
but for markdown.
|
||||||
|
|
||||||
* LaTeX output: renders output as LaTeX. This is currently part of the
|
* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
|
||||||
main Blackfriday repository, but may be split into its own project
|
renders output as LaTeX.
|
||||||
in the future. If you are interested in owning and maintaining the
|
|
||||||
LaTeX output component, please be in touch.
|
|
||||||
|
|
||||||
It renders some basic documents, but is only experimental at this
|
|
||||||
point. In particular, it does not do any inline escaping, so input
|
|
||||||
that happens to look like LaTeX code will be passed through without
|
|
||||||
modification.
|
|
||||||
|
|
||||||
|
|
||||||
Todo
|
Todo
|
||||||
|
@ -241,6 +275,9 @@ License
|
||||||
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
|
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
|
||||||
|
|
||||||
|
|
||||||
[1]: http://daringfireball.net/projects/markdown/ "Markdown"
|
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
|
||||||
[2]: http://golang.org/ "Go Language"
|
[2]: https://golang.org/ "Go Language"
|
||||||
[3]: https://github.com/vmg/sundown "Sundown"
|
[3]: https://github.com/vmg/sundown "Sundown"
|
||||||
|
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
|
||||||
|
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
|
||||||
|
[6]: https://labix.org/gopkg.in "gopkg.in"
|
||||||
|
|
782
vendor/github.com/documize/blackfriday/block.go
generated
vendored
782
vendor/github.com/documize/blackfriday/block.go
generated
vendored
File diff suppressed because it is too large
Load diff
450
vendor/github.com/documize/blackfriday/block_test.go
generated
vendored
450
vendor/github.com/documize/blackfriday/block_test.go
generated
vendored
|
@ -14,68 +14,10 @@
|
||||||
package blackfriday
|
package blackfriday
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runMarkdownBlockWithRenderer(input string, extensions int, renderer Renderer) string {
|
|
||||||
return string(Markdown([]byte(input), renderer, extensions))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runMarkdownBlock(input string, extensions int) string {
|
|
||||||
htmlFlags := 0
|
|
||||||
htmlFlags |= HTML_USE_XHTML
|
|
||||||
|
|
||||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
|
||||||
|
|
||||||
return runMarkdownBlockWithRenderer(input, extensions, renderer)
|
|
||||||
}
|
|
||||||
|
|
||||||
func runnerWithRendererParameters(parameters HtmlRendererParameters) func(string, int) string {
|
|
||||||
return func(input string, extensions int) string {
|
|
||||||
htmlFlags := 0
|
|
||||||
htmlFlags |= HTML_USE_XHTML
|
|
||||||
|
|
||||||
renderer := HtmlRendererWithParameters(htmlFlags, "", "", parameters)
|
|
||||||
|
|
||||||
return runMarkdownBlockWithRenderer(input, extensions, renderer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsBlock(t *testing.T, tests []string, extensions int) {
|
|
||||||
doTestsBlockWithRunner(t, tests, extensions, runMarkdownBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsBlockWithRunner(t *testing.T, tests []string, extensions int, runner func(string, int) string) {
|
|
||||||
// catch and report panics
|
|
||||||
var candidate string
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for i := 0; i+1 < len(tests); i += 2 {
|
|
||||||
input := tests[i]
|
|
||||||
candidate = input
|
|
||||||
expected := tests[i+1]
|
|
||||||
actual := runner(candidate, extensions)
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
|
||||||
candidate, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test every substring to stress test bounds checking
|
|
||||||
if !testing.Short() {
|
|
||||||
for start := 0; start < len(input); start++ {
|
|
||||||
for end := start + 1; end <= len(input); end++ {
|
|
||||||
candidate = input[start:end]
|
|
||||||
_ = runMarkdownBlock(candidate, extensions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrefixHeaderNoExtensions(t *testing.T) {
|
func TestPrefixHeaderNoExtensions(t *testing.T) {
|
||||||
var tests = []string{
|
var tests = []string{
|
||||||
"# Header 1\n",
|
"# Header 1\n",
|
||||||
|
@ -202,7 +144,7 @@ func TestPrefixHeaderSpaceExtension(t *testing.T) {
|
||||||
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
||||||
"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
"<h1>Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_SPACE_HEADERS)
|
doTestsBlock(t, tests, SpaceHeadings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixHeaderIdExtension(t *testing.T) {
|
func TestPrefixHeaderIdExtension(t *testing.T) {
|
||||||
|
@ -262,7 +204,7 @@ func TestPrefixHeaderIdExtension(t *testing.T) {
|
||||||
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
"<ul>\n<li><p>List</p>\n\n<ul>\n<li><p>Nested list</p>\n\n" +
|
||||||
"<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
"<h1 id=\"someid\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_HEADER_IDS)
|
doTestsBlock(t, tests, HeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
|
@ -305,12 +247,16 @@ func TestPrefixHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
"<h1 id=\"PRE:someid:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
"<h1 id=\"PRE:someid:POST\">Nested header</h1></li>\n</ul></li>\n</ul>\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters := HtmlRendererParameters{
|
parameters := HTMLRendererParameters{
|
||||||
HeaderIDPrefix: "PRE:",
|
HeadingIDPrefix: "PRE:",
|
||||||
HeaderIDSuffix: ":POST",
|
HeadingIDSuffix: ":POST",
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsBlockWithRunner(t, tests, EXTENSION_HEADER_IDS, runnerWithRendererParameters(parameters))
|
doTestsParam(t, tests, TestParams{
|
||||||
|
extensions: HeadingIDs,
|
||||||
|
HTMLFlags: UseXHTML,
|
||||||
|
HTMLRendererParameters: parameters,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixAutoHeaderIdExtension(t *testing.T) {
|
func TestPrefixAutoHeaderIdExtension(t *testing.T) {
|
||||||
|
@ -361,7 +307,7 @@ func TestPrefixAutoHeaderIdExtension(t *testing.T) {
|
||||||
"# Header\n\n# Header 1\n\n# Header\n\n# Header",
|
"# Header\n\n# Header 1\n\n# Header\n\n# Header",
|
||||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n",
|
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header</h1>\n\n<h1 id=\"header-1-2\">Header</h1>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS)
|
doTestsBlock(t, tests, AutoHeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
|
@ -413,12 +359,16 @@ func TestPrefixAutoHeaderIdExtensionWithPrefixAndSuffix(t *testing.T) {
|
||||||
"<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1-2:POST\">Header</h1>\n",
|
"<h1 id=\"PRE:header:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1:POST\">Header 1</h1>\n\n<h1 id=\"PRE:header-1-1:POST\">Header</h1>\n\n<h1 id=\"PRE:header-1-2:POST\">Header</h1>\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
parameters := HtmlRendererParameters{
|
parameters := HTMLRendererParameters{
|
||||||
HeaderIDPrefix: "PRE:",
|
HeadingIDPrefix: "PRE:",
|
||||||
HeaderIDSuffix: ":POST",
|
HeadingIDSuffix: ":POST",
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsBlockWithRunner(t, tests, EXTENSION_AUTO_HEADER_IDS, runnerWithRendererParameters(parameters))
|
doTestsParam(t, tests, TestParams{
|
||||||
|
extensions: AutoHeadingIDs,
|
||||||
|
HTMLFlags: UseXHTML,
|
||||||
|
HTMLRendererParameters: parameters,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrefixMultipleHeaderExtensions(t *testing.T) {
|
func TestPrefixMultipleHeaderExtensions(t *testing.T) {
|
||||||
|
@ -426,7 +376,7 @@ func TestPrefixMultipleHeaderExtensions(t *testing.T) {
|
||||||
"# Header\n\n# Header {#header}\n\n# Header 1",
|
"# Header\n\n# Header {#header}\n\n# Header 1",
|
||||||
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
"<h1 id=\"header\">Header</h1>\n\n<h1 id=\"header-1\">Header</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS|EXTENSION_HEADER_IDS)
|
doTestsBlock(t, tests, AutoHeadingIDs|HeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnderlineHeaders(t *testing.T) {
|
func TestUnderlineHeaders(t *testing.T) {
|
||||||
|
@ -526,7 +476,7 @@ func TestUnderlineHeadersAutoIDs(t *testing.T) {
|
||||||
"Header 1\n========\n\nHeader 1\n========\n",
|
"Header 1\n========\n\nHeader 1\n========\n",
|
||||||
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
"<h1 id=\"header-1\">Header 1</h1>\n\n<h1 id=\"header-1-1\">Header 1</h1>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_AUTO_HEADER_IDS)
|
doTestsBlock(t, tests, AutoHeadingIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHorizontalRule(t *testing.T) {
|
func TestHorizontalRule(t *testing.T) {
|
||||||
|
@ -900,7 +850,7 @@ func TestDefinitionList(t *testing.T) {
|
||||||
"</dl>\n" +
|
"</dl>\n" +
|
||||||
"\n<p>Text 2</p>\n",
|
"\n<p>Text 2</p>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS)
|
doTestsBlock(t, tests, DefinitionLists)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreformattedHtml(t *testing.T) {
|
func TestPreformattedHtml(t *testing.T) {
|
||||||
|
@ -976,7 +926,7 @@ func TestPreformattedHtmlLax(t *testing.T) {
|
||||||
"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n\nAnd here?\n",
|
"Paragraph\n\n<div>\nHow about here? >&<\n</div>\n\nAnd here?\n",
|
||||||
"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n",
|
"<p>Paragraph</p>\n\n<div>\nHow about here? >&<\n</div>\n\n<p>And here?</p>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_LAX_HTML_BLOCKS)
|
doTestsBlock(t, tests, LaxHTMLBlocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFencedCodeBlock(t *testing.T) {
|
func TestFencedCodeBlock(t *testing.T) {
|
||||||
|
@ -1061,8 +1011,126 @@ func TestFencedCodeBlock(t *testing.T) {
|
||||||
|
|
||||||
"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block",
|
"Some text before a fenced code block\n``` oz\ncode blocks breakup paragraphs\n```\nSome text in between\n``` oz\nmultiple code blocks work okay\n```\nAnd some text after a fenced code block",
|
||||||
"<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Some text in between</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n",
|
"<p>Some text before a fenced code block</p>\n\n<pre><code class=\"language-oz\">code blocks breakup paragraphs\n</code></pre>\n\n<p>Some text in between</p>\n\n<pre><code class=\"language-oz\">multiple code blocks work okay\n</code></pre>\n\n<p>And some text after a fenced code block</p>\n",
|
||||||
|
|
||||||
|
"```\n[]:()\n```\n",
|
||||||
|
"<pre><code>[]:()\n</code></pre>\n",
|
||||||
|
|
||||||
|
"```\n[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n```",
|
||||||
|
"<pre><code>[]:()\n[]:)\n[]:(\n[]:x\n[]:testing\n[:testing\n\n[]:\nlinebreak\n[]()\n\n[]:\n[]()\n</code></pre>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
|
doTestsBlock(t, tests, FencedCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFencedCodeInsideBlockquotes(t *testing.T) {
|
||||||
|
cat := func(s ...string) string { return strings.Join(s, "\n") }
|
||||||
|
var tests = []string{
|
||||||
|
cat("> ```go",
|
||||||
|
"package moo",
|
||||||
|
"",
|
||||||
|
"```",
|
||||||
|
""),
|
||||||
|
`<blockquote>
|
||||||
|
<pre><code class="language-go">package moo
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
// -------------------------------------------
|
||||||
|
cat("> foo",
|
||||||
|
"> ",
|
||||||
|
"> ```go",
|
||||||
|
"package moo",
|
||||||
|
"```",
|
||||||
|
"> ",
|
||||||
|
"> goo.",
|
||||||
|
""),
|
||||||
|
`<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
|
||||||
|
<pre><code class="language-go">package moo
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>goo.</p>
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
// -------------------------------------------
|
||||||
|
cat("> foo",
|
||||||
|
"> ",
|
||||||
|
"> quote",
|
||||||
|
"continues",
|
||||||
|
"```",
|
||||||
|
""),
|
||||||
|
`<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
|
||||||
|
<p>quote
|
||||||
|
continues
|
||||||
|
` + "```" + `</p>
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
// -------------------------------------------
|
||||||
|
cat("> foo",
|
||||||
|
"> ",
|
||||||
|
"> ```go",
|
||||||
|
"package moo",
|
||||||
|
"```",
|
||||||
|
"> ",
|
||||||
|
"> goo.",
|
||||||
|
"> ",
|
||||||
|
"> ```go",
|
||||||
|
"package zoo",
|
||||||
|
"```",
|
||||||
|
"> ",
|
||||||
|
"> woo.",
|
||||||
|
""),
|
||||||
|
`<blockquote>
|
||||||
|
<p>foo</p>
|
||||||
|
|
||||||
|
<pre><code class="language-go">package moo
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>goo.</p>
|
||||||
|
|
||||||
|
<pre><code class="language-go">package zoo
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>woo.</p>
|
||||||
|
</blockquote>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These 2 alternative forms of blockquoted fenced code blocks should produce same output.
|
||||||
|
forms := [2]string{
|
||||||
|
cat("> plain quoted text",
|
||||||
|
"> ```fenced",
|
||||||
|
"code",
|
||||||
|
" with leading single space correctly preserved",
|
||||||
|
"okay",
|
||||||
|
"```",
|
||||||
|
"> rest of quoted text"),
|
||||||
|
cat("> plain quoted text",
|
||||||
|
"> ```fenced",
|
||||||
|
"> code",
|
||||||
|
"> with leading single space correctly preserved",
|
||||||
|
"> okay",
|
||||||
|
"> ```",
|
||||||
|
"> rest of quoted text"),
|
||||||
|
}
|
||||||
|
want := `<blockquote>
|
||||||
|
<p>plain quoted text</p>
|
||||||
|
|
||||||
|
<pre><code class="language-fenced">code
|
||||||
|
with leading single space correctly preserved
|
||||||
|
okay
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<p>rest of quoted text</p>
|
||||||
|
</blockquote>
|
||||||
|
`
|
||||||
|
tests = append(tests, forms[0], want)
|
||||||
|
tests = append(tests, forms[1], want)
|
||||||
|
|
||||||
|
doTestsBlock(t, tests, FencedCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTable(t *testing.T) {
|
func TestTable(t *testing.T) {
|
||||||
|
@ -1109,7 +1177,7 @@ func TestTable(t *testing.T) {
|
||||||
"a|b\\|c|d\n---|---|---\nf|g\\|h|i\n",
|
"a|b\\|c|d\n---|---|---\nf|g\\|h|i\n",
|
||||||
"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b|c</th>\n<th>d</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td>f</td>\n<td>g|h</td>\n<td>i</td>\n</tr>\n</tbody>\n</table>\n",
|
"<table>\n<thead>\n<tr>\n<th>a</th>\n<th>b|c</th>\n<th>d</th>\n</tr>\n</thead>\n\n<tbody>\n<tr>\n<td>f</td>\n<td>g|h</td>\n<td>i</td>\n</tr>\n</tbody>\n</table>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_TABLES)
|
doTestsBlock(t, tests, Tables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
|
@ -1220,7 +1288,7 @@ func TestUnorderedListWith_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
"* List\n\n * sublist\n\n normal text\n\n * another sublist\n",
|
"* List\n\n * sublist\n\n normal text\n\n * another sublist\n",
|
||||||
"<ul>\n<li><p>List</p>\n\n<ul>\n<li>sublist</li>\n</ul>\n\n<p>normal text</p>\n\n<ul>\n<li>another sublist</li>\n</ul></li>\n</ul>\n",
|
"<ul>\n<li><p>List</p>\n\n<ul>\n<li>sublist</li>\n</ul>\n\n<p>normal text</p>\n\n<ul>\n<li>another sublist</li>\n</ul></li>\n</ul>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
|
doTestsBlock(t, tests, NoEmptyLineBeforeBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
|
@ -1316,7 +1384,7 @@ func TestOrderedList_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
"1. numbers\n1. are ignored\n",
|
"1. numbers\n1. are ignored\n",
|
||||||
"<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n",
|
"<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
|
doTestsBlock(t, tests, NoEmptyLineBeforeBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
|
@ -1387,7 +1455,7 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
" ``` oz\nleading spaces\n ```\n",
|
" ``` oz\nleading spaces\n ```\n",
|
||||||
"<pre><code>``` oz\n</code></pre>\n\n<p>leading spaces</p>\n\n<pre><code>```\n</code></pre>\n",
|
"<pre><code>``` oz\n</code></pre>\n\n<p>leading spaces</p>\n\n<pre><code>```\n</code></pre>\n",
|
||||||
}
|
}
|
||||||
doTestsBlock(t, tests, EXTENSION_FENCED_CODE|EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
|
doTestsBlock(t, tests, FencedCode|NoEmptyLineBeforeBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
|
func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
|
||||||
|
@ -1398,10 +1466,226 @@ func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
|
||||||
"<h1 class=\"title\">" +
|
"<h1 class=\"title\">" +
|
||||||
"Some title\n" +
|
"Some title\n" +
|
||||||
"Another title line\n" +
|
"Another title line\n" +
|
||||||
"Yep, more here too\n" +
|
"Yep, more here too" +
|
||||||
"</h1>",
|
"</h1>\n",
|
||||||
|
}
|
||||||
|
doTestsBlock(t, tests, Titleblock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlockComments(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"Some text\n\n<!-- comment -->\n",
|
||||||
|
"<p>Some text</p>\n\n<!-- comment -->\n",
|
||||||
|
|
||||||
|
"Some text\n\n<!--\n\nmultiline\ncomment\n-->\n",
|
||||||
|
"<p>Some text</p>\n\n<!--\n\nmultiline\ncomment\n-->\n",
|
||||||
|
|
||||||
|
"Some text\n\n<!--\n\n<div><p>Commented</p>\n<span>html</span></div>\n-->\n",
|
||||||
|
"<p>Some text</p>\n\n<!--\n\n<div><p>Commented</p>\n<span>html</span></div>\n-->\n",
|
||||||
|
}
|
||||||
|
doTestsBlock(t, tests, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTOC(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"# Title\n\n##Subtitle1\n\n##Subtitle2",
|
||||||
|
//"<nav>\n<ul>\n<li><a href=\"#toc_0\">Title</a>\n<ul>\n<li><a href=\"#toc_1\">Subtitle1</a></li>\n<li><a href=\"#toc_2\">Subtitle2</a></li>\n</ul></li>\n</ul>\n</nav>\n\n<h1 id=\"toc_0\">Title</h1>\n\n<h2 id=\"toc_1\">Subtitle1</h2>\n\n<h2 id=\"toc_2\">Subtitle2</h2>\n",
|
||||||
|
`<nav>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_0">Title</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_1">Subtitle1</a></li>
|
||||||
|
|
||||||
|
<li><a href="#toc_2">Subtitle2</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 id="toc_0">Title</h1>
|
||||||
|
|
||||||
|
<h2 id="toc_1">Subtitle1</h2>
|
||||||
|
|
||||||
|
<h2 id="toc_2">Subtitle2</h2>
|
||||||
|
`,
|
||||||
|
|
||||||
|
"# Title\n\n##Subtitle\n\n#Title2",
|
||||||
|
//"<nav>\n<ul>\n<li><a href=\"#toc_0\">Title</a>\n<ul>\n<li><a href=\"#toc_1\">Subtitle</a></li>\n</ul></li>\n<li><a href=\"#toc_2\">Title2</a></li>\n</ul>\n</nav>\n\n<h1 id=\"toc_0\">Title</h1>\n\n<h2 id=\"toc_1\">Subtitle</h2>\n\n<h1 id=\"toc_2\">Title2</h1>\n",
|
||||||
|
`<nav>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_0">Title</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_1">Subtitle</a></li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li><a href="#toc_2">Title2</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 id="toc_0">Title</h1>
|
||||||
|
|
||||||
|
<h2 id="toc_1">Subtitle</h2>
|
||||||
|
|
||||||
|
<h1 id="toc_2">Title2</h1>
|
||||||
|
`,
|
||||||
|
|
||||||
|
"## Subtitle\n\n# Title",
|
||||||
|
`<nav>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_0">Subtitle</a></li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li><a href="#toc_1">Title</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h2 id="toc_0">Subtitle</h2>
|
||||||
|
|
||||||
|
<h1 id="toc_1">Title</h1>
|
||||||
|
`,
|
||||||
|
|
||||||
|
"# Title 1\n\n## Subtitle 1\n\n### Subsubtitle 1\n\n# Title 2\n\n### Subsubtitle 2",
|
||||||
|
`<nav>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_0">Title 1</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_1">Subtitle 1</a>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_2">Subsubtitle 1</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
|
||||||
|
<li><a href="#toc_3">Title 2</a>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_4">Subsubtitle 2</a></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 id="toc_0">Title 1</h1>
|
||||||
|
|
||||||
|
<h2 id="toc_1">Subtitle 1</h2>
|
||||||
|
|
||||||
|
<h3 id="toc_2">Subsubtitle 1</h3>
|
||||||
|
|
||||||
|
<h1 id="toc_3">Title 2</h1>
|
||||||
|
|
||||||
|
<h3 id="toc_4">Subsubtitle 2</h3>
|
||||||
|
`,
|
||||||
|
|
||||||
|
"# Title with `code`",
|
||||||
|
`<nav>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="#toc_0">Title with <code>code</code></a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 id="toc_0">Title with <code>code</code></h1>
|
||||||
|
`,
|
||||||
|
|
||||||
|
// Trigger empty TOC
|
||||||
|
"#",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
doTestsParam(t, tests, TestParams{
|
||||||
|
HTMLFlags: UseXHTML | TOC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompletePage(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"*foo*",
|
||||||
|
`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta name="GENERATOR" content="Blackfriday Markdown Processor v2.0" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<p><em>foo</em></p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
doTestsParam(t, tests, TestParams{HTMLFlags: UseXHTML | CompletePage})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsFenceLine(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
data []byte
|
||||||
|
syntaxRequested bool
|
||||||
|
wantEnd int
|
||||||
|
wantMarker string
|
||||||
|
wantSyntax string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
data: []byte("```"),
|
||||||
|
wantEnd: 3,
|
||||||
|
wantMarker: "```",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: []byte("```\nstuff here\n"),
|
||||||
|
wantEnd: 4,
|
||||||
|
wantMarker: "```",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: []byte("```\nstuff here\n"),
|
||||||
|
syntaxRequested: true,
|
||||||
|
wantEnd: 4,
|
||||||
|
wantMarker: "```",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: []byte("stuff here\n```\n"),
|
||||||
|
wantEnd: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: []byte("```"),
|
||||||
|
syntaxRequested: true,
|
||||||
|
wantEnd: 3,
|
||||||
|
wantMarker: "```",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: []byte("``` go"),
|
||||||
|
syntaxRequested: true,
|
||||||
|
wantEnd: 6,
|
||||||
|
wantMarker: "```",
|
||||||
|
wantSyntax: "go",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsBlock(t, tests, EXTENSION_TITLEBLOCK)
|
for _, test := range tests {
|
||||||
|
var syntax *string
|
||||||
|
if test.syntaxRequested {
|
||||||
|
syntax = new(string)
|
||||||
|
}
|
||||||
|
end, marker := isFenceLine(test.data, syntax, "```")
|
||||||
|
if got, want := end, test.wantEnd; got != want {
|
||||||
|
t.Errorf("got end %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
if got, want := marker, test.wantMarker; got != want {
|
||||||
|
t.Errorf("got marker %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if test.syntaxRequested {
|
||||||
|
if got, want := *syntax, test.wantSyntax; got != want {
|
||||||
|
t.Errorf("got syntax %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
vendor/github.com/documize/blackfriday/doc.go
generated
vendored
Normal file
18
vendor/github.com/documize/blackfriday/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Package blackfriday is a markdown processor.
|
||||||
|
//
|
||||||
|
// It translates plain text with simple formatting rules into an AST, which can
|
||||||
|
// then be further processed to HTML (provided by Blackfriday itself) or other
|
||||||
|
// formats (provided by the community).
|
||||||
|
//
|
||||||
|
// The simplest way to invoke Blackfriday is to call the Run function. It will
|
||||||
|
// take a text input and produce a text output in HTML (or other format).
|
||||||
|
//
|
||||||
|
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
|
||||||
|
// processor and to call Parse, which returns a syntax tree for the input
|
||||||
|
// document. You can leverage Blackfriday's parsing for content extraction from
|
||||||
|
// markdown documents. You can assign a custom renderer and set various options
|
||||||
|
// to the Markdown processor.
|
||||||
|
//
|
||||||
|
// If you're interested in calling Blackfriday from command line, see
|
||||||
|
// https://github.com/russross/blackfriday-tool.
|
||||||
|
package blackfriday
|
34
vendor/github.com/documize/blackfriday/esc.go
generated
vendored
Normal file
34
vendor/github.com/documize/blackfriday/esc.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var htmlEscaper = [256][]byte{
|
||||||
|
'&': []byte("&"),
|
||||||
|
'<': []byte("<"),
|
||||||
|
'>': []byte(">"),
|
||||||
|
'"': []byte("""),
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeHTML(w io.Writer, s []byte) {
|
||||||
|
var start, end int
|
||||||
|
for end < len(s) {
|
||||||
|
escSeq := htmlEscaper[s[end]]
|
||||||
|
if escSeq != nil {
|
||||||
|
w.Write(s[start:end])
|
||||||
|
w.Write(escSeq)
|
||||||
|
start = end + 1
|
||||||
|
}
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if start < len(s) && end <= len(s) {
|
||||||
|
w.Write(s[start:end])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func escLink(w io.Writer, text []byte) {
|
||||||
|
unesc := html.UnescapeString(string(text))
|
||||||
|
escapeHTML(w, []byte(unesc))
|
||||||
|
}
|
48
vendor/github.com/documize/blackfriday/esc_test.go
generated
vendored
Normal file
48
vendor/github.com/documize/blackfriday/esc_test.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEsc(t *testing.T) {
|
||||||
|
tests := []string{
|
||||||
|
"abc", "abc",
|
||||||
|
"a&c", "a&c",
|
||||||
|
"<", "<",
|
||||||
|
"[]:<", "[]:<",
|
||||||
|
"Hello <!--", "Hello <!--",
|
||||||
|
}
|
||||||
|
for i := 0; i < len(tests); i += 2 {
|
||||||
|
var b bytes.Buffer
|
||||||
|
escapeHTML(&b, []byte(tests[i]))
|
||||||
|
if !bytes.Equal(b.Bytes(), []byte(tests[i+1])) {
|
||||||
|
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||||
|
tests[i], tests[i+1], b.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEscapeHTML(b *testing.B) {
|
||||||
|
tests := [][]byte{
|
||||||
|
[]byte(""),
|
||||||
|
[]byte("AT&T has an ampersand in their name."),
|
||||||
|
[]byte("AT&T is another way to write it."),
|
||||||
|
[]byte("This & that."),
|
||||||
|
[]byte("4 < 5."),
|
||||||
|
[]byte("6 > 5."),
|
||||||
|
[]byte("Here's a [link] [1] with an ampersand in the URL."),
|
||||||
|
[]byte("Here's a link with an ampersand in the link text: [AT&T] [2]."),
|
||||||
|
[]byte("Here's an inline [link](/script?foo=1&bar=2)."),
|
||||||
|
[]byte("Here's an inline [link](</script?foo=1&bar=2>)."),
|
||||||
|
[]byte("[1]: http://example.com/?foo=1&bar=2"),
|
||||||
|
[]byte("[2]: http://att.com/ \"AT&T\""),
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
for _, t := range tests {
|
||||||
|
escapeHTML(&buf, t)
|
||||||
|
buf.Reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
186
vendor/github.com/documize/blackfriday/helpers_test.go
generated
vendored
Normal file
186
vendor/github.com/documize/blackfriday/helpers_test.go
generated
vendored
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Helper functions for unit testing
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestParams struct {
|
||||||
|
extensions Extensions
|
||||||
|
referenceOverride ReferenceOverrideFunc
|
||||||
|
HTMLFlags
|
||||||
|
HTMLRendererParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
func execRecoverableTestSuite(t *testing.T, tests []string, params TestParams, suite func(candidate *string)) {
|
||||||
|
// Catch and report panics. This is useful when running 'go test -v' on
|
||||||
|
// the integration server. When developing, though, crash dump is often
|
||||||
|
// preferable, so recovery can be easily turned off with doRecover = false.
|
||||||
|
var candidate string
|
||||||
|
const doRecover = true
|
||||||
|
if doRecover {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
suite(&candidate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMarkdown(input string, params TestParams) string {
|
||||||
|
params.HTMLRendererParameters.Flags = params.HTMLFlags
|
||||||
|
renderer := NewHTMLRenderer(params.HTMLRendererParameters)
|
||||||
|
return string(Run([]byte(input), WithRenderer(renderer),
|
||||||
|
WithExtensions(params.extensions),
|
||||||
|
WithRefOverride(params.referenceOverride)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// doTests runs full document tests using MarkdownCommon configuration.
|
||||||
|
func doTests(t *testing.T, tests []string) {
|
||||||
|
doTestsParam(t, tests, TestParams{
|
||||||
|
extensions: CommonExtensions,
|
||||||
|
HTMLRendererParameters: HTMLRendererParameters{
|
||||||
|
Flags: CommonHTMLFlags,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestsBlock(t *testing.T, tests []string, extensions Extensions) {
|
||||||
|
doTestsParam(t, tests, TestParams{
|
||||||
|
extensions: extensions,
|
||||||
|
HTMLFlags: UseXHTML,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestsParam(t *testing.T, tests []string, params TestParams) {
|
||||||
|
execRecoverableTestSuite(t, tests, params, func(candidate *string) {
|
||||||
|
for i := 0; i+1 < len(tests); i += 2 {
|
||||||
|
input := tests[i]
|
||||||
|
*candidate = input
|
||||||
|
expected := tests[i+1]
|
||||||
|
actual := runMarkdown(*candidate, params)
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||||
|
*candidate, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now test every substring to stress test bounds checking
|
||||||
|
if !testing.Short() {
|
||||||
|
for start := 0; start < len(input); start++ {
|
||||||
|
for end := start + 1; end <= len(input); end++ {
|
||||||
|
*candidate = input[start:end]
|
||||||
|
runMarkdown(*candidate, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestsInline(t *testing.T, tests []string) {
|
||||||
|
doTestsInlineParam(t, tests, TestParams{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doLinkTestsInline(t *testing.T, tests []string) {
|
||||||
|
doTestsInline(t, tests)
|
||||||
|
|
||||||
|
prefix := "http://localhost"
|
||||||
|
params := HTMLRendererParameters{AbsolutePrefix: prefix}
|
||||||
|
transformTests := transformLinks(tests, prefix)
|
||||||
|
doTestsInlineParam(t, transformTests, TestParams{
|
||||||
|
HTMLRendererParameters: params,
|
||||||
|
})
|
||||||
|
doTestsInlineParam(t, transformTests, TestParams{
|
||||||
|
HTMLFlags: UseXHTML,
|
||||||
|
HTMLRendererParameters: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doSafeTestsInline(t *testing.T, tests []string) {
|
||||||
|
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Safelink})
|
||||||
|
|
||||||
|
// 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, TestParams{
|
||||||
|
HTMLFlags: Safelink,
|
||||||
|
HTMLRendererParameters: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestsInlineParam(t *testing.T, tests []string, params TestParams) {
|
||||||
|
params.extensions |= Autolink | Strikethrough
|
||||||
|
params.HTMLFlags |= UseXHTML
|
||||||
|
doTestsParam(t, tests, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformLinks(tests []string, prefix string) []string {
|
||||||
|
newTests := make([]string, len(tests))
|
||||||
|
anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
|
||||||
|
imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
|
||||||
|
for i, test := range tests {
|
||||||
|
if i%2 == 1 {
|
||||||
|
test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
|
||||||
|
test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
|
||||||
|
}
|
||||||
|
newTests[i] = test
|
||||||
|
}
|
||||||
|
return newTests
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestsReference(t *testing.T, files []string, flag Extensions) {
|
||||||
|
params := TestParams{extensions: flag}
|
||||||
|
execRecoverableTestSuite(t, files, params, func(candidate *string) {
|
||||||
|
for _, basename := range files {
|
||||||
|
filename := filepath.Join("testdata", basename+".text")
|
||||||
|
inputBytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
input := string(inputBytes)
|
||||||
|
|
||||||
|
filename = filepath.Join("testdata", basename+".html")
|
||||||
|
expectedBytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
expected := string(expectedBytes)
|
||||||
|
|
||||||
|
actual := string(runMarkdown(input, params))
|
||||||
|
if actual != expected {
|
||||||
|
t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]",
|
||||||
|
basename+".text", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// now test every prefix of every input to check for
|
||||||
|
// bounds checking
|
||||||
|
if !testing.Short() {
|
||||||
|
start, max := 0, len(input)
|
||||||
|
for end := start + 1; end <= max; end++ {
|
||||||
|
*candidate = input[start:end]
|
||||||
|
runMarkdown(*candidate, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
1540
vendor/github.com/documize/blackfriday/html.go
generated
vendored
1540
vendor/github.com/documize/blackfriday/html.go
generated
vendored
File diff suppressed because it is too large
Load diff
585
vendor/github.com/documize/blackfriday/inline.go
generated
vendored
585
vendor/github.com/documize/blackfriday/inline.go
generated
vendored
File diff suppressed because it is too large
Load diff
406
vendor/github.com/documize/blackfriday/inline_test.go
generated
vendored
406
vendor/github.com/documize/blackfriday/inline_test.go
generated
vendored
|
@ -20,90 +20,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
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(MarkdownOptions([]byte(input), renderer, opts))
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsInline(t *testing.T, tests []string) {
|
|
||||||
doTestsInlineParam(t, tests, Options{}, 0, HtmlRendererParameters{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func doLinkTestsInline(t *testing.T, tests []string) {
|
|
||||||
doTestsInline(t, tests)
|
|
||||||
|
|
||||||
prefix := "http://localhost"
|
|
||||||
params := HtmlRendererParameters{AbsolutePrefix: prefix}
|
|
||||||
transformTests := transformLinks(tests, prefix)
|
|
||||||
doTestsInlineParam(t, transformTests, Options{}, 0, params)
|
|
||||||
doTestsInlineParam(t, transformTests, Options{}, commonHtmlFlags, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doSafeTestsInline(t *testing.T, tests []string) {
|
|
||||||
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, Options{}, HTML_SAFELINK, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsInlineParam(t *testing.T, tests []string, opts Options, htmlFlags int,
|
|
||||||
params HtmlRendererParameters) {
|
|
||||||
// catch and report panics
|
|
||||||
var candidate string
|
|
||||||
/*
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
t.Errorf("\npanic while processing [%#v] (%v)\n", candidate, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
*/
|
|
||||||
|
|
||||||
for i := 0; i+1 < len(tests); i += 2 {
|
|
||||||
input := tests[i]
|
|
||||||
candidate = input
|
|
||||||
expected := tests[i+1]
|
|
||||||
actual := runMarkdownInline(candidate, opts, htmlFlags, params)
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("\nInput [%#v]\nExpected[%#v]\nActual [%#v]",
|
|
||||||
candidate, expected, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
// now test every substring to stress test bounds checking
|
|
||||||
if !testing.Short() {
|
|
||||||
for start := 0; start < len(input); start++ {
|
|
||||||
for end := start + 1; end <= len(input); end++ {
|
|
||||||
candidate = input[start:end]
|
|
||||||
_ = runMarkdownInline(candidate, opts, htmlFlags, params)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func transformLinks(tests []string, prefix string) []string {
|
|
||||||
newTests := make([]string, len(tests))
|
|
||||||
anchorRe := regexp.MustCompile(`<a href="/(.*?)"`)
|
|
||||||
imgRe := regexp.MustCompile(`<img src="/(.*?)"`)
|
|
||||||
for i, test := range tests {
|
|
||||||
if i%2 == 1 {
|
|
||||||
test = anchorRe.ReplaceAllString(test, `<a href="`+prefix+`/$1"`)
|
|
||||||
test = imgRe.ReplaceAllString(test, `<img src="`+prefix+`/$1"`)
|
|
||||||
}
|
|
||||||
newTests[i] = test
|
|
||||||
}
|
|
||||||
return newTests
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEmphasis(t *testing.T) {
|
func TestEmphasis(t *testing.T) {
|
||||||
var tests = []string{
|
var tests = []string{
|
||||||
"nothing inline\n",
|
"nothing inline\n",
|
||||||
|
@ -153,6 +69,9 @@ func TestEmphasis(t *testing.T) {
|
||||||
|
|
||||||
"mix of *markers_\n",
|
"mix of *markers_\n",
|
||||||
"<p>mix of *markers_</p>\n",
|
"<p>mix of *markers_</p>\n",
|
||||||
|
|
||||||
|
"*What is A\\* algorithm?*\n",
|
||||||
|
"<p><em>What is A* algorithm?</em></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInline(t, tests)
|
doTestsInline(t, tests)
|
||||||
}
|
}
|
||||||
|
@ -180,11 +99,11 @@ func TestReferenceOverride(t *testing.T) {
|
||||||
"test [ref5][]\n",
|
"test [ref5][]\n",
|
||||||
"<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, Options{
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
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 overriden reference exists without definition
|
// just an overridden reference exists without definition
|
||||||
return &Reference{
|
return &Reference{
|
||||||
Link: "http://www.ref1.com/",
|
Link: "http://www.ref1.com/",
|
||||||
Title: "Reference 1"}, true
|
Title: "Reference 1"}, true
|
||||||
|
@ -211,7 +130,8 @@ func TestReferenceOverride(t *testing.T) {
|
||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
return nil, false
|
return nil, false
|
||||||
}}, 0, HtmlRendererParameters{})
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStrong(t *testing.T) {
|
func TestStrong(t *testing.T) {
|
||||||
|
@ -263,6 +183,12 @@ func TestStrong(t *testing.T) {
|
||||||
|
|
||||||
"mix of **markers__\n",
|
"mix of **markers__\n",
|
||||||
"<p>mix of **markers__</p>\n",
|
"<p>mix of **markers__</p>\n",
|
||||||
|
|
||||||
|
"**`/usr`** : this folder is named `usr`\n",
|
||||||
|
"<p><strong><code>/usr</code></strong> : this folder is named <code>usr</code></p>\n",
|
||||||
|
|
||||||
|
"**`/usr`** :\n\n this folder is named `usr`\n",
|
||||||
|
"<p><strong><code>/usr</code></strong> :</p>\n\n<p>this folder is named <code>usr</code></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInline(t, tests)
|
doTestsInline(t, tests)
|
||||||
}
|
}
|
||||||
|
@ -291,7 +217,7 @@ func TestEmphasisMix(t *testing.T) {
|
||||||
"<p><strong>improper *nesting</strong> is* bad</p>\n",
|
"<p><strong>improper *nesting</strong> is* bad</p>\n",
|
||||||
|
|
||||||
"*improper **nesting* is** bad\n",
|
"*improper **nesting* is** bad\n",
|
||||||
"<p><em>improper **nesting</em> is** bad</p>\n",
|
"<p>*improper <strong>nesting* is</strong> bad</p>\n",
|
||||||
}
|
}
|
||||||
doTestsInline(t, tests)
|
doTestsInline(t, tests)
|
||||||
}
|
}
|
||||||
|
@ -415,9 +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, Options{
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
Extensions: EXTENSION_BACKSLASH_LINE_BREAK},
|
extensions: BackslashLineBreak})
|
||||||
0, HtmlRendererParameters{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInlineLink(t *testing.T) {
|
func TestInlineLink(t *testing.T) {
|
||||||
|
@ -557,8 +482,9 @@ func TestRelAttrLink(t *testing.T) {
|
||||||
"[foo](../bar)\n",
|
"[foo](../bar)\n",
|
||||||
"<p><a href=\"../bar\">foo</a></p>\n",
|
"<p><a href=\"../bar\">foo</a></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, nofollowTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS,
|
doTestsInlineParam(t, nofollowTests, TestParams{
|
||||||
HtmlRendererParameters{})
|
HTMLFlags: Safelink | NofollowLinks,
|
||||||
|
})
|
||||||
|
|
||||||
var noreferrerTests = []string{
|
var noreferrerTests = []string{
|
||||||
"[foo](http://bar.com/foo/)\n",
|
"[foo](http://bar.com/foo/)\n",
|
||||||
|
@ -567,8 +493,9 @@ func TestRelAttrLink(t *testing.T) {
|
||||||
"[foo](/bar/)\n",
|
"[foo](/bar/)\n",
|
||||||
"<p><a href=\"/bar/\">foo</a></p>\n",
|
"<p><a href=\"/bar/\">foo</a></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, noreferrerTests, Options{}, HTML_SAFELINK|HTML_NOREFERRER_LINKS,
|
doTestsInlineParam(t, noreferrerTests, TestParams{
|
||||||
HtmlRendererParameters{})
|
HTMLFlags: Safelink | NoreferrerLinks,
|
||||||
|
})
|
||||||
|
|
||||||
var nofollownoreferrerTests = []string{
|
var nofollownoreferrerTests = []string{
|
||||||
"[foo](http://bar.com/foo/)\n",
|
"[foo](http://bar.com/foo/)\n",
|
||||||
|
@ -577,8 +504,9 @@ func TestRelAttrLink(t *testing.T) {
|
||||||
"[foo](/bar/)\n",
|
"[foo](/bar/)\n",
|
||||||
"<p><a href=\"/bar/\">foo</a></p>\n",
|
"<p><a href=\"/bar/\">foo</a></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, nofollownoreferrerTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS,
|
doTestsInlineParam(t, nofollownoreferrerTests, TestParams{
|
||||||
HtmlRendererParameters{})
|
HTMLFlags: Safelink | NofollowLinks | NoreferrerLinks,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHrefTargetBlank(t *testing.T) {
|
func TestHrefTargetBlank(t *testing.T) {
|
||||||
|
@ -605,7 +533,9 @@ func TestHrefTargetBlank(t *testing.T) {
|
||||||
"[foo](http://example.com)\n",
|
"[foo](http://example.com)\n",
|
||||||
"<p><a href=\"http://example.com\" target=\"_blank\">foo</a></p>\n",
|
"<p><a href=\"http://example.com\" target=\"_blank\">foo</a></p>\n",
|
||||||
}
|
}
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_SAFELINK|HTML_HREF_TARGET_BLANK, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
|
HTMLFlags: Safelink | HrefTargetBlank,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSafeInlineLink(t *testing.T) {
|
func TestSafeInlineLink(t *testing.T) {
|
||||||
|
@ -672,6 +602,9 @@ func TestReferenceLink(t *testing.T) {
|
||||||
|
|
||||||
"[ref]\n [ref]: ../url/ \"title\"\n",
|
"[ref]\n [ref]: ../url/ \"title\"\n",
|
||||||
"<p><a href=\"../url/\" title=\"title\">ref</a></p>\n",
|
"<p><a href=\"../url/\" title=\"title\">ref</a></p>\n",
|
||||||
|
|
||||||
|
"[link][ref]\n [ref]: /url/",
|
||||||
|
"<p><a href=\"/url/\">link</a></p>\n",
|
||||||
}
|
}
|
||||||
doLinkTestsInline(t, tests)
|
doLinkTestsInline(t, tests)
|
||||||
}
|
}
|
||||||
|
@ -799,6 +732,9 @@ func TestAutoLink(t *testing.T) {
|
||||||
|
|
||||||
"http://foo.com/viewtopic.php?param="18"",
|
"http://foo.com/viewtopic.php?param="18"",
|
||||||
"<p><a href=\"http://foo.com/viewtopic.php?param="18"\">http://foo.com/viewtopic.php?param="18"</a></p>\n",
|
"<p><a href=\"http://foo.com/viewtopic.php?param="18"\">http://foo.com/viewtopic.php?param="18"</a></p>\n",
|
||||||
|
|
||||||
|
"<a href=\"https://fancy.com\">https://fancy.com</a>\n",
|
||||||
|
"<p><a href=\"https://fancy.com\">https://fancy.com</a></p>\n",
|
||||||
}
|
}
|
||||||
doLinkTestsInline(t, tests)
|
doLinkTestsInline(t, tests)
|
||||||
}
|
}
|
||||||
|
@ -806,14 +742,15 @@ func TestAutoLink(t *testing.T) {
|
||||||
var footnoteTests = []string{
|
var footnoteTests = []string{
|
||||||
"testing footnotes.[^a]\n\n[^a]: This is the note\n",
|
"testing footnotes.[^a]\n\n[^a]: This is the note\n",
|
||||||
`<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p>
|
`<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p>
|
||||||
|
|
||||||
<div class="footnotes">
|
<div class="footnotes">
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li id="fn:a">This is the note
|
<li id="fn:a">This is the note</li>
|
||||||
</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
|
@ -832,6 +769,7 @@ No longer in the footnote
|
||||||
`<p>testing long<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">1</a></sup> notes.</p>
|
`<p>testing long<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">1</a></sup> notes.</p>
|
||||||
|
|
||||||
<p>No longer in the footnote</p>
|
<p>No longer in the footnote</p>
|
||||||
|
|
||||||
<div class="footnotes">
|
<div class="footnotes">
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -845,9 +783,9 @@ No longer in the footnote
|
||||||
some code
|
some code
|
||||||
</code></p>
|
</code></p>
|
||||||
|
|
||||||
<p>Paragraph 3</p>
|
<p>Paragraph 3</p></li>
|
||||||
</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
|
@ -870,21 +808,23 @@ what happens here
|
||||||
<p>omg</p>
|
<p>omg</p>
|
||||||
|
|
||||||
<p>what happens here</p>
|
<p>what happens here</p>
|
||||||
|
|
||||||
<div class="footnotes">
|
<div class="footnotes">
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<ol>
|
<ol>
|
||||||
<li id="fn:c">this is <a href="/link/c">note</a> c
|
<li id="fn:c">this is <a href="/link/c">note</a> c</li>
|
||||||
</li>
|
|
||||||
<li id="fn:d">this is note d
|
<li id="fn:d">this is note d</li>
|
||||||
</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"testing inline^[this is the note] notes.\n",
|
"testing inline^[this is the note] notes.\n",
|
||||||
`<p>testing inline<sup class="footnote-ref" id="fnref:this-is-the-note"><a rel="footnote" href="#fn:this-is-the-note">1</a></sup> notes.</p>
|
`<p>testing inline<sup class="footnote-ref" id="fnref:this-is-the-note"><a rel="footnote" href="#fn:this-is-the-note">1</a></sup> notes.</p>
|
||||||
|
|
||||||
<div class="footnotes">
|
<div class="footnotes">
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -892,11 +832,13 @@ what happens here
|
||||||
<ol>
|
<ol>
|
||||||
<li id="fn:this-is-the-note">this is the note</li>
|
<li id="fn:this-is-the-note">this is the note</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"testing multiple[^1] types^[inline note] of notes[^2]\n\n[^2]: the second deferred note\n[^1]: the first deferred note\n\n\twhich happens to be a block\n",
|
"testing multiple[^1] types^[inline note] of notes[^2]\n\n[^2]: the second deferred note\n[^1]: the first deferred note\n\n\twhich happens to be a block\n",
|
||||||
`<p>testing multiple<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> types<sup class="footnote-ref" id="fnref:inline-note"><a rel="footnote" href="#fn:inline-note">2</a></sup> of notes<sup class="footnote-ref" id="fnref:2"><a rel="footnote" href="#fn:2">3</a></sup></p>
|
`<p>testing multiple<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> types<sup class="footnote-ref" id="fnref:inline-note"><a rel="footnote" href="#fn:inline-note">2</a></sup> of notes<sup class="footnote-ref" id="fnref:2"><a rel="footnote" href="#fn:2">3</a></sup></p>
|
||||||
|
|
||||||
<div class="footnotes">
|
<div class="footnotes">
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -904,12 +846,13 @@ what happens here
|
||||||
<ol>
|
<ol>
|
||||||
<li id="fn:1"><p>the first deferred note</p>
|
<li id="fn:1"><p>the first deferred note</p>
|
||||||
|
|
||||||
<p>which happens to be a block</p>
|
<p>which happens to be a block</p></li>
|
||||||
</li>
|
|
||||||
<li id="fn:inline-note">inline note</li>
|
<li id="fn:inline-note">inline note</li>
|
||||||
<li id="fn:2">the second deferred note
|
|
||||||
</li>
|
<li id="fn:2">the second deferred note</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
|
@ -920,6 +863,7 @@ what happens here
|
||||||
may be multiple paragraphs.
|
may be multiple paragraphs.
|
||||||
`,
|
`,
|
||||||
`<p>This is a footnote<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup><sup class="footnote-ref" id="fnref:and-this-is-an-i"><a rel="footnote" href="#fn:and-this-is-an-i">2</a></sup></p>
|
`<p>This is a footnote<sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup><sup class="footnote-ref" id="fnref:and-this-is-an-i"><a rel="footnote" href="#fn:and-this-is-an-i">2</a></sup></p>
|
||||||
|
|
||||||
<div class="footnotes">
|
<div class="footnotes">
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
@ -927,25 +871,70 @@ what happens here
|
||||||
<ol>
|
<ol>
|
||||||
<li id="fn:1"><p>the footnote text.</p>
|
<li id="fn:1"><p>the footnote text.</p>
|
||||||
|
|
||||||
<p>may be multiple paragraphs.</p>
|
<p>may be multiple paragraphs.</p></li>
|
||||||
</li>
|
|
||||||
<li id="fn:and-this-is-an-i">and this is an inline footnote</li>
|
<li id="fn:and-this-is-an-i">and this is an inline footnote</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|
||||||
"empty footnote[^]\n\n[^]: fn text",
|
"empty footnote[^]\n\n[^]: fn text",
|
||||||
"<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",
|
"<p>empty footnote<sup class=\"footnote-ref\" id=\"fnref:\"><a rel=\"footnote\" href=\"#fn:\">1</a></sup></p>\n\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:\">fn text</li>\n</ol>\n\n</div>\n",
|
||||||
|
|
||||||
"Some text.[^note1]\n\n[^note1]: fn1",
|
"Some text.[^note1]\n\n[^note1]: fn1",
|
||||||
"<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup></p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1\n</li>\n</ol>\n</div>\n",
|
"<p>Some text.<sup class=\"footnote-ref\" id=\"fnref:note1\"><a rel=\"footnote\" href=\"#fn:note1\">1</a></sup></p>\n\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1</li>\n</ol>\n\n</div>\n",
|
||||||
|
|
||||||
"Some text.[^note1][^note2]\n\n[^note1]: fn1\n[^note2]: fn2\n",
|
"Some text.[^note1][^note2]\n\n[^note1]: fn1\n[^note2]: fn2\n",
|
||||||
"<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",
|
"<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\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:note1\">fn1</li>\n\n<li id=\"fn:note2\">fn2</li>\n</ol>\n\n</div>\n",
|
||||||
|
|
||||||
|
`Bla bla [^1] [WWW][w3]
|
||||||
|
|
||||||
|
[^1]: This is a footnote
|
||||||
|
|
||||||
|
[w3]: http://www.w3.org/
|
||||||
|
`,
|
||||||
|
`<p>Bla bla <sup class="footnote-ref" id="fnref:1"><a rel="footnote" href="#fn:1">1</a></sup> <a href="http://www.w3.org/">WWW</a></p>
|
||||||
|
|
||||||
|
<div class="footnotes">
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li id="fn:1">This is a footnote</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
|
||||||
|
`This is exciting![^fn1]
|
||||||
|
|
||||||
|
[^fn1]: Fine print
|
||||||
|
`,
|
||||||
|
`<p>This is exciting!<sup class="footnote-ref" id="fnref:fn1"><a rel="footnote" href="#fn:fn1">1</a></sup></p>
|
||||||
|
|
||||||
|
<div class="footnotes">
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li id="fn:fn1">Fine print</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
|
||||||
|
`This text does not reference a footnote.
|
||||||
|
|
||||||
|
[^footnote]: But it has a footnote! And it gets omitted.
|
||||||
|
`,
|
||||||
|
"<p>This text does not reference a footnote.</p>\n",
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFootnotes(t *testing.T) {
|
func TestFootnotes(t *testing.T) {
|
||||||
doTestsInlineParam(t, footnoteTests, Options{Extensions: EXTENSION_FOOTNOTES}, 0, HtmlRendererParameters{})
|
doTestsInlineParam(t, footnoteTests, TestParams{
|
||||||
|
extensions: Footnotes,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFootnotesWithParameters(t *testing.T) {
|
func TestFootnotesWithParameters(t *testing.T) {
|
||||||
|
@ -965,12 +954,72 @@ func TestFootnotesWithParameters(t *testing.T) {
|
||||||
tests[i] = test
|
tests[i] = test
|
||||||
}
|
}
|
||||||
|
|
||||||
params := HtmlRendererParameters{
|
params := HTMLRendererParameters{
|
||||||
FootnoteAnchorPrefix: prefix,
|
FootnoteAnchorPrefix: prefix,
|
||||||
FootnoteReturnLinkContents: returnText,
|
FootnoteReturnLinkContents: returnText,
|
||||||
}
|
}
|
||||||
|
|
||||||
doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, HTML_FOOTNOTE_RETURN_LINKS, params)
|
doTestsInlineParam(t, tests, TestParams{
|
||||||
|
extensions: Footnotes,
|
||||||
|
HTMLFlags: FootnoteReturnLinks,
|
||||||
|
HTMLRendererParameters: params,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNestedFootnotes(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
`Paragraph.[^fn1]
|
||||||
|
|
||||||
|
[^fn1]:
|
||||||
|
Asterisk[^fn2]
|
||||||
|
|
||||||
|
[^fn2]:
|
||||||
|
Obelisk`,
|
||||||
|
`<p>Paragraph.<sup class="footnote-ref" id="fnref:fn1"><a rel="footnote" href="#fn:fn1">1</a></sup></p>
|
||||||
|
|
||||||
|
<div class="footnotes">
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li id="fn:fn1">Asterisk<sup class="footnote-ref" id="fnref:fn2"><a rel="footnote" href="#fn:fn2">2</a></sup></li>
|
||||||
|
|
||||||
|
<li id="fn:fn2">Obelisk</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
doTestsInlineParam(t, tests, TestParams{extensions: Footnotes})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInlineComments(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"Hello <!-- there ->\n",
|
||||||
|
"<p>Hello <!— there –></p>\n",
|
||||||
|
|
||||||
|
"Hello <!-- there -->\n",
|
||||||
|
"<p>Hello <!-- there --></p>\n",
|
||||||
|
|
||||||
|
"Hello <!-- there -->",
|
||||||
|
"<p>Hello <!-- there --></p>\n",
|
||||||
|
|
||||||
|
"Hello <!---->\n",
|
||||||
|
"<p>Hello <!----></p>\n",
|
||||||
|
|
||||||
|
"Hello <!-- there -->\na",
|
||||||
|
"<p>Hello <!-- there -->\na</p>\n",
|
||||||
|
|
||||||
|
"* list <!-- item -->\n",
|
||||||
|
"<ul>\n<li>list <!-- item --></li>\n</ul>\n",
|
||||||
|
|
||||||
|
"<!-- Front --> comment\n",
|
||||||
|
"<p><!-- Front --> comment</p>\n",
|
||||||
|
|
||||||
|
"blahblah\n<!--- foo -->\nrhubarb\n",
|
||||||
|
"<p>blahblah\n<!--- foo -->\nrhubarb</p>\n",
|
||||||
|
}
|
||||||
|
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsDashes})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSmartDoubleQuotes(t *testing.T) {
|
func TestSmartDoubleQuotes(t *testing.T) {
|
||||||
|
@ -982,7 +1031,19 @@ func TestSmartDoubleQuotes(t *testing.T) {
|
||||||
"two pair of \"some\" quoted \"text\".\n",
|
"two pair of \"some\" quoted \"text\".\n",
|
||||||
"<p>two pair of “some” quoted “text”.</p>\n"}
|
"<p>two pair of “some” quoted “text”.</p>\n"}
|
||||||
|
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSmartDoubleQuotesNBSP(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"this should be normal \"quoted\" text.\n",
|
||||||
|
"<p>this should be normal “ quoted ” text.</p>\n",
|
||||||
|
"this \" single double\n",
|
||||||
|
"<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, TestParams{HTMLFlags: Smartypants | SmartypantsQuotesNBSP})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSmartAngledDoubleQuotes(t *testing.T) {
|
func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||||
|
@ -994,7 +1055,19 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||||
"two pair of \"some\" quoted \"text\".\n",
|
"two pair of \"some\" quoted \"text\".\n",
|
||||||
"<p>two pair of «some» quoted «text».</p>\n"}
|
"<p>two pair of «some» quoted «text».</p>\n"}
|
||||||
|
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSmartAngledDoubleQuotesNBSP(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
"this should be angled \"quoted\" text.\n",
|
||||||
|
"<p>this should be angled « quoted » text.</p>\n",
|
||||||
|
"this \" single double\n",
|
||||||
|
"<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, TestParams{HTMLFlags: Smartypants | SmartypantsAngledQuotes | SmartypantsQuotesNBSP})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSmartFractions(t *testing.T) {
|
func TestSmartFractions(t *testing.T) {
|
||||||
|
@ -1004,7 +1077,7 @@ func TestSmartFractions(t *testing.T) {
|
||||||
"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\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"}
|
"<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"}
|
||||||
|
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants})
|
||||||
|
|
||||||
tests = []string{
|
tests = []string{
|
||||||
"1/2, 2/3, 81/100 and 1000000/1048576.\n",
|
"1/2, 2/3, 81/100 and 1000000/1048576.\n",
|
||||||
|
@ -1012,5 +1085,92 @@ func TestSmartFractions(t *testing.T) {
|
||||||
"1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.\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"}
|
"<p>1/2/2015, 1/4/2015, 3/4/2015; 2015/1/2, 2015/1/4, 2015/3/4.</p>\n"}
|
||||||
|
|
||||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_FRACTIONS, HtmlRendererParameters{})
|
doTestsInlineParam(t, tests, TestParams{HTMLFlags: Smartypants | SmartypantsFractions})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisableSmartDashes(t *testing.T) {
|
||||||
|
doTestsInlineParam(t, []string{
|
||||||
|
"foo - bar\n",
|
||||||
|
"<p>foo - bar</p>\n",
|
||||||
|
"foo -- bar\n",
|
||||||
|
"<p>foo -- bar</p>\n",
|
||||||
|
"foo --- bar\n",
|
||||||
|
"<p>foo --- bar</p>\n",
|
||||||
|
}, TestParams{})
|
||||||
|
doTestsInlineParam(t, []string{
|
||||||
|
"foo - bar\n",
|
||||||
|
"<p>foo – bar</p>\n",
|
||||||
|
"foo -- bar\n",
|
||||||
|
"<p>foo — bar</p>\n",
|
||||||
|
"foo --- bar\n",
|
||||||
|
"<p>foo —– bar</p>\n",
|
||||||
|
}, TestParams{HTMLFlags: Smartypants | SmartypantsDashes})
|
||||||
|
doTestsInlineParam(t, []string{
|
||||||
|
"foo - bar\n",
|
||||||
|
"<p>foo - bar</p>\n",
|
||||||
|
"foo -- bar\n",
|
||||||
|
"<p>foo – bar</p>\n",
|
||||||
|
"foo --- bar\n",
|
||||||
|
"<p>foo — bar</p>\n",
|
||||||
|
}, TestParams{HTMLFlags: Smartypants | SmartypantsLatexDashes | SmartypantsDashes})
|
||||||
|
doTestsInlineParam(t, []string{
|
||||||
|
"foo - bar\n",
|
||||||
|
"<p>foo - bar</p>\n",
|
||||||
|
"foo -- bar\n",
|
||||||
|
"<p>foo -- bar</p>\n",
|
||||||
|
"foo --- bar\n",
|
||||||
|
"<p>foo --- bar</p>\n",
|
||||||
|
}, TestParams{HTMLFlags: Smartypants | SmartypantsLatexDashes})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipLinks(t *testing.T) {
|
||||||
|
doTestsInlineParam(t, []string{
|
||||||
|
"[foo](gopher://foo.bar)",
|
||||||
|
"<p><tt>foo</tt></p>\n",
|
||||||
|
|
||||||
|
"[foo](mailto://bar/)\n",
|
||||||
|
"<p><tt>foo</tt></p>\n",
|
||||||
|
}, TestParams{
|
||||||
|
HTMLFlags: SkipLinks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipImages(t *testing.T) {
|
||||||
|
doTestsInlineParam(t, []string{
|
||||||
|
"\n",
|
||||||
|
"<p></p>\n",
|
||||||
|
}, TestParams{
|
||||||
|
HTMLFlags: SkipImages,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUseXHTML(t *testing.T) {
|
||||||
|
doTestsParam(t, []string{
|
||||||
|
"---",
|
||||||
|
"<hr>\n",
|
||||||
|
}, TestParams{})
|
||||||
|
doTestsParam(t, []string{
|
||||||
|
"---",
|
||||||
|
"<hr />\n",
|
||||||
|
}, TestParams{HTMLFlags: UseXHTML})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipHTML(t *testing.T) {
|
||||||
|
doTestsParam(t, []string{
|
||||||
|
"<div class=\"foo\"></div>\n\ntext\n\n<form>the form</form>",
|
||||||
|
"<p>text</p>\n\n<p>the form</p>\n",
|
||||||
|
|
||||||
|
"text <em>inline html</em> more text",
|
||||||
|
"<p>text inline html more text</p>\n",
|
||||||
|
}, TestParams{HTMLFlags: SkipHTML})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkSmartDoubleQuotes(b *testing.B) {
|
||||||
|
params := TestParams{HTMLFlags: Smartypants}
|
||||||
|
params.extensions |= Autolink | Strikethrough
|
||||||
|
params.HTMLFlags |= UseXHTML
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
runMarkdown("this should be normal \"quoted\" text.\n", params)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
332
vendor/github.com/documize/blackfriday/latex.go
generated
vendored
332
vendor/github.com/documize/blackfriday/latex.go
generated
vendored
|
@ -1,332 +0,0 @@
|
||||||
//
|
|
||||||
// Blackfriday Markdown Processor
|
|
||||||
// Available at http://github.com/russross/blackfriday
|
|
||||||
//
|
|
||||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
|
||||||
// Distributed under the Simplified BSD License.
|
|
||||||
// See README.md for details.
|
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// LaTeX rendering backend
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
package blackfriday
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Latex is a type that implements the Renderer interface for LaTeX output.
|
|
||||||
//
|
|
||||||
// Do not create this directly, instead use the LatexRenderer function.
|
|
||||||
type Latex struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// LatexRenderer creates and configures a Latex object, which
|
|
||||||
// satisfies the Renderer interface.
|
|
||||||
//
|
|
||||||
// flags is a set of LATEX_* options ORed together (currently no such options
|
|
||||||
// are defined).
|
|
||||||
func LatexRenderer(flags int) Renderer {
|
|
||||||
return &Latex{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) GetFlags() int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// render code chunks using verbatim, or listings if we have a language
|
|
||||||
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
|
||||||
if lang == "" {
|
|
||||||
out.WriteString("\n\\begin{verbatim}\n")
|
|
||||||
} else {
|
|
||||||
out.WriteString("\n\\begin{lstlisting}[language=")
|
|
||||||
out.WriteString(lang)
|
|
||||||
out.WriteString("]\n")
|
|
||||||
}
|
|
||||||
out.Write(text)
|
|
||||||
if lang == "" {
|
|
||||||
out.WriteString("\n\\end{verbatim}\n")
|
|
||||||
} else {
|
|
||||||
out.WriteString("\n\\end{lstlisting}\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
|
|
||||||
out.WriteString("\n\\begin{quotation}\n")
|
|
||||||
out.Write(text)
|
|
||||||
out.WriteString("\n\\end{quotation}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
|
|
||||||
// a pretty lame thing to do...
|
|
||||||
out.WriteString("\n\\begin{verbatim}\n")
|
|
||||||
out.Write(text)
|
|
||||||
out.WriteString("\n\\end{verbatim}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
|
|
||||||
marker := out.Len()
|
|
||||||
|
|
||||||
switch level {
|
|
||||||
case 1:
|
|
||||||
out.WriteString("\n\\section{")
|
|
||||||
case 2:
|
|
||||||
out.WriteString("\n\\subsection{")
|
|
||||||
case 3:
|
|
||||||
out.WriteString("\n\\subsubsection{")
|
|
||||||
case 4:
|
|
||||||
out.WriteString("\n\\paragraph{")
|
|
||||||
case 5:
|
|
||||||
out.WriteString("\n\\subparagraph{")
|
|
||||||
case 6:
|
|
||||||
out.WriteString("\n\\textbf{")
|
|
||||||
}
|
|
||||||
if !text() {
|
|
||||||
out.Truncate(marker)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out.WriteString("}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) HRule(out *bytes.Buffer) {
|
|
||||||
out.WriteString("\n\\HRule\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
|
|
||||||
marker := out.Len()
|
|
||||||
if flags&LIST_TYPE_ORDERED != 0 {
|
|
||||||
out.WriteString("\n\\begin{enumerate}\n")
|
|
||||||
} else {
|
|
||||||
out.WriteString("\n\\begin{itemize}\n")
|
|
||||||
}
|
|
||||||
if !text() {
|
|
||||||
out.Truncate(marker)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if flags&LIST_TYPE_ORDERED != 0 {
|
|
||||||
out.WriteString("\n\\end{enumerate}\n")
|
|
||||||
} else {
|
|
||||||
out.WriteString("\n\\end{itemize}\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
|
||||||
out.WriteString("\n\\item ")
|
|
||||||
out.Write(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
|
|
||||||
marker := out.Len()
|
|
||||||
out.WriteString("\n")
|
|
||||||
if !text() {
|
|
||||||
out.Truncate(marker)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
|
|
||||||
out.WriteString("\n\\begin{tabular}{")
|
|
||||||
for _, elt := range columnData {
|
|
||||||
switch elt {
|
|
||||||
case TABLE_ALIGNMENT_LEFT:
|
|
||||||
out.WriteByte('l')
|
|
||||||
case TABLE_ALIGNMENT_RIGHT:
|
|
||||||
out.WriteByte('r')
|
|
||||||
default:
|
|
||||||
out.WriteByte('c')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.WriteString("}\n")
|
|
||||||
out.Write(header)
|
|
||||||
out.WriteString(" \\\\\n\\hline\n")
|
|
||||||
out.Write(body)
|
|
||||||
out.WriteString("\n\\end{tabular}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
|
|
||||||
if out.Len() > 0 {
|
|
||||||
out.WriteString(" \\\\\n")
|
|
||||||
}
|
|
||||||
out.Write(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
|
|
||||||
if out.Len() > 0 {
|
|
||||||
out.WriteString(" & ")
|
|
||||||
}
|
|
||||||
out.Write(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
|
|
||||||
if out.Len() > 0 {
|
|
||||||
out.WriteString(" & ")
|
|
||||||
}
|
|
||||||
out.Write(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this
|
|
||||||
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
|
||||||
out.WriteString("\\href{")
|
|
||||||
if kind == LINK_TYPE_EMAIL {
|
|
||||||
out.WriteString("mailto:")
|
|
||||||
}
|
|
||||||
out.Write(link)
|
|
||||||
out.WriteString("}{")
|
|
||||||
out.Write(link)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
|
|
||||||
out.WriteString("\\texttt{")
|
|
||||||
escapeSpecialChars(out, text)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
|
|
||||||
out.WriteString("\\textbf{")
|
|
||||||
out.Write(text)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
|
|
||||||
out.WriteString("\\textit{")
|
|
||||||
out.Write(text)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
|
||||||
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
|
|
||||||
// treat it like a link
|
|
||||||
out.WriteString("\\href{")
|
|
||||||
out.Write(link)
|
|
||||||
out.WriteString("}{")
|
|
||||||
out.Write(alt)
|
|
||||||
out.WriteString("}")
|
|
||||||
} else {
|
|
||||||
out.WriteString("\\includegraphics{")
|
|
||||||
out.Write(link)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) LineBreak(out *bytes.Buffer) {
|
|
||||||
out.WriteString(" \\\\\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
|
|
||||||
out.WriteString("\\href{")
|
|
||||||
out.Write(link)
|
|
||||||
out.WriteString("}{")
|
|
||||||
out.Write(content)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
|
|
||||||
out.WriteString("\\textbf{\\textit{")
|
|
||||||
out.Write(text)
|
|
||||||
out.WriteString("}}")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
|
|
||||||
out.WriteString("\\sout{")
|
|
||||||
out.Write(text)
|
|
||||||
out.WriteString("}")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: this
|
|
||||||
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func needsBackslash(c byte) bool {
|
|
||||||
for _, r := range []byte("_{}%$&\\~#") {
|
|
||||||
if c == r {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
|
|
||||||
for i := 0; i < len(text); i++ {
|
|
||||||
// directly copy normal characters
|
|
||||||
org := i
|
|
||||||
|
|
||||||
for i < len(text) && !needsBackslash(text[i]) {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i > org {
|
|
||||||
out.Write(text[org:i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// escape a character
|
|
||||||
if i >= len(text) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
out.WriteByte('\\')
|
|
||||||
out.WriteByte(text[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
|
|
||||||
// TODO: convert this into a unicode character or something
|
|
||||||
out.Write(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
|
|
||||||
escapeSpecialChars(out, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// header and footer
|
|
||||||
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
|
|
||||||
out.WriteString("\\documentclass{article}\n")
|
|
||||||
out.WriteString("\n")
|
|
||||||
out.WriteString("\\usepackage{graphicx}\n")
|
|
||||||
out.WriteString("\\usepackage{listings}\n")
|
|
||||||
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
|
|
||||||
out.WriteString("\\usepackage[utf8]{inputenc}\n")
|
|
||||||
out.WriteString("\\usepackage{verbatim}\n")
|
|
||||||
out.WriteString("\\usepackage[normalem]{ulem}\n")
|
|
||||||
out.WriteString("\\usepackage{hyperref}\n")
|
|
||||||
out.WriteString("\n")
|
|
||||||
out.WriteString("\\hypersetup{colorlinks,%\n")
|
|
||||||
out.WriteString(" citecolor=black,%\n")
|
|
||||||
out.WriteString(" filecolor=black,%\n")
|
|
||||||
out.WriteString(" linkcolor=black,%\n")
|
|
||||||
out.WriteString(" urlcolor=black,%\n")
|
|
||||||
out.WriteString(" pdfstartview=FitH,%\n")
|
|
||||||
out.WriteString(" breaklinks=true,%\n")
|
|
||||||
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
|
|
||||||
out.WriteString(VERSION)
|
|
||||||
out.WriteString("}}\n")
|
|
||||||
out.WriteString("\n")
|
|
||||||
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
|
|
||||||
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
|
|
||||||
out.WriteString("\\parindent=0pt\n")
|
|
||||||
out.WriteString("\n")
|
|
||||||
out.WriteString("\\begin{document}\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
|
|
||||||
out.WriteString("\n\\end{document}\n")
|
|
||||||
}
|
|
755
vendor/github.com/documize/blackfriday/markdown.go
generated
vendored
755
vendor/github.com/documize/blackfriday/markdown.go
generated
vendored
|
@ -1,225 +1,200 @@
|
||||||
//
|
|
||||||
// Blackfriday Markdown Processor
|
// Blackfriday Markdown Processor
|
||||||
// Available at http://github.com/russross/blackfriday
|
// Available at http://github.com/russross/blackfriday
|
||||||
//
|
//
|
||||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
// Distributed under the Simplified BSD License.
|
// Distributed under the Simplified BSD License.
|
||||||
// See README.md for details.
|
// See README.md for details.
|
||||||
//
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Markdown parsing and processing
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
// Blackfriday markdown processor.
|
|
||||||
//
|
|
||||||
// Translates plain text with simple formatting rules into HTML or LaTeX.
|
|
||||||
package blackfriday
|
package blackfriday
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "1.1"
|
//
|
||||||
|
// Markdown parsing and processing
|
||||||
|
//
|
||||||
|
|
||||||
|
// Version string of the package. Appears in the rendered document when
|
||||||
|
// CompletePage flag is on.
|
||||||
|
const Version = "2.0"
|
||||||
|
|
||||||
|
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
|
||||||
|
// extensions.
|
||||||
|
type Extensions int
|
||||||
|
|
||||||
// These are the supported markdown parsing extensions.
|
// These are the supported markdown parsing extensions.
|
||||||
// OR these values together to select multiple extensions.
|
// OR these values together to select multiple extensions.
|
||||||
const (
|
const (
|
||||||
EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words
|
NoExtensions Extensions = 0
|
||||||
EXTENSION_TABLES // render tables
|
NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
|
||||||
EXTENSION_FENCED_CODE // render fenced code blocks
|
Tables // Render tables
|
||||||
EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked
|
FencedCode // Render fenced code blocks
|
||||||
EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~
|
Autolink // Detect embedded URLs that are not explicitly marked
|
||||||
EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules
|
Strikethrough // Strikethrough text using ~~test~~
|
||||||
EXTENSION_SPACE_HEADERS // be strict about prefix header rules
|
LaxHTMLBlocks // Loosen up HTML block parsing rules
|
||||||
EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks
|
SpaceHeadings // Be strict about prefix heading rules
|
||||||
EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four
|
HardLineBreak // Translate newlines into line breaks
|
||||||
EXTENSION_FOOTNOTES // Pandoc-style footnotes
|
TabSizeEight // Expand tabs to eight spaces instead of four
|
||||||
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
Footnotes // Pandoc-style footnotes
|
||||||
EXTENSION_HEADER_IDS // specify header IDs with {#id}
|
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
|
||||||
EXTENSION_TITLEBLOCK // Titleblock ala pandoc
|
HeadingIDs // specify heading IDs with {#id}
|
||||||
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
Titleblock // Titleblock ala pandoc
|
||||||
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
AutoHeadingIDs // Create the heading ID from the text
|
||||||
EXTENSION_DEFINITION_LISTS // render definition lists
|
BackslashLineBreak // Translate trailing backslashes into line breaks
|
||||||
|
DefinitionLists // Render definition lists
|
||||||
|
|
||||||
commonHtmlFlags = 0 |
|
CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
|
||||||
HTML_USE_XHTML |
|
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
|
||||||
HTML_USE_SMARTYPANTS |
|
|
||||||
HTML_SMARTYPANTS_FRACTIONS |
|
|
||||||
HTML_SMARTYPANTS_LATEX_DASHES
|
|
||||||
|
|
||||||
commonExtensions = 0 |
|
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
|
||||||
EXTENSION_NO_INTRA_EMPHASIS |
|
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
|
||||||
EXTENSION_TABLES |
|
BackslashLineBreak | DefinitionLists
|
||||||
EXTENSION_FENCED_CODE |
|
|
||||||
EXTENSION_AUTOLINK |
|
|
||||||
EXTENSION_STRIKETHROUGH |
|
|
||||||
EXTENSION_SPACE_HEADERS |
|
|
||||||
EXTENSION_HEADER_IDS |
|
|
||||||
EXTENSION_BACKSLASH_LINE_BREAK |
|
|
||||||
EXTENSION_DEFINITION_LISTS
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are the possible flag values for the link renderer.
|
// ListType contains bitwise or'ed flags for list and list item objects.
|
||||||
// Only a single one of these values will be used; they are not ORed together.
|
type ListType int
|
||||||
// These are mostly of interest if you are writing a new output format.
|
|
||||||
const (
|
|
||||||
LINK_TYPE_NOT_AUTOLINK = iota
|
|
||||||
LINK_TYPE_NORMAL
|
|
||||||
LINK_TYPE_EMAIL
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are the possible flag values for the ListItem renderer.
|
// These are the possible flag values for the ListItem renderer.
|
||||||
// Multiple flag values may be ORed together.
|
// Multiple flag values may be ORed together.
|
||||||
// These are mostly of interest if you are writing a new output format.
|
// These are mostly of interest if you are writing a new output format.
|
||||||
const (
|
const (
|
||||||
LIST_TYPE_ORDERED = 1 << iota
|
ListTypeOrdered ListType = 1 << iota
|
||||||
LIST_TYPE_DEFINITION
|
ListTypeDefinition
|
||||||
LIST_TYPE_TERM
|
ListTypeTerm
|
||||||
LIST_ITEM_CONTAINS_BLOCK
|
|
||||||
LIST_ITEM_BEGINNING_OF_LIST
|
ListItemContainsBlock
|
||||||
LIST_ITEM_END_OF_LIST
|
ListItemBeginningOfList // TODO: figure out if this is of any use now
|
||||||
|
ListItemEndOfList
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CellAlignFlags holds a type of alignment in a table cell.
|
||||||
|
type CellAlignFlags int
|
||||||
|
|
||||||
// These are the possible flag values for the table cell renderer.
|
// These are the possible flag values for the table cell renderer.
|
||||||
// Only a single one of these values will be used; they are not ORed together.
|
// Only a single one of these values will be used; they are not ORed together.
|
||||||
// These are mostly of interest if you are writing a new output format.
|
// These are mostly of interest if you are writing a new output format.
|
||||||
const (
|
const (
|
||||||
TABLE_ALIGNMENT_LEFT = 1 << iota
|
TableAlignmentLeft CellAlignFlags = 1 << iota
|
||||||
TABLE_ALIGNMENT_RIGHT
|
TableAlignmentRight
|
||||||
TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
|
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
|
||||||
)
|
)
|
||||||
|
|
||||||
// The size of a tab stop.
|
// The size of a tab stop.
|
||||||
const (
|
const (
|
||||||
TAB_SIZE_DEFAULT = 4
|
TabSizeDefault = 4
|
||||||
TAB_SIZE_EIGHT = 8
|
TabSizeDouble = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// These are the tags that are recognized as HTML block tags.
|
// blockTags is a set of tags that are recognized as HTML block tags.
|
||||||
// Any of these can be included in markdown text without special escaping.
|
// Any of these can be included in markdown text without special escaping.
|
||||||
var blockTags = map[string]bool{
|
var blockTags = map[string]struct{}{
|
||||||
"p": true,
|
"blockquote": struct{}{},
|
||||||
"dl": true,
|
"del": struct{}{},
|
||||||
"h1": true,
|
"div": struct{}{},
|
||||||
"h2": true,
|
"dl": struct{}{},
|
||||||
"h3": true,
|
"fieldset": struct{}{},
|
||||||
"h4": true,
|
"form": struct{}{},
|
||||||
"h5": true,
|
"h1": struct{}{},
|
||||||
"h6": true,
|
"h2": struct{}{},
|
||||||
"ol": true,
|
"h3": struct{}{},
|
||||||
"ul": true,
|
"h4": struct{}{},
|
||||||
"del": true,
|
"h5": struct{}{},
|
||||||
"div": true,
|
"h6": struct{}{},
|
||||||
"ins": true,
|
"iframe": struct{}{},
|
||||||
"pre": true,
|
"ins": struct{}{},
|
||||||
"form": true,
|
"math": struct{}{},
|
||||||
"math": true,
|
"noscript": struct{}{},
|
||||||
"table": true,
|
"ol": struct{}{},
|
||||||
"iframe": true,
|
"pre": struct{}{},
|
||||||
"script": true,
|
"p": struct{}{},
|
||||||
"fieldset": true,
|
"script": struct{}{},
|
||||||
"noscript": true,
|
"style": struct{}{},
|
||||||
"blockquote": true,
|
"table": struct{}{},
|
||||||
|
"ul": struct{}{},
|
||||||
|
|
||||||
// HTML5
|
// HTML5
|
||||||
"video": true,
|
"address": struct{}{},
|
||||||
"aside": true,
|
"article": struct{}{},
|
||||||
"canvas": true,
|
"aside": struct{}{},
|
||||||
"figure": true,
|
"canvas": struct{}{},
|
||||||
"footer": true,
|
"figcaption": struct{}{},
|
||||||
"header": true,
|
"figure": struct{}{},
|
||||||
"hgroup": true,
|
"footer": struct{}{},
|
||||||
"output": true,
|
"header": struct{}{},
|
||||||
"article": true,
|
"hgroup": struct{}{},
|
||||||
"section": true,
|
"main": struct{}{},
|
||||||
"progress": true,
|
"nav": struct{}{},
|
||||||
"figcaption": true,
|
"output": struct{}{},
|
||||||
|
"progress": struct{}{},
|
||||||
|
"section": struct{}{},
|
||||||
|
"video": struct{}{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer is the rendering interface.
|
// Renderer is the rendering interface. This is mostly of interest if you are
|
||||||
// This is mostly of interest if you are implementing a new rendering format.
|
// implementing a new rendering format.
|
||||||
//
|
//
|
||||||
// When a byte slice is provided, it contains the (rendered) contents of the
|
// Only an HTML implementation is provided in this repository, see the README
|
||||||
// element.
|
// for external implementations.
|
||||||
//
|
|
||||||
// When a callback is provided instead, it will write the contents of the
|
|
||||||
// respective element directly to the output buffer and return true on success.
|
|
||||||
// If the callback returns false, the rendering function should reset the
|
|
||||||
// output buffer as though it had never been called.
|
|
||||||
//
|
|
||||||
// Currently Html and Latex implementations are provided
|
|
||||||
type Renderer interface {
|
type Renderer interface {
|
||||||
// block-level callbacks
|
// RenderNode is the main rendering method. It will be called once for
|
||||||
BlockCode(out *bytes.Buffer, text []byte, lang string)
|
// every leaf node and twice for every non-leaf node (first with
|
||||||
BlockQuote(out *bytes.Buffer, text []byte)
|
// entering=true, then with entering=false). The method should write its
|
||||||
BlockHtml(out *bytes.Buffer, text []byte)
|
// rendition of the node to the supplied writer w.
|
||||||
Header(out *bytes.Buffer, text func() bool, level int, id string)
|
RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
|
||||||
HRule(out *bytes.Buffer)
|
|
||||||
List(out *bytes.Buffer, text func() bool, flags int)
|
|
||||||
ListItem(out *bytes.Buffer, text []byte, flags int)
|
|
||||||
Paragraph(out *bytes.Buffer, text func() bool)
|
|
||||||
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
|
|
||||||
TableRow(out *bytes.Buffer, text []byte)
|
|
||||||
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
|
|
||||||
TableCell(out *bytes.Buffer, text []byte, flags int)
|
|
||||||
Footnotes(out *bytes.Buffer, text func() bool)
|
|
||||||
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
|
|
||||||
TitleBlock(out *bytes.Buffer, text []byte)
|
|
||||||
|
|
||||||
// Span-level callbacks
|
// RenderHeader is a method that allows the renderer to produce some
|
||||||
AutoLink(out *bytes.Buffer, link []byte, kind int)
|
// content preceding the main body of the output document. The header is
|
||||||
CodeSpan(out *bytes.Buffer, text []byte)
|
// understood in the broad sense here. For example, the default HTML
|
||||||
DoubleEmphasis(out *bytes.Buffer, text []byte)
|
// renderer will write not only the HTML document preamble, but also the
|
||||||
Emphasis(out *bytes.Buffer, text []byte)
|
// table of contents if it was requested.
|
||||||
Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
|
//
|
||||||
LineBreak(out *bytes.Buffer)
|
// The method will be passed an entire document tree, in case a particular
|
||||||
Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
|
// implementation needs to inspect it to produce output.
|
||||||
RawHtmlTag(out *bytes.Buffer, tag []byte)
|
//
|
||||||
TripleEmphasis(out *bytes.Buffer, text []byte)
|
// The output should be written to the supplied writer w. If your
|
||||||
StrikeThrough(out *bytes.Buffer, text []byte)
|
// implementation has no header to write, supply an empty implementation.
|
||||||
FootnoteRef(out *bytes.Buffer, ref []byte, id int)
|
RenderHeader(w io.Writer, ast *Node)
|
||||||
|
|
||||||
// Low-level callbacks
|
// RenderFooter is a symmetric counterpart of RenderHeader.
|
||||||
Entity(out *bytes.Buffer, entity []byte)
|
RenderFooter(w io.Writer, ast *Node)
|
||||||
NormalText(out *bytes.Buffer, text []byte)
|
|
||||||
|
|
||||||
// Header and footer
|
|
||||||
DocumentHeader(out *bytes.Buffer)
|
|
||||||
DocumentFooter(out *bytes.Buffer)
|
|
||||||
|
|
||||||
GetFlags() int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, out *bytes.Buffer, data []byte, offset int) int
|
type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
|
||||||
|
|
||||||
// Parser holds runtime state used by the parser.
|
// Markdown is a type that holds extensions and the runtime state used by
|
||||||
// This is constructed by the Markdown function.
|
// Parse, and the renderer. You can not use it directly, construct it with New.
|
||||||
type parser struct {
|
type Markdown struct {
|
||||||
r Renderer
|
renderer Renderer
|
||||||
refOverride ReferenceOverrideFunc
|
referenceOverride ReferenceOverrideFunc
|
||||||
refs map[string]*reference
|
refs map[string]*reference
|
||||||
inlineCallback [256]inlineParser
|
inlineCallback [256]inlineParser
|
||||||
flags int
|
extensions Extensions
|
||||||
nesting int
|
nesting int
|
||||||
maxNesting int
|
maxNesting int
|
||||||
insideLink bool
|
insideLink bool
|
||||||
|
|
||||||
// Footnotes need to be ordered as well as available to quickly check for
|
// Footnotes need to be ordered as well as available to quickly check for
|
||||||
// presence. If a ref is also a footnote, it's stored both in refs and here
|
// presence. If a ref is also a footnote, it's stored both in refs and here
|
||||||
// in notes. Slice is nil if footnotes not enabled.
|
// in notes. Slice is nil if footnotes not enabled.
|
||||||
notes []*reference
|
notes []*reference
|
||||||
|
|
||||||
|
doc *Node
|
||||||
|
tip *Node // = doc
|
||||||
|
oldTip *Node
|
||||||
|
lastMatchedContainer *Node // = doc
|
||||||
|
allClosed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
|
||||||
if p.refOverride != nil {
|
if p.referenceOverride != nil {
|
||||||
r, overridden := p.refOverride(refid)
|
r, overridden := p.referenceOverride(refid)
|
||||||
if overridden {
|
if overridden {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
@ -227,7 +202,7 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||||
return &reference{
|
return &reference{
|
||||||
link: []byte(r.Link),
|
link: []byte(r.Link),
|
||||||
title: []byte(r.Title),
|
title: []byte(r.Title),
|
||||||
noteId: 0,
|
noteID: 0,
|
||||||
hasBlock: false,
|
hasBlock: false,
|
||||||
text: []byte(r.Text)}, true
|
text: []byte(r.Text)}, true
|
||||||
}
|
}
|
||||||
|
@ -237,6 +212,36 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||||
return ref, found
|
return ref, found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Markdown) finalize(block *Node) {
|
||||||
|
above := block.Parent
|
||||||
|
block.open = false
|
||||||
|
p.tip = above
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
|
||||||
|
return p.addExistingChild(NewNode(node), offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
|
||||||
|
for !p.tip.canContain(node.Type) {
|
||||||
|
p.finalize(p.tip)
|
||||||
|
}
|
||||||
|
p.tip.AppendChild(node)
|
||||||
|
p.tip = node
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Markdown) closeUnmatchedBlocks() {
|
||||||
|
if !p.allClosed {
|
||||||
|
for p.oldTip != p.lastMatchedContainer {
|
||||||
|
parent := p.oldTip.Parent
|
||||||
|
p.finalize(p.oldTip)
|
||||||
|
p.oldTip = parent
|
||||||
|
}
|
||||||
|
p.allClosed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// Public interface
|
// Public interface
|
||||||
|
@ -261,102 +266,27 @@ 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
|
// New constructs a Markdown processor. You can use the same With* functions as
|
||||||
// extension flag set) for configuring a Markdown parse.
|
// for Run() to customize parser's behavior and the renderer.
|
||||||
type Options struct {
|
func New(opts ...Option) *Markdown {
|
||||||
// Extensions is a flag set of bit-wise ORed extension bits. See the
|
var p Markdown
|
||||||
// EXTENSION_* flags defined in this package.
|
for _, opt := range opts {
|
||||||
Extensions int
|
opt(&p)
|
||||||
|
|
||||||
// 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 {
|
|
||||||
// set up the HTML renderer
|
|
||||||
htmlFlags := HTML_USE_XHTML
|
|
||||||
renderer := HtmlRenderer(htmlFlags, "", "")
|
|
||||||
|
|
||||||
// set up the parser
|
|
||||||
return MarkdownOptions(input, renderer, Options{Extensions: 0})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call Markdown with most useful extensions enabled
|
|
||||||
// MarkdownCommon is a convenience function for simple rendering.
|
|
||||||
// It processes markdown input with common extensions enabled, including:
|
|
||||||
//
|
|
||||||
// * Smartypants processing with smart fractions and LaTeX dashes
|
|
||||||
//
|
|
||||||
// * Intra-word emphasis suppression
|
|
||||||
//
|
|
||||||
// * Tables
|
|
||||||
//
|
|
||||||
// * Fenced code blocks
|
|
||||||
//
|
|
||||||
// * Autolinking
|
|
||||||
//
|
|
||||||
// * Strikethrough support
|
|
||||||
//
|
|
||||||
// * Strict header parsing
|
|
||||||
//
|
|
||||||
// * Custom Header IDs
|
|
||||||
func MarkdownCommon(input []byte) []byte {
|
|
||||||
// set up the HTML renderer
|
|
||||||
renderer := HtmlRenderer(commonHtmlFlags, "", "")
|
|
||||||
return MarkdownOptions(input, renderer, Options{
|
|
||||||
Extensions: commonExtensions})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 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.refs = make(map[string]*reference)
|
||||||
p.maxNesting = 16
|
p.maxNesting = 16
|
||||||
p.insideLink = false
|
p.insideLink = false
|
||||||
|
docNode := NewNode(Document)
|
||||||
|
p.doc = docNode
|
||||||
|
p.tip = docNode
|
||||||
|
p.oldTip = docNode
|
||||||
|
p.lastMatchedContainer = docNode
|
||||||
|
p.allClosed = true
|
||||||
// register inline parsers
|
// register inline parsers
|
||||||
|
p.inlineCallback[' '] = maybeLineBreak
|
||||||
p.inlineCallback['*'] = emphasis
|
p.inlineCallback['*'] = emphasis
|
||||||
p.inlineCallback['_'] = emphasis
|
p.inlineCallback['_'] = emphasis
|
||||||
if extensions&EXTENSION_STRIKETHROUGH != 0 {
|
if p.extensions&Strikethrough != 0 {
|
||||||
p.inlineCallback['~'] = emphasis
|
p.inlineCallback['~'] = emphasis
|
||||||
}
|
}
|
||||||
p.inlineCallback['`'] = codeSpan
|
p.inlineCallback['`'] = codeSpan
|
||||||
|
@ -365,119 +295,166 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
||||||
p.inlineCallback['<'] = leftAngle
|
p.inlineCallback['<'] = leftAngle
|
||||||
p.inlineCallback['\\'] = escape
|
p.inlineCallback['\\'] = escape
|
||||||
p.inlineCallback['&'] = entity
|
p.inlineCallback['&'] = entity
|
||||||
|
p.inlineCallback['!'] = maybeImage
|
||||||
if extensions&EXTENSION_AUTOLINK != 0 {
|
p.inlineCallback['^'] = maybeInlineFootnote
|
||||||
p.inlineCallback[':'] = autoLink
|
if p.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 p.extensions&Footnotes != 0 {
|
||||||
if extensions&EXTENSION_FOOTNOTES != 0 {
|
|
||||||
p.notes = make([]*reference, 0)
|
p.notes = make([]*reference, 0)
|
||||||
}
|
}
|
||||||
|
return &p
|
||||||
first := firstPass(p, input)
|
|
||||||
second := secondPass(p, first)
|
|
||||||
return second
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// first pass:
|
// Option customizes the Markdown processor's default behavior.
|
||||||
// - extract references
|
type Option func(*Markdown)
|
||||||
// - expand tabs
|
|
||||||
// - normalize newlines
|
// WithRenderer allows you to override the default renderer.
|
||||||
// - copy everything else
|
func WithRenderer(r Renderer) Option {
|
||||||
// - add missing newlines before fenced code blocks
|
return func(p *Markdown) {
|
||||||
func firstPass(p *parser, input []byte) []byte {
|
p.renderer = r
|
||||||
var out bytes.Buffer
|
|
||||||
tabSize := TAB_SIZE_DEFAULT
|
|
||||||
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
|
|
||||||
tabSize = TAB_SIZE_EIGHT
|
|
||||||
}
|
}
|
||||||
beg, end := 0, 0
|
|
||||||
lastLineWasBlank := false
|
|
||||||
lastFencedCodeBlockEnd := 0
|
|
||||||
for beg < len(input) { // iterate over lines
|
|
||||||
if end = isReference(p, input[beg:], tabSize); end > 0 {
|
|
||||||
beg += end
|
|
||||||
} else { // skip to the next line
|
|
||||||
end = beg
|
|
||||||
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
|
||||||
// when last line was none blank and a fenced code block comes after
|
|
||||||
if beg >= lastFencedCodeBlockEnd {
|
|
||||||
if i := p.fencedCode(&out, input[beg:], false); i > 0 {
|
|
||||||
if !lastLineWasBlank {
|
|
||||||
out.WriteByte('\n') // need to inject additional linebreak
|
|
||||||
}
|
|
||||||
lastFencedCodeBlockEnd = beg + i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastLineWasBlank = end == beg
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the line body if present
|
|
||||||
if end > beg {
|
|
||||||
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
|
|
||||||
out.Write(input[beg:end])
|
|
||||||
} else {
|
|
||||||
expandTabs(&out, input[beg:end], tabSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.WriteByte('\n')
|
|
||||||
|
|
||||||
if end < len(input) && input[end] == '\r' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
if end < len(input) && input[end] == '\n' {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
|
|
||||||
beg = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty input?
|
|
||||||
if out.Len() == 0 {
|
|
||||||
out.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
return out.Bytes()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// second pass: actual rendering
|
// WithExtensions allows you to pick some of the many extensions provided by
|
||||||
func secondPass(p *parser, input []byte) []byte {
|
// Blackfriday. You can bitwise OR them.
|
||||||
var output bytes.Buffer
|
func WithExtensions(e Extensions) Option {
|
||||||
|
return func(p *Markdown) {
|
||||||
|
p.extensions = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.r.DocumentHeader(&output)
|
// WithNoExtensions turns off all extensions and custom behavior.
|
||||||
p.block(&output, input)
|
func WithNoExtensions() Option {
|
||||||
|
return func(p *Markdown) {
|
||||||
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
|
p.extensions = NoExtensions
|
||||||
p.r.Footnotes(&output, func() bool {
|
p.renderer = NewHTMLRenderer(HTMLRendererParameters{
|
||||||
flags := LIST_ITEM_BEGINNING_OF_LIST
|
Flags: HTMLFlagsNone,
|
||||||
for _, ref := range p.notes {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if ref.hasBlock {
|
|
||||||
flags |= LIST_ITEM_CONTAINS_BLOCK
|
|
||||||
p.block(&buf, ref.title)
|
|
||||||
} else {
|
|
||||||
p.inline(&buf, ref.title)
|
|
||||||
}
|
|
||||||
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
|
|
||||||
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
p.r.DocumentFooter(&output)
|
// WithRefOverride sets an optional function callback that is called every
|
||||||
|
// time a reference is resolved.
|
||||||
if p.nesting != 0 {
|
//
|
||||||
panic("Nesting level did not end at zero")
|
// 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.
|
||||||
|
func WithRefOverride(o ReferenceOverrideFunc) Option {
|
||||||
|
return func(p *Markdown) {
|
||||||
|
p.referenceOverride = o
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return output.Bytes()
|
// Run is the main entry point to Blackfriday. It parses and renders a
|
||||||
|
// block of markdown-encoded text.
|
||||||
|
//
|
||||||
|
// The simplest invocation of Run takes one argument, input:
|
||||||
|
// output := Run(input)
|
||||||
|
// This will parse the input with CommonExtensions enabled and render it with
|
||||||
|
// the default HTMLRenderer (with CommonHTMLFlags).
|
||||||
|
//
|
||||||
|
// Variadic arguments opts can customize the default behavior. Since Markdown
|
||||||
|
// 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 := Run(input, WithNoExtensions())
|
||||||
|
//
|
||||||
|
// 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
|
||||||
|
// former:
|
||||||
|
// output := Run(input, WithNoExtensions(), WithExtensions(exts),
|
||||||
|
// WithRenderer(yourRenderer))
|
||||||
|
func Run(input []byte, opts ...Option) []byte {
|
||||||
|
r := NewHTMLRenderer(HTMLRendererParameters{
|
||||||
|
Flags: CommonHTMLFlags,
|
||||||
|
})
|
||||||
|
optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
|
||||||
|
optList = append(optList, opts...)
|
||||||
|
parser := New(optList...)
|
||||||
|
ast := parser.Parse(input)
|
||||||
|
var buf bytes.Buffer
|
||||||
|
parser.renderer.RenderHeader(&buf, ast)
|
||||||
|
ast.Walk(func(node *Node, entering bool) WalkStatus {
|
||||||
|
return parser.renderer.RenderNode(&buf, node, entering)
|
||||||
|
})
|
||||||
|
parser.renderer.RenderFooter(&buf, ast)
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// tree can then be rendered with a default or custom renderer, or
|
||||||
|
// analyzed/transformed by the caller to whatever non-standard needs they have.
|
||||||
|
// The return value is the root node of the syntax tree.
|
||||||
|
func (p *Markdown) Parse(input []byte) *Node {
|
||||||
|
p.block(input)
|
||||||
|
// Walk the tree and finish up some of unfinished blocks
|
||||||
|
for p.tip != nil {
|
||||||
|
p.finalize(p.tip)
|
||||||
|
}
|
||||||
|
// Walk the tree again and process inline markdown in each block
|
||||||
|
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
|
||||||
|
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
|
||||||
|
p.inline(node, node.content)
|
||||||
|
node.content = nil
|
||||||
|
}
|
||||||
|
return GoToNext
|
||||||
|
})
|
||||||
|
p.parseRefsToAST()
|
||||||
|
return p.doc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Markdown) parseRefsToAST() {
|
||||||
|
if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.tip = p.doc
|
||||||
|
block := p.addBlock(List, nil)
|
||||||
|
block.IsFootnotesList = true
|
||||||
|
block.ListFlags = ListTypeOrdered
|
||||||
|
flags := ListItemBeginningOfList
|
||||||
|
// Note: this loop is intentionally explicit, not range-form. This is
|
||||||
|
// because the body of the loop will append nested footnotes to p.notes and
|
||||||
|
// we need to process those late additions. Range form would only walk over
|
||||||
|
// the fixed initial set.
|
||||||
|
for i := 0; i < len(p.notes); i++ {
|
||||||
|
ref := p.notes[i]
|
||||||
|
p.addExistingChild(ref.footnote, 0)
|
||||||
|
block := ref.footnote
|
||||||
|
block.ListFlags = flags | ListTypeOrdered
|
||||||
|
block.RefLink = ref.link
|
||||||
|
if ref.hasBlock {
|
||||||
|
flags |= ListItemContainsBlock
|
||||||
|
p.block(ref.title)
|
||||||
|
} else {
|
||||||
|
p.inline(block, ref.title)
|
||||||
|
}
|
||||||
|
flags &^= ListItemBeginningOfList | ListItemContainsBlock
|
||||||
|
}
|
||||||
|
above := block.Parent
|
||||||
|
finalizeList(block)
|
||||||
|
p.tip = above
|
||||||
|
block.Walk(func(node *Node, entering bool) WalkStatus {
|
||||||
|
if node.Type == Paragraph || node.Type == Heading {
|
||||||
|
p.inline(node, node.content)
|
||||||
|
node.content = nil
|
||||||
|
}
|
||||||
|
return GoToNext
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -509,13 +486,56 @@ func secondPass(p *parser, input []byte) []byte {
|
||||||
//
|
//
|
||||||
// are not yet supported.
|
// are not yet supported.
|
||||||
|
|
||||||
// References are parsed and stored in this struct.
|
// reference holds all information necessary for a reference-style links or
|
||||||
|
// footnotes.
|
||||||
|
//
|
||||||
|
// Consider this markdown with reference-style links:
|
||||||
|
//
|
||||||
|
// [link][ref]
|
||||||
|
//
|
||||||
|
// [ref]: /url/ "tooltip title"
|
||||||
|
//
|
||||||
|
// It will be ultimately converted to this HTML:
|
||||||
|
//
|
||||||
|
// <p><a href=\"/url/\" title=\"title\">link</a></p>
|
||||||
|
//
|
||||||
|
// And a reference structure will be populated as follows:
|
||||||
|
//
|
||||||
|
// p.refs["ref"] = &reference{
|
||||||
|
// link: "/url/",
|
||||||
|
// title: "tooltip title",
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Alternatively, reference can contain information about a footnote. Consider
|
||||||
|
// this markdown:
|
||||||
|
//
|
||||||
|
// Text needing a footnote.[^a]
|
||||||
|
//
|
||||||
|
// [^a]: This is the note
|
||||||
|
//
|
||||||
|
// A reference structure will be populated as follows:
|
||||||
|
//
|
||||||
|
// p.refs["a"] = &reference{
|
||||||
|
// link: "a",
|
||||||
|
// title: "This is the note",
|
||||||
|
// noteID: <some positive int>,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// TODO: As you can see, it begs for splitting into two dedicated structures
|
||||||
|
// for refs and for footnotes.
|
||||||
type reference struct {
|
type reference struct {
|
||||||
link []byte
|
link []byte
|
||||||
title []byte
|
title []byte
|
||||||
noteId int // 0 if not a footnote ref
|
noteID int // 0 if not a footnote ref
|
||||||
hasBlock bool
|
hasBlock bool
|
||||||
text []byte
|
footnote *Node // a link to the Item node within a list of footnotes
|
||||||
|
|
||||||
|
text []byte // only gets populated by refOverride feature with Reference.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reference) String() string {
|
||||||
|
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
|
||||||
|
r.link, r.title, r.text, r.noteID, r.hasBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether or not data starts with a reference link.
|
// Check whether or not data starts with a reference link.
|
||||||
|
@ -523,7 +543,7 @@ type reference struct {
|
||||||
// (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 *Markdown, 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
|
||||||
|
@ -533,18 +553,18 @@ func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
noteId := 0
|
noteID := 0
|
||||||
|
|
||||||
// id part: anything but a newline between brackets
|
// id part: anything but a newline between brackets
|
||||||
if data[i] != '[' {
|
if data[i] != '[' {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
if p.flags&EXTENSION_FOOTNOTES != 0 {
|
if p.extensions&Footnotes != 0 {
|
||||||
if i < len(data) && data[i] == '^' {
|
if i < len(data) && data[i] == '^' {
|
||||||
// we can set it to anything here because the proper noteIds will
|
// we can set it to anything here because the proper noteIds will
|
||||||
// be assigned later during the second pass. It just has to be != 0
|
// be assigned later during the second pass. It just has to be != 0
|
||||||
noteId = 1
|
noteID = 1
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -556,7 +576,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
idEnd := i
|
idEnd := i
|
||||||
|
// footnotes can have empty ID, like this: [^], but a reference can not be
|
||||||
|
// empty like this: []. Break early if it's not a footnote and there's no ID
|
||||||
|
if noteID == 0 && idOffset == idEnd {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
// spacer: colon (space | tab)* newline? (space | tab)*
|
// spacer: colon (space | tab)* newline? (space | tab)*
|
||||||
i++
|
i++
|
||||||
if i >= len(data) || data[i] != ':' {
|
if i >= len(data) || data[i] != ':' {
|
||||||
|
@ -587,7 +611,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
hasBlock bool
|
hasBlock bool
|
||||||
)
|
)
|
||||||
|
|
||||||
if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
|
if p.extensions&Footnotes != 0 && noteID != 0 {
|
||||||
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
|
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
|
||||||
lineEnd = linkEnd
|
lineEnd = linkEnd
|
||||||
} else {
|
} else {
|
||||||
|
@ -600,11 +624,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
|
||||||
// a valid ref has been found
|
// a valid ref has been found
|
||||||
|
|
||||||
ref := &reference{
|
ref := &reference{
|
||||||
noteId: noteId,
|
noteID: noteID,
|
||||||
hasBlock: hasBlock,
|
hasBlock: hasBlock,
|
||||||
}
|
}
|
||||||
|
|
||||||
if noteId > 0 {
|
if noteID > 0 {
|
||||||
// reusing the link field for the id since footnotes don't have links
|
// reusing the link field for the id since footnotes don't have links
|
||||||
ref.link = data[idOffset:idEnd]
|
ref.link = data[idOffset:idEnd]
|
||||||
// if footnote, it's not really a title, it's the contained text
|
// if footnote, it's not really a title, it's the contained text
|
||||||
|
@ -622,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 *Markdown, 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++
|
||||||
|
@ -631,9 +655,6 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse
|
||||||
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if i == len(data) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
linkEnd = i
|
linkEnd = i
|
||||||
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
||||||
linkOffset++
|
linkOffset++
|
||||||
|
@ -693,13 +714,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 *Markdown, 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
|
||||||
}
|
}
|
||||||
|
|
38
vendor/github.com/documize/blackfriday/markdown_test.go
generated
vendored
Normal file
38
vendor/github.com/documize/blackfriday/markdown_test.go
generated
vendored
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// Blackfriday Markdown Processor
|
||||||
|
// Available at http://github.com/russross/blackfriday
|
||||||
|
//
|
||||||
|
// Copyright © 2011 Russ Ross <russ@russross.com>.
|
||||||
|
// Distributed under the Simplified BSD License.
|
||||||
|
// See README.md for details.
|
||||||
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// Unit tests for full document parsing and rendering
|
||||||
|
//
|
||||||
|
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDocument(t *testing.T) {
|
||||||
|
var tests = []string{
|
||||||
|
// Empty document.
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
|
||||||
|
" ",
|
||||||
|
"",
|
||||||
|
|
||||||
|
// This shouldn't panic.
|
||||||
|
// https://github.com/russross/blackfriday/issues/172
|
||||||
|
"[]:<",
|
||||||
|
"<p>[]:<</p>\n",
|
||||||
|
|
||||||
|
// This shouldn't panic.
|
||||||
|
// https://github.com/russross/blackfriday/issues/173
|
||||||
|
" [",
|
||||||
|
"<p>[</p>\n",
|
||||||
|
}
|
||||||
|
doTests(t, tests)
|
||||||
|
}
|
354
vendor/github.com/documize/blackfriday/node.go
generated
vendored
Normal file
354
vendor/github.com/documize/blackfriday/node.go
generated
vendored
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
package blackfriday
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeType specifies a type of a single node of a syntax tree. Usually one
|
||||||
|
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
|
||||||
|
// or code block.
|
||||||
|
type NodeType int
|
||||||
|
|
||||||
|
// Constants for identifying different types of nodes. See NodeType.
|
||||||
|
const (
|
||||||
|
Document NodeType = iota
|
||||||
|
BlockQuote
|
||||||
|
List
|
||||||
|
Item
|
||||||
|
Paragraph
|
||||||
|
Heading
|
||||||
|
HorizontalRule
|
||||||
|
Emph
|
||||||
|
Strong
|
||||||
|
Del
|
||||||
|
Link
|
||||||
|
Image
|
||||||
|
Text
|
||||||
|
HTMLBlock
|
||||||
|
CodeBlock
|
||||||
|
Softbreak
|
||||||
|
Hardbreak
|
||||||
|
Code
|
||||||
|
HTMLSpan
|
||||||
|
Table
|
||||||
|
TableCell
|
||||||
|
TableHead
|
||||||
|
TableBody
|
||||||
|
TableRow
|
||||||
|
)
|
||||||
|
|
||||||
|
var nodeTypeNames = []string{
|
||||||
|
Document: "Document",
|
||||||
|
BlockQuote: "BlockQuote",
|
||||||
|
List: "List",
|
||||||
|
Item: "Item",
|
||||||
|
Paragraph: "Paragraph",
|
||||||
|
Heading: "Heading",
|
||||||
|
HorizontalRule: "HorizontalRule",
|
||||||
|
Emph: "Emph",
|
||||||
|
Strong: "Strong",
|
||||||
|
Del: "Del",
|
||||||
|
Link: "Link",
|
||||||
|
Image: "Image",
|
||||||
|
Text: "Text",
|
||||||
|
HTMLBlock: "HTMLBlock",
|
||||||
|
CodeBlock: "CodeBlock",
|
||||||
|
Softbreak: "Softbreak",
|
||||||
|
Hardbreak: "Hardbreak",
|
||||||
|
Code: "Code",
|
||||||
|
HTMLSpan: "HTMLSpan",
|
||||||
|
Table: "Table",
|
||||||
|
TableCell: "TableCell",
|
||||||
|
TableHead: "TableHead",
|
||||||
|
TableBody: "TableBody",
|
||||||
|
TableRow: "TableRow",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t NodeType) String() string {
|
||||||
|
return nodeTypeNames[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListData contains fields relevant to a List and Item node type.
|
||||||
|
type ListData struct {
|
||||||
|
ListFlags ListType
|
||||||
|
Tight bool // Skip <p>s around list item data if true
|
||||||
|
BulletChar byte // '*', '+' or '-' in bullet lists
|
||||||
|
Delimiter byte // '.' or ')' after the number in ordered lists
|
||||||
|
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
|
||||||
|
IsFootnotesList bool // This is a list of footnotes
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkData contains fields relevant to a Link node type.
|
||||||
|
type LinkData struct {
|
||||||
|
Destination []byte // Destination is what goes into a href
|
||||||
|
Title []byte // Title is the tooltip thing that goes in a title attribute
|
||||||
|
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
|
||||||
|
Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeBlockData contains fields relevant to a CodeBlock node type.
|
||||||
|
type CodeBlockData struct {
|
||||||
|
IsFenced bool // Specifies whether it's a fenced code block or an indented one
|
||||||
|
Info []byte // This holds the info string
|
||||||
|
FenceChar byte
|
||||||
|
FenceLength int
|
||||||
|
FenceOffset int
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableCellData contains fields relevant to a TableCell node type.
|
||||||
|
type TableCellData struct {
|
||||||
|
IsHeader bool // This tells if it's under the header row
|
||||||
|
Align CellAlignFlags // This holds the value for align attribute
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeadingData contains fields relevant to a Heading node type.
|
||||||
|
type HeadingData struct {
|
||||||
|
Level int // This holds the heading level number
|
||||||
|
HeadingID string // This might hold heading ID, if present
|
||||||
|
IsTitleblock bool // Specifies whether it's a title block
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node is a single element in the abstract syntax tree of the parsed document.
|
||||||
|
// It holds connections to the structurally neighboring nodes and, for certain
|
||||||
|
// types of nodes, additional information that might be needed when rendering.
|
||||||
|
type Node struct {
|
||||||
|
Type NodeType // Determines the type of the node
|
||||||
|
Parent *Node // Points to the parent
|
||||||
|
FirstChild *Node // Points to the first child, if any
|
||||||
|
LastChild *Node // Points to the last child, if any
|
||||||
|
Prev *Node // Previous sibling; nil if it's the first child
|
||||||
|
Next *Node // Next sibling; nil if it's the last child
|
||||||
|
|
||||||
|
Literal []byte // Text contents of the leaf nodes
|
||||||
|
|
||||||
|
HeadingData // Populated if Type is Heading
|
||||||
|
ListData // Populated if Type is List
|
||||||
|
CodeBlockData // Populated if Type is CodeBlock
|
||||||
|
LinkData // Populated if Type is Link
|
||||||
|
TableCellData // Populated if Type is TableCell
|
||||||
|
|
||||||
|
content []byte // Markdown content of the block nodes
|
||||||
|
open bool // Specifies an open block node that has not been finished to process yet
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNode allocates a node of a specified type.
|
||||||
|
func NewNode(typ NodeType) *Node {
|
||||||
|
return &Node{
|
||||||
|
Type: typ,
|
||||||
|
open: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) String() string {
|
||||||
|
ellipsis := ""
|
||||||
|
snippet := n.Literal
|
||||||
|
if len(snippet) > 16 {
|
||||||
|
snippet = snippet[:16]
|
||||||
|
ellipsis = "..."
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlink removes node 'n' from the tree.
|
||||||
|
// It panics if the node is nil.
|
||||||
|
func (n *Node) Unlink() {
|
||||||
|
if n.Prev != nil {
|
||||||
|
n.Prev.Next = n.Next
|
||||||
|
} else if n.Parent != nil {
|
||||||
|
n.Parent.FirstChild = n.Next
|
||||||
|
}
|
||||||
|
if n.Next != nil {
|
||||||
|
n.Next.Prev = n.Prev
|
||||||
|
} else if n.Parent != nil {
|
||||||
|
n.Parent.LastChild = n.Prev
|
||||||
|
}
|
||||||
|
n.Parent = nil
|
||||||
|
n.Next = nil
|
||||||
|
n.Prev = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendChild adds a node 'child' as a child of 'n'.
|
||||||
|
// It panics if either node is nil.
|
||||||
|
func (n *Node) AppendChild(child *Node) {
|
||||||
|
child.Unlink()
|
||||||
|
child.Parent = n
|
||||||
|
if n.LastChild != nil {
|
||||||
|
n.LastChild.Next = child
|
||||||
|
child.Prev = n.LastChild
|
||||||
|
n.LastChild = child
|
||||||
|
} else {
|
||||||
|
n.FirstChild = child
|
||||||
|
n.LastChild = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InsertBefore inserts 'sibling' immediately before 'n'.
|
||||||
|
// It panics if either node is nil.
|
||||||
|
func (n *Node) InsertBefore(sibling *Node) {
|
||||||
|
sibling.Unlink()
|
||||||
|
sibling.Prev = n.Prev
|
||||||
|
if sibling.Prev != nil {
|
||||||
|
sibling.Prev.Next = sibling
|
||||||
|
}
|
||||||
|
sibling.Next = n
|
||||||
|
n.Prev = sibling
|
||||||
|
sibling.Parent = n.Parent
|
||||||
|
if sibling.Prev == nil {
|
||||||
|
sibling.Parent.FirstChild = sibling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) isContainer() bool {
|
||||||
|
switch n.Type {
|
||||||
|
case Document:
|
||||||
|
fallthrough
|
||||||
|
case BlockQuote:
|
||||||
|
fallthrough
|
||||||
|
case List:
|
||||||
|
fallthrough
|
||||||
|
case Item:
|
||||||
|
fallthrough
|
||||||
|
case Paragraph:
|
||||||
|
fallthrough
|
||||||
|
case Heading:
|
||||||
|
fallthrough
|
||||||
|
case Emph:
|
||||||
|
fallthrough
|
||||||
|
case Strong:
|
||||||
|
fallthrough
|
||||||
|
case Del:
|
||||||
|
fallthrough
|
||||||
|
case Link:
|
||||||
|
fallthrough
|
||||||
|
case Image:
|
||||||
|
fallthrough
|
||||||
|
case Table:
|
||||||
|
fallthrough
|
||||||
|
case TableHead:
|
||||||
|
fallthrough
|
||||||
|
case TableBody:
|
||||||
|
fallthrough
|
||||||
|
case TableRow:
|
||||||
|
fallthrough
|
||||||
|
case TableCell:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) canContain(t NodeType) bool {
|
||||||
|
if n.Type == List {
|
||||||
|
return t == Item
|
||||||
|
}
|
||||||
|
if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
|
||||||
|
return t != Item
|
||||||
|
}
|
||||||
|
if n.Type == Table {
|
||||||
|
return t == TableHead || t == TableBody
|
||||||
|
}
|
||||||
|
if n.Type == TableHead || n.Type == TableBody {
|
||||||
|
return t == TableRow
|
||||||
|
}
|
||||||
|
if n.Type == TableRow {
|
||||||
|
return t == TableCell
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
|
||||||
|
// It is returned from NodeVisitor and different values allow Node.Walk to
|
||||||
|
// decide which node to go to next.
|
||||||
|
type WalkStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// GoToNext is the default traversal of every node.
|
||||||
|
GoToNext WalkStatus = iota
|
||||||
|
// SkipChildren tells walker to skip all children of current node.
|
||||||
|
SkipChildren
|
||||||
|
// Terminate tells walker to terminate the traversal.
|
||||||
|
Terminate
|
||||||
|
)
|
||||||
|
|
||||||
|
// NodeVisitor is a callback to be called when traversing the syntax tree.
|
||||||
|
// Called twice for every node: once with entering=true when the branch is
|
||||||
|
// first visited, then with entering=false after all the children are done.
|
||||||
|
type NodeVisitor func(node *Node, entering bool) WalkStatus
|
||||||
|
|
||||||
|
// Walk is a convenience method that instantiates a walker and starts a
|
||||||
|
// traversal of subtree rooted at n.
|
||||||
|
func (n *Node) Walk(visitor NodeVisitor) {
|
||||||
|
w := newNodeWalker(n)
|
||||||
|
for w.current != nil {
|
||||||
|
status := visitor(w.current, w.entering)
|
||||||
|
switch status {
|
||||||
|
case GoToNext:
|
||||||
|
w.next()
|
||||||
|
case SkipChildren:
|
||||||
|
w.entering = false
|
||||||
|
w.next()
|
||||||
|
case Terminate:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeWalker struct {
|
||||||
|
current *Node
|
||||||
|
root *Node
|
||||||
|
entering bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNodeWalker(root *Node) *nodeWalker {
|
||||||
|
return &nodeWalker{
|
||||||
|
current: root,
|
||||||
|
root: root,
|
||||||
|
entering: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nw *nodeWalker) next() {
|
||||||
|
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
|
||||||
|
nw.current = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nw.entering && nw.current.isContainer() {
|
||||||
|
if nw.current.FirstChild != nil {
|
||||||
|
nw.current = nw.current.FirstChild
|
||||||
|
nw.entering = true
|
||||||
|
} else {
|
||||||
|
nw.entering = false
|
||||||
|
}
|
||||||
|
} else if nw.current.Next == nil {
|
||||||
|
nw.current = nw.current.Parent
|
||||||
|
nw.entering = false
|
||||||
|
} else {
|
||||||
|
nw.current = nw.current.Next
|
||||||
|
nw.entering = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dump(ast *Node) {
|
||||||
|
fmt.Println(dumpString(ast))
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpR(ast *Node, depth int) string {
|
||||||
|
if ast == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
indent := bytes.Repeat([]byte("\t"), depth)
|
||||||
|
content := ast.Literal
|
||||||
|
if content == nil {
|
||||||
|
content = ast.content
|
||||||
|
}
|
||||||
|
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
|
||||||
|
for n := ast.FirstChild; n != nil; n = n.Next {
|
||||||
|
result += dumpR(n, depth+1)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpString(ast *Node) string {
|
||||||
|
return dumpR(ast, 0)
|
||||||
|
}
|
102
vendor/github.com/documize/blackfriday/ref_test.go
generated
vendored
102
vendor/github.com/documize/blackfriday/ref_test.go
generated
vendored
|
@ -19,58 +19,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func runMarkdownReference(input string, flag int) string {
|
|
||||||
renderer := HtmlRenderer(0, "", "")
|
|
||||||
return string(Markdown([]byte(input), renderer, flag))
|
|
||||||
}
|
|
||||||
|
|
||||||
func doTestsReference(t *testing.T, files []string, flag int) {
|
|
||||||
// catch and report panics
|
|
||||||
var candidate string
|
|
||||||
defer func() {
|
|
||||||
if err := recover(); err != nil {
|
|
||||||
t.Errorf("\npanic while processing [%#v]\n", candidate)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for _, basename := range files {
|
|
||||||
filename := filepath.Join("testdata", basename+".text")
|
|
||||||
inputBytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
input := string(inputBytes)
|
|
||||||
|
|
||||||
filename = filepath.Join("testdata", basename+".html")
|
|
||||||
expectedBytes, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expected := string(expectedBytes)
|
|
||||||
|
|
||||||
// fmt.Fprintf(os.Stderr, "processing %s ...", filename)
|
|
||||||
actual := string(runMarkdownReference(input, flag))
|
|
||||||
if actual != expected {
|
|
||||||
t.Errorf("\n [%#v]\nExpected[%#v]\nActual [%#v]",
|
|
||||||
basename+".text", expected, actual)
|
|
||||||
}
|
|
||||||
// fmt.Fprintf(os.Stderr, " ok\n")
|
|
||||||
|
|
||||||
// now test every prefix of every input to check for
|
|
||||||
// bounds checking
|
|
||||||
if !testing.Short() {
|
|
||||||
start, max := 0, len(input)
|
|
||||||
for end := start + 1; end <= max; end++ {
|
|
||||||
candidate = input[start:end]
|
|
||||||
// fmt.Fprintf(os.Stderr, " %s %d:%d/%d\n", filename, start, end, max)
|
|
||||||
_ = runMarkdownReference(candidate, flag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReference(t *testing.T) {
|
func TestReference(t *testing.T) {
|
||||||
files := []string{
|
files := []string{
|
||||||
"Amps and angle encoding",
|
"Amps and angle encoding",
|
||||||
|
@ -124,5 +72,53 @@ func TestReference_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
||||||
"Tabs",
|
"Tabs",
|
||||||
"Tidyness",
|
"Tidyness",
|
||||||
}
|
}
|
||||||
doTestsReference(t, files, EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
|
doTestsReference(t, files, NoEmptyLineBeforeBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// benchResultAnchor is an anchor variable to store the result of a benchmarked
|
||||||
|
// code so that compiler could never optimize away the call to runMarkdown()
|
||||||
|
var benchResultAnchor string
|
||||||
|
|
||||||
|
func BenchmarkReference(b *testing.B) {
|
||||||
|
params := TestParams{extensions: CommonExtensions}
|
||||||
|
files := []string{
|
||||||
|
"Amps and angle encoding",
|
||||||
|
"Auto links",
|
||||||
|
"Backslash escapes",
|
||||||
|
"Blockquotes with code blocks",
|
||||||
|
"Code Blocks",
|
||||||
|
"Code Spans",
|
||||||
|
"Hard-wrapped paragraphs with list-like lines",
|
||||||
|
"Horizontal rules",
|
||||||
|
"Inline HTML (Advanced)",
|
||||||
|
"Inline HTML (Simple)",
|
||||||
|
"Inline HTML comments",
|
||||||
|
"Links, inline style",
|
||||||
|
"Links, reference style",
|
||||||
|
"Links, shortcut references",
|
||||||
|
"Literal quotes in titles",
|
||||||
|
"Markdown Documentation - Basics",
|
||||||
|
"Markdown Documentation - Syntax",
|
||||||
|
"Nested blockquotes",
|
||||||
|
"Ordered and unordered lists",
|
||||||
|
"Strong and em together",
|
||||||
|
"Tabs",
|
||||||
|
"Tidyness",
|
||||||
|
}
|
||||||
|
var tests []string
|
||||||
|
for _, basename := range files {
|
||||||
|
filename := filepath.Join("testdata", basename+".text")
|
||||||
|
inputBytes, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
b.Errorf("Couldn't open '%s', error: %v\n", filename, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tests = append(tests, string(inputBytes))
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
for _, test := range tests {
|
||||||
|
benchResultAnchor = runMarkdown(test, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
169
vendor/github.com/documize/blackfriday/smartypants.go
generated
vendored
169
vendor/github.com/documize/blackfriday/smartypants.go
generated
vendored
|
@ -17,11 +17,14 @@ package blackfriday
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
type smartypantsData struct {
|
// SPRenderer is a struct containing state of a Smartypants renderer.
|
||||||
|
type SPRenderer struct {
|
||||||
inSingleQuote bool
|
inSingleQuote bool
|
||||||
inDoubleQuote bool
|
inDoubleQuote bool
|
||||||
|
callbacks [256]smartCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
func wordBoundary(c byte) bool {
|
func wordBoundary(c byte) bool {
|
||||||
|
@ -39,7 +42,7 @@ func isdigit(c byte) bool {
|
||||||
return c >= '0' && c <= '9'
|
return c >= '0' && c <= '9'
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool) bool {
|
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
|
||||||
// edge of the buffer is likely to be a tag that we don't get to see,
|
// edge of the buffer is likely to be a tag that we don't get to see,
|
||||||
// so we treat it like text sometimes
|
// so we treat it like text sometimes
|
||||||
|
|
||||||
|
@ -96,6 +99,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
||||||
*isOpen = false
|
*isOpen = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note that with the limited lookahead, this non-breaking
|
||||||
|
// space will also be appended to single double quotes.
|
||||||
|
if addNBSP && !*isOpen {
|
||||||
|
out.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
out.WriteByte('&')
|
out.WriteByte('&')
|
||||||
if *isOpen {
|
if *isOpen {
|
||||||
out.WriteByte('l')
|
out.WriteByte('l')
|
||||||
|
@ -104,10 +113,15 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
||||||
}
|
}
|
||||||
out.WriteByte(quote)
|
out.WriteByte(quote)
|
||||||
out.WriteString("quo;")
|
out.WriteString("quo;")
|
||||||
|
|
||||||
|
if addNBSP && *isOpen {
|
||||||
|
out.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if len(text) >= 2 {
|
if len(text) >= 2 {
|
||||||
t1 := tolower(text[1])
|
t1 := tolower(text[1])
|
||||||
|
|
||||||
|
@ -116,7 +130,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||||
if len(text) >= 3 {
|
if len(text) >= 3 {
|
||||||
nextChar = text[2]
|
nextChar = text[2]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +155,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||||
if len(text) > 1 {
|
if len(text) > 1 {
|
||||||
nextChar = text[1]
|
nextChar = text[1]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +163,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if len(text) >= 3 {
|
if len(text) >= 3 {
|
||||||
t1 := tolower(text[1])
|
t1 := tolower(text[1])
|
||||||
t2 := tolower(text[2])
|
t2 := tolower(text[2])
|
||||||
|
@ -174,7 +188,7 @@ func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if len(text) >= 2 {
|
if len(text) >= 2 {
|
||||||
if text[1] == '-' {
|
if text[1] == '-' {
|
||||||
out.WriteString("—")
|
out.WriteString("—")
|
||||||
|
@ -191,7 +205,7 @@ func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
|
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
|
||||||
out.WriteString("—")
|
out.WriteString("—")
|
||||||
return 2
|
return 2
|
||||||
|
@ -205,13 +219,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int {
|
||||||
if bytes.HasPrefix(text, []byte(""")) {
|
if bytes.HasPrefix(text, []byte(""")) {
|
||||||
nextChar := byte(0)
|
nextChar := byte(0)
|
||||||
if len(text) >= 7 {
|
if len(text) >= 7 {
|
||||||
nextChar = text[6]
|
nextChar = text[6]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
|
||||||
return 5
|
return 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,15 +238,18 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
|
||||||
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
var quote byte = 'd'
|
||||||
|
if angledQuotes {
|
||||||
|
quote = 'a'
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
|
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
|
||||||
}
|
|
||||||
|
|
||||||
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
|
||||||
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
|
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
|
||||||
out.WriteString("…")
|
out.WriteString("…")
|
||||||
return 2
|
return 2
|
||||||
|
@ -247,13 +264,13 @@ func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if len(text) >= 2 && text[1] == '`' {
|
if len(text) >= 2 && text[1] == '`' {
|
||||||
nextChar := byte(0)
|
nextChar := byte(0)
|
||||||
if len(text) >= 3 {
|
if len(text) >= 3 {
|
||||||
nextChar = text[2]
|
nextChar = text[2]
|
||||||
}
|
}
|
||||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -262,7 +279,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||||
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
|
||||||
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
// note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8)
|
||||||
|
@ -304,7 +321,7 @@ func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar b
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
|
||||||
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
|
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
|
||||||
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
|
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
|
||||||
|
@ -332,27 +349,27 @@ func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, te
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
|
||||||
nextChar := byte(0)
|
nextChar := byte(0)
|
||||||
if len(text) > 1 {
|
if len(text) > 1 {
|
||||||
nextChar = text[1]
|
nextChar = text[1]
|
||||||
}
|
}
|
||||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) {
|
||||||
out.WriteString(""")
|
out.WriteString(""")
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
|
return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
|
return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
|
||||||
}
|
}
|
||||||
|
|
||||||
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for i < len(text) && text[i] != '>' {
|
for i < len(text) && text[i] != '>' {
|
||||||
|
@ -363,36 +380,78 @@ func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
|
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
|
||||||
|
|
||||||
type smartypantsRenderer [256]smartCallback
|
// NewSmartypantsRenderer constructs a Smartypants renderer object.
|
||||||
|
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
|
||||||
|
var (
|
||||||
|
r SPRenderer
|
||||||
|
|
||||||
func smartypants(flags int) *smartypantsRenderer {
|
smartAmpAngled = r.smartAmp(true, false)
|
||||||
r := new(smartypantsRenderer)
|
smartAmpAngledNBSP = r.smartAmp(true, true)
|
||||||
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
smartAmpRegular = r.smartAmp(false, false)
|
||||||
r['"'] = smartDoubleQuote
|
smartAmpRegularNBSP = r.smartAmp(false, true)
|
||||||
r['&'] = smartAmp
|
|
||||||
|
addNBSP = flags&SmartypantsQuotesNBSP != 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if flags&SmartypantsAngledQuotes == 0 {
|
||||||
|
r.callbacks['"'] = r.smartDoubleQuote
|
||||||
|
if !addNBSP {
|
||||||
|
r.callbacks['&'] = smartAmpRegular
|
||||||
|
} else {
|
||||||
|
r.callbacks['&'] = smartAmpRegularNBSP
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
r['"'] = smartAngledDoubleQuote
|
r.callbacks['"'] = r.smartAngledDoubleQuote
|
||||||
r['&'] = smartAmpAngledQuote
|
if !addNBSP {
|
||||||
}
|
r.callbacks['&'] = smartAmpAngled
|
||||||
r['\''] = smartSingleQuote
|
} else {
|
||||||
r['('] = smartParens
|
r.callbacks['&'] = smartAmpAngledNBSP
|
||||||
if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
|
|
||||||
r['-'] = smartDash
|
|
||||||
} else {
|
|
||||||
r['-'] = smartDashLatex
|
|
||||||
}
|
|
||||||
r['.'] = smartPeriod
|
|
||||||
if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
|
|
||||||
r['1'] = smartNumber
|
|
||||||
r['3'] = smartNumber
|
|
||||||
} else {
|
|
||||||
for ch := '1'; ch <= '9'; ch++ {
|
|
||||||
r[ch] = smartNumberGeneric
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r['<'] = smartLeftAngle
|
r.callbacks['\''] = r.smartSingleQuote
|
||||||
r['`'] = smartBacktick
|
r.callbacks['('] = r.smartParens
|
||||||
return r
|
if flags&SmartypantsDashes != 0 {
|
||||||
|
if flags&SmartypantsLatexDashes == 0 {
|
||||||
|
r.callbacks['-'] = r.smartDash
|
||||||
|
} else {
|
||||||
|
r.callbacks['-'] = r.smartDashLatex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.callbacks['.'] = r.smartPeriod
|
||||||
|
if flags&SmartypantsFractions == 0 {
|
||||||
|
r.callbacks['1'] = r.smartNumber
|
||||||
|
r.callbacks['3'] = r.smartNumber
|
||||||
|
} else {
|
||||||
|
for ch := '1'; ch <= '9'; ch++ {
|
||||||
|
r.callbacks[ch] = r.smartNumberGeneric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r.callbacks['<'] = r.smartLeftAngle
|
||||||
|
r.callbacks['`'] = r.smartBacktick
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process is the entry point of the Smartypants renderer.
|
||||||
|
func (r *SPRenderer) Process(w io.Writer, text []byte) {
|
||||||
|
mark := 0
|
||||||
|
for i := 0; i < len(text); i++ {
|
||||||
|
if action := r.callbacks[text[i]]; action != nil {
|
||||||
|
if i > mark {
|
||||||
|
w.Write(text[mark:i])
|
||||||
|
}
|
||||||
|
previousChar := byte(0)
|
||||||
|
if i > 0 {
|
||||||
|
previousChar = text[i-1]
|
||||||
|
}
|
||||||
|
var tmp bytes.Buffer
|
||||||
|
i += action(&tmp, previousChar, text[i:])
|
||||||
|
w.Write(tmp.Bytes())
|
||||||
|
mark = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mark < len(text) {
|
||||||
|
w.Write(text[mark:])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
14
vendor/github.com/documize/blackfriday/testdata/Inline HTML (Simple).html
generated
vendored
14
vendor/github.com/documize/blackfriday/testdata/Inline HTML (Simple).html
generated
vendored
|
@ -1,13 +1,13 @@
|
||||||
<p>Here's a simple block:</p>
|
<p>Here's a simple block:</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
foo
|
foo
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>This should be a code block, though:</p>
|
<p>This should be a code block, though:</p>
|
||||||
|
|
||||||
<pre><code><div>
|
<pre><code><div>
|
||||||
foo
|
foo
|
||||||
</div>
|
</div>
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
|
@ -19,11 +19,11 @@
|
||||||
<p>Now, nested:</p>
|
<p>Now, nested:</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
foo
|
foo
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p>This should just be an HTML comment:</p>
|
<p>This should just be an HTML comment:</p>
|
||||||
|
|
2
vendor/github.com/documize/blackfriday/testdata/Inline HTML comments.html
generated
vendored
2
vendor/github.com/documize/blackfriday/testdata/Inline HTML comments.html
generated
vendored
|
@ -3,7 +3,7 @@
|
||||||
<!-- This is a simple comment -->
|
<!-- This is a simple comment -->
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
This is another comment.
|
This is another comment.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<p>Paragraph two.</p>
|
<p>Paragraph two.</p>
|
||||||
|
|
4
vendor/github.com/documize/blackfriday/testdata/Markdown Documentation - Syntax.html
generated
vendored
4
vendor/github.com/documize/blackfriday/testdata/Markdown Documentation - Syntax.html
generated
vendored
|
@ -939,8 +939,8 @@ _ underscore
|
||||||
[] square brackets
|
[] square brackets
|
||||||
() parentheses
|
() parentheses
|
||||||
# hash mark
|
# hash mark
|
||||||
+ plus sign
|
+ plus sign
|
||||||
- minus sign (hyphen)
|
- minus sign (hyphen)
|
||||||
. dot
|
. dot
|
||||||
! exclamation mark
|
! exclamation mark
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
6
vendor/github.com/documize/blackfriday/testdata/Tabs.html
generated
vendored
6
vendor/github.com/documize/blackfriday/testdata/Tabs.html
generated
vendored
|
@ -13,13 +13,13 @@ indented with spaces</p></li>
|
||||||
|
|
||||||
<p>And:</p>
|
<p>And:</p>
|
||||||
|
|
||||||
<pre><code> this code block is indented by two tabs
|
<pre><code> this code block is indented by two tabs
|
||||||
</code></pre>
|
</code></pre>
|
||||||
|
|
||||||
<p>And:</p>
|
<p>And:</p>
|
||||||
|
|
||||||
<pre><code>+ this is an example list item
|
<pre><code>+ this is an example list item
|
||||||
indented with tabs
|
indented with tabs
|
||||||
|
|
||||||
+ this is an example list item
|
+ this is an example list item
|
||||||
indented with spaces
|
indented with spaces
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue