Compare commits

..

22 Commits

Author SHA1 Message Date
Dmitri Shuralyov e96880f42b
README: make a definitive decision on v2 import path
This change makes it clear that the v2 import path
is github.com/russross/blackfriday/v2, and updates
various links accordingly.

See https://github.com/russross/blackfriday/issues/587#issuecomment-703393820 for details.

This change also converges the README for v1 and v2
to be consistent, as they've started to drift apart.
(See PR #675 for the equivalent change to the README
on v2 branch.)

For #587.

GitHub-Pull-Request: #674
2020-10-26 23:46:40 -04:00
Russ Ross abb995c466
Merge pull request #586 from kolyshkin/gomod
go.mod: add go version
2020-03-25 09:18:22 -06:00
Kir Kolyshkin 665c4def65 go.mod: add go version
This is a result of running `go mod tidy` using go 1.13.6.

In my devel toolchain (vim-go -> gopls -> go 1.13), go apparently
wants to add this line to go.mod, but since the modules are read-only,
it fails, which leads to gopls failing, too.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-03-23 19:11:51 -07:00
Russ Ross 41c5fccfd6
Merge pull request #559 from alanhdu/patch-1
Add <details> to recognized HTML5 block tags
2020-02-18 16:49:12 -07:00
Russ Ross 75d2440b2e
Merge pull request #570 from orf/add-no-opener
Backport noopener links to v1
2020-02-18 16:47:11 -07:00
Russ Ross 89e6d96148
Merge pull request #580 from MarkAtwood/master
Update LICENSE.txt
2020-02-18 16:43:38 -07:00
Mark Atwood c04ab11830
Update LICENSE.txt
License is not changed.  Remove line quote characters so that license scanners can recognize the license.
2020-02-03 13:41:32 -08:00
Tom Forbes 41eb1fe8a7
Backport noopener links (from commit 670777b536) 2019-09-22 15:27:07 +01:00
Alan Du e2f6cd9f54
Add <summary> tag too 2019-08-20 17:54:39 -04:00
Alan Du 5c6bc5df8a
Add <details> to recognized HTML5 block tags
Closes #525
2019-08-20 17:40:27 -04:00
Anatoly Milkov a925a152c1 README: update URL of LaTeX renderer (#465)
It has moved from Bitbucket, first to GitHub, then to GitLab.
Point to its latest location.

Updates #388.
2019-06-16 15:52:46 -04:00
Christian Oliff f3ccc8fc06 https link to GitHub (#538) 2019-04-17 22:17:06 +03:00
Ignas Anikevicius a477dd1646 Fix fenced code in lists (#521)
This attempts to fix #495 and #485.

Note the test cases which were added at the bottom of the list. The first added test case was passing even before the changes, but the second was not.
2019-01-24 10:23:35 +02:00
Iskander (Alex) Sharipov 13768b07fa readme: use fenced code blocks (#519)
It's more consistent and it's possible to mark code blocks with "go" hint,
so we get syntax highlighting on GitHub.
2019-01-20 20:42:03 +02:00
autopp 8c3eacd7a5 Fix checking of backslash in link text (#510) (#512) 2019-01-20 20:38:36 +02:00
Harald Nordgren abafa45cd8 Bump Go versions (#499) 2018-11-15 22:38:43 +02:00
Bjørn Erik Pedersen 05f3235734 Add go.mod (#488) 2018-09-17 21:59:06 +03:00
Tim Fogarty f1f45ab762 Fix block parsing for fenced code blocks in lists (#476)
This is a backport of the initial fix in v2 (#372).
2018-08-29 21:04:01 +03:00
Vytautas Šaltenis 46c73eb196
Add a link to bfchroma, glue library with Chroma (#400)
Chroma is a general purpose code highlighting library, and bfchroma
provides an easy to use glue layer for Blackfriday to interface with
Chroma.

Fixes #10.
2018-08-04 13:11:49 +03:00
Vytautas Šaltenis 34d6fae961
Don't run tests against ancient versions on Travis (#438)
Don't run tests against ancient versions on Travis.
2018-08-04 13:08:49 +03:00
Nathan Glenn 11635eb403 Accept info strings in code fences (#448)
* Accept info strings in code fences

According to the common mark standard, code fence info strings can be anything,
not just single words. Update the tests and parser accordingly.

The formatter already expected an info string with a language and HTML classes,
so this does not need to change. Update the LaTeX formatter to take the first
word of the info string as the language.

Fixes #410 (in v1).

* Don't output whole info string as code classes

This follows the common mark specification.

* run go fmt
2018-04-28 13:25:19 +03:00
Yoshiya Hinosawa 16ac584625 docs(README.md): fix typo (#451) 2018-04-26 21:50:03 +03:00
13 changed files with 292 additions and 165 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 &amp; char &lt; &gt; &quot; 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 &amp; char &lt; &gt; &quot; 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)
}
}
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module github.com/russross/blackfriday
go 1.13

35
html.go
View File

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

View File

@ -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] == '[':

View File

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

View File

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

View File

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

View File

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

View File

@ -8,5 +8,6 @@ Just a [URL](/url/).
[URL and title](/url/ "title has spaces afterward" ).
[URL with backslashes\\](/url/).
[Empty]().