mirror of
https://github.com/restic/restic.git
synced 2026-08-29 08:47:31 +00:00
Update dependencies
This commit is contained in:
+26
-14
@@ -1,18 +1,30 @@
|
||||
# Travis CI (http://travis-ci.org/) is a continuous integration service for
|
||||
# open source projects. This file configures it to run unit tests for
|
||||
# blackfriday.
|
||||
|
||||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- tip
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.2.2
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- go test -v -race ./...
|
||||
- go: 1.3.3
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- go test -v -race ./...
|
||||
- go: 1.4.3
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- go test -v -race ./...
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
install:
|
||||
- go get -d -t -v ./...
|
||||
- go build -v ./...
|
||||
|
||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||
script:
|
||||
- go test -v ./...
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||
- go tool vet .
|
||||
- go test -v -race ./...
|
||||
|
||||
+29
-2
@@ -1,4 +1,4 @@
|
||||
Blackfriday [](https://travis-ci.org/russross/blackfriday)
|
||||
Blackfriday [](https://travis-ci.org/russross/blackfriday) [](https://godoc.org/github.com/russross/blackfriday)
|
||||
===========
|
||||
|
||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||
@@ -93,6 +93,22 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that
|
||||
can be copied to wherever you need it without worrying about
|
||||
dependencies and library versions.
|
||||
|
||||
### Sanitized anchor names
|
||||
|
||||
Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
corresponding to a given input text. This algorithm is used to create
|
||||
anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The
|
||||
algorithm has a specification, so that other packages can create
|
||||
compatible anchor names and links to those anchors.
|
||||
|
||||
The specification is located at https://godoc.org/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names.
|
||||
|
||||
[`SanitizedAnchorName`](https://godoc.org/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to
|
||||
create compatible links to the anchor names generated by blackfriday.
|
||||
This algorithm is also implemented in a small standalone package at
|
||||
[`github.com/shurcooL/sanitized_anchor_name`](https://godoc.org/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
|
||||
that want a small package and don't need full functionality of blackfriday.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -114,7 +130,7 @@ All features of Sundown are supported, including:
|
||||
know and send me the input that does it.
|
||||
|
||||
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).
|
||||
|
||||
* **Fast processing**. It is fast enough to render on-demand in
|
||||
@@ -169,6 +185,15 @@ implements the following extensions:
|
||||
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.
|
||||
|
||||
To preserve classes of fenced code blocks while using the bluemonday
|
||||
HTML sanitizer, use the following policy:
|
||||
|
||||
``` go
|
||||
p := bluemonday.UGCPolicy()
|
||||
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
|
||||
html := p.SanitizeBytes(unsafe)
|
||||
```
|
||||
|
||||
* **Definition lists**. A simple definition list is made of a single-line
|
||||
term followed by a colon and the definition for that term.
|
||||
|
||||
@@ -242,6 +267,8 @@ are a few of note:
|
||||
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.
|
||||
|
||||
* [Md2Vim](https://github.com/FooSoft/md2vim): transforms markdown files into vimdoc format.
|
||||
|
||||
|
||||
Todo
|
||||
|
||||
+116
-64
@@ -15,8 +15,7 @@ package blackfriday
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/shurcooL/sanitized_anchor_name"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Parse block-level data.
|
||||
@@ -102,7 +101,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) {
|
||||
// }
|
||||
// ```
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||
if i := p.fencedCode(out, data, true); i > 0 {
|
||||
if i := p.fencedCodeBlock(out, data, true); i > 0 {
|
||||
data = data[i:]
|
||||
continue
|
||||
}
|
||||
@@ -243,7 +242,7 @@ func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int {
|
||||
}
|
||||
if end > i {
|
||||
if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
|
||||
id = sanitized_anchor_name.Create(string(data[i:end]))
|
||||
id = SanitizedAnchorName(string(data[i:end]))
|
||||
}
|
||||
work := func() bool {
|
||||
p.inline(out, data[i:end])
|
||||
@@ -320,6 +319,11 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
return size
|
||||
}
|
||||
|
||||
// check for HTML CDATA
|
||||
if size := p.htmlCDATA(out, data, doRender); size > 0 {
|
||||
return size
|
||||
}
|
||||
|
||||
// no special case recognized
|
||||
return 0
|
||||
}
|
||||
@@ -397,12 +401,10 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
return i
|
||||
}
|
||||
|
||||
// HTML comment, lax form
|
||||
func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
i := p.inlineHtmlComment(out, data)
|
||||
// needs to end with a blank line
|
||||
if j := p.isEmpty(data[i:]); j > 0 {
|
||||
size := i + j
|
||||
func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int {
|
||||
// html block needs to end with a blank line
|
||||
if i := p.isEmpty(data[start:]); i > 0 {
|
||||
size := start + i
|
||||
if doRender {
|
||||
// trim trailing newlines
|
||||
end := size
|
||||
@@ -416,6 +418,35 @@ func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int
|
||||
return 0
|
||||
}
|
||||
|
||||
// HTML comment, lax form
|
||||
func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
i := p.inlineHTMLComment(out, data)
|
||||
return p.renderHTMLBlock(out, data, i, doRender)
|
||||
}
|
||||
|
||||
// HTML CDATA section
|
||||
func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
const cdataTag = "<![cdata["
|
||||
const cdataTagLen = len(cdataTag)
|
||||
if len(data) < cdataTagLen+1 {
|
||||
return 0
|
||||
}
|
||||
if !bytes.Equal(bytes.ToLower(data[:cdataTagLen]), []byte(cdataTag)) {
|
||||
return 0
|
||||
}
|
||||
i := cdataTagLen
|
||||
// scan for an end-of-comment marker, across lines if necessary
|
||||
for i < len(data) && !(data[i-2] == ']' && data[i-1] == ']' && data[i] == '>') {
|
||||
i++
|
||||
}
|
||||
i++
|
||||
// no end-of-comment marker
|
||||
if i >= len(data) {
|
||||
return 0
|
||||
}
|
||||
return p.renderHTMLBlock(out, data, i, doRender)
|
||||
}
|
||||
|
||||
// HR, which is the only self-closing block tag considered
|
||||
func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
|
||||
@@ -432,19 +463,7 @@ func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
}
|
||||
|
||||
if data[i] == '>' {
|
||||
i++
|
||||
if j := p.isEmpty(data[i:]); j > 0 {
|
||||
size := i + j
|
||||
if doRender {
|
||||
// trim newlines
|
||||
end := size
|
||||
for end > 0 && data[end-1] == '\n' {
|
||||
end--
|
||||
}
|
||||
p.r.BlockHtml(out, data[:end])
|
||||
}
|
||||
return size
|
||||
}
|
||||
return p.renderHTMLBlock(out, data, i+1, doRender)
|
||||
}
|
||||
|
||||
return 0
|
||||
@@ -495,7 +514,7 @@ func (p *parser) htmlFindEnd(tag string, data []byte) int {
|
||||
return i + skip
|
||||
}
|
||||
|
||||
func (p *parser) isEmpty(data []byte) int {
|
||||
func (*parser) isEmpty(data []byte) int {
|
||||
// it is okay to call isEmpty on an empty buffer
|
||||
if len(data) == 0 {
|
||||
return 0
|
||||
@@ -510,7 +529,7 @@ func (p *parser) isEmpty(data []byte) int {
|
||||
return i + 1
|
||||
}
|
||||
|
||||
func (p *parser) isHRule(data []byte) bool {
|
||||
func (*parser) isHRule(data []byte) bool {
|
||||
i := 0
|
||||
|
||||
// skip up to three spaces
|
||||
@@ -539,21 +558,24 @@ func (p *parser) isHRule(data []byte) bool {
|
||||
return n >= 3
|
||||
}
|
||||
|
||||
func (p *parser) isFencedCode(data []byte, syntax **string, oldmarker string) (skip int, marker string) {
|
||||
// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
|
||||
// and returns the end index if so, or 0 otherwise. It also returns the marker found.
|
||||
// If syntax is not nil, it gets set to the syntax specified in the fence line.
|
||||
// A final newline is mandatory to recognize the fence line, unless newlineOptional is true.
|
||||
func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) {
|
||||
i, size := 0, 0
|
||||
skip = 0
|
||||
|
||||
// skip up to three spaces
|
||||
for i < len(data) && i < 3 && data[i] == ' ' {
|
||||
i++
|
||||
}
|
||||
if i >= len(data) {
|
||||
return
|
||||
}
|
||||
|
||||
// check for the marker characters: ~ or `
|
||||
if i >= len(data) {
|
||||
return 0, ""
|
||||
}
|
||||
if data[i] != '~' && data[i] != '`' {
|
||||
return
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
c := data[i]
|
||||
@@ -564,27 +586,28 @@ func (p *parser) isFencedCode(data []byte, syntax **string, oldmarker string) (s
|
||||
i++
|
||||
}
|
||||
|
||||
if i >= len(data) {
|
||||
return
|
||||
}
|
||||
|
||||
// the marker char must occur at least 3 times
|
||||
if size < 3 {
|
||||
return
|
||||
return 0, ""
|
||||
}
|
||||
marker = string(data[i-size : i])
|
||||
|
||||
// if this is the end marker, it must match the beginning marker
|
||||
if oldmarker != "" && marker != oldmarker {
|
||||
return
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
|
||||
// into one, always get the syntax, and discard it if the caller doesn't care.
|
||||
if syntax != nil {
|
||||
syn := 0
|
||||
i = skipChar(data, i, ' ')
|
||||
|
||||
if i >= len(data) {
|
||||
return
|
||||
if newlineOptional && i == len(data) {
|
||||
return i, marker
|
||||
}
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
syntaxStart := i
|
||||
@@ -599,7 +622,7 @@ func (p *parser) isFencedCode(data []byte, syntax **string, oldmarker string) (s
|
||||
}
|
||||
|
||||
if i >= len(data) || data[i] != '}' {
|
||||
return
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
// strip all whitespace at the beginning and the end
|
||||
@@ -621,22 +644,26 @@ func (p *parser) isFencedCode(data []byte, syntax **string, oldmarker string) (s
|
||||
}
|
||||
}
|
||||
|
||||
language := string(data[syntaxStart : syntaxStart+syn])
|
||||
*syntax = &language
|
||||
*syntax = string(data[syntaxStart : syntaxStart+syn])
|
||||
}
|
||||
|
||||
i = skipChar(data, i, ' ')
|
||||
if i >= len(data) || data[i] != '\n' {
|
||||
return
|
||||
if newlineOptional && i == len(data) {
|
||||
return i, marker
|
||||
}
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
skip = i + 1
|
||||
return
|
||||
return i + 1, marker // Take newline into account.
|
||||
}
|
||||
|
||||
func (p *parser) fencedCode(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
var lang *string
|
||||
beg, marker := p.isFencedCode(data, &lang, "")
|
||||
// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning,
|
||||
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
|
||||
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
|
||||
func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
var syntax string
|
||||
beg, marker := isFenceLine(data, &syntax, "", false)
|
||||
if beg == 0 || beg >= len(data) {
|
||||
return 0
|
||||
}
|
||||
@@ -647,7 +674,8 @@ func (p *parser) fencedCode(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
// safe to assume beg < len(data)
|
||||
|
||||
// check for the end of the code block
|
||||
fenceEnd, _ := p.isFencedCode(data[beg:], nil, marker)
|
||||
newlineOptional := !doRender
|
||||
fenceEnd, _ := isFenceLine(data[beg:], nil, marker, newlineOptional)
|
||||
if fenceEnd != 0 {
|
||||
beg += fenceEnd
|
||||
break
|
||||
@@ -668,11 +696,6 @@ func (p *parser) fencedCode(out *bytes.Buffer, data []byte, doRender bool) int {
|
||||
beg = end
|
||||
}
|
||||
|
||||
syntax := ""
|
||||
if lang != nil {
|
||||
syntax = *lang
|
||||
}
|
||||
|
||||
if doRender {
|
||||
p.r.BlockCode(out, work.Bytes(), syntax)
|
||||
}
|
||||
@@ -914,7 +937,7 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int {
|
||||
// irregardless of any contents inside it
|
||||
for data[end] != '\n' {
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||
if i := p.fencedCode(out, data[end:], false); i > 0 {
|
||||
if i := p.fencedCodeBlock(out, data[end:], false); i > 0 {
|
||||
// -1 to compensate for the extra end++ after the loop:
|
||||
end += i - 1
|
||||
break
|
||||
@@ -1133,6 +1156,7 @@ gatherlines:
|
||||
// and move on to the next line
|
||||
if p.isEmpty(data[line:i]) > 0 {
|
||||
containsBlankLine = true
|
||||
raw.Write(data[line:i])
|
||||
line = i
|
||||
continue
|
||||
}
|
||||
@@ -1153,6 +1177,14 @@ gatherlines:
|
||||
p.dliPrefix(chunk) > 0:
|
||||
|
||||
if containsBlankLine {
|
||||
// end the list if the type changed after a blank line
|
||||
if indent <= itemIndent &&
|
||||
((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) ||
|
||||
(*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) {
|
||||
|
||||
*flags |= LIST_ITEM_END_OF_LIST
|
||||
break gatherlines
|
||||
}
|
||||
*flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||
}
|
||||
|
||||
@@ -1200,17 +1232,10 @@ gatherlines:
|
||||
|
||||
// a blank line means this should be parsed as a block
|
||||
case containsBlankLine:
|
||||
raw.WriteByte('\n')
|
||||
*flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||
}
|
||||
|
||||
// if this line was preceeded by one or more blanks,
|
||||
// re-introduce the blank into the buffer
|
||||
if containsBlankLine {
|
||||
containsBlankLine = false
|
||||
raw.WriteByte('\n')
|
||||
|
||||
}
|
||||
containsBlankLine = false
|
||||
|
||||
// add the line into the working buffer without prefix
|
||||
raw.Write(data[line+indent : i])
|
||||
@@ -1218,6 +1243,12 @@ gatherlines:
|
||||
line = i
|
||||
}
|
||||
|
||||
// If reached end of data, the Renderer.ListItem call we're going to make below
|
||||
// is definitely the last in the list.
|
||||
if line >= len(data) {
|
||||
*flags |= LIST_ITEM_END_OF_LIST
|
||||
}
|
||||
|
||||
rawBytes := raw.Bytes()
|
||||
|
||||
// render the contents of the list item
|
||||
@@ -1332,7 +1363,7 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
||||
|
||||
id := ""
|
||||
if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 {
|
||||
id = sanitized_anchor_name.Create(string(data[prev:eol]))
|
||||
id = SanitizedAnchorName(string(data[prev:eol]))
|
||||
}
|
||||
|
||||
p.r.Header(out, work, level, id)
|
||||
@@ -1362,7 +1393,7 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
||||
|
||||
// if there's a fenced code block, paragraph is over
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||
if p.fencedCode(out, current, false) > 0 {
|
||||
if p.fencedCodeBlock(out, current, false) > 0 {
|
||||
p.renderParagraph(out, data[:i])
|
||||
return i
|
||||
}
|
||||
@@ -1396,3 +1427,24 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int {
|
||||
p.renderParagraph(out, data[:i])
|
||||
return i
|
||||
}
|
||||
|
||||
// SanitizedAnchorName returns a sanitized anchor name for the given text.
|
||||
//
|
||||
// It implements the algorithm specified in the package comment.
|
||||
func SanitizedAnchorName(text string) string {
|
||||
var anchorName []rune
|
||||
futureDash := false
|
||||
for _, r := range text {
|
||||
switch {
|
||||
case unicode.IsLetter(r) || unicode.IsNumber(r):
|
||||
if futureDash && len(anchorName) > 0 {
|
||||
anchorName = append(anchorName, '-')
|
||||
}
|
||||
futureDash = false
|
||||
anchorName = append(anchorName, unicode.ToLower(r))
|
||||
default:
|
||||
futureDash = true
|
||||
}
|
||||
}
|
||||
return string(anchorName)
|
||||
}
|
||||
|
||||
+249
@@ -657,6 +657,9 @@ func TestUnorderedList(t *testing.T) {
|
||||
"Paragraph\n\n* Linebreak\n",
|
||||
"<p>Paragraph</p>\n\n<ul>\n<li>Linebreak</li>\n</ul>\n",
|
||||
|
||||
"* List\n\n1. Spacer Mixed listing\n",
|
||||
"<ul>\n<li>List</li>\n</ul>\n\n<ol>\n<li>Spacer Mixed listing</li>\n</ol>\n",
|
||||
|
||||
"* List\n * Nested list\n",
|
||||
"<ul>\n<li>List\n\n<ul>\n<li>Nested list</li>\n</ul></li>\n</ul>\n",
|
||||
|
||||
@@ -702,10 +705,41 @@ func TestUnorderedList(t *testing.T) {
|
||||
|
||||
"* 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",
|
||||
|
||||
`* Foo
|
||||
|
||||
bar
|
||||
|
||||
qux
|
||||
`,
|
||||
`<ul>
|
||||
<li><p>Foo</p>
|
||||
|
||||
<pre><code>bar
|
||||
|
||||
qux
|
||||
</code></pre></li>
|
||||
</ul>
|
||||
`,
|
||||
}
|
||||
doTestsBlock(t, tests, 0)
|
||||
}
|
||||
|
||||
func TestFencedCodeBlockWithinList(t *testing.T) {
|
||||
doTestsBlock(t, []string{
|
||||
"* Foo\n\n ```\n bar\n\n qux\n ```\n",
|
||||
`<ul>
|
||||
<li><p>Foo</p>
|
||||
|
||||
<pre><code>bar
|
||||
|
||||
qux
|
||||
</code></pre></li>
|
||||
</ul>
|
||||
`,
|
||||
}, EXTENSION_FENCED_CODE)
|
||||
}
|
||||
|
||||
func TestOrderedList(t *testing.T) {
|
||||
var tests = []string{
|
||||
"1. Hello\n",
|
||||
@@ -784,6 +818,12 @@ func TestOrderedList(t *testing.T) {
|
||||
"1. List\n\n code block with spaces\n",
|
||||
"<ol>\n<li><p>List</p>\n\n<pre><code> code block with spaces\n</code></pre></li>\n</ol>\n",
|
||||
|
||||
"1. List\n\n* Spacer Mixed listing\n",
|
||||
"<ol>\n<li>List</li>\n</ol>\n\n<ul>\n<li>Spacer Mixed listing</li>\n</ul>\n",
|
||||
|
||||
"1. List\n* Mixed listing\n",
|
||||
"<ol>\n<li>List</li>\n<li>Mixed listing</li>\n</ol>\n",
|
||||
|
||||
"1. List\n * Mixted list\n",
|
||||
"<ol>\n<li>List\n\n<ul>\n<li>Mixted list</li>\n</ul></li>\n</ol>\n",
|
||||
|
||||
@@ -798,6 +838,26 @@ func TestOrderedList(t *testing.T) {
|
||||
|
||||
"1. numbers\n1. are ignored\n",
|
||||
"<ol>\n<li>numbers</li>\n<li>are ignored</li>\n</ol>\n",
|
||||
|
||||
`1. Foo
|
||||
|
||||
bar
|
||||
|
||||
|
||||
|
||||
qux
|
||||
`,
|
||||
`<ol>
|
||||
<li><p>Foo</p>
|
||||
|
||||
<pre><code>bar
|
||||
|
||||
|
||||
|
||||
qux
|
||||
</code></pre></li>
|
||||
</ol>
|
||||
`,
|
||||
}
|
||||
doTestsBlock(t, tests, 0)
|
||||
}
|
||||
@@ -900,6 +960,14 @@ func TestDefinitionList(t *testing.T) {
|
||||
"<dd><p>Definition b</p></dd>\n" +
|
||||
"</dl>\n" +
|
||||
"\n<p>Text 2</p>\n",
|
||||
|
||||
"Term 1\n: Definition a\n\n Text 1\n\n 1. First\n 2. Second",
|
||||
"<dl>\n" +
|
||||
"<dt>Term 1</dt>\n" +
|
||||
"<dd><p>Definition a</p>\n\n" +
|
||||
"<p>Text 1</p>\n\n" +
|
||||
"<ol>\n<li>First</li>\n<li>Second</li>\n</ol></dd>\n" +
|
||||
"</dl>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, EXTENSION_DEFINITION_LISTS)
|
||||
}
|
||||
@@ -1062,6 +1130,12 @@ 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",
|
||||
"<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)
|
||||
}
|
||||
@@ -1530,3 +1604,178 @@ func TestBlockComments(t *testing.T) {
|
||||
}
|
||||
doTestsBlock(t, tests, 0)
|
||||
}
|
||||
|
||||
func TestCDATA(t *testing.T) {
|
||||
var tests = []string{
|
||||
"Some text\n\n<![CDATA[foo]]>\n",
|
||||
"<p>Some text</p>\n\n<![CDATA[foo]]>\n",
|
||||
|
||||
"CDATA ]]\n\n<![CDATA[]]]]>\n",
|
||||
"<p>CDATA ]]</p>\n\n<![CDATA[]]]]>\n",
|
||||
|
||||
"CDATA >\n\n<![CDATA[>]]>\n",
|
||||
"<p>CDATA ></p>\n\n<![CDATA[>]]>\n",
|
||||
|
||||
"Lots of text\n\n<![CDATA[lots of te><t\non\nseveral\nlines]]>\n",
|
||||
"<p>Lots of text</p>\n\n<![CDATA[lots of te><t\non\nseveral\nlines]]>\n",
|
||||
|
||||
"<![CDATA[>]]>\n",
|
||||
"<![CDATA[>]]>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, 0)
|
||||
doTestsBlock(t, []string{
|
||||
"``` html\n<![CDATA[foo]]>\n```\n",
|
||||
"<pre><code class=\"language-html\"><![CDATA[foo]]>\n</code></pre>\n",
|
||||
|
||||
"<![CDATA[\n``` python\ndef func():\n pass\n```\n]]>\n",
|
||||
"<![CDATA[\n``` python\ndef func():\n pass\n```\n]]>\n",
|
||||
|
||||
`<![CDATA[
|
||||
> def func():
|
||||
> pass
|
||||
]]>
|
||||
`,
|
||||
`<![CDATA[
|
||||
> def func():
|
||||
> pass
|
||||
]]>
|
||||
`,
|
||||
}, EXTENSION_FENCED_CODE)
|
||||
}
|
||||
|
||||
func TestIsFenceLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
syntaxRequested bool
|
||||
newlineOptional bool
|
||||
wantEnd int
|
||||
wantMarker string
|
||||
wantSyntax string
|
||||
}{
|
||||
{
|
||||
data: []byte("```"),
|
||||
wantEnd: 0,
|
||||
},
|
||||
{
|
||||
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("```"),
|
||||
newlineOptional: true,
|
||||
wantEnd: 3,
|
||||
wantMarker: "```",
|
||||
},
|
||||
{
|
||||
data: []byte("```"),
|
||||
syntaxRequested: true,
|
||||
newlineOptional: true,
|
||||
wantEnd: 3,
|
||||
wantMarker: "```",
|
||||
},
|
||||
{
|
||||
data: []byte("``` go"),
|
||||
syntaxRequested: true,
|
||||
newlineOptional: true,
|
||||
wantEnd: 6,
|
||||
wantMarker: "```",
|
||||
wantSyntax: "go",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var syntax *string
|
||||
if test.syntaxRequested {
|
||||
syntax = new(string)
|
||||
}
|
||||
end, marker := isFenceLine(test.data, syntax, "```", test.newlineOptional)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestJoinLines(t *testing.T) {
|
||||
input := `# 标题
|
||||
|
||||
第一
|
||||
行文字。
|
||||
|
||||
第
|
||||
二
|
||||
行文字。
|
||||
`
|
||||
result := `<h1>标题</h1>
|
||||
|
||||
<p>第一行文字。</p>
|
||||
|
||||
<p>第二行文字。</p>
|
||||
`
|
||||
opt := Options{Extensions: commonExtensions | EXTENSION_JOIN_LINES}
|
||||
renderer := HtmlRenderer(commonHtmlFlags, "", "")
|
||||
output := MarkdownOptions([]byte(input), renderer, opt)
|
||||
|
||||
if string(output) != result {
|
||||
t.Error("output dose not match.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizedAnchorName(t *testing.T) {
|
||||
tests := []struct {
|
||||
text string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
text: "This is a header",
|
||||
want: "this-is-a-header",
|
||||
},
|
||||
{
|
||||
text: "This is also a header",
|
||||
want: "this-is-also-a-header",
|
||||
},
|
||||
{
|
||||
text: "main.go",
|
||||
want: "main-go",
|
||||
},
|
||||
{
|
||||
text: "Article 123",
|
||||
want: "article-123",
|
||||
},
|
||||
{
|
||||
text: "<- Let's try this, shall we?",
|
||||
want: "let-s-try-this-shall-we",
|
||||
},
|
||||
{
|
||||
text: " ",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
text: "Hello, 世界",
|
||||
want: "hello-世界",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
if got := SanitizedAnchorName(test.text); got != test.want {
|
||||
t.Errorf("SanitizedAnchorName(%q):\ngot %q\nwant %q", test.text, got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Package blackfriday is a Markdown processor.
|
||||
//
|
||||
// It translates plain text with simple formatting rules into HTML or LaTeX.
|
||||
//
|
||||
// Sanitized Anchor Names
|
||||
//
|
||||
// Blackfriday includes an algorithm for creating sanitized anchor names
|
||||
// corresponding to a given input text. This algorithm is used to create
|
||||
// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The
|
||||
// algorithm is specified below, so that other packages can create
|
||||
// compatible anchor names and links to those anchors.
|
||||
//
|
||||
// The algorithm iterates over the input text, interpreted as UTF-8,
|
||||
// one Unicode code point (rune) at a time. All runes that are letters (category L)
|
||||
// or numbers (category N) are considered valid characters. They are mapped to
|
||||
// lower case, and included in the output. All other runes are considered
|
||||
// invalid characters. Invalid characters that preceed the first valid character,
|
||||
// as well as invalid character that follow the last valid character
|
||||
// are dropped completely. All other sequences of invalid characters
|
||||
// between two valid characters are replaced with a single dash character '-'.
|
||||
//
|
||||
// SanitizedAnchorName exposes this functionality, and can be used to
|
||||
// create compatible links to the anchor names generated by blackfriday.
|
||||
// This algorithm is also implemented in a small standalone package at
|
||||
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
|
||||
// that want a small package and don't need full functionality of blackfriday.
|
||||
package blackfriday
|
||||
|
||||
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
|
||||
// github.com/shurcooL/sanitized_anchor_name.
|
||||
// Otherwise, users of sanitized_anchor_name will get anchor names
|
||||
// that are incompatible with those generated by blackfriday.
|
||||
+1
@@ -42,6 +42,7 @@ const (
|
||||
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
|
||||
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
|
||||
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
|
||||
HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS)
|
||||
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
|
||||
)
|
||||
|
||||
|
||||
+28
-7
@@ -170,6 +170,10 @@ func lineBreak(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
precededByBackslash := offset >= 1 && data[offset-1] == '\\' // see http://spec.commonmark.org/0.18/#example-527
|
||||
precededByBackslash = precededByBackslash && p.flags&EXTENSION_BACKSLASH_LINE_BREAK != 0
|
||||
|
||||
if p.flags&EXTENSION_JOIN_LINES != 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
// should there be a hard line break here?
|
||||
if p.flags&EXTENSION_HARD_LINE_BREAK == 0 && !precededByTwoSpaces && !precededByBackslash {
|
||||
return 0
|
||||
@@ -240,6 +244,8 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
i++
|
||||
}
|
||||
|
||||
brace := 0
|
||||
|
||||
// look for the matching closing bracket
|
||||
for level := 1; level > 0 && i < len(data); i++ {
|
||||
switch {
|
||||
@@ -273,8 +279,8 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
i++
|
||||
}
|
||||
|
||||
// inline style link
|
||||
switch {
|
||||
// inline style link
|
||||
case i < len(data) && data[i] == '(':
|
||||
// skip initial whitespace
|
||||
i++
|
||||
@@ -285,14 +291,27 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
|
||||
linkB := i
|
||||
|
||||
// look for link end: ' " )
|
||||
// look for link end: ' " ), check for new opening braces and take this
|
||||
// into account, this may lead for overshooting and probably will require
|
||||
// some fine-tuning.
|
||||
findlinkend:
|
||||
for i < len(data) {
|
||||
switch {
|
||||
case data[i] == '\\':
|
||||
i += 2
|
||||
|
||||
case data[i] == ')' || data[i] == '\'' || data[i] == '"':
|
||||
case data[i] == '(':
|
||||
brace++
|
||||
i++
|
||||
|
||||
case data[i] == ')':
|
||||
if brace <= 0 {
|
||||
break findlinkend
|
||||
}
|
||||
brace--
|
||||
i++
|
||||
|
||||
case data[i] == '\'' || data[i] == '"':
|
||||
break findlinkend
|
||||
|
||||
default:
|
||||
@@ -469,6 +488,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
}
|
||||
|
||||
p.notes = append(p.notes, ref)
|
||||
p.notesRecord[string(ref.link)] = struct{}{}
|
||||
|
||||
link = ref.link
|
||||
title = ref.title
|
||||
@@ -479,9 +499,10 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
if t == linkDeferredFootnote {
|
||||
if t == linkDeferredFootnote && !p.isFootnote(lr) {
|
||||
lr.noteId = len(p.notes) + 1
|
||||
p.notes = append(p.notes, lr)
|
||||
p.notesRecord[string(lr.link)] = struct{}{}
|
||||
}
|
||||
|
||||
// keep link and title from reference
|
||||
@@ -560,7 +581,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
return i
|
||||
}
|
||||
|
||||
func (p *parser) inlineHtmlComment(out *bytes.Buffer, data []byte) int {
|
||||
func (p *parser) inlineHTMLComment(out *bytes.Buffer, data []byte) int {
|
||||
if len(data) < 5 {
|
||||
return 0
|
||||
}
|
||||
@@ -584,7 +605,7 @@ func leftAngle(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
||||
data = data[offset:]
|
||||
altype := LINK_TYPE_NOT_AUTOLINK
|
||||
end := tagLength(data, &altype)
|
||||
if size := p.inlineHtmlComment(out, data); size > 0 {
|
||||
if size := p.inlineHTMLComment(out, data); size > 0 {
|
||||
end = size
|
||||
}
|
||||
if end > 2 {
|
||||
@@ -923,7 +944,7 @@ func isMailtoAutoLink(data []byte) int {
|
||||
nb++
|
||||
|
||||
case '-', '.', '_':
|
||||
break
|
||||
// Do nothing.
|
||||
|
||||
case '>':
|
||||
if nb == 1 {
|
||||
|
||||
+128
-9
@@ -15,9 +15,8 @@ package blackfriday
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func runMarkdownInline(input string, opts Options, htmlFlags int, params HtmlRendererParameters) string {
|
||||
@@ -60,13 +59,11 @@ func doTestsInlineParam(t *testing.T, tests []string, opts Options, htmlFlags in
|
||||
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)
|
||||
}
|
||||
}()
|
||||
*/
|
||||
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]
|
||||
@@ -156,10 +153,35 @@ func TestEmphasis(t *testing.T) {
|
||||
|
||||
"*What is A\\* algorithm?*\n",
|
||||
"<p><em>What is A* algorithm?</em></p>\n",
|
||||
|
||||
"some para_graph with _emphasised_ text.\n",
|
||||
"<p>some para_graph with <em>emphasised</em> text.</p>\n",
|
||||
|
||||
"some paragraph with _emphasised_ te_xt.\n",
|
||||
"<p>some paragraph with <em>emphasised</em> te_xt.</p>\n",
|
||||
|
||||
"some paragraph with t_wo bi_ts of _emphasised_ text.\n",
|
||||
"<p>some paragraph with t<em>wo bi</em>ts of <em>emphasised</em> text.</p>\n",
|
||||
|
||||
"un*frigging*believable\n",
|
||||
"<p>un<em>frigging</em>believable</p>\n",
|
||||
}
|
||||
doTestsInline(t, tests)
|
||||
}
|
||||
|
||||
func TestNoIntraEmphasis(t *testing.T) {
|
||||
tests := []string{
|
||||
"some para_graph with _emphasised_ text.\n",
|
||||
"<p>some para_graph with <em>emphasised</em> text.</p>\n",
|
||||
|
||||
"un*frigging*believable\n",
|
||||
"<p>un*frigging*believable</p>\n",
|
||||
}
|
||||
doTestsInlineParam(t, tests, Options{
|
||||
Extensions: EXTENSION_NO_INTRA_EMPHASIS},
|
||||
0, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func TestReferenceOverride(t *testing.T) {
|
||||
var tests = []string{
|
||||
"test [ref1][]\n",
|
||||
@@ -541,6 +563,20 @@ func TestInlineLink(t *testing.T) {
|
||||
|
||||
"[link](<../>)\n",
|
||||
"<p><a href=\"../\">link</a></p>\n",
|
||||
|
||||
// Issue 116 in blackfriday
|
||||
".jpg)",
|
||||
"<p><img src=\"http://www.broadgate.co.uk/Content/Upload/DetailImages/Cyclus700(1).jpg\" alt=\"\" /></p>\n",
|
||||
|
||||
// no closing ), autolinking detects the url next
|
||||
"[disambiguation](http://en.wikipedia.org/wiki/Disambiguation_(disambiguation) is the",
|
||||
"<p>[disambiguation](<a href=\"http://en.wikipedia.org/wiki/Disambiguation_(disambiguation\">http://en.wikipedia.org/wiki/Disambiguation_(disambiguation</a>) is the</p>\n",
|
||||
|
||||
"[disambiguation](http://en.wikipedia.org/wiki/Disambiguation_(disambiguation)) is the",
|
||||
"<p><a href=\"http://en.wikipedia.org/wiki/Disambiguation_(disambiguation)\">disambiguation</a> is the</p>\n",
|
||||
|
||||
"[disambiguation](http://en.wikipedia.org/wiki/Disambiguation_(disambiguation))",
|
||||
"<p><a href=\"http://en.wikipedia.org/wiki/Disambiguation_(disambiguation)\">disambiguation</a></p>\n",
|
||||
}
|
||||
doLinkTestsInline(t, tests)
|
||||
|
||||
@@ -681,6 +717,9 @@ func TestReferenceLink(t *testing.T) {
|
||||
|
||||
"[ref]\n [ref]: ../url/ \"title\"\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)
|
||||
}
|
||||
@@ -984,6 +1023,28 @@ what happens here
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
`,
|
||||
`testing footnotes.[^a]
|
||||
|
||||
test footnotes the second.[^b]
|
||||
|
||||
[^a]: This is the first note[^a].
|
||||
[^b]: this is the second note.[^a]
|
||||
`,
|
||||
`<p>testing footnotes.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup></p>
|
||||
|
||||
<p>test footnotes the second.<sup class="footnote-ref" id="fnref:b"><a rel="footnote" href="#fn:b">2</a></sup></p>
|
||||
<div class="footnotes">
|
||||
|
||||
<hr />
|
||||
|
||||
<ol>
|
||||
<li id="fn:a">This is the first note<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup>.
|
||||
</li>
|
||||
<li id="fn:b">this is the second note.<sup class="footnote-ref" id="fnref:a"><a rel="footnote" href="#fn:a">1</a></sup>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
|
||||
@@ -1037,6 +1098,34 @@ func TestNestedFootnotes(t *testing.T) {
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
`,
|
||||
`This uses footnote A.[^A]
|
||||
|
||||
This uses footnote C.[^C]
|
||||
|
||||
[^A]:
|
||||
A note. use itself.[^A]
|
||||
[^B]:
|
||||
B note, uses A to test duplicate.[^A]
|
||||
[^C]:
|
||||
C note, uses B.[^B]
|
||||
`,
|
||||
`<p>This uses footnote A.<sup class="footnote-ref" id="fnref:A"><a rel="footnote" href="#fn:A">1</a></sup></p>
|
||||
|
||||
<p>This uses footnote C.<sup class="footnote-ref" id="fnref:C"><a rel="footnote" href="#fn:C">2</a></sup></p>
|
||||
<div class="footnotes">
|
||||
|
||||
<hr />
|
||||
|
||||
<ol>
|
||||
<li id="fn:A">A note. use itself.<sup class="footnote-ref" id="fnref:A"><a rel="footnote" href="#fn:A">1</a></sup>
|
||||
</li>
|
||||
<li id="fn:C">C note, uses B.<sup class="footnote-ref" id="fnref:B"><a rel="footnote" href="#fn:B">3</a></sup>
|
||||
</li>
|
||||
<li id="fn:B">B note, uses A to test duplicate.<sup class="footnote-ref" id="fnref:A"><a rel="footnote" href="#fn:A">1</a></sup>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
doTestsInlineParam(t, tests, Options{Extensions: EXTENSION_FOOTNOTES}, 0,
|
||||
@@ -1084,6 +1173,18 @@ func TestSmartDoubleQuotes(t *testing.T) {
|
||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
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, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||
var tests = []string{
|
||||
"this should be angled \"quoted\" text.\n",
|
||||
@@ -1096,6 +1197,18 @@ func TestSmartAngledDoubleQuotes(t *testing.T) {
|
||||
doTestsInlineParam(t, tests, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
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, Options{}, HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_ANGLED_QUOTES|HTML_SMARTYPANTS_QUOTES_NBSP, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func TestSmartFractions(t *testing.T) {
|
||||
var tests = []string{
|
||||
"1/2, 1/4 and 3/4; 1/4th and 3/4ths\n",
|
||||
@@ -1151,3 +1264,9 @@ func TestDisableSmartDashes(t *testing.T) {
|
||||
HTML_USE_SMARTYPANTS|HTML_SMARTYPANTS_LATEX_DASHES,
|
||||
HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func BenchmarkSmartDoubleQuotes(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
runMarkdownInline("this should be normal \"quoted\" text.\n", Options{}, HTML_USE_SMARTYPANTS, HtmlRendererParameters{})
|
||||
}
|
||||
}
|
||||
|
||||
+91
-86
@@ -13,9 +13,6 @@
|
||||
//
|
||||
//
|
||||
|
||||
// Blackfriday markdown processor.
|
||||
//
|
||||
// Translates plain text with simple formatting rules into HTML or LaTeX.
|
||||
package blackfriday
|
||||
|
||||
import (
|
||||
@@ -25,7 +22,7 @@ import (
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const VERSION = "1.4"
|
||||
const VERSION = "1.5"
|
||||
|
||||
// These are the supported markdown parsing extensions.
|
||||
// OR these values together to select multiple extensions.
|
||||
@@ -46,6 +43,7 @@ const (
|
||||
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
|
||||
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
|
||||
EXTENSION_DEFINITION_LISTS // render definition lists
|
||||
EXTENSION_JOIN_LINES // delete newline and join lines
|
||||
|
||||
commonHtmlFlags = 0 |
|
||||
HTML_USE_XHTML |
|
||||
@@ -105,46 +103,46 @@ const (
|
||||
// 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.
|
||||
var blockTags = map[string]struct{}{
|
||||
"blockquote": struct{}{},
|
||||
"del": struct{}{},
|
||||
"div": struct{}{},
|
||||
"dl": struct{}{},
|
||||
"fieldset": struct{}{},
|
||||
"form": struct{}{},
|
||||
"h1": struct{}{},
|
||||
"h2": struct{}{},
|
||||
"h3": struct{}{},
|
||||
"h4": struct{}{},
|
||||
"h5": struct{}{},
|
||||
"h6": struct{}{},
|
||||
"iframe": struct{}{},
|
||||
"ins": struct{}{},
|
||||
"math": struct{}{},
|
||||
"noscript": struct{}{},
|
||||
"ol": struct{}{},
|
||||
"pre": struct{}{},
|
||||
"p": struct{}{},
|
||||
"script": struct{}{},
|
||||
"style": struct{}{},
|
||||
"table": struct{}{},
|
||||
"ul": struct{}{},
|
||||
"blockquote": {},
|
||||
"del": {},
|
||||
"div": {},
|
||||
"dl": {},
|
||||
"fieldset": {},
|
||||
"form": {},
|
||||
"h1": {},
|
||||
"h2": {},
|
||||
"h3": {},
|
||||
"h4": {},
|
||||
"h5": {},
|
||||
"h6": {},
|
||||
"iframe": {},
|
||||
"ins": {},
|
||||
"math": {},
|
||||
"noscript": {},
|
||||
"ol": {},
|
||||
"pre": {},
|
||||
"p": {},
|
||||
"script": {},
|
||||
"style": {},
|
||||
"table": {},
|
||||
"ul": {},
|
||||
|
||||
// HTML5
|
||||
"address": struct{}{},
|
||||
"article": struct{}{},
|
||||
"aside": struct{}{},
|
||||
"canvas": struct{}{},
|
||||
"figcaption": struct{}{},
|
||||
"figure": struct{}{},
|
||||
"footer": struct{}{},
|
||||
"header": struct{}{},
|
||||
"hgroup": struct{}{},
|
||||
"main": struct{}{},
|
||||
"nav": struct{}{},
|
||||
"output": struct{}{},
|
||||
"progress": struct{}{},
|
||||
"section": struct{}{},
|
||||
"video": struct{}{},
|
||||
"address": {},
|
||||
"article": {},
|
||||
"aside": {},
|
||||
"canvas": {},
|
||||
"figcaption": {},
|
||||
"figure": {},
|
||||
"footer": {},
|
||||
"header": {},
|
||||
"hgroup": {},
|
||||
"main": {},
|
||||
"nav": {},
|
||||
"output": {},
|
||||
"progress": {},
|
||||
"section": {},
|
||||
"video": {},
|
||||
}
|
||||
|
||||
// Renderer is the rendering interface.
|
||||
@@ -220,7 +218,8 @@ type parser struct {
|
||||
// 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
|
||||
// in notes. Slice is nil if footnotes not enabled.
|
||||
notes []*reference
|
||||
notes []*reference
|
||||
notesRecord map[string]struct{}
|
||||
}
|
||||
|
||||
func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||
@@ -243,6 +242,11 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
|
||||
return ref, found
|
||||
}
|
||||
|
||||
func (p *parser) isFootnote(ref *reference) bool {
|
||||
_, ok := p.notesRecord[string(ref.link)]
|
||||
return ok
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Public interface
|
||||
@@ -378,6 +382,7 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
||||
|
||||
if extensions&EXTENSION_FOOTNOTES != 0 {
|
||||
p.notes = make([]*reference, 0)
|
||||
p.notesRecord = make(map[string]struct{})
|
||||
}
|
||||
|
||||
first := firstPass(p, input)
|
||||
@@ -386,9 +391,9 @@ func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
|
||||
}
|
||||
|
||||
// first pass:
|
||||
// - extract references
|
||||
// - expand tabs
|
||||
// - normalize newlines
|
||||
// - extract references (outside of fenced code blocks)
|
||||
// - expand tabs (outside of fenced code blocks)
|
||||
// - copy everything else
|
||||
func firstPass(p *parser, input []byte) []byte {
|
||||
var out bytes.Buffer
|
||||
@@ -396,46 +401,46 @@ func firstPass(p *parser, input []byte) []byte {
|
||||
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
|
||||
tabSize = TAB_SIZE_EIGHT
|
||||
}
|
||||
beg, end := 0, 0
|
||||
beg := 0
|
||||
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 {
|
||||
// track fenced code block boundaries to suppress tab expansion
|
||||
// inside them:
|
||||
if beg >= lastFencedCodeBlockEnd {
|
||||
if i := p.fencedCode(&out, input[beg:], false); i > 0 {
|
||||
lastFencedCodeBlockEnd = beg + i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
for beg < len(input) {
|
||||
// Find end of this line, then process the line.
|
||||
end := beg
|
||||
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
|
||||
end++
|
||||
}
|
||||
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||
// track fenced code block boundaries to suppress tab expansion
|
||||
// and reference extraction inside them:
|
||||
if beg >= lastFencedCodeBlockEnd {
|
||||
if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
|
||||
lastFencedCodeBlockEnd = beg + i
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
|
||||
beg += refEnd
|
||||
continue
|
||||
} else {
|
||||
expandTabs(&out, input[beg:end], tabSize)
|
||||
}
|
||||
}
|
||||
|
||||
if end < len(input) && input[end] == '\r' {
|
||||
end++
|
||||
}
|
||||
if end < len(input) && input[end] == '\n' {
|
||||
end++
|
||||
}
|
||||
out.WriteByte('\n')
|
||||
|
||||
beg = end
|
||||
}
|
||||
|
||||
// empty input?
|
||||
@@ -635,12 +640,12 @@ func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffse
|
||||
i++
|
||||
}
|
||||
linkOffset = i
|
||||
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
if i == len(data) {
|
||||
return
|
||||
}
|
||||
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||
i++
|
||||
}
|
||||
linkEnd = i
|
||||
if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
|
||||
linkOffset++
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// 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 runMarkdown(input string) string {
|
||||
return string(MarkdownCommon([]byte(input)))
|
||||
}
|
||||
|
||||
func doTests(t *testing.T, tests []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 := runMarkdown(candidate)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
+1
-1
@@ -29,7 +29,7 @@ func doTestsReference(t *testing.T, files []string, flag int) {
|
||||
var candidate string
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
t.Errorf("\npanic while processing [%#v]\n", candidate)
|
||||
t.Errorf("\npanic while processing [%#v]: %s\n", candidate, err)
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
+44
-14
@@ -39,7 +39,7 @@ func isdigit(c byte) bool {
|
||||
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,
|
||||
// so we treat it like text sometimes
|
||||
|
||||
@@ -96,6 +96,12 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
||||
*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('&')
|
||||
if *isOpen {
|
||||
out.WriteByte('l')
|
||||
@@ -104,6 +110,11 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
|
||||
}
|
||||
out.WriteByte(quote)
|
||||
out.WriteString("quo;")
|
||||
|
||||
if addNBSP && *isOpen {
|
||||
out.WriteString(" ")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -116,7 +127,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@@ -141,7 +152,7 @@ func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byt
|
||||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -205,13 +216,13 @@ func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
|
||||
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int {
|
||||
if bytes.HasPrefix(text, []byte(""")) {
|
||||
nextChar := byte(0)
|
||||
if len(text) >= 7 {
|
||||
nextChar = text[6]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
|
||||
return 5
|
||||
}
|
||||
}
|
||||
@@ -224,12 +235,15 @@ func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte
|
||||
return 0
|
||||
}
|
||||
|
||||
func smartAmp(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, 'd')
|
||||
}
|
||||
func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
var quote byte = 'd'
|
||||
if angledQuotes {
|
||||
quote = 'a'
|
||||
}
|
||||
|
||||
func smartAmpAngledQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, 'a')
|
||||
return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
|
||||
}
|
||||
}
|
||||
|
||||
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
|
||||
@@ -253,7 +267,7 @@ func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte,
|
||||
if len(text) >= 3 {
|
||||
nextChar = text[2]
|
||||
}
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) {
|
||||
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@@ -337,7 +351,7 @@ func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousC
|
||||
if len(text) > 1 {
|
||||
nextChar = text[1]
|
||||
}
|
||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) {
|
||||
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
|
||||
out.WriteString(""")
|
||||
}
|
||||
|
||||
@@ -367,14 +381,30 @@ type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar b
|
||||
|
||||
type smartypantsRenderer [256]smartCallback
|
||||
|
||||
var (
|
||||
smartAmpAngled = smartAmp(true, false)
|
||||
smartAmpAngledNBSP = smartAmp(true, true)
|
||||
smartAmpRegular = smartAmp(false, false)
|
||||
smartAmpRegularNBSP = smartAmp(false, true)
|
||||
)
|
||||
|
||||
func smartypants(flags int) *smartypantsRenderer {
|
||||
r := new(smartypantsRenderer)
|
||||
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
|
||||
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
|
||||
r['"'] = smartDoubleQuote
|
||||
r['&'] = smartAmp
|
||||
if !addNBSP {
|
||||
r['&'] = smartAmpRegular
|
||||
} else {
|
||||
r['&'] = smartAmpRegularNBSP
|
||||
}
|
||||
} else {
|
||||
r['"'] = smartAngledDoubleQuote
|
||||
r['&'] = smartAmpAngledQuote
|
||||
if !addNBSP {
|
||||
r['&'] = smartAmpAngled
|
||||
} else {
|
||||
r['&'] = smartAmpAngledNBSP
|
||||
}
|
||||
}
|
||||
r['\''] = smartSingleQuote
|
||||
r['('] = smartParens
|
||||
|
||||
Reference in New Issue
Block a user