Fix regression in HTML handling (telegram). Closes #734

* Revert back to blackfriday v1
* Add testing
This commit is contained in:
Wim 2019-02-24 15:13:56 +01:00
parent f92735d35d
commit 96841c70c7
22 changed files with 2336 additions and 2562 deletions

View File

@ -3,7 +3,6 @@ package btelegram
import ( import (
"bytes" "bytes"
"html" "html"
"io"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
) )
@ -33,7 +32,7 @@ func (options *customHTML) Header(out *bytes.Buffer, text func() bool, level int
options.Paragraph(out, text) options.Paragraph(out, text)
} }
func (options *customHTML) HRule(out io.ByteWriter) { func (options *customHTML) HRule(out *bytes.Buffer) {
out.WriteByte('\n') //nolint:errcheck out.WriteByte('\n') //nolint:errcheck
} }
@ -54,16 +53,13 @@ func (options *customHTML) ListItem(out *bytes.Buffer, text []byte, flags int) {
} }
func makeHTML(input string) string { func makeHTML(input string) string {
extensions := blackfriday.NoIntraEmphasis | return string(blackfriday.Markdown([]byte(input),
blackfriday.FencedCode | &customHTML{blackfriday.HtmlRenderer(blackfriday.HTML_USE_XHTML|blackfriday.HTML_SKIP_IMAGES, "", "")},
blackfriday.Autolink | blackfriday.EXTENSION_NO_INTRA_EMPHASIS|
blackfriday.SpaceHeadings | blackfriday.EXTENSION_FENCED_CODE|
blackfriday.HeadingIDs | blackfriday.EXTENSION_AUTOLINK|
blackfriday.BackslashLineBreak | blackfriday.EXTENSION_SPACE_HEADERS|
blackfriday.DefinitionLists blackfriday.EXTENSION_HEADER_IDS|
blackfriday.EXTENSION_BACKSLASH_LINE_BREAK|
renderer := &customHTML{blackfriday.NewHTMLRenderer(blackfriday.HTMLRendererParameters{ blackfriday.EXTENSION_DEFINITION_LISTS))
Flags: blackfriday.UseXHTML | blackfriday.SkipImages,
})}
return string(blackfriday.Run([]byte(input), blackfriday.WithExtensions(extensions), blackfriday.WithRenderer(renderer)))
} }

2
go.mod
View File

@ -46,7 +46,7 @@ require (
github.com/peterhellberg/emojilib v0.0.0-20190124112554-c18758d55320 github.com/peterhellberg/emojilib v0.0.0-20190124112554-c18758d55320
github.com/pkg/errors v0.8.0 // indirect github.com/pkg/errors v0.8.0 // indirect
github.com/rs/xid v1.2.1 github.com/rs/xid v1.2.1
github.com/russross/blackfriday v2.0.0+incompatible github.com/russross/blackfriday v1.5.2
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296 github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
github.com/sirupsen/logrus v1.3.0 github.com/sirupsen/logrus v1.3.0

4
go.sum
View File

@ -129,8 +129,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=

View File

@ -15,7 +15,7 @@ import (
) )
var ( var (
version = "1.14.0-rc1" version = "1.14.0-rc1-dev"
githash string githash string
flagConfig = flag.String("conf", "matterbridge.toml", "config file") flagConfig = flag.String("conf", "matterbridge.toml", "config file")

View File

@ -1,18 +1,17 @@
# Travis CI (http://travis-ci.org/) is a continuous integration service for sudo: false
# open source projects. This file configures it to run unit tests for
# blackfriday.
language: go language: go
go: go:
- 1.5 - "1.9.x"
- 1.6 - "1.10.x"
- 1.7 - tip
matrix:
fast_finish: true
allow_failures:
- go: tip
install: install:
- go get -d -t -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).
- go build -v ./...
script: script:
- go test -v ./... - go get -t -v ./...
- go test -run=^$ -bench=BenchmarkReference -benchmem - diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...

View File

@ -1,4 +1,6 @@
Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) Blackfriday
[![Build Status][BuildSVG]][BuildURL]
[![Godoc][GodocV2SVG]][GodocV2URL]
=========== ===========
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
@ -16,27 +18,27 @@ It started as a translation from C of [Sundown][3].
Installation Installation
------------ ------------
Blackfriday is compatible with any modern Go release. With Go 1.7 and git Blackfriday is compatible with any modern Go release. With Go and git installed:
installed:
go get gopkg.in/russross/blackfriday.v2 go get -u gopkg.in/russross/blackfriday.v2
will download, compile, and install the package into your `$GOPATH` will download, compile, and install the package into your `$GOPATH` directory
directory hierarchy. Alternatively, you can achieve the same if you hierarchy.
import it into a project:
import "gopkg.in/russross/blackfriday.v2"
and `go get` without parameters.
Versions Versions
-------- --------
Currently maintained and recommended version of Blackfriday is `v2`. It's being Currently maintained and recommended version of Blackfriday is `v2`. It's being
developed on its own branch: https://github.com/russross/blackfriday/v2. You developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the
should install and import it via [gopkg.in][6] at documentation is available at
`gopkg.in/russross/blackfriday.v2`. https://godoc.org/gopkg.in/russross/blackfriday.v2.
It is `go get`-able 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.
Version 2 offers a number of improvements over v1: Version 2 offers a number of improvements over v1:
@ -56,9 +58,43 @@ Potential drawbacks:
v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
tracking. tracking.
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.
Usage Usage
----- -----
### v1
For basic usage, it is as simple as getting your input into a byte
slice and calling:
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)
### v2
For the most sensible markdown processing, it is as simple as getting your input For the most sensible markdown processing, it is as simple as getting your input
into a byte slice and calling: into a byte slice and calling:
@ -85,7 +121,7 @@ Here's an example of simple usage of Blackfriday together with Bluemonday:
```go ```go
import ( import (
"github.com/microcosm-cc/bluemonday" "github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday" "gopkg.in/russross/blackfriday.v2"
) )
// ... // ...
@ -93,11 +129,21 @@ unsafe := blackfriday.Run(input)
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
``` ```
### Custom options ### Custom options, v1
If you want to customize the set of options, first get a renderer
(currently only the HTML output engine), then use it to
call the more general `Markdown` function. For examples, see the
implementations of `MarkdownBasic` and `MarkdownCommon` in
`markdown.go`.
### Custom options, v2
If you want to customize the set of options, use `blackfriday.WithExtensions`, If you want to customize the set of options, use `blackfriday.WithExtensions`,
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`. `blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
### `blackfriday-tool`
You can also check out `blackfriday-tool` for a more complete example You can also check out `blackfriday-tool` for a more complete example
of how to use it. Download and install it using: of how to use it. Download and install it using:
@ -117,6 +163,22 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that
can be copied to wherever you need it without worrying about can be copied to wherever you need it without worrying about
dependencies and library versions. 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 Features
-------- --------
@ -184,7 +246,7 @@ implements the following extensions:
and supply a language (to make syntax highlighting simple). Just and supply a language (to make syntax highlighting simple). Just
mark it like this: mark it like this:
```go ``` go
func getTrue() bool { func getTrue() bool {
return true return true
} }
@ -193,6 +255,15 @@ implements the following extensions:
You can use 3 or more backticks to mark the beginning of the You can use 3 or more backticks to mark the beginning of the
block, and the same number to mark the end of the block. block, and the same number to mark the end of the block.
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 * **Definition lists**. A simple definition list is made of a single-line
term followed by a colon and the definition for that term. term followed by a colon and the definition for that term.
@ -218,8 +289,10 @@ implements the following extensions:
* **Strikethrough**. Use two tildes (`~~`) to mark text that * **Strikethrough**. Use two tildes (`~~`) to mark text that
should be crossed out. should be crossed out.
* **Hard line breaks**. With this extension enabled newlines in the input * **Hard line breaks**. With this extension enabled (it is off by
translate into line breaks in the output. This extension is off by default. default in the `MarkdownBasic` and `MarkdownCommon` convenience
functions), newlines in the input translate into line breaks in
the output.
* **Smart quotes**. Smartypants-style punctuation substitution is * **Smart quotes**. Smartypants-style punctuation substitution is
supported, turning normal double- and single-quote marks into supported, turning normal double- and single-quote marks into
@ -258,15 +331,21 @@ are a few of note:
* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex): * [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex):
renders output as 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.
Todo
TODO
---- ----
* More unit testing * More unit testing
* Improve unicode support. It does not understand all unicode * Improve Unicode support. It does not understand all Unicode
rules (about what constitutes a letter, a punctuation symbol, rules (about what constitutes a letter, a punctuation symbol,
etc.), so it may fail to detect word boundaries correctly in etc.), so it may fail to detect word boundaries correctly in
some instances. It is safe on all utf-8 input. some instances. It is safe on all UTF-8 input.
License License
@ -281,3 +360,10 @@ License
[4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
[5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
[6]: https://labix.org/gopkg.in "gopkg.in" [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

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,32 @@
// Package blackfriday is a markdown processor. // Package blackfriday is a Markdown processor.
// //
// It translates plain text with simple formatting rules into an AST, which can // It translates plain text with simple formatting rules into HTML or LaTeX.
// then be further processed to HTML (provided by Blackfriday itself) or other
// formats (provided by the community).
// //
// The simplest way to invoke Blackfriday is to call the Run function. It will // Sanitized Anchor Names
// take a text input and produce a text output in HTML (or other format).
// //
// A slightly more sophisticated way to use Blackfriday is to create a Markdown // Blackfriday includes an algorithm for creating sanitized anchor names
// processor and to call Parse, which returns a syntax tree for the input // corresponding to a given input text. This algorithm is used to create
// document. You can leverage Blackfriday's parsing for content extraction from // anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The
// markdown documents. You can assign a custom renderer and set various options // algorithm is specified below, so that other packages can create
// to the Markdown processor. // compatible anchor names and links to those anchors.
// //
// If you're interested in calling Blackfriday from command line, see // The algorithm iterates over the input text, interpreted as UTF-8,
// https://github.com/russross/blackfriday-tool. // 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 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.

View File

@ -1,34 +0,0 @@
package blackfriday
import (
"html"
"io"
)
var htmlEscaper = [256][]byte{
'&': []byte("&amp;"),
'<': []byte("&lt;"),
'>': []byte("&gt;"),
'"': []byte("&quot;"),
}
func escapeHTML(w io.Writer, s []byte) {
var start, end int
for end < len(s) {
escSeq := htmlEscaper[s[end]]
if escSeq != nil {
w.Write(s[start:end])
w.Write(escSeq)
start = end + 1
}
end++
}
if start < len(s) && end <= len(s) {
w.Write(s[start:end])
}
}
func escLink(w io.Writer, text []byte) {
unesc := html.UnescapeString(string(text))
escapeHTML(w, []byte(unesc))
}

1
vendor/github.com/russross/blackfriday/go.mod generated vendored Normal file
View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

334
vendor/github.com/russross/blackfriday/latex.go generated vendored Normal file
View File

@ -0,0 +1,334 @@
//
// Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday
//
// Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License.
// See README.md for details.
//
//
//
// LaTeX rendering backend
//
//
package blackfriday
import (
"bytes"
"strings"
)
// Latex is a type that implements the Renderer interface for LaTeX output.
//
// Do not create this directly, instead use the LatexRenderer function.
type Latex struct {
}
// LatexRenderer creates and configures a Latex object, which
// satisfies the Renderer interface.
//
// flags is a set of LATEX_* options ORed together (currently no such options
// are defined).
func LatexRenderer(flags int) Renderer {
return &Latex{}
}
func (options *Latex) GetFlags() int {
return 0
}
// render code chunks using verbatim, or listings if we have a language
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, 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 info == "" {
out.WriteString("\n\\end{verbatim}\n")
} else {
out.WriteString("\n\\end{lstlisting}\n")
}
}
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
}
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
out.WriteString("\n\\begin{quotation}\n")
out.Write(text)
out.WriteString("\n\\end{quotation}\n")
}
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
// a pretty lame thing to do...
out.WriteString("\n\\begin{verbatim}\n")
out.Write(text)
out.WriteString("\n\\end{verbatim}\n")
}
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
marker := out.Len()
switch level {
case 1:
out.WriteString("\n\\section{")
case 2:
out.WriteString("\n\\subsection{")
case 3:
out.WriteString("\n\\subsubsection{")
case 4:
out.WriteString("\n\\paragraph{")
case 5:
out.WriteString("\n\\subparagraph{")
case 6:
out.WriteString("\n\\textbf{")
}
if !text() {
out.Truncate(marker)
return
}
out.WriteString("}\n")
}
func (options *Latex) HRule(out *bytes.Buffer) {
out.WriteString("\n\\HRule\n")
}
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
marker := out.Len()
if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("\n\\begin{enumerate}\n")
} else {
out.WriteString("\n\\begin{itemize}\n")
}
if !text() {
out.Truncate(marker)
return
}
if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("\n\\end{enumerate}\n")
} else {
out.WriteString("\n\\end{itemize}\n")
}
}
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
out.WriteString("\n\\item ")
out.Write(text)
}
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
marker := out.Len()
out.WriteString("\n")
if !text() {
out.Truncate(marker)
return
}
out.WriteString("\n")
}
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
out.WriteString("\n\\begin{tabular}{")
for _, elt := range columnData {
switch elt {
case TABLE_ALIGNMENT_LEFT:
out.WriteByte('l')
case TABLE_ALIGNMENT_RIGHT:
out.WriteByte('r')
default:
out.WriteByte('c')
}
}
out.WriteString("}\n")
out.Write(header)
out.WriteString(" \\\\\n\\hline\n")
out.Write(body)
out.WriteString("\n\\end{tabular}\n")
}
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
if out.Len() > 0 {
out.WriteString(" \\\\\n")
}
out.Write(text)
}
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString(" & ")
}
out.Write(text)
}
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString(" & ")
}
out.Write(text)
}
// TODO: this
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
}
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
}
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
out.WriteString("\\href{")
if kind == LINK_TYPE_EMAIL {
out.WriteString("mailto:")
}
out.Write(link)
out.WriteString("}{")
out.Write(link)
out.WriteString("}")
}
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
out.WriteString("\\texttt{")
escapeSpecialChars(out, text)
out.WriteString("}")
}
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textbf{")
out.Write(text)
out.WriteString("}")
}
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textit{")
out.Write(text)
out.WriteString("}")
}
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
// treat it like a link
out.WriteString("\\href{")
out.Write(link)
out.WriteString("}{")
out.Write(alt)
out.WriteString("}")
} else {
out.WriteString("\\includegraphics{")
out.Write(link)
out.WriteString("}")
}
}
func (options *Latex) LineBreak(out *bytes.Buffer) {
out.WriteString(" \\\\\n")
}
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
out.WriteString("\\href{")
out.Write(link)
out.WriteString("}{")
out.Write(content)
out.WriteString("}")
}
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
}
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textbf{\\textit{")
out.Write(text)
out.WriteString("}}")
}
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
out.WriteString("\\sout{")
out.Write(text)
out.WriteString("}")
}
// TODO: this
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
}
func needsBackslash(c byte) bool {
for _, r := range []byte("_{}%$&\\~#") {
if c == r {
return true
}
}
return false
}
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
for i := 0; i < len(text); i++ {
// directly copy normal characters
org := i
for i < len(text) && !needsBackslash(text[i]) {
i++
}
if i > org {
out.Write(text[org:i])
}
// escape a character
if i >= len(text) {
break
}
out.WriteByte('\\')
out.WriteByte(text[i])
}
}
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
// TODO: convert this into a unicode character or something
out.Write(entity)
}
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
escapeSpecialChars(out, text)
}
// header and footer
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
out.WriteString("\\documentclass{article}\n")
out.WriteString("\n")
out.WriteString("\\usepackage{graphicx}\n")
out.WriteString("\\usepackage{listings}\n")
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
out.WriteString("\\usepackage[utf8]{inputenc}\n")
out.WriteString("\\usepackage{verbatim}\n")
out.WriteString("\\usepackage[normalem]{ulem}\n")
out.WriteString("\\usepackage{hyperref}\n")
out.WriteString("\n")
out.WriteString("\\hypersetup{colorlinks,%\n")
out.WriteString(" citecolor=black,%\n")
out.WriteString(" filecolor=black,%\n")
out.WriteString(" linkcolor=black,%\n")
out.WriteString(" urlcolor=black,%\n")
out.WriteString(" pdfstartview=FitH,%\n")
out.WriteString(" breaklinks=true,%\n")
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
out.WriteString(VERSION)
out.WriteString("}}\n")
out.WriteString("\n")
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
out.WriteString("\\parindent=0pt\n")
out.WriteString("\n")
out.WriteString("\\begin{document}\n")
}
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
out.WriteString("\n\\end{document}\n")
}

View File

@ -1,181 +1,216 @@
//
// Blackfriday Markdown Processor // Blackfriday Markdown Processor
// Available at http://github.com/russross/blackfriday // Available at http://github.com/russross/blackfriday
// //
// Copyright © 2011 Russ Ross <russ@russross.com>. // Copyright © 2011 Russ Ross <russ@russross.com>.
// Distributed under the Simplified BSD License. // Distributed under the Simplified BSD License.
// See README.md for details. // See README.md for details.
//
//
//
// Markdown parsing and processing
//
//
package blackfriday package blackfriday
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
// const VERSION = "1.5"
// Markdown parsing and processing
//
// Version string of the package. Appears in the rendered document when
// CompletePage flag is on.
const Version = "2.0"
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
// extensions.
type Extensions int
// These are the supported markdown parsing extensions. // These are the supported markdown parsing extensions.
// OR these values together to select multiple extensions. // OR these values together to select multiple extensions.
const ( const (
NoExtensions Extensions = 0 EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words
NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words EXTENSION_TABLES // render tables
Tables // Render tables EXTENSION_FENCED_CODE // render fenced code blocks
FencedCode // Render fenced code blocks EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked
Autolink // Detect embedded URLs that are not explicitly marked EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~
Strikethrough // Strikethrough text using ~~test~~ EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules
LaxHTMLBlocks // Loosen up HTML block parsing rules EXTENSION_SPACE_HEADERS // be strict about prefix header rules
SpaceHeadings // Be strict about prefix heading rules EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks
HardLineBreak // Translate newlines into line breaks EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four
TabSizeEight // Expand tabs to eight spaces instead of four EXTENSION_FOOTNOTES // Pandoc-style footnotes
Footnotes // Pandoc-style footnotes EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block EXTENSION_HEADER_IDS // specify header IDs with {#id}
HeadingIDs // specify heading IDs with {#id} EXTENSION_TITLEBLOCK // Titleblock ala pandoc
Titleblock // Titleblock ala pandoc EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
AutoHeadingIDs // Create the heading ID from the text EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
BackslashLineBreak // Translate trailing backslashes into line breaks EXTENSION_DEFINITION_LISTS // render definition lists
DefinitionLists // Render definition lists EXTENSION_JOIN_LINES // delete newline and join lines
CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | commonHtmlFlags = 0 |
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes HTML_USE_XHTML |
HTML_USE_SMARTYPANTS |
HTML_SMARTYPANTS_FRACTIONS |
HTML_SMARTYPANTS_DASHES |
HTML_SMARTYPANTS_LATEX_DASHES
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | commonExtensions = 0 |
Autolink | Strikethrough | SpaceHeadings | HeadingIDs | EXTENSION_NO_INTRA_EMPHASIS |
BackslashLineBreak | DefinitionLists EXTENSION_TABLES |
EXTENSION_FENCED_CODE |
EXTENSION_AUTOLINK |
EXTENSION_STRIKETHROUGH |
EXTENSION_SPACE_HEADERS |
EXTENSION_HEADER_IDS |
EXTENSION_BACKSLASH_LINE_BREAK |
EXTENSION_DEFINITION_LISTS
) )
// ListType contains bitwise or'ed flags for list and list item objects. // These are the possible flag values for the link renderer.
type ListType int // Only a single one of these values will be used; they are not ORed together.
// These are mostly of interest if you are writing a new output format.
const (
LINK_TYPE_NOT_AUTOLINK = iota
LINK_TYPE_NORMAL
LINK_TYPE_EMAIL
)
// These are the possible flag values for the ListItem renderer. // These are the possible flag values for the ListItem renderer.
// Multiple flag values may be ORed together. // Multiple flag values may be ORed together.
// These are mostly of interest if you are writing a new output format. // These are mostly of interest if you are writing a new output format.
const ( const (
ListTypeOrdered ListType = 1 << iota LIST_TYPE_ORDERED = 1 << iota
ListTypeDefinition LIST_TYPE_DEFINITION
ListTypeTerm LIST_TYPE_TERM
LIST_ITEM_CONTAINS_BLOCK
ListItemContainsBlock LIST_ITEM_BEGINNING_OF_LIST
ListItemBeginningOfList // TODO: figure out if this is of any use now LIST_ITEM_END_OF_LIST
ListItemEndOfList
) )
// CellAlignFlags holds a type of alignment in a table cell.
type CellAlignFlags int
// These are the possible flag values for the table cell renderer. // These are the possible flag values for the table cell renderer.
// Only a single one of these values will be used; they are not ORed together. // Only a single one of these values will be used; they are not ORed together.
// These are mostly of interest if you are writing a new output format. // These are mostly of interest if you are writing a new output format.
const ( const (
TableAlignmentLeft CellAlignFlags = 1 << iota TABLE_ALIGNMENT_LEFT = 1 << iota
TableAlignmentRight TABLE_ALIGNMENT_RIGHT
TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
) )
// The size of a tab stop. // The size of a tab stop.
const ( const (
TabSizeDefault = 4 TAB_SIZE_DEFAULT = 4
TabSizeDouble = 8 TAB_SIZE_EIGHT = 8
) )
// blockTags is a set of tags that are recognized as HTML block tags. // blockTags is a set of tags that are recognized as HTML block tags.
// Any of these can be included in markdown text without special escaping. // Any of these can be included in markdown text without special escaping.
var blockTags = map[string]struct{}{ var blockTags = map[string]struct{}{
"blockquote": struct{}{}, "blockquote": {},
"del": struct{}{}, "del": {},
"div": struct{}{}, "div": {},
"dl": struct{}{}, "dl": {},
"fieldset": struct{}{}, "fieldset": {},
"form": struct{}{}, "form": {},
"h1": struct{}{}, "h1": {},
"h2": struct{}{}, "h2": {},
"h3": struct{}{}, "h3": {},
"h4": struct{}{}, "h4": {},
"h5": struct{}{}, "h5": {},
"h6": struct{}{}, "h6": {},
"iframe": struct{}{}, "iframe": {},
"ins": struct{}{}, "ins": {},
"math": struct{}{}, "math": {},
"noscript": struct{}{}, "noscript": {},
"ol": struct{}{}, "ol": {},
"pre": struct{}{}, "pre": {},
"p": struct{}{}, "p": {},
"script": struct{}{}, "script": {},
"style": struct{}{}, "style": {},
"table": struct{}{}, "table": {},
"ul": struct{}{}, "ul": {},
// HTML5 // HTML5
"address": struct{}{}, "address": {},
"article": struct{}{}, "article": {},
"aside": struct{}{}, "aside": {},
"canvas": struct{}{}, "canvas": {},
"figcaption": struct{}{}, "figcaption": {},
"figure": struct{}{}, "figure": {},
"footer": struct{}{}, "footer": {},
"header": struct{}{}, "header": {},
"hgroup": struct{}{}, "hgroup": {},
"main": struct{}{}, "main": {},
"nav": struct{}{}, "nav": {},
"output": struct{}{}, "output": {},
"progress": struct{}{}, "progress": {},
"section": struct{}{}, "section": {},
"video": struct{}{}, "video": {},
} }
// Renderer is the rendering interface. This is mostly of interest if you are // Renderer is the rendering interface.
// implementing a new rendering format. // This is mostly of interest if you are implementing a new rendering format.
// //
// Only an HTML implementation is provided in this repository, see the README // When a byte slice is provided, it contains the (rendered) contents of the
// for external implementations. // element.
//
// When a callback is provided instead, it will write the contents of the
// respective element directly to the output buffer and return true on success.
// If the callback returns false, the rendering function should reset the
// output buffer as though it had never been called.
//
// Currently Html and Latex implementations are provided
type Renderer interface { type Renderer interface {
// RenderNode is the main rendering method. It will be called once for // block-level callbacks
// every leaf node and twice for every non-leaf node (first with BlockCode(out *bytes.Buffer, text []byte, infoString string)
// entering=true, then with entering=false). The method should write its BlockQuote(out *bytes.Buffer, text []byte)
// rendition of the node to the supplied writer w. BlockHtml(out *bytes.Buffer, text []byte)
RenderNode(w io.Writer, node *Node, entering bool) WalkStatus Header(out *bytes.Buffer, text func() bool, level int, id string)
HRule(out *bytes.Buffer)
List(out *bytes.Buffer, text func() bool, flags int)
ListItem(out *bytes.Buffer, text []byte, flags int)
Paragraph(out *bytes.Buffer, text func() bool)
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
TableRow(out *bytes.Buffer, text []byte)
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
TableCell(out *bytes.Buffer, text []byte, flags int)
Footnotes(out *bytes.Buffer, text func() bool)
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
TitleBlock(out *bytes.Buffer, text []byte)
// RenderHeader is a method that allows the renderer to produce some // Span-level callbacks
// content preceding the main body of the output document. The header is AutoLink(out *bytes.Buffer, link []byte, kind int)
// understood in the broad sense here. For example, the default HTML CodeSpan(out *bytes.Buffer, text []byte)
// renderer will write not only the HTML document preamble, but also the DoubleEmphasis(out *bytes.Buffer, text []byte)
// table of contents if it was requested. Emphasis(out *bytes.Buffer, text []byte)
// Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
// The method will be passed an entire document tree, in case a particular LineBreak(out *bytes.Buffer)
// implementation needs to inspect it to produce output. Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
// RawHtmlTag(out *bytes.Buffer, tag []byte)
// The output should be written to the supplied writer w. If your TripleEmphasis(out *bytes.Buffer, text []byte)
// implementation has no header to write, supply an empty implementation. StrikeThrough(out *bytes.Buffer, text []byte)
RenderHeader(w io.Writer, ast *Node) FootnoteRef(out *bytes.Buffer, ref []byte, id int)
// RenderFooter is a symmetric counterpart of RenderHeader. // Low-level callbacks
RenderFooter(w io.Writer, ast *Node) Entity(out *bytes.Buffer, entity []byte)
NormalText(out *bytes.Buffer, text []byte)
// Header and footer
DocumentHeader(out *bytes.Buffer)
DocumentFooter(out *bytes.Buffer)
GetFlags() int
} }
// Callback functions for inline parsing. One such function is defined // Callback functions for inline parsing. One such function is defined
// for each character that triggers a response when parsing inline data. // for each character that triggers a response when parsing inline data.
type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node) type inlineParser func(p *parser, out *bytes.Buffer, data []byte, offset int) int
// Markdown is a type that holds extensions and the runtime state used by // Parser holds runtime state used by the parser.
// Parse, and the renderer. You can not use it directly, construct it with New. // This is constructed by the Markdown function.
type Markdown struct { type parser struct {
renderer Renderer r Renderer
referenceOverride ReferenceOverrideFunc refOverride ReferenceOverrideFunc
refs map[string]*reference refs map[string]*reference
inlineCallback [256]inlineParser inlineCallback [256]inlineParser
extensions Extensions flags int
nesting int nesting int
maxNesting int maxNesting int
insideLink bool insideLink bool
@ -184,17 +219,12 @@ type Markdown struct {
// presence. If a ref is also a footnote, it's stored both in refs and here // presence. If a ref is also a footnote, it's stored both in refs and here
// in notes. Slice is nil if footnotes not enabled. // in notes. Slice is nil if footnotes not enabled.
notes []*reference notes []*reference
notesRecord map[string]struct{}
doc *Node
tip *Node // = doc
oldTip *Node
lastMatchedContainer *Node // = doc
allClosed bool
} }
func (p *Markdown) getRef(refid string) (ref *reference, found bool) { func (p *parser) getRef(refid string) (ref *reference, found bool) {
if p.referenceOverride != nil { if p.refOverride != nil {
r, overridden := p.referenceOverride(refid) r, overridden := p.refOverride(refid)
if overridden { if overridden {
if r == nil { if r == nil {
return nil, false return nil, false
@ -202,7 +232,7 @@ func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
return &reference{ return &reference{
link: []byte(r.Link), link: []byte(r.Link),
title: []byte(r.Title), title: []byte(r.Title),
noteID: 0, noteId: 0,
hasBlock: false, hasBlock: false,
text: []byte(r.Text)}, true text: []byte(r.Text)}, true
} }
@ -212,34 +242,9 @@ func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
return ref, found return ref, found
} }
func (p *Markdown) finalize(block *Node) { func (p *parser) isFootnote(ref *reference) bool {
above := block.Parent _, ok := p.notesRecord[string(ref.link)]
block.open = false return ok
p.tip = above
}
func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
return p.addExistingChild(NewNode(node), offset)
}
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
for !p.tip.canContain(node.Type) {
p.finalize(p.tip)
}
p.tip.AppendChild(node)
p.tip = node
return node
}
func (p *Markdown) closeUnmatchedBlocks() {
if !p.allClosed {
for p.oldTip != p.lastMatchedContainer {
parent := p.oldTip.Parent
p.finalize(p.oldTip)
p.oldTip = parent
}
p.allClosed = true
}
} }
// //
@ -266,27 +271,102 @@ type Reference struct {
// See the documentation in Options for more details on use-case. // See the documentation in Options for more details on use-case.
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
// New constructs a Markdown processor. You can use the same With* functions as // Options represents configurable overrides and callbacks (in addition to the
// for Run() to customize parser's behavior and the renderer. // extension flag set) for configuring a Markdown parse.
func New(opts ...Option) *Markdown { type Options struct {
var p Markdown // Extensions is a flag set of bit-wise ORed extension bits. See the
for _, opt := range opts { // EXTENSION_* flags defined in this package.
opt(&p) Extensions int
// ReferenceOverride is an optional function callback that is called every
// time a reference is resolved.
//
// In Markdown, the link reference syntax can be made to resolve a link to
// a reference instead of an inline URL, in one of the following ways:
//
// * [link text][refid]
// * [refid][]
//
// Usually, the refid is defined at the bottom of the Markdown document. If
// this override function is provided, the refid is passed to the override
// function first, before consulting the defined refids at the bottom. If
// the override function indicates an override did not occur, the refids at
// the bottom will be used to fill in the link details.
ReferenceOverride ReferenceOverrideFunc
}
// MarkdownBasic is a convenience function for simple rendering.
// It processes markdown input with no extensions enabled.
func MarkdownBasic(input []byte) []byte {
// set up the HTML renderer
htmlFlags := HTML_USE_XHTML
renderer := HtmlRenderer(htmlFlags, "", "")
// set up the parser
return MarkdownOptions(input, renderer, Options{Extensions: 0})
}
// Call Markdown with most useful extensions enabled
// MarkdownCommon is a convenience function for simple rendering.
// It processes markdown input with common extensions enabled, including:
//
// * Smartypants processing with smart fractions and LaTeX dashes
//
// * Intra-word emphasis suppression
//
// * Tables
//
// * Fenced code blocks
//
// * Autolinking
//
// * Strikethrough support
//
// * Strict header parsing
//
// * Custom Header IDs
func MarkdownCommon(input []byte) []byte {
// set up the HTML renderer
renderer := HtmlRenderer(commonHtmlFlags, "", "")
return MarkdownOptions(input, renderer, Options{
Extensions: commonExtensions})
}
// Markdown is the main rendering function.
// It parses and renders a block of markdown-encoded text.
// The supplied Renderer is used to format the output, and extensions dictates
// which non-standard extensions are enabled.
//
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
// LatexRenderer, respectively.
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
return MarkdownOptions(input, renderer, Options{
Extensions: extensions})
}
// MarkdownOptions is just like Markdown but takes additional options through
// the Options struct.
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
// no point in parsing if we can't render
if renderer == nil {
return nil
} }
extensions := opts.Extensions
// fill in the render structure
p := new(parser)
p.r = renderer
p.flags = extensions
p.refOverride = opts.ReferenceOverride
p.refs = make(map[string]*reference) p.refs = make(map[string]*reference)
p.maxNesting = 16 p.maxNesting = 16
p.insideLink = false p.insideLink = false
docNode := NewNode(Document)
p.doc = docNode
p.tip = docNode
p.oldTip = docNode
p.lastMatchedContainer = docNode
p.allClosed = true
// register inline parsers // register inline parsers
p.inlineCallback[' '] = maybeLineBreak
p.inlineCallback['*'] = emphasis p.inlineCallback['*'] = emphasis
p.inlineCallback['_'] = emphasis p.inlineCallback['_'] = emphasis
if p.extensions&Strikethrough != 0 { if extensions&EXTENSION_STRIKETHROUGH != 0 {
p.inlineCallback['~'] = emphasis p.inlineCallback['~'] = emphasis
} }
p.inlineCallback['`'] = codeSpan p.inlineCallback['`'] = codeSpan
@ -295,166 +375,116 @@ func New(opts ...Option) *Markdown {
p.inlineCallback['<'] = leftAngle p.inlineCallback['<'] = leftAngle
p.inlineCallback['\\'] = escape p.inlineCallback['\\'] = escape
p.inlineCallback['&'] = entity p.inlineCallback['&'] = entity
p.inlineCallback['!'] = maybeImage
p.inlineCallback['^'] = maybeInlineFootnote if extensions&EXTENSION_AUTOLINK != 0 {
if p.extensions&Autolink != 0 { p.inlineCallback[':'] = autoLink
p.inlineCallback['h'] = maybeAutoLink
p.inlineCallback['m'] = maybeAutoLink
p.inlineCallback['f'] = maybeAutoLink
p.inlineCallback['H'] = maybeAutoLink
p.inlineCallback['M'] = maybeAutoLink
p.inlineCallback['F'] = maybeAutoLink
} }
if p.extensions&Footnotes != 0 {
if extensions&EXTENSION_FOOTNOTES != 0 {
p.notes = make([]*reference, 0) p.notes = make([]*reference, 0)
p.notesRecord = make(map[string]struct{})
} }
return &p
first := firstPass(p, input)
second := secondPass(p, first)
return second
} }
// Option customizes the Markdown processor's default behavior. // first pass:
type Option func(*Markdown) // - normalize newlines
// - extract references (outside of fenced code blocks)
// WithRenderer allows you to override the default renderer. // - expand tabs (outside of fenced code blocks)
func WithRenderer(r Renderer) Option { // - copy everything else
return func(p *Markdown) { func firstPass(p *parser, input []byte) []byte {
p.renderer = r var out bytes.Buffer
tabSize := TAB_SIZE_DEFAULT
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
tabSize = TAB_SIZE_EIGHT
} }
} beg := 0
lastFencedCodeBlockEnd := 0
// WithExtensions allows you to pick some of the many extensions provided by for beg < len(input) {
// Blackfriday. You can bitwise OR them. // Find end of this line, then process the line.
func WithExtensions(e Extensions) Option { end := beg
return func(p *Markdown) { for end < len(input) && input[end] != '\n' && input[end] != '\r' {
p.extensions = e end++
} }
}
// WithNoExtensions turns off all extensions and custom behavior. if p.flags&EXTENSION_FENCED_CODE != 0 {
func WithNoExtensions() Option { // track fenced code block boundaries to suppress tab expansion
return func(p *Markdown) { // and reference extraction inside them:
p.extensions = NoExtensions if beg >= lastFencedCodeBlockEnd {
p.renderer = NewHTMLRenderer(HTMLRendererParameters{ if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
Flags: HTMLFlagsNone, lastFencedCodeBlockEnd = beg + i
}) }
}
} }
}
// WithRefOverride sets an optional function callback that is called every // add the line body if present
// time a reference is resolved. if end > beg {
// if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
// In Markdown, the link reference syntax can be made to resolve a link to out.Write(input[beg:end])
// a reference instead of an inline URL, in one of the following ways: } else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
// beg += refEnd
// * [link text][refid] continue
// * [refid][]
//
// Usually, the refid is defined at the bottom of the Markdown document. If
// this override function is provided, the refid is passed to the override
// function first, before consulting the defined refids at the bottom. If
// the override function indicates an override did not occur, the refids at
// the bottom will be used to fill in the link details.
func WithRefOverride(o ReferenceOverrideFunc) Option {
return func(p *Markdown) {
p.referenceOverride = o
}
}
// Run is the main entry point to Blackfriday. It parses and renders a
// block of markdown-encoded text.
//
// The simplest invocation of Run takes one argument, input:
// output := Run(input)
// This will parse the input with CommonExtensions enabled and render it with
// the default HTMLRenderer (with CommonHTMLFlags).
//
// Variadic arguments opts can customize the default behavior. Since Markdown
// type does not contain exported fields, you can not use it directly. Instead,
// use the With* functions. For example, this will call the most basic
// functionality, with no extensions:
// output := Run(input, WithNoExtensions())
//
// You can use any number of With* arguments, even contradicting ones. They
// will be applied in order of appearance and the latter will override the
// former:
// output := Run(input, WithNoExtensions(), WithExtensions(exts),
// WithRenderer(yourRenderer))
func Run(input []byte, opts ...Option) []byte {
r := NewHTMLRenderer(HTMLRendererParameters{
Flags: CommonHTMLFlags,
})
optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
optList = append(optList, opts...)
parser := New(optList...)
ast := parser.Parse(input)
var buf bytes.Buffer
parser.renderer.RenderHeader(&buf, ast)
ast.Walk(func(node *Node, entering bool) WalkStatus {
return parser.renderer.RenderNode(&buf, node, entering)
})
parser.renderer.RenderFooter(&buf, ast)
return buf.Bytes()
}
// Parse is an entry point to the parsing part of Blackfriday. It takes an
// input markdown document and produces a syntax tree for its contents. This
// tree can then be rendered with a default or custom renderer, or
// analyzed/transformed by the caller to whatever non-standard needs they have.
// The return value is the root node of the syntax tree.
func (p *Markdown) Parse(input []byte) *Node {
p.block(input)
// Walk the tree and finish up some of unfinished blocks
for p.tip != nil {
p.finalize(p.tip)
}
// Walk the tree again and process inline markdown in each block
p.doc.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
p.inline(node, node.content)
node.content = nil
}
return GoToNext
})
p.parseRefsToAST()
return p.doc
}
func (p *Markdown) parseRefsToAST() {
if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
return
}
p.tip = p.doc
block := p.addBlock(List, nil)
block.IsFootnotesList = true
block.ListFlags = ListTypeOrdered
flags := ListItemBeginningOfList
// Note: this loop is intentionally explicit, not range-form. This is
// because the body of the loop will append nested footnotes to p.notes and
// we need to process those late additions. Range form would only walk over
// the fixed initial set.
for i := 0; i < len(p.notes); i++ {
ref := p.notes[i]
p.addExistingChild(ref.footnote, 0)
block := ref.footnote
block.ListFlags = flags | ListTypeOrdered
block.RefLink = ref.link
if ref.hasBlock {
flags |= ListItemContainsBlock
p.block(ref.title)
} else { } else {
p.inline(block, ref.title) expandTabs(&out, input[beg:end], tabSize)
} }
flags &^= ListItemBeginningOfList | ListItemContainsBlock
} }
above := block.Parent
finalizeList(block) if end < len(input) && input[end] == '\r' {
p.tip = above end++
block.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Heading {
p.inline(node, node.content)
node.content = nil
} }
return GoToNext if end < len(input) && input[end] == '\n' {
end++
}
out.WriteByte('\n')
beg = end
}
// empty input?
if out.Len() == 0 {
out.WriteByte('\n')
}
return out.Bytes()
}
// second pass: actual rendering
func secondPass(p *parser, input []byte) []byte {
var output bytes.Buffer
p.r.DocumentHeader(&output)
p.block(&output, input)
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
p.r.Footnotes(&output, func() bool {
flags := LIST_ITEM_BEGINNING_OF_LIST
for i := 0; i < len(p.notes); i += 1 {
ref := p.notes[i]
var buf bytes.Buffer
if ref.hasBlock {
flags |= LIST_ITEM_CONTAINS_BLOCK
p.block(&buf, ref.title)
} else {
p.inline(&buf, ref.title)
}
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
}
return true
}) })
}
p.r.DocumentFooter(&output)
if p.nesting != 0 {
panic("Nesting level did not end at zero")
}
return output.Bytes()
} }
// //
@ -486,56 +516,18 @@ func (p *Markdown) parseRefsToAST() {
// //
// are not yet supported. // are not yet supported.
// reference holds all information necessary for a reference-style links or // References are parsed and stored in this struct.
// footnotes.
//
// Consider this markdown with reference-style links:
//
// [link][ref]
//
// [ref]: /url/ "tooltip title"
//
// It will be ultimately converted to this HTML:
//
// <p><a href=\"/url/\" title=\"title\">link</a></p>
//
// And a reference structure will be populated as follows:
//
// p.refs["ref"] = &reference{
// link: "/url/",
// title: "tooltip title",
// }
//
// Alternatively, reference can contain information about a footnote. Consider
// this markdown:
//
// Text needing a footnote.[^a]
//
// [^a]: This is the note
//
// A reference structure will be populated as follows:
//
// p.refs["a"] = &reference{
// link: "a",
// title: "This is the note",
// noteID: <some positive int>,
// }
//
// TODO: As you can see, it begs for splitting into two dedicated structures
// for refs and for footnotes.
type reference struct { type reference struct {
link []byte link []byte
title []byte title []byte
noteID int // 0 if not a footnote ref noteId int // 0 if not a footnote ref
hasBlock bool hasBlock bool
footnote *Node // a link to the Item node within a list of footnotes text []byte
text []byte // only gets populated by refOverride feature with Reference.Text
} }
func (r *reference) String() string { func (r *reference) String() string {
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}", return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
r.link, r.title, r.text, r.noteID, r.hasBlock) r.link, r.title, r.text, r.noteId, r.hasBlock)
} }
// Check whether or not data starts with a reference link. // Check whether or not data starts with a reference link.
@ -543,7 +535,7 @@ func (r *reference) String() string {
// (in the render struct). // (in the render struct).
// Returns the number of bytes to skip to move past it, // Returns the number of bytes to skip to move past it,
// or zero if the first line is not a reference. // or zero if the first line is not a reference.
func isReference(p *Markdown, data []byte, tabSize int) int { func isReference(p *parser, data []byte, tabSize int) int {
// up to 3 optional leading spaces // up to 3 optional leading spaces
if len(data) < 4 { if len(data) < 4 {
return 0 return 0
@ -553,18 +545,18 @@ func isReference(p *Markdown, data []byte, tabSize int) int {
i++ i++
} }
noteID := 0 noteId := 0
// id part: anything but a newline between brackets // id part: anything but a newline between brackets
if data[i] != '[' { if data[i] != '[' {
return 0 return 0
} }
i++ i++
if p.extensions&Footnotes != 0 { if p.flags&EXTENSION_FOOTNOTES != 0 {
if i < len(data) && data[i] == '^' { if i < len(data) && data[i] == '^' {
// we can set it to anything here because the proper noteIds will // we can set it to anything here because the proper noteIds will
// be assigned later during the second pass. It just has to be != 0 // be assigned later during the second pass. It just has to be != 0
noteID = 1 noteId = 1
i++ i++
} }
} }
@ -576,11 +568,7 @@ func isReference(p *Markdown, data []byte, tabSize int) int {
return 0 return 0
} }
idEnd := i idEnd := i
// footnotes can have empty ID, like this: [^], but a reference can not be
// empty like this: []. Break early if it's not a footnote and there's no ID
if noteID == 0 && idOffset == idEnd {
return 0
}
// spacer: colon (space | tab)* newline? (space | tab)* // spacer: colon (space | tab)* newline? (space | tab)*
i++ i++
if i >= len(data) || data[i] != ':' { if i >= len(data) || data[i] != ':' {
@ -611,7 +599,7 @@ func isReference(p *Markdown, data []byte, tabSize int) int {
hasBlock bool hasBlock bool
) )
if p.extensions&Footnotes != 0 && noteID != 0 { if p.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
lineEnd = linkEnd lineEnd = linkEnd
} else { } else {
@ -624,11 +612,11 @@ func isReference(p *Markdown, data []byte, tabSize int) int {
// a valid ref has been found // a valid ref has been found
ref := &reference{ ref := &reference{
noteID: noteID, noteId: noteId,
hasBlock: hasBlock, hasBlock: hasBlock,
} }
if noteID > 0 { if noteId > 0 {
// reusing the link field for the id since footnotes don't have links // reusing the link field for the id since footnotes don't have links
ref.link = data[idOffset:idEnd] ref.link = data[idOffset:idEnd]
// if footnote, it's not really a title, it's the contained text // if footnote, it's not really a title, it's the contained text
@ -646,12 +634,15 @@ func isReference(p *Markdown, data []byte, tabSize int) int {
return lineEnd return lineEnd
} }
func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
// link: whitespace-free sequence, optionally between angle brackets // link: whitespace-free sequence, optionally between angle brackets
if data[i] == '<' { if data[i] == '<' {
i++ i++
} }
linkOffset = i linkOffset = i
if i == len(data) {
return
}
for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
i++ i++
} }
@ -714,13 +705,13 @@ func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOff
return return
} }
// The first bit of this logic is the same as Parser.listItem, but the rest // The first bit of this logic is the same as (*parser).listItem, but the rest
// is much simpler. This function simply finds the entire block and shifts it // is much simpler. This function simply finds the entire block and shifts it
// over by one tab if it is indeed a block (just returns the line if it's not). // over by one tab if it is indeed a block (just returns the line if it's not).
// blockEnd is the end of the section in the input buffer, and contents is the // blockEnd is the end of the section in the input buffer, and contents is the
// extracted text that was shifted over one tab. It will need to be rendered at // extracted text that was shifted over one tab. It will need to be rendered at
// the end of the document. // the end of the document.
func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { func scanFootnote(p *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
if i == 0 || len(data) == 0 { if i == 0 || len(data) == 0 {
return return
} }
@ -813,7 +804,17 @@ func ispunct(c byte) bool {
// Test if a character is a whitespace character. // Test if a character is a whitespace character.
func isspace(c byte) bool { 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. // Test if a character is letter.

View File

@ -1,354 +0,0 @@
package blackfriday
import (
"bytes"
"fmt"
)
// NodeType specifies a type of a single node of a syntax tree. Usually one
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
// or code block.
type NodeType int
// Constants for identifying different types of nodes. See NodeType.
const (
Document NodeType = iota
BlockQuote
List
Item
Paragraph
Heading
HorizontalRule
Emph
Strong
Del
Link
Image
Text
HTMLBlock
CodeBlock
Softbreak
Hardbreak
Code
HTMLSpan
Table
TableCell
TableHead
TableBody
TableRow
)
var nodeTypeNames = []string{
Document: "Document",
BlockQuote: "BlockQuote",
List: "List",
Item: "Item",
Paragraph: "Paragraph",
Heading: "Heading",
HorizontalRule: "HorizontalRule",
Emph: "Emph",
Strong: "Strong",
Del: "Del",
Link: "Link",
Image: "Image",
Text: "Text",
HTMLBlock: "HTMLBlock",
CodeBlock: "CodeBlock",
Softbreak: "Softbreak",
Hardbreak: "Hardbreak",
Code: "Code",
HTMLSpan: "HTMLSpan",
Table: "Table",
TableCell: "TableCell",
TableHead: "TableHead",
TableBody: "TableBody",
TableRow: "TableRow",
}
func (t NodeType) String() string {
return nodeTypeNames[t]
}
// ListData contains fields relevant to a List and Item node type.
type ListData struct {
ListFlags ListType
Tight bool // Skip <p>s around list item data if true
BulletChar byte // '*', '+' or '-' in bullet lists
Delimiter byte // '.' or ')' after the number in ordered lists
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
IsFootnotesList bool // This is a list of footnotes
}
// LinkData contains fields relevant to a Link node type.
type LinkData struct {
Destination []byte // Destination is what goes into a href
Title []byte // Title is the tooltip thing that goes in a title attribute
NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote
Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
}
// CodeBlockData contains fields relevant to a CodeBlock node type.
type CodeBlockData struct {
IsFenced bool // Specifies whether it's a fenced code block or an indented one
Info []byte // This holds the info string
FenceChar byte
FenceLength int
FenceOffset int
}
// TableCellData contains fields relevant to a TableCell node type.
type TableCellData struct {
IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute
}
// HeadingData contains fields relevant to a Heading node type.
type HeadingData struct {
Level int // This holds the heading level number
HeadingID string // This might hold heading ID, if present
IsTitleblock bool // Specifies whether it's a title block
}
// Node is a single element in the abstract syntax tree of the parsed document.
// It holds connections to the structurally neighboring nodes and, for certain
// types of nodes, additional information that might be needed when rendering.
type Node struct {
Type NodeType // Determines the type of the node
Parent *Node // Points to the parent
FirstChild *Node // Points to the first child, if any
LastChild *Node // Points to the last child, if any
Prev *Node // Previous sibling; nil if it's the first child
Next *Node // Next sibling; nil if it's the last child
Literal []byte // Text contents of the leaf nodes
HeadingData // Populated if Type is Heading
ListData // Populated if Type is List
CodeBlockData // Populated if Type is CodeBlock
LinkData // Populated if Type is Link
TableCellData // Populated if Type is TableCell
content []byte // Markdown content of the block nodes
open bool // Specifies an open block node that has not been finished to process yet
}
// NewNode allocates a node of a specified type.
func NewNode(typ NodeType) *Node {
return &Node{
Type: typ,
open: true,
}
}
func (n *Node) String() string {
ellipsis := ""
snippet := n.Literal
if len(snippet) > 16 {
snippet = snippet[:16]
ellipsis = "..."
}
return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
}
// Unlink removes node 'n' from the tree.
// It panics if the node is nil.
func (n *Node) Unlink() {
if n.Prev != nil {
n.Prev.Next = n.Next
} else if n.Parent != nil {
n.Parent.FirstChild = n.Next
}
if n.Next != nil {
n.Next.Prev = n.Prev
} else if n.Parent != nil {
n.Parent.LastChild = n.Prev
}
n.Parent = nil
n.Next = nil
n.Prev = nil
}
// AppendChild adds a node 'child' as a child of 'n'.
// It panics if either node is nil.
func (n *Node) AppendChild(child *Node) {
child.Unlink()
child.Parent = n
if n.LastChild != nil {
n.LastChild.Next = child
child.Prev = n.LastChild
n.LastChild = child
} else {
n.FirstChild = child
n.LastChild = child
}
}
// InsertBefore inserts 'sibling' immediately before 'n'.
// It panics if either node is nil.
func (n *Node) InsertBefore(sibling *Node) {
sibling.Unlink()
sibling.Prev = n.Prev
if sibling.Prev != nil {
sibling.Prev.Next = sibling
}
sibling.Next = n
n.Prev = sibling
sibling.Parent = n.Parent
if sibling.Prev == nil {
sibling.Parent.FirstChild = sibling
}
}
func (n *Node) isContainer() bool {
switch n.Type {
case Document:
fallthrough
case BlockQuote:
fallthrough
case List:
fallthrough
case Item:
fallthrough
case Paragraph:
fallthrough
case Heading:
fallthrough
case Emph:
fallthrough
case Strong:
fallthrough
case Del:
fallthrough
case Link:
fallthrough
case Image:
fallthrough
case Table:
fallthrough
case TableHead:
fallthrough
case TableBody:
fallthrough
case TableRow:
fallthrough
case TableCell:
return true
default:
return false
}
}
func (n *Node) canContain(t NodeType) bool {
if n.Type == List {
return t == Item
}
if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
return t != Item
}
if n.Type == Table {
return t == TableHead || t == TableBody
}
if n.Type == TableHead || n.Type == TableBody {
return t == TableRow
}
if n.Type == TableRow {
return t == TableCell
}
return false
}
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
// It is returned from NodeVisitor and different values allow Node.Walk to
// decide which node to go to next.
type WalkStatus int
const (
// GoToNext is the default traversal of every node.
GoToNext WalkStatus = iota
// SkipChildren tells walker to skip all children of current node.
SkipChildren
// Terminate tells walker to terminate the traversal.
Terminate
)
// NodeVisitor is a callback to be called when traversing the syntax tree.
// Called twice for every node: once with entering=true when the branch is
// first visited, then with entering=false after all the children are done.
type NodeVisitor func(node *Node, entering bool) WalkStatus
// Walk is a convenience method that instantiates a walker and starts a
// traversal of subtree rooted at n.
func (n *Node) Walk(visitor NodeVisitor) {
w := newNodeWalker(n)
for w.current != nil {
status := visitor(w.current, w.entering)
switch status {
case GoToNext:
w.next()
case SkipChildren:
w.entering = false
w.next()
case Terminate:
return
}
}
}
type nodeWalker struct {
current *Node
root *Node
entering bool
}
func newNodeWalker(root *Node) *nodeWalker {
return &nodeWalker{
current: root,
root: root,
entering: true,
}
}
func (nw *nodeWalker) next() {
if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
nw.current = nil
return
}
if nw.entering && nw.current.isContainer() {
if nw.current.FirstChild != nil {
nw.current = nw.current.FirstChild
nw.entering = true
} else {
nw.entering = false
}
} else if nw.current.Next == nil {
nw.current = nw.current.Parent
nw.entering = false
} else {
nw.current = nw.current.Next
nw.entering = true
}
}
func dump(ast *Node) {
fmt.Println(dumpString(ast))
}
func dumpR(ast *Node, depth int) string {
if ast == nil {
return ""
}
indent := bytes.Repeat([]byte("\t"), depth)
content := ast.Literal
if content == nil {
content = ast.content
}
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
for n := ast.FirstChild; n != nil; n = n.Next {
result += dumpR(n, depth+1)
}
return result
}
func dumpString(ast *Node) string {
return dumpR(ast, 0)
}

View File

@ -17,14 +17,11 @@ package blackfriday
import ( import (
"bytes" "bytes"
"io"
) )
// SPRenderer is a struct containing state of a Smartypants renderer. type smartypantsData struct {
type SPRenderer struct {
inSingleQuote bool inSingleQuote bool
inDoubleQuote bool inDoubleQuote bool
callbacks [256]smartCallback
} }
func wordBoundary(c byte) bool { func wordBoundary(c byte) bool {
@ -121,7 +118,7 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
return true return true
} }
func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 2 { if len(text) >= 2 {
t1 := tolower(text[1]) t1 := tolower(text[1])
@ -130,7 +127,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
return 1 return 1
} }
} }
@ -155,7 +152,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) { if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
return 0 return 0
} }
@ -163,7 +160,7 @@ func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text
return 0 return 0
} }
func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int { func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 { if len(text) >= 3 {
t1 := tolower(text[1]) t1 := tolower(text[1])
t2 := tolower(text[2]) t2 := tolower(text[2])
@ -188,7 +185,7 @@ func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []by
return 0 return 0
} }
func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int { func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 2 { if len(text) >= 2 {
if text[1] == '-' { if text[1] == '-' {
out.WriteString("&mdash;") out.WriteString("&mdash;")
@ -205,7 +202,7 @@ func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte
return 0 return 0
} }
func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int { func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '-' && text[2] == '-' { if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
out.WriteString("&mdash;") out.WriteString("&mdash;")
return 2 return 2
@ -219,13 +216,13 @@ func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text [
return 0 return 0
} }
func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int { func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int {
if bytes.HasPrefix(text, []byte("&quot;")) { if bytes.HasPrefix(text, []byte("&quot;")) {
nextChar := byte(0) nextChar := byte(0)
if len(text) >= 7 { if len(text) >= 7 {
nextChar = text[6] nextChar = text[6]
} }
if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) { if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
return 5 return 5
} }
} }
@ -238,18 +235,18 @@ func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text
return 0 return 0
} }
func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int { func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
var quote byte = 'd' var quote byte = 'd'
if angledQuotes { if angledQuotes {
quote = 'a' quote = 'a'
} }
return func(out *bytes.Buffer, previousChar byte, text []byte) int { return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return r.smartAmpVariant(out, previousChar, text, quote, addNBSP) return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
} }
} }
func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '.' && text[2] == '.' { if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
out.WriteString("&hellip;") out.WriteString("&hellip;")
return 2 return 2
@ -264,13 +261,13 @@ func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []by
return 0 return 0
} }
func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int { func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 2 && text[1] == '`' { if len(text) >= 2 && text[1] == '`' {
nextChar := byte(0) nextChar := byte(0)
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote, false) {
return 1 return 1
} }
} }
@ -279,7 +276,7 @@ func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []
return 0 return 0
} }
func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int { func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
// note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8) // note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8)
@ -321,7 +318,7 @@ func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, te
return 0 return 0
} }
func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int { func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
if text[0] == '1' && text[1] == '/' && text[2] == '2' { if text[0] == '1' && text[1] == '/' && text[2] == '2' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
@ -349,27 +346,27 @@ func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []by
return 0 return 0
} }
func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
nextChar := byte(0) nextChar := byte(0)
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) { if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
out.WriteString("&quot;") out.WriteString("&quot;")
} }
return 0 return 0
} }
func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return r.smartDoubleQuoteVariant(out, previousChar, text, 'd') return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
} }
func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return r.smartDoubleQuoteVariant(out, previousChar, text, 'a') return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
} }
func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int { func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
i := 0 i := 0
for i < len(text) && text[i] != '>' { for i < len(text) && text[i] != '>' {
@ -380,78 +377,54 @@ func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text [
return i return i
} }
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int type smartCallback func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int
// NewSmartypantsRenderer constructs a Smartypants renderer object. type smartypantsRenderer [256]smartCallback
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
var (
r SPRenderer
smartAmpAngled = r.smartAmp(true, false) var (
smartAmpAngledNBSP = r.smartAmp(true, true) smartAmpAngled = smartAmp(true, false)
smartAmpRegular = r.smartAmp(false, false) smartAmpAngledNBSP = smartAmp(true, true)
smartAmpRegularNBSP = r.smartAmp(false, true) smartAmpRegular = smartAmp(false, false)
smartAmpRegularNBSP = smartAmp(false, true)
)
addNBSP = flags&SmartypantsQuotesNBSP != 0 func smartypants(flags int) *smartypantsRenderer {
) r := new(smartypantsRenderer)
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
if flags&SmartypantsAngledQuotes == 0 { if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
r.callbacks['"'] = r.smartDoubleQuote r['"'] = smartDoubleQuote
if !addNBSP { if !addNBSP {
r.callbacks['&'] = smartAmpRegular r['&'] = smartAmpRegular
} else { } else {
r.callbacks['&'] = smartAmpRegularNBSP r['&'] = smartAmpRegularNBSP
} }
} else { } else {
r.callbacks['"'] = r.smartAngledDoubleQuote r['"'] = smartAngledDoubleQuote
if !addNBSP { if !addNBSP {
r.callbacks['&'] = smartAmpAngled r['&'] = smartAmpAngled
} else { } else {
r.callbacks['&'] = smartAmpAngledNBSP r['&'] = smartAmpAngledNBSP
} }
} }
r.callbacks['\''] = r.smartSingleQuote r['\''] = smartSingleQuote
r.callbacks['('] = r.smartParens r['('] = smartParens
if flags&SmartypantsDashes != 0 { if flags&HTML_SMARTYPANTS_DASHES != 0 {
if flags&SmartypantsLatexDashes == 0 { if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
r.callbacks['-'] = r.smartDash r['-'] = smartDash
} else { } else {
r.callbacks['-'] = r.smartDashLatex r['-'] = smartDashLatex
} }
} }
r.callbacks['.'] = r.smartPeriod r['.'] = smartPeriod
if flags&SmartypantsFractions == 0 { if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
r.callbacks['1'] = r.smartNumber r['1'] = smartNumber
r.callbacks['3'] = r.smartNumber r['3'] = smartNumber
} else { } else {
for ch := '1'; ch <= '9'; ch++ { for ch := '1'; ch <= '9'; ch++ {
r.callbacks[ch] = r.smartNumberGeneric r[ch] = smartNumberGeneric
} }
} }
r.callbacks['<'] = r.smartLeftAngle r['<'] = smartLeftAngle
r.callbacks['`'] = r.smartBacktick r['`'] = smartBacktick
return &r return r
}
// Process is the entry point of the Smartypants renderer.
func (r *SPRenderer) Process(w io.Writer, text []byte) {
mark := 0
for i := 0; i < len(text); i++ {
if action := r.callbacks[text[i]]; action != nil {
if i > mark {
w.Write(text[mark:i])
}
previousChar := byte(0)
if i > 0 {
previousChar = text[i-1]
}
var tmp bytes.Buffer
i += action(&tmp, previousChar, text[i:])
w.Write(tmp.Bytes())
mark = i + 1
}
}
if mark < len(text) {
w.Write(text[mark:])
}
} }

View File

@ -1,16 +0,0 @@
sudo: false
language: go
go:
- 1.x
- master
matrix:
allow_failures:
- go: master
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:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2015 Dmitri Shuralyov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,36 +0,0 @@
sanitized_anchor_name
=====================
[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name)
Package sanitized_anchor_name provides a func to create sanitized anchor names.
Its logic can be reused by multiple packages to create interoperable anchor names
and links to those anchors.
At this time, it does not try to ensure that generated anchor names
are unique, that responsibility falls on the caller.
Installation
------------
```bash
go get -u github.com/shurcooL/sanitized_anchor_name
```
Example
-------
```Go
anchorName := sanitized_anchor_name.Create("This is a header")
fmt.Println(anchorName)
// Output:
// this-is-a-header
```
License
-------
- [MIT License](LICENSE)

View File

@ -1 +0,0 @@
module github.com/shurcooL/sanitized_anchor_name

View File

@ -1,29 +0,0 @@
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
//
// Its logic can be reused by multiple packages to create interoperable anchor names
// and links to those anchors.
//
// At this time, it does not try to ensure that generated anchor names
// are unique, that responsibility falls on the caller.
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
import "unicode"
// Create returns a sanitized anchor name for the given text.
func Create(text string) string {
var anchorName []rune
var 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)
}

4
vendor/modules.txt vendored
View File

@ -145,7 +145,7 @@ github.com/pkg/errors
github.com/pmezard/go-difflib/difflib github.com/pmezard/go-difflib/difflib
# github.com/rs/xid v1.2.1 # github.com/rs/xid v1.2.1
github.com/rs/xid github.com/rs/xid
# github.com/russross/blackfriday v2.0.0+incompatible # github.com/russross/blackfriday v1.5.2
github.com/russross/blackfriday github.com/russross/blackfriday
# github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca # github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/saintfish/chardet github.com/saintfish/chardet
@ -154,8 +154,6 @@ github.com/shazow/rateio
# github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296 # github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
github.com/shazow/ssh-chat/sshd github.com/shazow/ssh-chat/sshd
github.com/shazow/ssh-chat/internal/sanitize github.com/shazow/ssh-chat/internal/sanitize
# github.com/shurcooL/sanitized_anchor_name v1.0.0
github.com/shurcooL/sanitized_anchor_name
# github.com/sirupsen/logrus v1.3.0 # github.com/sirupsen/logrus v1.3.0
github.com/sirupsen/logrus github.com/sirupsen/logrus
# github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 # github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9