Compare commits
22 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
e96880f42b | |
|
|
abb995c466 | |
|
|
665c4def65 | |
|
|
41c5fccfd6 | |
|
|
75d2440b2e | |
|
|
89e6d96148 | |
|
|
c04ab11830 | |
|
|
41eb1fe8a7 | |
|
|
e2f6cd9f54 | |
|
|
5c6bc5df8a | |
|
|
a925a152c1 | |
|
|
f3ccc8fc06 | |
|
|
a477dd1646 | |
|
|
13768b07fa | |
|
|
8c3eacd7a5 | |
|
|
abafa45cd8 | |
|
|
05f3235734 | |
|
|
f1f45ab762 | |
|
|
46c73eb196 | |
|
|
34d6fae961 | |
|
|
11635eb403 | |
|
|
16ac584625 |
20
.travis.yml
20
.travis.yml
|
|
@ -1,26 +1,14 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- 1.5.4
|
||||
- 1.6.2
|
||||
- "1.9.x"
|
||||
- "1.10.x"
|
||||
- "1.11.x"
|
||||
- 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 ./...
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
install:
|
||||
- # 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:
|
||||
|
|
|
|||
53
LICENSE.txt
53
LICENSE.txt
|
|
@ -1,29 +1,28 @@
|
|||
Blackfriday is distributed under the Simplified BSD License:
|
||||
|
||||
> Copyright © 2011 Russ Ross
|
||||
> All rights reserved.
|
||||
>
|
||||
> Redistribution and use in source and binary forms, with or without
|
||||
> modification, are permitted provided that the following conditions
|
||||
> are met:
|
||||
>
|
||||
> 1. Redistributions of source code must retain the above copyright
|
||||
> notice, this list of conditions and the following disclaimer.
|
||||
>
|
||||
> 2. Redistributions in binary form must reproduce the above
|
||||
> copyright notice, this list of conditions and the following
|
||||
> disclaimer in the documentation and/or other materials provided with
|
||||
> the distribution.
|
||||
>
|
||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
> POSSIBILITY OF SUCH DAMAGE.
|
||||
Copyright © 2011 Russ Ross
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided with
|
||||
the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
|
|||
99
README.md
99
README.md
|
|
@ -1,6 +1,6 @@
|
|||
Blackfriday
|
||||
[![Build Status][BuildSVG]][BuildURL]
|
||||
[![Godoc][GodocV2SVG]][GodocV2URL]
|
||||
[![Build Status][BuildV2SVG]][BuildV2URL]
|
||||
[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
|
||||
===========
|
||||
|
||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
|
||||
|
|
@ -18,12 +18,21 @@ It started as a translation from C of [Sundown][3].
|
|||
Installation
|
||||
------------
|
||||
|
||||
Blackfriday is compatible with any modern Go release. With Go and git installed:
|
||||
Blackfriday is compatible with modern Go releases in module mode.
|
||||
With Go installed:
|
||||
|
||||
go get -u gopkg.in/russross/blackfriday.v2
|
||||
go get github.com/russross/blackfriday
|
||||
|
||||
will download, compile, and install the package into your `$GOPATH` directory
|
||||
hierarchy.
|
||||
will resolve and add the package to the current development module,
|
||||
then build and install it. Alternatively, you can achieve the same
|
||||
if you import it in a package:
|
||||
|
||||
import "github.com/russross/blackfriday"
|
||||
|
||||
and `go get` without parameters.
|
||||
|
||||
Old versions of Go and legacy GOPATH mode might work,
|
||||
but no effort is made to keep them working.
|
||||
|
||||
|
||||
Versions
|
||||
|
|
@ -32,13 +41,9 @@ Versions
|
|||
Currently maintained and recommended version of Blackfriday is `v2`. It's being
|
||||
developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
|
||||
documentation is available at
|
||||
https://godoc.org/gopkg.in/russross/blackfriday.v2.
|
||||
https://pkg.go.dev/github.com/russross/blackfriday/v2.
|
||||
|
||||
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
|
||||
but we highly recommend using package management tool like [dep][7] or
|
||||
[Glide][8] and make use of semantic versioning. With package management you
|
||||
should import `github.com/russross/blackfriday` and specify that you're using
|
||||
version 2.0.0.
|
||||
It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
|
||||
|
||||
Version 2 offers a number of improvements over v1:
|
||||
|
||||
|
|
@ -60,22 +65,7 @@ Potential drawbacks:
|
|||
|
||||
If you are still interested in the legacy `v1`, you can import it from
|
||||
`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
|
||||
here: https://godoc.org/github.com/russross/blackfriday
|
||||
|
||||
### Known issue with `dep`
|
||||
|
||||
There is a known problem with using Blackfriday v1 _transitively_ and `dep`.
|
||||
Currently `dep` prioritizes semver versions over anything else, and picks the
|
||||
latest one, plus it does not apply a `[[constraint]]` specifier to transitively
|
||||
pulled in packages. So if you're using something that uses Blackfriday v1, but
|
||||
that something does not use `dep` yet, you will get Blackfriday v2 pulled in and
|
||||
your first dependency will fail to build.
|
||||
|
||||
There are couple of fixes for it, documented here:
|
||||
https://github.com/golang/dep/blob/master/docs/FAQ.md#how-do-i-constrain-a-transitive-dependencys-version
|
||||
|
||||
Meanwhile, `dep` team is working on a more general solution to the constraints
|
||||
on transitive dependencies problem: https://github.com/golang/dep/issues/1124.
|
||||
here: https://pkg.go.dev/github.com/russross/blackfriday.
|
||||
|
||||
|
||||
Usage
|
||||
|
|
@ -86,12 +76,16 @@ Usage
|
|||
For basic usage, it is as simple as getting your input into a byte
|
||||
slice and calling:
|
||||
|
||||
output := blackfriday.MarkdownBasic(input)
|
||||
```go
|
||||
output := blackfriday.MarkdownBasic(input)
|
||||
```
|
||||
|
||||
This renders it with no extensions enabled. To get a more useful
|
||||
feature set, use this instead:
|
||||
|
||||
output := blackfriday.MarkdownCommon(input)
|
||||
```go
|
||||
output := blackfriday.MarkdownCommon(input)
|
||||
```
|
||||
|
||||
### v2
|
||||
|
||||
|
|
@ -121,7 +115,7 @@ Here's an example of simple usage of Blackfriday together with Bluemonday:
|
|||
```go
|
||||
import (
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"gopkg.in/russross/blackfriday.v2"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
// ...
|
||||
|
|
@ -154,7 +148,7 @@ markdown file using a standalone program. You can also browse the
|
|||
source directly on github if you are just looking for some example
|
||||
code:
|
||||
|
||||
* <http://github.com/russross/blackfriday-tool>
|
||||
* <https://github.com/russross/blackfriday-tool>
|
||||
|
||||
Note that if you have not already done so, installing
|
||||
`blackfriday-tool` will be sufficient to download and install
|
||||
|
|
@ -171,12 +165,12 @@ 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.
|
||||
The specification is located at https://pkg.go.dev/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
|
||||
[`SanitizedAnchorName`](https://pkg.go.dev/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
|
||||
[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/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.
|
||||
|
||||
|
||||
|
|
@ -246,7 +240,7 @@ implements the following extensions:
|
|||
and supply a language (to make syntax highlighting simple). Just
|
||||
mark it like this:
|
||||
|
||||
``` go
|
||||
```go
|
||||
func getTrue() bool {
|
||||
return true
|
||||
}
|
||||
|
|
@ -258,7 +252,7 @@ implements the following extensions:
|
|||
To preserve classes of fenced code blocks while using the bluemonday
|
||||
HTML sanitizer, use the following policy:
|
||||
|
||||
``` go
|
||||
```go
|
||||
p := bluemonday.UGCPolicy()
|
||||
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
|
||||
html := p.SanitizeBytes(unsafe)
|
||||
|
|
@ -269,7 +263,7 @@ implements the following extensions:
|
|||
|
||||
Cat
|
||||
: Fluffy animal everyone likes
|
||||
|
||||
|
||||
Internet
|
||||
: Vector of transmission for pictures of cats
|
||||
|
||||
|
|
@ -280,7 +274,7 @@ implements the following extensions:
|
|||
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
|
||||
|
|
@ -317,7 +311,7 @@ Other renderers
|
|||
Blackfriday is structured to allow alternative rendering engines. Here
|
||||
are a few of note:
|
||||
|
||||
* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown):
|
||||
* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown):
|
||||
provides a GitHub Flavored Markdown renderer with fenced code block
|
||||
highlighting, clickable heading anchor links.
|
||||
|
||||
|
|
@ -328,9 +322,19 @@ are a few of note:
|
|||
* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
|
||||
but for markdown.
|
||||
|
||||
* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
|
||||
* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex):
|
||||
renders output as LaTeX.
|
||||
|
||||
* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
|
||||
integration with the [Chroma](https://github.com/alecthomas/chroma) code
|
||||
highlighting library. bfchroma is only compatible with v2 of Blackfriday and
|
||||
provides a drop-in renderer ready to use with Blackfriday, as well as
|
||||
options and means for further customization.
|
||||
|
||||
* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer.
|
||||
|
||||
* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style
|
||||
|
||||
|
||||
TODO
|
||||
----
|
||||
|
|
@ -351,13 +355,10 @@ License
|
|||
[1]: https://daringfireball.net/projects/markdown/ "Markdown"
|
||||
[2]: https://golang.org/ "Go Language"
|
||||
[3]: https://github.com/vmg/sundown "Sundown"
|
||||
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
|
||||
[4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
|
||||
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
|
||||
[6]: https://labix.org/gopkg.in "gopkg.in"
|
||||
[7]: https://github.com/golang/dep/ "dep"
|
||||
[8]: https://github.com/Masterminds/glide "Glide"
|
||||
|
||||
[BuildSVG]: https://travis-ci.org/russross/blackfriday.svg?branch=master
|
||||
[BuildURL]: https://travis-ci.org/russross/blackfriday
|
||||
[GodocV2SVG]: https://godoc.org/gopkg.in/russross/blackfriday.v2?status.svg
|
||||
[GodocV2URL]: https://godoc.org/gopkg.in/russross/blackfriday.v2
|
||||
[BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2
|
||||
[BuildV2URL]: https://travis-ci.org/russross/blackfriday
|
||||
[PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2
|
||||
[PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2
|
||||
|
|
|
|||
84
block.go
84
block.go
|
|
@ -15,6 +15,7 @@ package blackfriday
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) {
|
|||
|
||||
// fenced code block:
|
||||
//
|
||||
// ``` go
|
||||
// ``` go info string here
|
||||
// func fact(n int) int {
|
||||
// if n <= 1 {
|
||||
// return n
|
||||
|
|
@ -562,7 +563,7 @@ func (*parser) isHRule(data []byte) bool {
|
|||
// 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) {
|
||||
func isFenceLine(data []byte, info *string, oldmarker string, newlineOptional bool) (end int, marker string) {
|
||||
i, size := 0, 0
|
||||
|
||||
// skip up to three spaces
|
||||
|
|
@ -598,9 +599,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
}
|
||||
|
||||
// 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
|
||||
// into one, always get the info string, and discard it if the caller doesn't care.
|
||||
if info != nil {
|
||||
infoLength := 0
|
||||
i = skipChar(data, i, ' ')
|
||||
|
||||
if i >= len(data) {
|
||||
|
|
@ -610,14 +611,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
return 0, ""
|
||||
}
|
||||
|
||||
syntaxStart := i
|
||||
infoStart := i
|
||||
|
||||
if data[i] == '{' {
|
||||
i++
|
||||
syntaxStart++
|
||||
infoStart++
|
||||
|
||||
for i < len(data) && data[i] != '}' && data[i] != '\n' {
|
||||
syn++
|
||||
infoLength++
|
||||
i++
|
||||
}
|
||||
|
||||
|
|
@ -627,43 +628,46 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
|
|||
|
||||
// strip all whitespace at the beginning and the end
|
||||
// of the {} block
|
||||
for syn > 0 && isspace(data[syntaxStart]) {
|
||||
syntaxStart++
|
||||
syn--
|
||||
for infoLength > 0 && isspace(data[infoStart]) {
|
||||
infoStart++
|
||||
infoLength--
|
||||
}
|
||||
|
||||
for syn > 0 && isspace(data[syntaxStart+syn-1]) {
|
||||
syn--
|
||||
for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
|
||||
infoLength--
|
||||
}
|
||||
|
||||
i++
|
||||
} else {
|
||||
for i < len(data) && !isspace(data[i]) {
|
||||
syn++
|
||||
for i < len(data) && !isverticalspace(data[i]) {
|
||||
infoLength++
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
*syntax = string(data[syntaxStart : syntaxStart+syn])
|
||||
*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
|
||||
}
|
||||
|
||||
i = skipChar(data, i, ' ')
|
||||
if i >= len(data) || data[i] != '\n' {
|
||||
if newlineOptional && i == len(data) {
|
||||
if i >= len(data) {
|
||||
if newlineOptional {
|
||||
return i, marker
|
||||
}
|
||||
return 0, ""
|
||||
}
|
||||
if data[i] == '\n' {
|
||||
i++ // Take newline into account
|
||||
}
|
||||
|
||||
return i + 1, marker // Take newline into account.
|
||||
return i, marker
|
||||
}
|
||||
|
||||
// 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)
|
||||
var infoString string
|
||||
beg, marker := isFenceLine(data, &infoString, "", false)
|
||||
if beg == 0 || beg >= len(data) {
|
||||
return 0
|
||||
}
|
||||
|
|
@ -697,7 +701,7 @@ func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool)
|
|||
}
|
||||
|
||||
if doRender {
|
||||
p.r.BlockCode(out, work.Bytes(), syntax)
|
||||
p.r.BlockCode(out, work.Bytes(), infoString)
|
||||
}
|
||||
|
||||
return beg
|
||||
|
|
@ -1132,6 +1136,15 @@ func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
|
|||
i++
|
||||
}
|
||||
|
||||
// process the following lines
|
||||
containsBlankLine := false
|
||||
sublist := 0
|
||||
codeBlockMarker := ""
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 && i > line {
|
||||
// determine if codeblock starts on the first line
|
||||
_, codeBlockMarker = isFenceLine(data[line:i], nil, "", false)
|
||||
}
|
||||
|
||||
// get working buffer
|
||||
var raw bytes.Buffer
|
||||
|
||||
|
|
@ -1139,10 +1152,6 @@ func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int {
|
|||
raw.Write(data[line:i])
|
||||
line = i
|
||||
|
||||
// process the following lines
|
||||
containsBlankLine := false
|
||||
sublist := 0
|
||||
|
||||
gatherlines:
|
||||
for line < len(data) {
|
||||
i++
|
||||
|
|
@ -1151,7 +1160,6 @@ gatherlines:
|
|||
for data[i-1] != '\n' {
|
||||
i++
|
||||
}
|
||||
|
||||
// if it is an empty line, guess that it is part of this item
|
||||
// and move on to the next line
|
||||
if p.isEmpty(data[line:i]) > 0 {
|
||||
|
|
@ -1169,6 +1177,28 @@ gatherlines:
|
|||
|
||||
chunk := data[line+indent : i]
|
||||
|
||||
if p.flags&EXTENSION_FENCED_CODE != 0 {
|
||||
// determine if in or out of codeblock
|
||||
// if in codeblock, ignore normal list processing
|
||||
_, marker := isFenceLine(chunk, nil, codeBlockMarker, false)
|
||||
if marker != "" {
|
||||
if codeBlockMarker == "" {
|
||||
// start of codeblock
|
||||
codeBlockMarker = marker
|
||||
} else {
|
||||
// end of codeblock.
|
||||
*flags |= LIST_ITEM_CONTAINS_BLOCK
|
||||
codeBlockMarker = ""
|
||||
}
|
||||
}
|
||||
// we are in a codeblock, write line, and continue
|
||||
if codeBlockMarker != "" || marker != "" {
|
||||
raw.Write(data[line+indent : i])
|
||||
line = i
|
||||
continue gatherlines
|
||||
}
|
||||
}
|
||||
|
||||
// evaluate how this line fits in
|
||||
switch {
|
||||
// is this a nested list item?
|
||||
|
|
|
|||
115
block_test.go
115
block_test.go
|
|
@ -697,8 +697,8 @@ func TestUnorderedList(t *testing.T) {
|
|||
"* List\n extra indent, same paragraph\n",
|
||||
"<ul>\n<li>List\n extra indent, same paragraph</li>\n</ul>\n",
|
||||
|
||||
"* List\n\n code block\n",
|
||||
"<ul>\n<li><p>List</p>\n\n<pre><code>code block\n</code></pre></li>\n</ul>\n",
|
||||
"* List\n\n code block\n\n* List continues",
|
||||
"<ul>\n<li><p>List</p>\n\n<pre><code>code block\n</code></pre></li>\n\n<li><p>List continues</p></li>\n</ul>\n",
|
||||
|
||||
"* List\n\n code block with spaces\n",
|
||||
"<ul>\n<li><p>List</p>\n\n<pre><code> code block with spaces\n</code></pre></li>\n</ul>\n",
|
||||
|
|
@ -1053,6 +1053,9 @@ func TestFencedCodeBlock(t *testing.T) {
|
|||
"``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n",
|
||||
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",
|
||||
|
||||
"``` go foo bar\nfunc foo() bool {\n\treturn true;\n}\n```\n",
|
||||
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",
|
||||
|
||||
"``` c\n/* special & char < > \" escaping */\n```\n",
|
||||
"<pre><code class=\"language-c\">/* special & char < > " escaping */\n</code></pre>\n",
|
||||
|
||||
|
|
@ -1093,7 +1096,7 @@ func TestFencedCodeBlock(t *testing.T) {
|
|||
"<p>``` lisp\nno ending</p>\n",
|
||||
|
||||
"~~~ lisp\nend with language\n~~~ lisp\n",
|
||||
"<p>~~~ lisp\nend with language\n~~~ lisp</p>\n",
|
||||
"<pre><code class=\"language-lisp\">end with language\n</code></pre>\n\n<p>lisp</p>\n",
|
||||
|
||||
"```\nmismatched begin and end\n~~~\n",
|
||||
"<p>```\nmismatched begin and end\n~~~</p>\n",
|
||||
|
|
@ -1136,6 +1139,21 @@ func TestFencedCodeBlock(t *testing.T) {
|
|||
|
||||
"```\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",
|
||||
|
||||
"- test\n\n```\n codeblock\n ```\ntest\n",
|
||||
"<ul>\n<li><p>test</p>\n\n<pre><code>codeblock\n</code></pre></li>\n</ul>\n\n<p>test</p>\n",
|
||||
|
||||
"- ```\n codeblock\n ```\n\n- test\n",
|
||||
"<ul>\n<li><pre><code>codeblock\n</code></pre></li>\n\n<li><p>test</p></li>\n</ul>\n",
|
||||
|
||||
"- test\n- ```\n codeblock\n ```\n",
|
||||
"<ul>\n<li>test</li>\n\n<li><pre><code>codeblock\n</code></pre></li>\n</ul>\n",
|
||||
|
||||
"- test\n```\ncodeblock\n```\n\n- test\n",
|
||||
"<ul>\n<li><p>test</p>\n\n<pre><code>codeblock\n</code></pre></li>\n\n<li><p>test</p></li>\n</ul>\n",
|
||||
|
||||
"- test\n```go\nfunc foo() bool {\n\treturn true;\n}\n```\n\n- test\n",
|
||||
"<ul>\n<li><p>test</p>\n\n<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre></li>\n\n<li><p>test</p></li>\n</ul>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
|
||||
}
|
||||
|
|
@ -1511,6 +1529,9 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
|||
"``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n",
|
||||
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",
|
||||
|
||||
"``` go foo bar\nfunc foo() bool {\n\treturn true;\n}\n```\n",
|
||||
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",
|
||||
|
||||
"``` c\n/* special & char < > \" escaping */\n```\n",
|
||||
"<pre><code class=\"language-c\">/* special & char < > " escaping */\n</code></pre>\n",
|
||||
|
||||
|
|
@ -1551,7 +1572,7 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
|||
"<p>``` lisp\nno ending</p>\n",
|
||||
|
||||
"~~~ lisp\nend with language\n~~~ lisp\n",
|
||||
"<p>~~~ lisp\nend with language\n~~~ lisp</p>\n",
|
||||
"<pre><code class=\"language-lisp\">end with language\n</code></pre>\n\n<p>lisp</p>\n",
|
||||
|
||||
"```\nmismatched begin and end\n~~~\n",
|
||||
"<p>```\nmismatched begin and end\n~~~</p>\n",
|
||||
|
|
@ -1577,6 +1598,44 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
|
|||
doTestsBlock(t, tests, EXTENSION_FENCED_CODE|EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK)
|
||||
}
|
||||
|
||||
func TestListWithFencedCodeBlock(t *testing.T) {
|
||||
var tests = []string{
|
||||
"1. one\n\n ```\n code\n ```\n\n2. two\n",
|
||||
"<ol>\n<li><p>one</p>\n\n<pre><code>code\n</code></pre></li>\n\n<li><p>two</p></li>\n</ol>\n",
|
||||
// https://github.com/russross/blackfriday/issues/239
|
||||
"1. one\n\n ```\n - code\n ```\n\n2. two\n",
|
||||
"<ol>\n<li><p>one</p>\n\n<pre><code>- code\n</code></pre></li>\n\n<li><p>two</p></li>\n</ol>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
|
||||
}
|
||||
|
||||
func TestListWithMalformedFencedCodeBlock(t *testing.T) {
|
||||
// Ensure that in the case of an unclosed fenced code block in a list,
|
||||
// no source gets ommitted (even if it is malformed).
|
||||
// See russross/blackfriday#372 for context.
|
||||
var tests = []string{
|
||||
"1. one\n\n ```\n code\n\n2. two\n",
|
||||
"<ol>\n<li>one\n\n```\ncode\n\n2. two</li>\n</ol>\n",
|
||||
|
||||
"1. one\n\n ```\n - code\n\n2. two\n",
|
||||
"<ol>\n<li>one\n\n```\n- code\n\n2. two</li>\n</ol>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, EXTENSION_FENCED_CODE)
|
||||
}
|
||||
|
||||
func TestListWithFencedCodeBlockNoExtensions(t *testing.T) {
|
||||
// If there is a fenced code block in a list, and FencedCode is not set,
|
||||
// lists should be processed normally.
|
||||
var tests = []string{
|
||||
"1. one\n\n ```\n code\n ```\n\n2. two\n",
|
||||
"<ol>\n<li><p>one</p>\n\n<p><code>\ncode\n</code></p></li>\n\n<li><p>two</p></li>\n</ol>\n",
|
||||
|
||||
"1. one\n\n ```\n - code\n ```\n\n2. two\n",
|
||||
"<ol>\n<li><p>one</p>\n\n<p>```</p>\n\n<ul>\n<li>code\n```</li>\n</ul></li>\n\n<li><p>two</p></li>\n</ol>\n",
|
||||
}
|
||||
doTestsBlock(t, tests, 0)
|
||||
}
|
||||
|
||||
func TestTitleBlock_EXTENSION_TITLEBLOCK(t *testing.T) {
|
||||
var tests = []string{
|
||||
"% Some title\n" +
|
||||
|
|
@ -1646,11 +1705,11 @@ func TestCDATA(t *testing.T) {
|
|||
func TestIsFenceLine(t *testing.T) {
|
||||
tests := []struct {
|
||||
data []byte
|
||||
syntaxRequested bool
|
||||
infoRequested bool
|
||||
newlineOptional bool
|
||||
wantEnd int
|
||||
wantMarker string
|
||||
wantSyntax string
|
||||
wantInfo string
|
||||
}{
|
||||
{
|
||||
data: []byte("```"),
|
||||
|
|
@ -1662,10 +1721,10 @@ func TestIsFenceLine(t *testing.T) {
|
|||
wantMarker: "```",
|
||||
},
|
||||
{
|
||||
data: []byte("```\nstuff here\n"),
|
||||
syntaxRequested: true,
|
||||
wantEnd: 4,
|
||||
wantMarker: "```",
|
||||
data: []byte("```\nstuff here\n"),
|
||||
infoRequested: true,
|
||||
wantEnd: 4,
|
||||
wantMarker: "```",
|
||||
},
|
||||
{
|
||||
data: []byte("stuff here\n```\n"),
|
||||
|
|
@ -1679,36 +1738,52 @@ func TestIsFenceLine(t *testing.T) {
|
|||
},
|
||||
{
|
||||
data: []byte("```"),
|
||||
syntaxRequested: true,
|
||||
infoRequested: true,
|
||||
newlineOptional: true,
|
||||
wantEnd: 3,
|
||||
wantMarker: "```",
|
||||
},
|
||||
{
|
||||
data: []byte("``` go"),
|
||||
syntaxRequested: true,
|
||||
infoRequested: true,
|
||||
newlineOptional: true,
|
||||
wantEnd: 6,
|
||||
wantMarker: "```",
|
||||
wantSyntax: "go",
|
||||
wantInfo: "go",
|
||||
},
|
||||
{
|
||||
data: []byte("``` go foo bar"),
|
||||
infoRequested: true,
|
||||
newlineOptional: true,
|
||||
wantEnd: 14,
|
||||
wantMarker: "```",
|
||||
wantInfo: "go foo bar",
|
||||
},
|
||||
{
|
||||
data: []byte("``` go foo bar "),
|
||||
infoRequested: true,
|
||||
newlineOptional: true,
|
||||
wantEnd: 16,
|
||||
wantMarker: "```",
|
||||
wantInfo: "go foo bar",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
var syntax *string
|
||||
if test.syntaxRequested {
|
||||
syntax = new(string)
|
||||
var info *string
|
||||
if test.infoRequested {
|
||||
info = new(string)
|
||||
}
|
||||
end, marker := isFenceLine(test.data, syntax, "```", test.newlineOptional)
|
||||
end, marker := isFenceLine(test.data, info, "```", 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)
|
||||
if test.infoRequested {
|
||||
if got, want := *info, test.wantInfo; got != want {
|
||||
t.Errorf("got info %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
35
html.go
35
html.go
|
|
@ -32,6 +32,7 @@ const (
|
|||
HTML_SAFELINK // only link to trusted protocols
|
||||
HTML_NOFOLLOW_LINKS // only link with rel="nofollow"
|
||||
HTML_NOREFERRER_LINKS // only link with rel="noreferrer"
|
||||
HTML_NOOPENER_LINKS // only link with rel="noopener"
|
||||
HTML_HREF_TARGET_BLANK // add a blank target
|
||||
HTML_TOC // generate a table of contents
|
||||
HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents)
|
||||
|
|
@ -255,33 +256,21 @@ func (options *Html) HRule(out *bytes.Buffer) {
|
|||
out.WriteByte('\n')
|
||||
}
|
||||
|
||||
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
|
||||
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, info string) {
|
||||
doubleSpace(out)
|
||||
|
||||
// parse out the language names/classes
|
||||
count := 0
|
||||
for _, elt := range strings.Fields(lang) {
|
||||
if elt[0] == '.' {
|
||||
elt = elt[1:]
|
||||
}
|
||||
if len(elt) == 0 {
|
||||
continue
|
||||
}
|
||||
if count == 0 {
|
||||
out.WriteString("<pre><code class=\"language-")
|
||||
} else {
|
||||
out.WriteByte(' ')
|
||||
}
|
||||
attrEscape(out, []byte(elt))
|
||||
count++
|
||||
endOfLang := strings.IndexAny(info, "\t ")
|
||||
if endOfLang < 0 {
|
||||
endOfLang = len(info)
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
lang := info[:endOfLang]
|
||||
if len(lang) == 0 || lang == "." {
|
||||
out.WriteString("<pre><code>")
|
||||
} else {
|
||||
out.WriteString("<pre><code class=\"language-")
|
||||
attrEscape(out, []byte(lang))
|
||||
out.WriteString("\">")
|
||||
}
|
||||
|
||||
attrEscape(out, text)
|
||||
out.WriteString("</code></pre>\n")
|
||||
}
|
||||
|
|
@ -457,6 +446,9 @@ func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
|||
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "noreferrer")
|
||||
}
|
||||
if options.flags&HTML_NOOPENER_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "noopener")
|
||||
}
|
||||
if len(relAttrs) > 0 {
|
||||
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||
}
|
||||
|
|
@ -571,6 +563,9 @@ func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content
|
|||
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "noreferrer")
|
||||
}
|
||||
if options.flags&HTML_NOOPENER_LINKS != 0 && !isRelativeLink(link) {
|
||||
relAttrs = append(relAttrs, "noopener")
|
||||
}
|
||||
if len(relAttrs) > 0 {
|
||||
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ func link(p *parser, out *bytes.Buffer, data []byte, offset int) int {
|
|||
case data[i] == '\n':
|
||||
textHasNl = true
|
||||
|
||||
case data[i-1] == '\\':
|
||||
case isBackslashEscaped(data, i):
|
||||
continue
|
||||
|
||||
case data[i] == '[':
|
||||
|
|
|
|||
|
|
@ -624,6 +624,25 @@ func TestRelAttrLink(t *testing.T) {
|
|||
}
|
||||
doTestsInlineParam(t, nofollownoreferrerTests, Options{}, HTML_SAFELINK|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS,
|
||||
HtmlRendererParameters{})
|
||||
|
||||
var noopenerTests = []string{
|
||||
"[foo](http://bar.com/foo/)\n",
|
||||
"<p><a href=\"http://bar.com/foo/\" rel=\"noopener\">foo</a></p>\n",
|
||||
|
||||
"[foo](/bar/)\n",
|
||||
"<p><a href=\"/bar/\">foo</a></p>\n",
|
||||
}
|
||||
doTestsInlineParam(t, noopenerTests, Options{}, HTML_SAFELINK|HTML_NOOPENER_LINKS, HtmlRendererParameters{})
|
||||
|
||||
var nofollownoreferrernoopenerTests = []string{
|
||||
"[foo](http://bar.com/foo/)\n",
|
||||
"<p><a href=\"http://bar.com/foo/\" rel=\"nofollow noreferrer noopener\">foo</a></p>\n",
|
||||
|
||||
"[foo](/bar/)\n",
|
||||
"<p><a href=\"/bar/\">foo</a></p>\n",
|
||||
}
|
||||
doTestsInlineParam(t, nofollownoreferrernoopenerTests, Options{},
|
||||
HTML_SAFELINK|HTML_NOOPENER_LINKS|HTML_NOFOLLOW_LINKS|HTML_NOREFERRER_LINKS, HtmlRendererParameters{})
|
||||
}
|
||||
|
||||
func TestHrefTargetBlank(t *testing.T) {
|
||||
|
|
|
|||
8
latex.go
8
latex.go
|
|
@ -17,6 +17,7 @@ package blackfriday
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Latex is a type that implements the Renderer interface for LaTeX output.
|
||||
|
|
@ -39,16 +40,17 @@ func (options *Latex) GetFlags() int {
|
|||
}
|
||||
|
||||
// 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 == "" {
|
||||
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, info string) {
|
||||
if info == "" {
|
||||
out.WriteString("\n\\begin{verbatim}\n")
|
||||
} else {
|
||||
lang := strings.Fields(info)[0]
|
||||
out.WriteString("\n\\begin{lstlisting}[language=")
|
||||
out.WriteString(lang)
|
||||
out.WriteString("]\n")
|
||||
}
|
||||
out.Write(text)
|
||||
if lang == "" {
|
||||
if info == "" {
|
||||
out.WriteString("\n\\end{verbatim}\n")
|
||||
} else {
|
||||
out.WriteString("\n\\end{lstlisting}\n")
|
||||
|
|
|
|||
16
markdown.go
16
markdown.go
|
|
@ -132,6 +132,7 @@ var blockTags = map[string]struct{}{
|
|||
"article": {},
|
||||
"aside": {},
|
||||
"canvas": {},
|
||||
"details": {},
|
||||
"figcaption": {},
|
||||
"figure": {},
|
||||
"footer": {},
|
||||
|
|
@ -142,6 +143,7 @@ var blockTags = map[string]struct{}{
|
|||
"output": {},
|
||||
"progress": {},
|
||||
"section": {},
|
||||
"summary": {},
|
||||
"video": {},
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +161,7 @@ var blockTags = map[string]struct{}{
|
|||
// Currently Html and Latex implementations are provided
|
||||
type Renderer interface {
|
||||
// block-level callbacks
|
||||
BlockCode(out *bytes.Buffer, text []byte, lang string)
|
||||
BlockCode(out *bytes.Buffer, text []byte, infoString string)
|
||||
BlockQuote(out *bytes.Buffer, text []byte)
|
||||
BlockHtml(out *bytes.Buffer, text []byte)
|
||||
Header(out *bytes.Buffer, text func() bool, level int, id string)
|
||||
|
|
@ -804,7 +806,17 @@ func ispunct(c byte) bool {
|
|||
|
||||
// Test if a character is a whitespace character.
|
||||
func isspace(c byte) bool {
|
||||
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
return ishorizontalspace(c) || isverticalspace(c)
|
||||
}
|
||||
|
||||
// Test if a character is a horizontal whitespace character.
|
||||
func ishorizontalspace(c byte) bool {
|
||||
return c == ' ' || c == '\t'
|
||||
}
|
||||
|
||||
// Test if a character is a vertical whitespace character.
|
||||
func isverticalspace(c byte) bool {
|
||||
return c == '\n' || c == '\r' || c == '\f' || c == '\v'
|
||||
}
|
||||
|
||||
// Test if a character is letter.
|
||||
|
|
|
|||
|
|
@ -8,4 +8,6 @@
|
|||
|
||||
<p><a href="/url/" title="title has spaces afterward">URL and title</a>.</p>
|
||||
|
||||
<p><a href="/url/">URL with backslashes\</a>.</p>
|
||||
|
||||
<p>[Empty]().</p>
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ Just a [URL](/url/).
|
|||
|
||||
[URL and title](/url/ "title has spaces afterward" ).
|
||||
|
||||
[URL with backslashes\\](/url/).
|
||||
|
||||
[Empty]().
|
||||
|
|
|
|||
Loading…
Reference in New Issue