aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com')
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/.gitignore2
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/Dockerfile20
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md21
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/Makefile35
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/README.md15
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/go-md2man.1.md23
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/go.mod5
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/go.sum2
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/md2man.go51
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go14
-rw-r--r--vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go336
-rw-r--r--vendor/github.com/hashicorp/go-version/LICENSE354
-rw-r--r--vendor/github.com/hashicorp/go-version/README.md66
-rw-r--r--vendor/github.com/hashicorp/go-version/constraint.go204
-rw-r--r--vendor/github.com/hashicorp/go-version/go.mod1
-rw-r--r--vendor/github.com/hashicorp/go-version/version.go384
-rw-r--r--vendor/github.com/hashicorp/go-version/version_collection.go17
-rw-r--r--vendor/github.com/russross/blackfriday/v2/.gitignore8
-rw-r--r--vendor/github.com/russross/blackfriday/v2/.travis.yml17
-rw-r--r--vendor/github.com/russross/blackfriday/v2/LICENSE.txt29
-rw-r--r--vendor/github.com/russross/blackfriday/v2/README.md335
-rw-r--r--vendor/github.com/russross/blackfriday/v2/block.go1612
-rw-r--r--vendor/github.com/russross/blackfriday/v2/doc.go46
-rw-r--r--vendor/github.com/russross/blackfriday/v2/entities.go2236
-rw-r--r--vendor/github.com/russross/blackfriday/v2/esc.go70
-rw-r--r--vendor/github.com/russross/blackfriday/v2/go.mod1
-rw-r--r--vendor/github.com/russross/blackfriday/v2/html.go952
-rw-r--r--vendor/github.com/russross/blackfriday/v2/inline.go1228
-rw-r--r--vendor/github.com/russross/blackfriday/v2/markdown.go950
-rw-r--r--vendor/github.com/russross/blackfriday/v2/node.go360
-rw-r--r--vendor/github.com/russross/blackfriday/v2/smartypants.go457
-rw-r--r--vendor/github.com/vbatts/git-validation/.gitignore2
-rw-r--r--vendor/github.com/vbatts/git-validation/.travis.yml37
-rw-r--r--vendor/github.com/vbatts/git-validation/LICENSE21
-rw-r--r--vendor/github.com/vbatts/git-validation/README.md106
-rw-r--r--vendor/github.com/vbatts/git-validation/git/commits.go192
-rw-r--r--vendor/github.com/vbatts/git-validation/go.mod8
-rw-r--r--vendor/github.com/vbatts/git-validation/go.sum15
-rw-r--r--vendor/github.com/vbatts/git-validation/main.go92
-rw-r--r--vendor/github.com/vbatts/git-validation/rules/danglingwhitespace/rule.go39
-rw-r--r--vendor/github.com/vbatts/git-validation/rules/dco/dco.go51
-rw-r--r--vendor/github.com/vbatts/git-validation/rules/messageregexp/rule.go61
-rw-r--r--vendor/github.com/vbatts/git-validation/rules/shortsubject/shortsubject.go44
-rw-r--r--vendor/github.com/vbatts/git-validation/validate/rules.go134
-rw-r--r--vendor/github.com/vbatts/git-validation/validate/runner.go109
45 files changed, 10762 insertions, 0 deletions
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/.gitignore b/vendor/github.com/cpuguy83/go-md2man/v2/.gitignore
new file mode 100644
index 000000000..30f97c3d7
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/.gitignore
@@ -0,0 +1,2 @@
+go-md2man
+bin
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/Dockerfile b/vendor/github.com/cpuguy83/go-md2man/v2/Dockerfile
new file mode 100644
index 000000000..7181c5306
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/Dockerfile
@@ -0,0 +1,20 @@
+ARG GO_VERSION=1.18
+ARG GO_IMAGE=golang:${GO_VERSION}
+
+FROM --platform=$BUILDPLATFORM $GO_IMAGE AS build
+COPY . /go/src/github.com/cpuguy83/go-md2man
+WORKDIR /go/src/github.com/cpuguy83/go-md2man
+ARG TARGETOS
+ARG TARGETARCH
+ARG TARGETVARIANT
+RUN \
+ export GOOS="${TARGETOS}"; \
+ export GOARCH="${TARGETARCH}"; \
+ if [ "${TARGETARCH}" = "arm" ] && [ "${TARGETVARIANT}" ]; then \
+ export GOARM="${TARGETVARIANT#v}"; \
+ fi; \
+ CGO_ENABLED=0 go build
+
+FROM scratch
+COPY --from=build /go/src/github.com/cpuguy83/go-md2man/go-md2man /go-md2man
+ENTRYPOINT ["/go-md2man"]
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md b/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md
new file mode 100644
index 000000000..1cade6cef
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Brian Goff
+
+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.
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/Makefile b/vendor/github.com/cpuguy83/go-md2man/v2/Makefile
new file mode 100644
index 000000000..437fc9997
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/Makefile
@@ -0,0 +1,35 @@
+GO111MODULE ?= on
+LINTER_BIN ?= golangci-lint
+
+export GO111MODULE
+
+.PHONY:
+build: bin/go-md2man
+
+.PHONY: clean
+clean:
+ @rm -rf bin/*
+
+.PHONY: test
+test:
+ @go test $(TEST_FLAGS) ./...
+
+bin/go-md2man: actual_build_flags := $(BUILD_FLAGS) -o bin/go-md2man
+bin/go-md2man: bin
+ @CGO_ENABLED=0 go build $(actual_build_flags)
+
+bin:
+ @mkdir ./bin
+
+.PHONY: mod
+mod:
+ @go mod tidy
+
+.PHONY: check-mod
+check-mod: # verifies that module changes for go.mod and go.sum are checked in
+ @hack/ci/check_mods.sh
+
+.PHONY: vendor
+vendor: mod
+ @go mod vendor -v
+
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/README.md b/vendor/github.com/cpuguy83/go-md2man/v2/README.md
new file mode 100644
index 000000000..0e30d3414
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/README.md
@@ -0,0 +1,15 @@
+go-md2man
+=========
+
+Converts markdown into roff (man pages).
+
+Uses blackfriday to process markdown into man pages.
+
+### Usage
+
+./md2man -in /path/to/markdownfile.md -out /manfile/output/path
+
+### How to contribute
+
+We use go modules to manage dependencies.
+As such you must be using at lest go1.11.
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/go-md2man.1.md b/vendor/github.com/cpuguy83/go-md2man/v2/go-md2man.1.md
new file mode 100644
index 000000000..16d1133aa
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/go-md2man.1.md
@@ -0,0 +1,23 @@
+go-md2man 1 "January 2015" go-md2man "User Manual"
+==================================================
+
+# NAME
+ go-md2man - Convert markdown files into manpages
+
+# SYNOPSIS
+ go-md2man -in=[/path/to/md/file] -out=[/path/to/output]
+
+# Description
+ go-md2man converts standard markdown formatted documents into manpages. It is
+ written purely in Go so as to reduce dependencies on 3rd party libs.
+
+ By default, the input is stdin and the output is stdout.
+
+# Example
+ Convert the markdown file "go-md2man.1.md" into a manpage.
+
+ go-md2man -in=go-md2man.1.md -out=go-md2man.1.out
+
+# HISTORY
+ January 2015, Originally compiled by Brian Goff( cpuguy83@gmail.com )
+
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/go.mod b/vendor/github.com/cpuguy83/go-md2man/v2/go.mod
new file mode 100644
index 000000000..0bc888da0
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/go.mod
@@ -0,0 +1,5 @@
+module github.com/cpuguy83/go-md2man/v2
+
+go 1.11
+
+require github.com/russross/blackfriday/v2 v2.1.0
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/go.sum b/vendor/github.com/cpuguy83/go-md2man/v2/go.sum
new file mode 100644
index 000000000..502a072c0
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/go.sum
@@ -0,0 +1,2 @@
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man.go
new file mode 100644
index 000000000..6078864a3
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man.go
@@ -0,0 +1,51 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "io/ioutil"
+ "os"
+
+ "github.com/cpuguy83/go-md2man/v2/md2man"
+)
+
+var inFilePath = flag.String("in", "", "Path to file to be processed (default: stdin)")
+var outFilePath = flag.String("out", "", "Path to output processed file (default: stdout)")
+
+func main() {
+ var err error
+ flag.Parse()
+
+ inFile := os.Stdin
+ if *inFilePath != "" {
+ inFile, err = os.Open(*inFilePath)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+ defer inFile.Close() // nolint: errcheck
+
+ doc, err := ioutil.ReadAll(inFile)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+
+ out := md2man.Render(doc)
+
+ outFile := os.Stdout
+ if *outFilePath != "" {
+ outFile, err = os.Create(*outFilePath)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ defer outFile.Close() // nolint: errcheck
+ }
+ _, err = outFile.Write(out)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
new file mode 100644
index 000000000..b48005673
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go
@@ -0,0 +1,14 @@
+package md2man
+
+import (
+ "github.com/russross/blackfriday/v2"
+)
+
+// Render converts a markdown document into a roff formatted document.
+func Render(doc []byte) []byte {
+ renderer := NewRoffRenderer()
+
+ return blackfriday.Run(doc,
+ []blackfriday.Option{blackfriday.WithRenderer(renderer),
+ blackfriday.WithExtensions(renderer.GetExtensions())}...)
+}
diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
new file mode 100644
index 000000000..be2b34360
--- /dev/null
+++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go
@@ -0,0 +1,336 @@
+package md2man
+
+import (
+ "fmt"
+ "io"
+ "os"
+ "strings"
+
+ "github.com/russross/blackfriday/v2"
+)
+
+// roffRenderer implements the blackfriday.Renderer interface for creating
+// roff format (manpages) from markdown text
+type roffRenderer struct {
+ extensions blackfriday.Extensions
+ listCounters []int
+ firstHeader bool
+ firstDD bool
+ listDepth int
+}
+
+const (
+ titleHeader = ".TH "
+ topLevelHeader = "\n\n.SH "
+ secondLevelHdr = "\n.SH "
+ otherHeader = "\n.SS "
+ crTag = "\n"
+ emphTag = "\\fI"
+ emphCloseTag = "\\fP"
+ strongTag = "\\fB"
+ strongCloseTag = "\\fP"
+ breakTag = "\n.br\n"
+ paraTag = "\n.PP\n"
+ hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
+ linkTag = "\n\\[la]"
+ linkCloseTag = "\\[ra]"
+ codespanTag = "\\fB\\fC"
+ codespanCloseTag = "\\fR"
+ codeTag = "\n.PP\n.RS\n\n.nf\n"
+ codeCloseTag = "\n.fi\n.RE\n"
+ quoteTag = "\n.PP\n.RS\n"
+ quoteCloseTag = "\n.RE\n"
+ listTag = "\n.RS\n"
+ listCloseTag = "\n.RE\n"
+ dtTag = "\n.TP\n"
+ dd2Tag = "\n"
+ tableStart = "\n.TS\nallbox;\n"
+ tableEnd = ".TE\n"
+ tableCellStart = "T{\n"
+ tableCellEnd = "\nT}\n"
+)
+
+// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
+// from markdown
+func NewRoffRenderer() *roffRenderer { // nolint: golint
+ var extensions blackfriday.Extensions
+
+ extensions |= blackfriday.NoIntraEmphasis
+ extensions |= blackfriday.Tables
+ extensions |= blackfriday.FencedCode
+ extensions |= blackfriday.SpaceHeadings
+ extensions |= blackfriday.Footnotes
+ extensions |= blackfriday.Titleblock
+ extensions |= blackfriday.DefinitionLists
+ return &roffRenderer{
+ extensions: extensions,
+ }
+}
+
+// GetExtensions returns the list of extensions used by this renderer implementation
+func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
+ return r.extensions
+}
+
+// RenderHeader handles outputting the header at document start
+func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
+ // disable hyphenation
+ out(w, ".nh\n")
+}
+
+// RenderFooter handles outputting the footer at the document end; the roff
+// renderer has no footer information
+func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
+}
+
+// RenderNode is called for each node in a markdown document; based on the node
+// type the equivalent roff output is sent to the writer
+func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+
+ var walkAction = blackfriday.GoToNext
+
+ switch node.Type {
+ case blackfriday.Text:
+ escapeSpecialChars(w, node.Literal)
+ case blackfriday.Softbreak:
+ out(w, crTag)
+ case blackfriday.Hardbreak:
+ out(w, breakTag)
+ case blackfriday.Emph:
+ if entering {
+ out(w, emphTag)
+ } else {
+ out(w, emphCloseTag)
+ }
+ case blackfriday.Strong:
+ if entering {
+ out(w, strongTag)
+ } else {
+ out(w, strongCloseTag)
+ }
+ case blackfriday.Link:
+ if !entering {
+ out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag)
+ }
+ case blackfriday.Image:
+ // ignore images
+ walkAction = blackfriday.SkipChildren
+ case blackfriday.Code:
+ out(w, codespanTag)
+ escapeSpecialChars(w, node.Literal)
+ out(w, codespanCloseTag)
+ case blackfriday.Document:
+ break
+ case blackfriday.Paragraph:
+ // roff .PP markers break lists
+ if r.listDepth > 0 {
+ return blackfriday.GoToNext
+ }
+ if entering {
+ out(w, paraTag)
+ } else {
+ out(w, crTag)
+ }
+ case blackfriday.BlockQuote:
+ if entering {
+ out(w, quoteTag)
+ } else {
+ out(w, quoteCloseTag)
+ }
+ case blackfriday.Heading:
+ r.handleHeading(w, node, entering)
+ case blackfriday.HorizontalRule:
+ out(w, hruleTag)
+ case blackfriday.List:
+ r.handleList(w, node, entering)
+ case blackfriday.Item:
+ r.handleItem(w, node, entering)
+ case blackfriday.CodeBlock:
+ out(w, codeTag)
+ escapeSpecialChars(w, node.Literal)
+ out(w, codeCloseTag)
+ case blackfriday.Table:
+ r.handleTable(w, node, entering)
+ case blackfriday.TableHead:
+ case blackfriday.TableBody:
+ case blackfriday.TableRow:
+ // no action as cell entries do all the nroff formatting
+ return blackfriday.GoToNext
+ case blackfriday.TableCell:
+ r.handleTableCell(w, node, entering)
+ case blackfriday.HTMLSpan:
+ // ignore other HTML tags
+ default:
+ fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
+ }
+ return walkAction
+}
+
+func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
+ if entering {
+ switch node.Level {
+ case 1:
+ if !r.firstHeader {
+ out(w, titleHeader)
+ r.firstHeader = true
+ break
+ }
+ out(w, topLevelHeader)
+ case 2:
+ out(w, secondLevelHdr)
+ default:
+ out(w, otherHeader)
+ }
+ }
+}
+
+func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
+ openTag := listTag
+ closeTag := listCloseTag
+ if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
+ // tags for definition lists handled within Item node
+ openTag = ""
+ closeTag = ""
+ }
+ if entering {
+ r.listDepth++
+ if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
+ r.listCounters = append(r.listCounters, 1)
+ }
+ out(w, openTag)
+ } else {
+ if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
+ r.listCounters = r.listCounters[:len(r.listCounters)-1]
+ }
+ out(w, closeTag)
+ r.listDepth--
+ }
+}
+
+func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
+ if entering {
+ if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
+ out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
+ r.listCounters[len(r.listCounters)-1]++
+ } else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
+ // DT (definition term): line just before DD (see below).
+ out(w, dtTag)
+ r.firstDD = true
+ } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
+ // DD (definition description): line that starts with ": ".
+ //
+ // We have to distinguish between the first DD and the
+ // subsequent ones, as there should be no vertical
+ // whitespace between the DT and the first DD.
+ if r.firstDD {
+ r.firstDD = false
+ } else {
+ out(w, dd2Tag)
+ }
+ } else {
+ out(w, ".IP \\(bu 2\n")
+ }
+ } else {
+ out(w, "\n")
+ }
+}
+
+func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
+ if entering {
+ out(w, tableStart)
+ // call walker to count cells (and rows?) so format section can be produced
+ columns := countColumns(node)
+ out(w, strings.Repeat("l ", columns)+"\n")
+ out(w, strings.Repeat("l ", columns)+".\n")
+ } else {
+ out(w, tableEnd)
+ }
+}
+
+func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
+ if entering {
+ var start string
+ if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
+ start = "\t"
+ }
+ if node.IsHeader {
+ start += codespanTag
+ } else if nodeLiteralSize(node) > 30 {
+ start += tableCellStart
+ }
+ out(w, start)
+ } else {
+ var end string
+ if node.IsHeader {
+ end = codespanCloseTag
+ } else if nodeLiteralSize(node) > 30 {
+ end = tableCellEnd
+ }
+ if node.Next == nil && end != tableCellEnd {
+ // Last cell: need to carriage return if we are at the end of the
+ // header row and content isn't wrapped in a "tablecell"
+ end += crTag
+ }
+ out(w, end)
+ }
+}
+
+func nodeLiteralSize(node *blackfriday.Node) int {
+ total := 0
+ for n := node.FirstChild; n != nil; n = n.FirstChild {
+ total += len(n.Literal)
+ }
+ return total
+}
+
+// because roff format requires knowing the column count before outputting any table
+// data we need to walk a table tree and count the columns
+func countColumns(node *blackfriday.Node) int {
+ var columns int
+
+ node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
+ switch node.Type {
+ case blackfriday.TableRow:
+ if !entering {
+ return blackfriday.Terminate
+ }
+ case blackfriday.TableCell:
+ if entering {
+ columns++
+ }
+ default:
+ }
+ return blackfriday.GoToNext
+ })
+ return columns
+}
+
+func out(w io.Writer, output string) {
+ io.WriteString(w, output) // nolint: errcheck
+}
+
+func escapeSpecialChars(w io.Writer, text []byte) {
+ for i := 0; i < len(text); i++ {
+ // escape initial apostrophe or period
+ if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
+ out(w, "\\&")
+ }
+
+ // directly copy normal characters
+ org := i
+
+ for i < len(text) && text[i] != '\\' {
+ i++
+ }
+ if i > org {
+ w.Write(text[org:i]) // nolint: errcheck
+ }
+
+ // escape a character
+ if i >= len(text) {
+ break
+ }
+
+ w.Write([]byte{'\\', text[i]}) // nolint: errcheck
+ }
+}
diff --git a/vendor/github.com/hashicorp/go-version/LICENSE b/vendor/github.com/hashicorp/go-version/LICENSE
new file mode 100644
index 000000000..c33dcc7c9
--- /dev/null
+++ b/vendor/github.com/hashicorp/go-version/LICENSE
@@ -0,0 +1,354 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. “Contributor”
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. “Contributor Version”
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor’s Contribution.
+
+1.3. “Contribution”
+
+ means Covered Software of a particular Contributor.
+
+1.4. “Covered Software”
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. “Incompatible With Secondary Licenses”
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of version
+ 1.1 or earlier of the License, but not also under the terms of a
+ Secondary License.
+
+1.6. “Executable Form”
+
+ means any form of the work other than Source Code Form.
+
+1.7. “Larger Work”
+
+ means a work that combines Covered Software with other material, in a separate
+ file or files, that is not Covered Software.
+
+1.8. “License”
+
+ means this document.
+
+1.9. “Licensable”
+
+ means having the right to grant, to the maximum extent possible, whether at the
+ time of the initial grant or subsequently, any and all of the rights conveyed by
+ this License.
+
+1.10. “Modifications”
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to, deletion
+ from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. “Patent Claims” of a Contributor
+
+ means any patent claim(s), including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by such Contributor that
+ would be infringed, but for the grant of the License, by the making,
+ using, selling, offering for sale, having made, import, or transfer of
+ either its Contributions or its Contributor Version.
+
+1.12. “Secondary License”
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. “Source Code Form”
+
+ means the form of the work preferred for making modifications.
+
+1.14. “You” (or “Your”)
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, “You” includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, “control” means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or as
+ part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its Contributions
+ or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution become
+ effective for each Contribution on the date the Contributor first distributes
+ such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under this
+ License. No additional rights or licenses will be implied from the distribution
+ or licensing of Covered Software under this License. Notwithstanding Section
+ 2.1(b) above, no patent license is granted by a Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party’s
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of its
+ Contributions.
+
+ This License does not grant any rights in the trademarks, service marks, or
+ logos of any Contributor (except as may be necessary to comply with the
+ notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this License
+ (see Section 10.2) or under the terms of a Secondary License (if permitted
+ under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its Contributions
+ are its original creation(s) or it has sufficient rights to grant the
+ rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under applicable
+ copyright doctrines of fair use, fair dealing, or other equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under the
+ terms of this License. You must inform recipients that the Source Code Form
+ of the Covered Software is governed by the terms of this License, and how
+ they can obtain a copy of this License. You may not attempt to alter or
+ restrict the recipients’ rights in the Source Code Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this License,
+ or sublicense it under different terms, provided that the license for
+ the Executable Form does not attempt to limit or alter the recipients’
+ rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for the
+ Covered Software. If the Larger Work is a combination of Covered Software
+ with a work governed by one or more Secondary Licenses, and the Covered
+ Software is not Incompatible With Secondary Licenses, this License permits
+ You to additionally distribute such Covered Software under the terms of
+ such Secondary License(s), so that the recipient of the Larger Work may, at
+ their option, further distribute the Covered Software under the terms of
+ either this License or such Secondary License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices (including
+ copyright notices, patent notices, disclaimers of warranty, or limitations
+ of liability) contained within the Source Code Form of the Covered
+ Software, except that You may alter any license notices to the extent
+ required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on behalf
+ of any Contributor. You must make it absolutely clear that any such
+ warranty, support, indemnity, or liability obligation is offered by You
+ alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute, judicial
+ order, or regulation then You must: (a) comply with the terms of this License
+ to the maximum extent possible; and (b) describe the limitations and the code
+ they affect. Such description must be placed in a text file included with all
+ distributions of the Covered Software under this License. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing basis,
+ if such Contributor fails to notify You of the non-compliance by some
+ reasonable means prior to 60 days after You have come back into compliance.
+ Moreover, Your grants from a particular Contributor are reinstated on an
+ ongoing basis if such Contributor notifies You of the non-compliance by
+ some reasonable means, this is the first time You have received notice of
+ non-compliance with this License from such Contributor, and You become
+ compliant prior to 30 days after Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions, counter-claims,
+ and cross-claims) alleging that a Contributor Version directly or
+ indirectly infringes any patent, then the rights granted to You by any and
+ all Contributors for the Covered Software under Section 2.1 of this License
+ shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an “as is” basis, without
+ warranty of any kind, either expressed, implied, or statutory, including,
+ without limitation, warranties that the Covered Software is free of defects,
+ merchantable, fit for a particular purpose or non-infringing. The entire
+ risk as to the quality and performance of the Covered Software is with You.
+ Should any Covered Software prove defective in any respect, You (not any
+ Contributor) assume the cost of any necessary servicing, repair, or
+ correction. This disclaimer of warranty constitutes an essential part of this
+ License. No use of any Covered Software is authorized under this License
+ except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from such
+ party’s negligence to the extent applicable law prohibits such limitation.
+ Some jurisdictions do not allow the exclusion or limitation of incidental or
+ consequential damages, so this exclusion and limitation may not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts of
+ a jurisdiction where the defendant maintains its principal place of business
+ and such litigation shall be governed by laws of that jurisdiction, without
+ reference to its conflict-of-law provisions. Nothing in this Section shall
+ prevent a party’s ability to bring cross-claims or counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject matter
+ hereof. If any provision of this License is held to be unenforceable, such
+ provision shall be reformed only to the extent necessary to make it
+ enforceable. Any law or regulation which provides that the language of a
+ contract shall be construed against the drafter shall not be used to construe
+ this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version of
+ the License under which You originally received the Covered Software, or
+ under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a modified
+ version of this License if you rename the license and remove any
+ references to the name of the license steward (except to note that such
+ modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
+ If You choose to distribute Source Code Form that is Incompatible With
+ Secondary Licenses under the terms of this version of the License, the
+ notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the
+ terms of the Mozilla Public License, v.
+ 2.0. If a copy of the MPL was not
+ distributed with this file, You can
+ obtain one at
+ http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file, then
+You may include the notice in a location (such as a LICENSE file in a relevant
+directory) where a recipient would be likely to look for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - “Incompatible With Secondary Licenses” Notice
+
+ This Source Code Form is “Incompatible
+ With Secondary Licenses”, as defined by
+ the Mozilla Public License, v. 2.0.
+
diff --git a/vendor/github.com/hashicorp/go-version/README.md b/vendor/github.com/hashicorp/go-version/README.md
new file mode 100644
index 000000000..851a337be
--- /dev/null
+++ b/vendor/github.com/hashicorp/go-version/README.md
@@ -0,0 +1,66 @@
+# Versioning Library for Go
+[![Build Status](https://circleci.com/gh/hashicorp/go-version/tree/master.svg?style=svg)](https://circleci.com/gh/hashicorp/go-version/tree/master)
+[![GoDoc](https://godoc.org/github.com/hashicorp/go-version?status.svg)](https://godoc.org/github.com/hashicorp/go-version)
+
+go-version is a library for parsing versions and version constraints,
+and verifying versions against a set of constraints. go-version
+can sort a collection of versions properly, handles prerelease/beta
+versions, can increment versions, etc.
+
+Versions used with go-version must follow [SemVer](http://semver.org/).
+
+## Installation and Usage
+
+Package documentation can be found on
+[GoDoc](http://godoc.org/github.com/hashicorp/go-version).
+
+Installation can be done with a normal `go get`:
+
+```
+$ go get github.com/hashicorp/go-version
+```
+
+#### Version Parsing and Comparison
+
+```go
+v1, err := version.NewVersion("1.2")
+v2, err := version.NewVersion("1.5+metadata")
+
+// Comparison example. There is also GreaterThan, Equal, and just
+// a simple Compare that returns an int allowing easy >=, <=, etc.
+if v1.LessThan(v2) {
+ fmt.Printf("%s is less than %s", v1, v2)
+}
+```
+
+#### Version Constraints
+
+```go
+v1, err := version.NewVersion("1.2")
+
+// Constraints example.
+constraints, err := version.NewConstraint(">= 1.0, < 1.4")
+if constraints.Check(v1) {
+ fmt.Printf("%s satisfies constraints %s", v1, constraints)
+}
+```
+
+#### Version Sorting
+
+```go
+versionsRaw := []string{"1.1", "0.7.1", "1.4-beta", "1.4", "2"}
+versions := make([]*version.Version, len(versionsRaw))
+for i, raw := range versionsRaw {
+ v, _ := version.NewVersion(raw)
+ versions[i] = v
+}
+
+// After this, the versions are properly sorted
+sort.Sort(version.Collection(versions))
+```
+
+## Issues and Contributing
+
+If you find an issue with this library, please report an issue. If you'd
+like, we welcome any contributions. Fork this library and submit a pull
+request.
diff --git a/vendor/github.com/hashicorp/go-version/constraint.go b/vendor/github.com/hashicorp/go-version/constraint.go
new file mode 100644
index 000000000..d05575961
--- /dev/null
+++ b/vendor/github.com/hashicorp/go-version/constraint.go
@@ -0,0 +1,204 @@
+package version
+
+import (
+ "fmt"
+ "reflect"
+ "regexp"
+ "strings"
+)
+
+// Constraint represents a single constraint for a version, such as
+// ">= 1.0".
+type Constraint struct {
+ f constraintFunc
+ check *Version
+ original string
+}
+
+// Constraints is a slice of constraints. We make a custom type so that
+// we can add methods to it.
+type Constraints []*Constraint
+
+type constraintFunc func(v, c *Version) bool
+
+var constraintOperators map[string]constraintFunc
+
+var constraintRegexp *regexp.Regexp
+
+func init() {
+ constraintOperators = map[string]constraintFunc{
+ "": constraintEqual,
+ "=": constraintEqual,
+ "!=": constraintNotEqual,
+ ">": constraintGreaterThan,
+ "<": constraintLessThan,
+ ">=": constraintGreaterThanEqual,
+ "<=": constraintLessThanEqual,
+ "~>": constraintPessimistic,
+ }
+
+ ops := make([]string, 0, len(constraintOperators))
+ for k := range constraintOperators {
+ ops = append(ops, regexp.QuoteMeta(k))
+ }
+
+ constraintRegexp = regexp.MustCompile(fmt.Sprintf(
+ `^\s*(%s)\s*(%s)\s*$`,
+ strings.Join(ops, "|"),
+ VersionRegexpRaw))
+}
+
+// NewConstraint will parse one or more constraints from the given
+// constraint string. The string must be a comma-separated list of
+// constraints.
+func NewConstraint(v string) (Constraints, error) {
+ vs := strings.Split(v, ",")
+ result := make([]*Constraint, len(vs))
+ for i, single := range vs {
+ c, err := parseSingle(single)
+ if err != nil {
+ return nil, err
+ }
+
+ result[i] = c
+ }
+
+ return Constraints(result), nil
+}
+
+// Check tests if a version satisfies all the constraints.
+func (cs Constraints) Check(v *Version) bool {
+ for _, c := range cs {
+ if !c.Check(v) {
+ return false
+ }
+ }
+
+ return true
+}
+
+// Returns the string format of the constraints
+func (cs Constraints) String() string {
+ csStr := make([]string, len(cs))
+ for i, c := range cs {
+ csStr[i] = c.String()
+ }
+
+ return strings.Join(csStr, ",")
+}
+
+// Check tests if a constraint is validated by the given version.
+func (c *Constraint) Check(v *Version) bool {
+ return c.f(v, c.check)
+}
+
+func (c *Constraint) String() string {
+ return c.original
+}
+
+func parseSingle(v string) (*Constraint, error) {
+ matches := constraintRegexp.FindStringSubmatch(v)
+ if matches == nil {
+ return nil, fmt.Errorf("Malformed constraint: %s", v)
+ }
+
+ check, err := NewVersion(matches[2])
+ if err != nil {
+ return nil, err
+ }
+
+ return &Constraint{
+ f: constraintOperators[matches[1]],
+ check: check,
+ original: v,
+ }, nil
+}
+
+func prereleaseCheck(v, c *Version) bool {
+ switch vPre, cPre := v.Prerelease() != "", c.Prerelease() != ""; {
+ case cPre && vPre:
+ // A constraint with a pre-release can only match a pre-release version
+ // with the same base segments.
+ return reflect.DeepEqual(c.Segments64(), v.Segments64())
+
+ case !cPre && vPre:
+ // A constraint without a pre-release can only match a version without a
+ // pre-release.
+ return false
+
+ case cPre && !vPre:
+ // OK, except with the pessimistic operator
+ case !cPre && !vPre:
+ // OK
+ }
+ return true
+}
+
+//-------------------------------------------------------------------
+// Constraint functions
+//-------------------------------------------------------------------
+
+func constraintEqual(v, c *Version) bool {
+ return v.Equal(c)
+}
+
+func constraintNotEqual(v, c *Version) bool {
+ return !v.Equal(c)
+}
+
+func constraintGreaterThan(v, c *Version) bool {
+ return prereleaseCheck(v, c) && v.Compare(c) == 1
+}
+
+func constraintLessThan(v, c *Version) bool {
+ return prereleaseCheck(v, c) && v.Compare(c) == -1
+}
+
+func constraintGreaterThanEqual(v, c *Version) bool {
+ return prereleaseCheck(v, c) && v.Compare(c) >= 0
+}
+
+func constraintLessThanEqual(v, c *Version) bool {
+ return prereleaseCheck(v, c) && v.Compare(c) <= 0
+}
+
+func constraintPessimistic(v, c *Version) bool {
+ // Using a pessimistic constraint with a pre-release, restricts versions to pre-releases
+ if !prereleaseCheck(v, c) || (c.Prerelease() != "" && v.Prerelease() == "") {
+ return false
+ }
+
+ // If the version being checked is naturally less than the constraint, then there
+ // is no way for the version to be valid against the constraint
+ if v.LessThan(c) {
+ return false
+ }
+ // We'll use this more than once, so grab the length now so it's a little cleaner
+ // to write the later checks
+ cs := len(c.segments)
+
+ // If the version being checked has less specificity than the constraint, then there
+ // is no way for the version to be valid against the constraint
+ if cs > len(v.segments) {
+ return false
+ }
+
+ // Check the segments in the constraint against those in the version. If the version
+ // being checked, at any point, does not have the same values in each index of the
+ // constraints segments, then it cannot be valid against the constraint.
+ for i := 0; i < c.si-1; i++ {
+ if v.segments[i] != c.segments[i] {
+ return false
+ }
+ }
+
+ // Check the last part of the segment in the constraint. If the version segment at
+ // this index is less than the constraints segment at this index, then it cannot
+ // be valid against the constraint
+ if c.segments[cs-1] > v.segments[cs-1] {
+ return false
+ }
+
+ // If nothing has rejected the version by now, it's valid
+ return true
+}
diff --git a/vendor/github.com/hashicorp/go-version/go.mod b/vendor/github.com/hashicorp/go-version/go.mod
new file mode 100644
index 000000000..f5285555f
--- /dev/null
+++ b/vendor/github.com/hashicorp/go-version/go.mod
@@ -0,0 +1 @@
+module github.com/hashicorp/go-version
diff --git a/vendor/github.com/hashicorp/go-version/version.go b/vendor/github.com/hashicorp/go-version/version.go
new file mode 100644
index 000000000..09703e8e6
--- /dev/null
+++ b/vendor/github.com/hashicorp/go-version/version.go
@@ -0,0 +1,384 @@
+package version
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+// The compiled regular expression used to test the validity of a version.
+var (
+ versionRegexp *regexp.Regexp
+ semverRegexp *regexp.Regexp
+)
+
+// The raw regular expression string used for testing the validity
+// of a version.
+const (
+ VersionRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
+ `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-?([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
+ `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
+ `?`
+
+ // SemverRegexpRaw requires a separator between version and prerelease
+ SemverRegexpRaw string = `v?([0-9]+(\.[0-9]+)*?)` +
+ `(-([0-9]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)|(-([A-Za-z\-~]+[0-9A-Za-z\-~]*(\.[0-9A-Za-z\-~]+)*)))?` +
+ `(\+([0-9A-Za-z\-~]+(\.[0-9A-Za-z\-~]+)*))?` +
+ `?`
+)
+
+// Version represents a single version.
+type Version struct {
+ metadata string
+ pre string
+ segments []int64
+ si int
+ original string
+}
+
+func init() {
+ versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
+ semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$")
+}
+
+// NewVersion parses the given version and returns a new
+// Version.
+func NewVersion(v string) (*Version, error) {
+ return newVersion(v, versionRegexp)
+}
+
+// NewSemver parses the given version and returns a new
+// Version that adheres strictly to SemVer specs
+// https://semver.org/
+func NewSemver(v string) (*Version, error) {
+ return newVersion(v, semverRegexp)
+}
+
+func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
+ matches := pattern.FindStringSubmatch(v)
+ if matches == nil {
+ return nil, fmt.Errorf("Malformed version: %s", v)
+ }
+ segmentsStr := strings.Split(matches[1], ".")
+ segments := make([]int64, len(segmentsStr))
+ si := 0
+ for i, str := range segmentsStr {
+ val, err := strconv.ParseInt(str, 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf(
+ "Error parsing version: %s", err)
+ }
+
+ segments[i] = int64(val)
+ si++
+ }
+
+ // Even though we could support more than three segments, if we
+ // got less than three, pad it with 0s. This is to cover the basic
+ // default usecase of semver, which is MAJOR.MINOR.PATCH at the minimum
+ for i := len(segments); i < 3; i++ {
+ segments = append(segments, 0)
+ }
+
+ pre := matches[7]
+ if pre == "" {
+ pre = matches[4]
+ }
+
+ return &Version{
+ metadata: matches[10],
+ pre: pre,
+ segments: segments,
+ si: si,
+ original: v,
+ }, nil
+}
+
+// Must is a helper that wraps a call to a function returning (*Version, error)
+// and panics if error is non-nil.
+func Must(v *Version, err error) *Version {
+ if err != nil {
+ panic(err)
+ }
+
+ return v
+}
+
+// Compare compares this version to another version. This
+// returns -1, 0, or 1 if this version is smaller, equal,
+// or larger than the other version, respectively.
+//
+// If you want boolean results, use the LessThan, Equal,
+// GreaterThan, GreaterThanOrEqual or LessThanOrEqual methods.
+func (v *Version) Compare(other *Version) int {
+ // A quick, efficient equality check
+ if v.String() == other.String() {
+ return 0
+ }
+
+ segmentsSelf := v.Segments64()
+ segmentsOther := other.Segments64()
+
+ // If the segments are the same, we must compare on prerelease info
+ if reflect.DeepEqual(segmentsSelf, segmentsOther) {
+ preSelf := v.Prerelease()
+ preOther := other.Prerelease()
+ if preSelf == "" && preOther == "" {
+ return 0
+ }
+ if preSelf == "" {
+ return 1
+ }
+ if preOther == "" {
+ return -1
+ }
+
+ return comparePrereleases(preSelf, preOther)
+ }
+
+ // Get the highest specificity (hS), or if they're equal, just use segmentSelf length
+ lenSelf := len(segmentsSelf)
+ lenOther := len(segmentsOther)
+ hS := lenSelf
+ if lenSelf < lenOther {
+ hS = lenOther
+ }
+ // Compare the segments
+ // Because a constraint could have more/less specificity than the version it's
+ // checking, we need to account for a lopsided or jagged comparison
+ for i := 0; i < hS; i++ {
+ if i > lenSelf-1 {
+ // This means Self had the lower specificity
+ // Check to see if the remaining segments in Other are all zeros
+ if !allZero(segmentsOther[i:]) {
+ // if not, it means that Other has to be greater than Self
+ return -1
+ }
+ break
+ } else if i > lenOther-1 {
+ // this means Other had the lower specificity
+ // Check to see if the remaining segments in Self are all zeros -
+ if !allZero(segmentsSelf[i:]) {
+ //if not, it means that Self has to be greater than Other
+ return 1
+ }
+ break
+ }
+ lhs := segmentsSelf[i]
+ rhs := segmentsOther[i]
+ if lhs == rhs {
+ continue
+ } else if lhs < rhs {
+ return -1
+ }
+ // Otherwis, rhs was > lhs, they're not equal
+ return 1
+ }
+
+ // if we got this far, they're equal
+ return 0
+}
+
+func allZero(segs []int64) bool {
+ for _, s := range segs {
+ if s != 0 {
+ return false
+ }
+ }
+ return true
+}
+
+func comparePart(preSelf string, preOther string) int {
+ if preSelf == preOther {
+ return 0
+ }
+
+ var selfInt int64
+ selfNumeric := true
+ selfInt, err := strconv.ParseInt(preSelf, 10, 64)
+ if err != nil {
+ selfNumeric = false
+ }
+
+ var otherInt int64
+ otherNumeric := true
+ otherInt, err = strconv.ParseInt(preOther, 10, 64)
+ if err != nil {
+ otherNumeric = false
+ }
+
+ // if a part is empty, we use the other to decide
+ if preSelf == "" {
+ if otherNumeric {
+ return -1
+ }
+ return 1
+ }
+
+ if preOther == "" {
+ if selfNumeric {
+ return 1
+ }
+ return -1
+ }
+
+ if selfNumeric && !otherNumeric {
+ return -1
+ } else if !selfNumeric && otherNumeric {
+ return 1
+ } else if !selfNumeric && !otherNumeric && preSelf > preOther {
+ return 1
+ } else if selfInt > otherInt {
+ return 1
+ }
+
+ return -1
+}
+
+func comparePrereleases(v string, other string) int {
+ // the same pre release!
+ if v == other {
+ return 0
+ }
+
+ // split both pre releases for analyse their parts
+ selfPreReleaseMeta := strings.Split(v, ".")
+ otherPreReleaseMeta := strings.Split(other, ".")
+
+ selfPreReleaseLen := len(selfPreReleaseMeta)
+ otherPreReleaseLen := len(otherPreReleaseMeta)
+
+ biggestLen := otherPreReleaseLen
+ if selfPreReleaseLen > otherPreReleaseLen {
+ biggestLen = selfPreReleaseLen
+ }
+
+ // loop for parts to find the first difference
+ for i := 0; i < biggestLen; i = i + 1 {
+ partSelfPre := ""
+ if i < selfPreReleaseLen {
+ partSelfPre = selfPreReleaseMeta[i]
+ }
+
+ partOtherPre := ""
+ if i < otherPreReleaseLen {
+ partOtherPre = otherPreReleaseMeta[i]
+ }
+
+ compare := comparePart(partSelfPre, partOtherPre)
+ // if parts are equals, continue the loop
+ if compare != 0 {
+ return compare
+ }
+ }
+
+ return 0
+}
+
+// Equal tests if two versions are equal.
+func (v *Version) Equal(o *Version) bool {
+ if v == nil || o == nil {
+ return v == o
+ }
+
+ return v.Compare(o) == 0
+}
+
+// GreaterThan tests if this version is greater than another version.
+func (v *Version) GreaterThan(o *Version) bool {
+ return v.Compare(o) > 0
+}
+
+// GreaterThanOrEqual tests if this version is greater than or equal to another version.
+func (v *Version) GreaterThanOrEqual(o *Version) bool {
+ return v.Compare(o) >= 0
+}
+
+// LessThan tests if this version is less than another version.
+func (v *Version) LessThan(o *Version) bool {
+ return v.Compare(o) < 0
+}
+
+// LessThanOrEqual tests if this version is less than or equal to another version.
+func (v *Version) LessThanOrEqual(o *Version) bool {
+ return v.Compare(o) <= 0
+}
+
+// Metadata returns any metadata that was part of the version
+// string.
+//
+// Metadata is anything that comes after the "+" in the version.
+// For example, with "1.2.3+beta", the metadata is "beta".
+func (v *Version) Metadata() string {
+ return v.metadata
+}
+
+// Prerelease returns any prerelease data that is part of the version,
+// or blank if there is no prerelease data.
+//
+// Prerelease information is anything that comes after the "-" in the
+// version (but before any metadata). For example, with "1.2.3-beta",
+// the prerelease information is "beta".
+func (v *Version) Prerelease() string {
+ return v.pre
+}
+
+// Segments returns the numeric segments of the version as a slice of ints.
+//
+// This excludes any metadata or pre-release information. For example,
+// for a version "1.2.3-beta", segments will return a slice of
+// 1, 2, 3.
+func (v *Version) Segments() []int {
+ segmentSlice := make([]int, len(v.segments))
+ for i, v := range v.segments {
+ segmentSlice[i] = int(v)
+ }
+ return segmentSlice
+}
+
+// Segments64 returns the numeric segments of the version as a slice of int64s.
+//
+// This excludes any metadata or pre-release information. For example,
+// for a version "1.2.3-beta", segments will return a slice of
+// 1, 2, 3.
+func (v *Version) Segments64() []int64 {
+ result := make([]int64, len(v.segments))
+ copy(result, v.segments)
+ return result
+}
+
+// String returns the full version string included pre-release
+// and metadata information.
+//
+// This value is rebuilt according to the parsed segments and other
+// information. Therefore, ambiguities in the version string such as
+// prefixed zeroes (1.04.0 => 1.4.0), `v` prefix (v1.0.0 => 1.0.0), and
+// missing parts (1.0 => 1.0.0) will be made into a canonicalized form
+// as shown in the parenthesized examples.
+func (v *Version) String() string {
+ var buf bytes.Buffer
+ fmtParts := make([]string, len(v.segments))
+ for i, s := range v.segments {
+ // We can ignore err here since we've pre-parsed the values in segments
+ str := strconv.FormatInt(s, 10)
+ fmtParts[i] = str
+ }
+ fmt.Fprintf(&buf, strings.Join(fmtParts, "."))
+ if v.pre != "" {
+ fmt.Fprintf(&buf, "-%s", v.pre)
+ }
+ if v.metadata != "" {
+ fmt.Fprintf(&buf, "+%s", v.metadata)
+ }
+
+ return buf.String()
+}
+
+// Original returns the original parsed version as-is, including any
+// potential whitespace, `v` prefix, etc.
+func (v *Version) Original() string {
+ return v.original
+}
diff --git a/vendor/github.com/hashicorp/go-version/version_collection.go b/vendor/github.com/hashicorp/go-version/version_collection.go
new file mode 100644
index 000000000..cc888d43e
--- /dev/null
+++ b/vendor/github.com/hashicorp/go-version/version_collection.go
@@ -0,0 +1,17 @@
+package version
+
+// Collection is a type that implements the sort.Interface interface
+// so that versions can be sorted.
+type Collection []*Version
+
+func (v Collection) Len() int {
+ return len(v)
+}
+
+func (v Collection) Less(i, j int) bool {
+ return v[i].LessThan(v[j])
+}
+
+func (v Collection) Swap(i, j int) {
+ v[i], v[j] = v[j], v[i]
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/.gitignore b/vendor/github.com/russross/blackfriday/v2/.gitignore
new file mode 100644
index 000000000..75623dccc
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/.gitignore
@@ -0,0 +1,8 @@
+*.out
+*.swp
+*.8
+*.6
+_obj
+_test*
+markdown
+tags
diff --git a/vendor/github.com/russross/blackfriday/v2/.travis.yml b/vendor/github.com/russross/blackfriday/v2/.travis.yml
new file mode 100644
index 000000000..b0b525a5a
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/.travis.yml
@@ -0,0 +1,17 @@
+sudo: false
+language: go
+go:
+ - "1.10.x"
+ - "1.11.x"
+ - tip
+matrix:
+ fast_finish: true
+ allow_failures:
+ - go: tip
+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 ./...
diff --git a/vendor/github.com/russross/blackfriday/v2/LICENSE.txt b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt
new file mode 100644
index 000000000..2885af360
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt
@@ -0,0 +1,29 @@
+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.
diff --git a/vendor/github.com/russross/blackfriday/v2/README.md b/vendor/github.com/russross/blackfriday/v2/README.md
new file mode 100644
index 000000000..d9c08a22f
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/README.md
@@ -0,0 +1,335 @@
+Blackfriday
+[![Build Status][BuildV2SVG]][BuildV2URL]
+[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL]
+===========
+
+Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
+is paranoid about its input (so you can safely feed it user-supplied
+data), it is fast, it supports common extensions (tables, smart
+punctuation substitutions, etc.), and it is safe for all utf-8
+(unicode) input.
+
+HTML output is currently supported, along with Smartypants
+extensions.
+
+It started as a translation from C of [Sundown][3].
+
+
+Installation
+------------
+
+Blackfriday is compatible with modern Go releases in module mode.
+With Go installed:
+
+ go get github.com/russross/blackfriday/v2
+
+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/v2"
+
+and `go get` without parameters.
+
+Legacy GOPATH mode is unsupported.
+
+
+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://pkg.go.dev/github.com/russross/blackfriday/v2.
+
+It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`.
+
+Version 2 offers a number of improvements over v1:
+
+* Cleaned up API
+* A separate call to [`Parse`][4], which produces an abstract syntax tree for
+ the document
+* Latest bug fixes
+* Flexibility to easily add your own rendering extensions
+
+Potential drawbacks:
+
+* Our benchmarks show v2 to be slightly slower than v1. Currently in the
+ ballpark of around 15%.
+* API breakage. If you can't afford modifying your code to adhere to the new API
+ and don't care too much about the new features, v2 is probably not for you.
+* Several bug fixes are trailing behind and still need to be forward-ported to
+ v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
+ tracking.
+
+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://pkg.go.dev/github.com/russross/blackfriday.
+
+
+Usage
+-----
+
+For the most sensible markdown processing, it is as simple as getting your input
+into a byte slice and calling:
+
+```go
+output := blackfriday.Run(input)
+```
+
+Your input will be parsed and the output rendered with a set of most popular
+extensions enabled. If you want the most basic feature set, corresponding with
+the bare Markdown specification, use:
+
+```go
+output := blackfriday.Run(input, blackfriday.WithNoExtensions())
+```
+
+### Sanitize untrusted content
+
+Blackfriday itself does nothing to protect against malicious content. If you are
+dealing with user-supplied markdown, we recommend running Blackfriday's output
+through HTML sanitizer such as [Bluemonday][5].
+
+Here's an example of simple usage of Blackfriday together with Bluemonday:
+
+```go
+import (
+ "github.com/microcosm-cc/bluemonday"
+ "github.com/russross/blackfriday/v2"
+)
+
+// ...
+unsafe := blackfriday.Run(input)
+html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
+```
+
+### Custom options
+
+If you want to customize the set of options, use `blackfriday.WithExtensions`,
+`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
+
+### `blackfriday-tool`
+
+You can also check out `blackfriday-tool` for a more complete example
+of how to use it. Download and install it using:
+
+ go get github.com/russross/blackfriday-tool
+
+This is a simple command-line tool that allows you to process a
+markdown file using a standalone program. You can also browse the
+source directly on github if you are just looking for some example
+code:
+
+* <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
+blackfriday in addition to the tool itself. The tool binary will be
+installed in `$GOPATH/bin`. This is a statically-linked binary that
+can be copied to wherever you need it without worrying about
+dependencies and library versions.
+
+### Sanitized anchor names
+
+Blackfriday includes an algorithm for creating sanitized anchor names
+corresponding to a given input text. This algorithm is used to create
+anchors for headings when `AutoHeadingIDs` extension 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://pkg.go.dev/github.com/russross/blackfriday/v2#hdr-Sanitized_Anchor_Names.
+
+[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday/v2#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://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.
+
+
+Features
+--------
+
+All features of Sundown are supported, including:
+
+* **Compatibility**. The Markdown v1.0.3 test suite passes with
+ the `--tidy` option. Without `--tidy`, the differences are
+ mostly in whitespace and entity escaping, where blackfriday is
+ more consistent and cleaner.
+
+* **Common extensions**, including table support, fenced code
+ blocks, autolinks, strikethroughs, non-strict emphasis, etc.
+
+* **Safety**. Blackfriday is paranoid when parsing, making it safe
+ to feed untrusted user input without fear of bad things
+ happening. The test suite stress tests this and there are no
+ known inputs that make it crash. If you find one, please let me
+ know and send me the input that does it.
+
+ NOTE: "safety" in this context means *runtime safety only*. In order to
+ protect yourself against JavaScript injection in untrusted content, see
+ [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
+
+* **Fast processing**. It is fast enough to render on-demand in
+ most web applications without having to cache the output.
+
+* **Thread safety**. You can run multiple parsers in different
+ goroutines without ill effect. There is no dependence on global
+ shared state.
+
+* **Minimal dependencies**. Blackfriday only depends on standard
+ library packages in Go. The source code is pretty
+ self-contained, so it is easy to add to any project, including
+ Google App Engine projects.
+
+* **Standards compliant**. Output successfully validates using the
+ W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
+
+
+Extensions
+----------
+
+In addition to the standard markdown syntax, this package
+implements the following extensions:
+
+* **Intra-word emphasis supression**. The `_` character is
+ commonly used inside words when discussing code, so having
+ markdown interpret it as an emphasis command is usually the
+ wrong thing. Blackfriday lets you treat all emphasis markers as
+ normal characters when they occur inside a word.
+
+* **Tables**. Tables can be created by drawing them in the input
+ using a simple syntax:
+
+ ```
+ Name | Age
+ --------|------
+ Bob | 27
+ Alice | 23
+ ```
+
+* **Fenced code blocks**. In addition to the normal 4-space
+ indentation to mark code blocks, you can explicitly mark them
+ and supply a language (to make syntax highlighting simple). Just
+ mark it like this:
+
+ ```go
+ func getTrue() bool {
+ return true
+ }
+ ```
+
+ You can use 3 or more backticks to mark the beginning of the
+ block, and the same number to mark the end of the block.
+
+ To preserve classes of fenced code blocks while using the bluemonday
+ HTML sanitizer, use the following policy:
+
+ ```go
+ p := bluemonday.UGCPolicy()
+ p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
+ html := p.SanitizeBytes(unsafe)
+ ```
+
+* **Definition lists**. A simple definition list is made of a single-line
+ term followed by a colon and the definition for that term.
+
+ Cat
+ : Fluffy animal everyone likes
+
+ Internet
+ : Vector of transmission for pictures of cats
+
+ Terms must be separated from the previous definition by a blank line.
+
+* **Footnotes**. A marker in the text that will become a superscript number;
+ a footnote definition that will be placed in a list of footnotes at the
+ end of the document. A footnote looks like this:
+
+ This is a footnote.[^1]
+
+ [^1]: the footnote text.
+
+* **Autolinking**. Blackfriday can find URLs that have not been
+ explicitly marked as links and turn them into links.
+
+* **Strikethrough**. Use two tildes (`~~`) to mark text that
+ should be crossed out.
+
+* **Hard line breaks**. With this extension enabled newlines in the input
+ translate into line breaks in the output. This extension is off by default.
+
+* **Smart quotes**. Smartypants-style punctuation substitution is
+ supported, turning normal double- and single-quote marks into
+ curly quotes, etc.
+
+* **LaTeX-style dash parsing** is an additional option, where `--`
+ is translated into `&ndash;`, and `---` is translated into
+ `&mdash;`. This differs from most smartypants processors, which
+ turn a single hyphen into an ndash and a double hyphen into an
+ mdash.
+
+* **Smart fractions**, where anything that looks like a fraction
+ is translated into suitable HTML (instead of just a few special
+ cases like most smartypant processors). For example, `4/5`
+ becomes `<sup>4</sup>&frasl;<sub>5</sub>`, which renders as
+ <sup>4</sup>&frasl;<sub>5</sub>.
+
+
+Other renderers
+---------------
+
+Blackfriday is structured to allow alternative rendering engines. Here
+are a few of note:
+
+* [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.
+
+ It's not customizable, and its goal is to produce HTML output
+ equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
+ except the rendering is performed locally.
+
+* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
+ but for markdown.
+
+* [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
+----
+
+* More unit testing
+* Improve Unicode support. It does not understand all Unicode
+ rules (about what constitutes a letter, a punctuation symbol,
+ etc.), so it may fail to detect word boundaries correctly in
+ some instances. It is safe on all UTF-8 input.
+
+
+License
+-------
+
+[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
+
+
+ [1]: https://daringfireball.net/projects/markdown/ "Markdown"
+ [2]: https://golang.org/ "Go Language"
+ [3]: https://github.com/vmg/sundown "Sundown"
+ [4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func"
+ [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
+
+ [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
diff --git a/vendor/github.com/russross/blackfriday/v2/block.go b/vendor/github.com/russross/blackfriday/v2/block.go
new file mode 100644
index 000000000..dcd61e6e3
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/block.go
@@ -0,0 +1,1612 @@
+//
+// 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.
+//
+
+//
+// Functions to parse block-level elements.
+//
+
+package blackfriday
+
+import (
+ "bytes"
+ "html"
+ "regexp"
+ "strings"
+ "unicode"
+)
+
+const (
+ charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
+ escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
+)
+
+var (
+ reBackslashOrAmp = regexp.MustCompile("[\\&]")
+ reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
+)
+
+// Parse block-level data.
+// Note: this function and many that it calls assume that
+// the input buffer ends with a newline.
+func (p *Markdown) block(data []byte) {
+ // this is called recursively: enforce a maximum depth
+ if p.nesting >= p.maxNesting {
+ return
+ }
+ p.nesting++
+
+ // parse out one block-level construct at a time
+ for len(data) > 0 {
+ // prefixed heading:
+ //
+ // # Heading 1
+ // ## Heading 2
+ // ...
+ // ###### Heading 6
+ if p.isPrefixHeading(data) {
+ data = data[p.prefixHeading(data):]
+ continue
+ }
+
+ // block of preformatted HTML:
+ //
+ // <div>
+ // ...
+ // </div>
+ if data[0] == '<' {
+ if i := p.html(data, true); i > 0 {
+ data = data[i:]
+ continue
+ }
+ }
+
+ // title block
+ //
+ // % stuff
+ // % more stuff
+ // % even more stuff
+ if p.extensions&Titleblock != 0 {
+ if data[0] == '%' {
+ if i := p.titleBlock(data, true); i > 0 {
+ data = data[i:]
+ continue
+ }
+ }
+ }
+
+ // blank lines. note: returns the # of bytes to skip
+ if i := p.isEmpty(data); i > 0 {
+ data = data[i:]
+ continue
+ }
+
+ // indented code block:
+ //
+ // func max(a, b int) int {
+ // if a > b {
+ // return a
+ // }
+ // return b
+ // }
+ if p.codePrefix(data) > 0 {
+ data = data[p.code(data):]
+ continue
+ }
+
+ // fenced code block:
+ //
+ // ``` go
+ // func fact(n int) int {
+ // if n <= 1 {
+ // return n
+ // }
+ // return n * fact(n-1)
+ // }
+ // ```
+ if p.extensions&FencedCode != 0 {
+ if i := p.fencedCodeBlock(data, true); i > 0 {
+ data = data[i:]
+ continue
+ }
+ }
+
+ // horizontal rule:
+ //
+ // ------
+ // or
+ // ******
+ // or
+ // ______
+ if p.isHRule(data) {
+ p.addBlock(HorizontalRule, nil)
+ var i int
+ for i = 0; i < len(data) && data[i] != '\n'; i++ {
+ }
+ data = data[i:]
+ continue
+ }
+
+ // block quote:
+ //
+ // > A big quote I found somewhere
+ // > on the web
+ if p.quotePrefix(data) > 0 {
+ data = data[p.quote(data):]
+ continue
+ }
+
+ // table:
+ //
+ // Name | Age | Phone
+ // ------|-----|---------
+ // Bob | 31 | 555-1234
+ // Alice | 27 | 555-4321
+ if p.extensions&Tables != 0 {
+ if i := p.table(data); i > 0 {
+ data = data[i:]
+ continue
+ }
+ }
+
+ // an itemized/unordered list:
+ //
+ // * Item 1
+ // * Item 2
+ //
+ // also works with + or -
+ if p.uliPrefix(data) > 0 {
+ data = data[p.list(data, 0):]
+ continue
+ }
+
+ // a numbered/ordered list:
+ //
+ // 1. Item 1
+ // 2. Item 2
+ if p.oliPrefix(data) > 0 {
+ data = data[p.list(data, ListTypeOrdered):]
+ continue
+ }
+
+ // definition lists:
+ //
+ // Term 1
+ // : Definition a
+ // : Definition b
+ //
+ // Term 2
+ // : Definition c
+ if p.extensions&DefinitionLists != 0 {
+ if p.dliPrefix(data) > 0 {
+ data = data[p.list(data, ListTypeDefinition):]
+ continue
+ }
+ }
+
+ // anything else must look like a normal paragraph
+ // note: this finds underlined headings, too
+ data = data[p.paragraph(data):]
+ }
+
+ p.nesting--
+}
+
+func (p *Markdown) addBlock(typ NodeType, content []byte) *Node {
+ p.closeUnmatchedBlocks()
+ container := p.addChild(typ, 0)
+ container.content = content
+ return container
+}
+
+func (p *Markdown) isPrefixHeading(data []byte) bool {
+ if data[0] != '#' {
+ return false
+ }
+
+ if p.extensions&SpaceHeadings != 0 {
+ level := 0
+ for level < 6 && level < len(data) && data[level] == '#' {
+ level++
+ }
+ if level == len(data) || data[level] != ' ' {
+ return false
+ }
+ }
+ return true
+}
+
+func (p *Markdown) prefixHeading(data []byte) int {
+ level := 0
+ for level < 6 && level < len(data) && data[level] == '#' {
+ level++
+ }
+ i := skipChar(data, level, ' ')
+ end := skipUntilChar(data, i, '\n')
+ skip := end
+ id := ""
+ if p.extensions&HeadingIDs != 0 {
+ j, k := 0, 0
+ // find start/end of heading id
+ for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
+ }
+ for k = j + 1; k < end && data[k] != '}'; k++ {
+ }
+ // extract heading id iff found
+ if j < end && k < end {
+ id = string(data[j+2 : k])
+ end = j
+ skip = k + 1
+ for end > 0 && data[end-1] == ' ' {
+ end--
+ }
+ }
+ }
+ for end > 0 && data[end-1] == '#' {
+ if isBackslashEscaped(data, end-1) {
+ break
+ }
+ end--
+ }
+ for end > 0 && data[end-1] == ' ' {
+ end--
+ }
+ if end > i {
+ if id == "" && p.extensions&AutoHeadingIDs != 0 {
+ id = SanitizedAnchorName(string(data[i:end]))
+ }
+ block := p.addBlock(Heading, data[i:end])
+ block.HeadingID = id
+ block.Level = level
+ }
+ return skip
+}
+
+func (p *Markdown) isUnderlinedHeading(data []byte) int {
+ // test of level 1 heading
+ if data[0] == '=' {
+ i := skipChar(data, 1, '=')
+ i = skipChar(data, i, ' ')
+ if i < len(data) && data[i] == '\n' {
+ return 1
+ }
+ return 0
+ }
+
+ // test of level 2 heading
+ if data[0] == '-' {
+ i := skipChar(data, 1, '-')
+ i = skipChar(data, i, ' ')
+ if i < len(data) && data[i] == '\n' {
+ return 2
+ }
+ return 0
+ }
+
+ return 0
+}
+
+func (p *Markdown) titleBlock(data []byte, doRender bool) int {
+ if data[0] != '%' {
+ return 0
+ }
+ splitData := bytes.Split(data, []byte("\n"))
+ var i int
+ for idx, b := range splitData {
+ if !bytes.HasPrefix(b, []byte("%")) {
+ i = idx // - 1
+ break
+ }
+ }
+
+ data = bytes.Join(splitData[0:i], []byte("\n"))
+ consumed := len(data)
+ data = bytes.TrimPrefix(data, []byte("% "))
+ data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1)
+ block := p.addBlock(Heading, data)
+ block.Level = 1
+ block.IsTitleblock = true
+
+ return consumed
+}
+
+func (p *Markdown) html(data []byte, doRender bool) int {
+ var i, j int
+
+ // identify the opening tag
+ if data[0] != '<' {
+ return 0
+ }
+ curtag, tagfound := p.htmlFindTag(data[1:])
+
+ // handle special cases
+ if !tagfound {
+ // check for an HTML comment
+ if size := p.htmlComment(data, doRender); size > 0 {
+ return size
+ }
+
+ // check for an <hr> tag
+ if size := p.htmlHr(data, doRender); size > 0 {
+ return size
+ }
+
+ // no special case recognized
+ return 0
+ }
+
+ // look for an unindented matching closing tag
+ // followed by a blank line
+ found := false
+ /*
+ closetag := []byte("\n</" + curtag + ">")
+ j = len(curtag) + 1
+ for !found {
+ // scan for a closing tag at the beginning of a line
+ if skip := bytes.Index(data[j:], closetag); skip >= 0 {
+ j += skip + len(closetag)
+ } else {
+ break
+ }
+
+ // see if it is the only thing on the line
+ if skip := p.isEmpty(data[j:]); skip > 0 {
+ // see if it is followed by a blank line/eof
+ j += skip
+ if j >= len(data) {
+ found = true
+ i = j
+ } else {
+ if skip := p.isEmpty(data[j:]); skip > 0 {
+ j += skip
+ found = true
+ i = j
+ }
+ }
+ }
+ }
+ */
+
+ // if not found, try a second pass looking for indented match
+ // but not if tag is "ins" or "del" (following original Markdown.pl)
+ if !found && curtag != "ins" && curtag != "del" {
+ i = 1
+ for i < len(data) {
+ i++
+ for i < len(data) && !(data[i-1] == '<' && data[i] == '/') {
+ i++
+ }
+
+ if i+2+len(curtag) >= len(data) {
+ break
+ }
+
+ j = p.htmlFindEnd(curtag, data[i-1:])
+
+ if j > 0 {
+ i += j - 1
+ found = true
+ break
+ }
+ }
+ }
+
+ if !found {
+ return 0
+ }
+
+ // the end of the block has been found
+ if doRender {
+ // trim newlines
+ end := i
+ for end > 0 && data[end-1] == '\n' {
+ end--
+ }
+ finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
+ }
+
+ return i
+}
+
+func finalizeHTMLBlock(block *Node) {
+ block.Literal = block.content
+ block.content = nil
+}
+
+// HTML comment, lax form
+func (p *Markdown) htmlComment(data []byte, doRender bool) int {
+ i := p.inlineHTMLComment(data)
+ // needs to end with a blank line
+ if j := p.isEmpty(data[i:]); j > 0 {
+ size := i + j
+ if doRender {
+ // trim trailing newlines
+ end := size
+ for end > 0 && data[end-1] == '\n' {
+ end--
+ }
+ block := p.addBlock(HTMLBlock, data[:end])
+ finalizeHTMLBlock(block)
+ }
+ return size
+ }
+ return 0
+}
+
+// HR, which is the only self-closing block tag considered
+func (p *Markdown) htmlHr(data []byte, doRender bool) int {
+ if len(data) < 4 {
+ return 0
+ }
+ if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') {
+ return 0
+ }
+ if data[3] != ' ' && data[3] != '/' && data[3] != '>' {
+ // not an <hr> tag after all; at least not a valid one
+ return 0
+ }
+ i := 3
+ for i < len(data) && data[i] != '>' && data[i] != '\n' {
+ i++
+ }
+ if i < len(data) && data[i] == '>' {
+ i++
+ if j := p.isEmpty(data[i:]); j > 0 {
+ size := i + j
+ if doRender {
+ // trim newlines
+ end := size
+ for end > 0 && data[end-1] == '\n' {
+ end--
+ }
+ finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
+ }
+ return size
+ }
+ }
+ return 0
+}
+
+func (p *Markdown) htmlFindTag(data []byte) (string, bool) {
+ i := 0
+ for i < len(data) && isalnum(data[i]) {
+ i++
+ }
+ key := string(data[:i])
+ if _, ok := blockTags[key]; ok {
+ return key, true
+ }
+ return "", false
+}
+
+func (p *Markdown) htmlFindEnd(tag string, data []byte) int {
+ // assume data[0] == '<' && data[1] == '/' already tested
+ if tag == "hr" {
+ return 2
+ }
+ // check if tag is a match
+ closetag := []byte("</" + tag + ">")
+ if !bytes.HasPrefix(data, closetag) {
+ return 0
+ }
+ i := len(closetag)
+
+ // check that the rest of the line is blank
+ skip := 0
+ if skip = p.isEmpty(data[i:]); skip == 0 {
+ return 0
+ }
+ i += skip
+ skip = 0
+
+ if i >= len(data) {
+ return i
+ }
+
+ if p.extensions&LaxHTMLBlocks != 0 {
+ return i
+ }
+ if skip = p.isEmpty(data[i:]); skip == 0 {
+ // following line must be blank
+ return 0
+ }
+
+ return i + skip
+}
+
+func (*Markdown) isEmpty(data []byte) int {
+ // it is okay to call isEmpty on an empty buffer
+ if len(data) == 0 {
+ return 0
+ }
+
+ var i int
+ for i = 0; i < len(data) && data[i] != '\n'; i++ {
+ if data[i] != ' ' && data[i] != '\t' {
+ return 0
+ }
+ }
+ if i < len(data) && data[i] == '\n' {
+ i++
+ }
+ return i
+}
+
+func (*Markdown) isHRule(data []byte) bool {
+ i := 0
+
+ // skip up to three spaces
+ for i < 3 && data[i] == ' ' {
+ i++
+ }
+
+ // look at the hrule char
+ if data[i] != '*' && data[i] != '-' && data[i] != '_' {
+ return false
+ }
+ c := data[i]
+
+ // the whole line must be the char or whitespace
+ n := 0
+ for i < len(data) && data[i] != '\n' {
+ switch {
+ case data[i] == c:
+ n++
+ case data[i] != ' ':
+ return false
+ }
+ i++
+ }
+
+ return n >= 3
+}
+
+// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
+// and returns the end index if so, or 0 otherwise. It also returns the marker found.
+// If info is not nil, it gets set to the syntax specified in the fence line.
+func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
+ i, size := 0, 0
+
+ // skip up to three spaces
+ for i < len(data) && i < 3 && data[i] == ' ' {
+ i++
+ }
+
+ // check for the marker characters: ~ or `
+ if i >= len(data) {
+ return 0, ""
+ }
+ if data[i] != '~' && data[i] != '`' {
+ return 0, ""
+ }
+
+ c := data[i]
+
+ // the whole line must be the same char or whitespace
+ for i < len(data) && data[i] == c {
+ size++
+ i++
+ }
+
+ // the marker char must occur at least 3 times
+ if size < 3 {
+ return 0, ""
+ }
+ marker = string(data[i-size : i])
+
+ // if this is the end marker, it must match the beginning marker
+ if oldmarker != "" && marker != oldmarker {
+ return 0, ""
+ }
+
+ // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
+ // 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) {
+ if i == len(data) {
+ return i, marker
+ }
+ return 0, ""
+ }
+
+ infoStart := i
+
+ if data[i] == '{' {
+ i++
+ infoStart++
+
+ for i < len(data) && data[i] != '}' && data[i] != '\n' {
+ infoLength++
+ i++
+ }
+
+ if i >= len(data) || data[i] != '}' {
+ return 0, ""
+ }
+
+ // strip all whitespace at the beginning and the end
+ // of the {} block
+ for infoLength > 0 && isspace(data[infoStart]) {
+ infoStart++
+ infoLength--
+ }
+
+ for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
+ infoLength--
+ }
+ i++
+ i = skipChar(data, i, ' ')
+ } else {
+ for i < len(data) && !isverticalspace(data[i]) {
+ infoLength++
+ i++
+ }
+ }
+
+ *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
+ }
+
+ if i == len(data) {
+ return i, marker
+ }
+ if i > len(data) || data[i] != '\n' {
+ return 0, ""
+ }
+ return i + 1, marker // Take newline into account.
+}
+
+// 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 *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
+ var info string
+ beg, marker := isFenceLine(data, &info, "")
+ if beg == 0 || beg >= len(data) {
+ return 0
+ }
+ fenceLength := beg - 1
+
+ var work bytes.Buffer
+ work.Write([]byte(info))
+ work.WriteByte('\n')
+
+ for {
+ // safe to assume beg < len(data)
+
+ // check for the end of the code block
+ fenceEnd, _ := isFenceLine(data[beg:], nil, marker)
+ if fenceEnd != 0 {
+ beg += fenceEnd
+ break
+ }
+
+ // copy the current line
+ end := skipUntilChar(data, beg, '\n') + 1
+
+ // did we reach the end of the buffer without a closing marker?
+ if end >= len(data) {
+ return 0
+ }
+
+ // verbatim copy to the working buffer
+ if doRender {
+ work.Write(data[beg:end])
+ }
+ beg = end
+ }
+
+ if doRender {
+ block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
+ block.IsFenced = true
+ block.FenceLength = fenceLength
+ finalizeCodeBlock(block)
+ }
+
+ return beg
+}
+
+func unescapeChar(str []byte) []byte {
+ if str[0] == '\\' {
+ return []byte{str[1]}
+ }
+ return []byte(html.UnescapeString(string(str)))
+}
+
+func unescapeString(str []byte) []byte {
+ if reBackslashOrAmp.Match(str) {
+ return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar)
+ }
+ return str
+}
+
+func finalizeCodeBlock(block *Node) {
+ if block.IsFenced {
+ newlinePos := bytes.IndexByte(block.content, '\n')
+ firstLine := block.content[:newlinePos]
+ rest := block.content[newlinePos+1:]
+ block.Info = unescapeString(bytes.Trim(firstLine, "\n"))
+ block.Literal = rest
+ } else {
+ block.Literal = block.content
+ }
+ block.content = nil
+}
+
+func (p *Markdown) table(data []byte) int {
+ table := p.addBlock(Table, nil)
+ i, columns := p.tableHeader(data)
+ if i == 0 {
+ p.tip = table.Parent
+ table.Unlink()
+ return 0
+ }
+
+ p.addBlock(TableBody, nil)
+
+ for i < len(data) {
+ pipes, rowStart := 0, i
+ for ; i < len(data) && data[i] != '\n'; i++ {
+ if data[i] == '|' {
+ pipes++
+ }
+ }
+
+ if pipes == 0 {
+ i = rowStart
+ break
+ }
+
+ // include the newline in data sent to tableRow
+ if i < len(data) && data[i] == '\n' {
+ i++
+ }
+ p.tableRow(data[rowStart:i], columns, false)
+ }
+
+ return i
+}
+
+// check if the specified position is preceded by an odd number of backslashes
+func isBackslashEscaped(data []byte, i int) bool {
+ backslashes := 0
+ for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' {
+ backslashes++
+ }
+ return backslashes&1 == 1
+}
+
+func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) {
+ i := 0
+ colCount := 1
+ for i = 0; i < len(data) && data[i] != '\n'; i++ {
+ if data[i] == '|' && !isBackslashEscaped(data, i) {
+ colCount++
+ }
+ }
+
+ // doesn't look like a table header
+ if colCount == 1 {
+ return
+ }
+
+ // include the newline in the data sent to tableRow
+ j := i
+ if j < len(data) && data[j] == '\n' {
+ j++
+ }
+ header := data[:j]
+
+ // column count ignores pipes at beginning or end of line
+ if data[0] == '|' {
+ colCount--
+ }
+ if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) {
+ colCount--
+ }
+
+ columns = make([]CellAlignFlags, colCount)
+
+ // move on to the header underline
+ i++
+ if i >= len(data) {
+ return
+ }
+
+ if data[i] == '|' && !isBackslashEscaped(data, i) {
+ i++
+ }
+ i = skipChar(data, i, ' ')
+
+ // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3
+ // and trailing | optional on last column
+ col := 0
+ for i < len(data) && data[i] != '\n' {
+ dashes := 0
+
+ if data[i] == ':' {
+ i++
+ columns[col] |= TableAlignmentLeft
+ dashes++
+ }
+ for i < len(data) && data[i] == '-' {
+ i++
+ dashes++
+ }
+ if i < len(data) && data[i] == ':' {
+ i++
+ columns[col] |= TableAlignmentRight
+ dashes++
+ }
+ for i < len(data) && data[i] == ' ' {
+ i++
+ }
+ if i == len(data) {
+ return
+ }
+ // end of column test is messy
+ switch {
+ case dashes < 3:
+ // not a valid column
+ return
+
+ case data[i] == '|' && !isBackslashEscaped(data, i):
+ // marker found, now skip past trailing whitespace
+ col++
+ i++
+ for i < len(data) && data[i] == ' ' {
+ i++
+ }
+
+ // trailing junk found after last column
+ if col >= colCount && i < len(data) && data[i] != '\n' {
+ return
+ }
+
+ case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount:
+ // something else found where marker was required
+ return
+
+ case data[i] == '\n':
+ // marker is optional for the last column
+ col++
+
+ default:
+ // trailing junk found after last column
+ return
+ }
+ }
+ if col != colCount {
+ return
+ }
+
+ p.addBlock(TableHead, nil)
+ p.tableRow(header, columns, true)
+ size = i
+ if size < len(data) && data[size] == '\n' {
+ size++
+ }
+ return
+}
+
+func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) {
+ p.addBlock(TableRow, nil)
+ i, col := 0, 0
+
+ if data[i] == '|' && !isBackslashEscaped(data, i) {
+ i++
+ }
+
+ for col = 0; col < len(columns) && i < len(data); col++ {
+ for i < len(data) && data[i] == ' ' {
+ i++
+ }
+
+ cellStart := i
+
+ for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
+ i++
+ }
+
+ cellEnd := i
+
+ // skip the end-of-cell marker, possibly taking us past end of buffer
+ i++
+
+ for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' {
+ cellEnd--
+ }
+
+ cell := p.addBlock(TableCell, data[cellStart:cellEnd])
+ cell.IsHeader = header
+ cell.Align = columns[col]
+ }
+
+ // pad it out with empty columns to get the right number
+ for ; col < len(columns); col++ {
+ cell := p.addBlock(TableCell, nil)
+ cell.IsHeader = header
+ cell.Align = columns[col]
+ }
+
+ // silently ignore rows with too many cells
+}
+
+// returns blockquote prefix length
+func (p *Markdown) quotePrefix(data []byte) int {
+ i := 0
+ for i < 3 && i < len(data) && data[i] == ' ' {
+ i++
+ }
+ if i < len(data) && data[i] == '>' {
+ if i+1 < len(data) && data[i+1] == ' ' {
+ return i + 2
+ }
+ return i + 1
+ }
+ return 0
+}
+
+// blockquote ends with at least one blank line
+// followed by something without a blockquote prefix
+func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool {
+ if p.isEmpty(data[beg:]) <= 0 {
+ return false
+ }
+ if end >= len(data) {
+ return true
+ }
+ return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0
+}
+
+// parse a blockquote fragment
+func (p *Markdown) quote(data []byte) int {
+ block := p.addBlock(BlockQuote, nil)
+ var raw bytes.Buffer
+ beg, end := 0, 0
+ for beg < len(data) {
+ end = beg
+ // Step over whole lines, collecting them. While doing that, check for
+ // fenced code and if one's found, incorporate it altogether,
+ // irregardless of any contents inside it
+ for end < len(data) && data[end] != '\n' {
+ if p.extensions&FencedCode != 0 {
+ if i := p.fencedCodeBlock(data[end:], false); i > 0 {
+ // -1 to compensate for the extra end++ after the loop:
+ end += i - 1
+ break
+ }
+ }
+ end++
+ }
+ if end < len(data) && data[end] == '\n' {
+ end++
+ }
+ if pre := p.quotePrefix(data[beg:]); pre > 0 {
+ // skip the prefix
+ beg += pre
+ } else if p.terminateBlockquote(data, beg, end) {
+ break
+ }
+ // this line is part of the blockquote
+ raw.Write(data[beg:end])
+ beg = end
+ }
+ p.block(raw.Bytes())
+ p.finalize(block)
+ return end
+}
+
+// returns prefix length for block code
+func (p *Markdown) codePrefix(data []byte) int {
+ if len(data) >= 1 && data[0] == '\t' {
+ return 1
+ }
+ if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' {
+ return 4
+ }
+ return 0
+}
+
+func (p *Markdown) code(data []byte) int {
+ var work bytes.Buffer
+
+ i := 0
+ for i < len(data) {
+ beg := i
+ for i < len(data) && data[i] != '\n' {
+ i++
+ }
+ if i < len(data) && data[i] == '\n' {
+ i++
+ }
+
+ blankline := p.isEmpty(data[beg:i]) > 0
+ if pre := p.codePrefix(data[beg:i]); pre > 0 {
+ beg += pre
+ } else if !blankline {
+ // non-empty, non-prefixed line breaks the pre
+ i = beg
+ break
+ }
+
+ // verbatim copy to the working buffer
+ if blankline {
+ work.WriteByte('\n')
+ } else {
+ work.Write(data[beg:i])
+ }
+ }
+
+ // trim all the \n off the end of work
+ workbytes := work.Bytes()
+ eol := len(workbytes)
+ for eol > 0 && workbytes[eol-1] == '\n' {
+ eol--
+ }
+ if eol != len(workbytes) {
+ work.Truncate(eol)
+ }
+
+ work.WriteByte('\n')
+
+ block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer
+ block.IsFenced = false
+ finalizeCodeBlock(block)
+
+ return i
+}
+
+// returns unordered list item prefix
+func (p *Markdown) uliPrefix(data []byte) int {
+ i := 0
+ // start with up to 3 spaces
+ for i < len(data) && i < 3 && data[i] == ' ' {
+ i++
+ }
+ if i >= len(data)-1 {
+ return 0
+ }
+ // need one of {'*', '+', '-'} followed by a space or a tab
+ if (data[i] != '*' && data[i] != '+' && data[i] != '-') ||
+ (data[i+1] != ' ' && data[i+1] != '\t') {
+ return 0
+ }
+ return i + 2
+}
+
+// returns ordered list item prefix
+func (p *Markdown) oliPrefix(data []byte) int {
+ i := 0
+
+ // start with up to 3 spaces
+ for i < 3 && i < len(data) && data[i] == ' ' {
+ i++
+ }
+
+ // count the digits
+ start := i
+ for i < len(data) && data[i] >= '0' && data[i] <= '9' {
+ i++
+ }
+ if start == i || i >= len(data)-1 {
+ return 0
+ }
+
+ // we need >= 1 digits followed by a dot and a space or a tab
+ if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') {
+ return 0
+ }
+ return i + 2
+}
+
+// returns definition list item prefix
+func (p *Markdown) dliPrefix(data []byte) int {
+ if len(data) < 2 {
+ return 0
+ }
+ i := 0
+ // need a ':' followed by a space or a tab
+ if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') {
+ return 0
+ }
+ for i < len(data) && data[i] == ' ' {
+ i++
+ }
+ return i + 2
+}
+
+// parse ordered or unordered list block
+func (p *Markdown) list(data []byte, flags ListType) int {
+ i := 0
+ flags |= ListItemBeginningOfList
+ block := p.addBlock(List, nil)
+ block.ListFlags = flags
+ block.Tight = true
+
+ for i < len(data) {
+ skip := p.listItem(data[i:], &flags)
+ if flags&ListItemContainsBlock != 0 {
+ block.ListData.Tight = false
+ }
+ i += skip
+ if skip == 0 || flags&ListItemEndOfList != 0 {
+ break
+ }
+ flags &= ^ListItemBeginningOfList
+ }
+
+ above := block.Parent
+ finalizeList(block)
+ p.tip = above
+ return i
+}
+
+// Returns true if the list item is not the same type as its parent list
+func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool {
+ if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 {
+ return true
+ } else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 {
+ return true
+ } else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) {
+ return true
+ }
+ return false
+}
+
+// Returns true if block ends with a blank line, descending if needed
+// into lists and sublists.
+func endsWithBlankLine(block *Node) bool {
+ // TODO: figure this out. Always false now.
+ for block != nil {
+ //if block.lastLineBlank {
+ //return true
+ //}
+ t := block.Type
+ if t == List || t == Item {
+ block = block.LastChild
+ } else {
+ break
+ }
+ }
+ return false
+}
+
+func finalizeList(block *Node) {
+ block.open = false
+ item := block.FirstChild
+ for item != nil {
+ // check for non-final list item ending with blank line:
+ if endsWithBlankLine(item) && item.Next != nil {
+ block.ListData.Tight = false
+ break
+ }
+ // recurse into children of list item, to see if there are spaces
+ // between any of them:
+ subItem := item.FirstChild
+ for subItem != nil {
+ if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) {
+ block.ListData.Tight = false
+ break
+ }
+ subItem = subItem.Next
+ }
+ item = item.Next
+ }
+}
+
+// Parse a single list item.
+// Assumes initial prefix is already removed if this is a sublist.
+func (p *Markdown) listItem(data []byte, flags *ListType) int {
+ // keep track of the indentation of the first line
+ itemIndent := 0
+ if data[0] == '\t' {
+ itemIndent += 4
+ } else {
+ for itemIndent < 3 && data[itemIndent] == ' ' {
+ itemIndent++
+ }
+ }
+
+ var bulletChar byte = '*'
+ i := p.uliPrefix(data)
+ if i == 0 {
+ i = p.oliPrefix(data)
+ } else {
+ bulletChar = data[i-2]
+ }
+ if i == 0 {
+ i = p.dliPrefix(data)
+ // reset definition term flag
+ if i > 0 {
+ *flags &= ^ListTypeTerm
+ }
+ }
+ if i == 0 {
+ // if in definition list, set term flag and continue
+ if *flags&ListTypeDefinition != 0 {
+ *flags |= ListTypeTerm
+ } else {
+ return 0
+ }
+ }
+
+ // skip leading whitespace on first line
+ for i < len(data) && data[i] == ' ' {
+ i++
+ }
+
+ // find the end of the line
+ line := i
+ for i > 0 && i < len(data) && data[i-1] != '\n' {
+ i++
+ }
+
+ // get working buffer
+ var raw bytes.Buffer
+
+ // put the first line into the working buffer
+ raw.Write(data[line:i])
+ line = i
+
+ // process the following lines
+ containsBlankLine := false
+ sublist := 0
+ codeBlockMarker := ""
+
+gatherlines:
+ for line < len(data) {
+ i++
+
+ // find the end of this line
+ for i < len(data) && 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 {
+ containsBlankLine = true
+ line = i
+ continue
+ }
+
+ // calculate the indentation
+ indent := 0
+ indentIndex := 0
+ if data[line] == '\t' {
+ indentIndex++
+ indent += 4
+ } else {
+ for indent < 4 && line+indent < i && data[line+indent] == ' ' {
+ indent++
+ indentIndex++
+ }
+ }
+
+ chunk := data[line+indentIndex : i]
+
+ if p.extensions&FencedCode != 0 {
+ // determine if in or out of codeblock
+ // if in codeblock, ignore normal list processing
+ _, marker := isFenceLine(chunk, nil, codeBlockMarker)
+ if marker != "" {
+ if codeBlockMarker == "" {
+ // start of codeblock
+ codeBlockMarker = marker
+ } else {
+ // end of codeblock.
+ codeBlockMarker = ""
+ }
+ }
+ // we are in a codeblock, write line, and continue
+ if codeBlockMarker != "" || marker != "" {
+ raw.Write(data[line+indentIndex : i])
+ line = i
+ continue gatherlines
+ }
+ }
+
+ // evaluate how this line fits in
+ switch {
+ // is this a nested list item?
+ case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) ||
+ p.oliPrefix(chunk) > 0 ||
+ p.dliPrefix(chunk) > 0:
+
+ // to be a nested list, it must be indented more
+ // if not, it is either a different kind of list
+ // or the next item in the same list
+ if indent <= itemIndent {
+ if p.listTypeChanged(chunk, flags) {
+ *flags |= ListItemEndOfList
+ } else if containsBlankLine {
+ *flags |= ListItemContainsBlock
+ }
+
+ break gatherlines
+ }
+
+ if containsBlankLine {
+ *flags |= ListItemContainsBlock
+ }
+
+ // is this the first item in the nested list?
+ if sublist == 0 {
+ sublist = raw.Len()
+ }
+
+ // is this a nested prefix heading?
+ case p.isPrefixHeading(chunk):
+ // if the heading is not indented, it is not nested in the list
+ // and thus ends the list
+ if containsBlankLine && indent < 4 {
+ *flags |= ListItemEndOfList
+ break gatherlines
+ }
+ *flags |= ListItemContainsBlock
+
+ // anything following an empty line is only part
+ // of this item if it is indented 4 spaces
+ // (regardless of the indentation of the beginning of the item)
+ case containsBlankLine && indent < 4:
+ if *flags&ListTypeDefinition != 0 && i < len(data)-1 {
+ // is the next item still a part of this list?
+ next := i
+ for next < len(data) && data[next] != '\n' {
+ next++
+ }
+ for next < len(data)-1 && data[next] == '\n' {
+ next++
+ }
+ if i < len(data)-1 && data[i] != ':' && data[next] != ':' {
+ *flags |= ListItemEndOfList
+ }
+ } else {
+ *flags |= ListItemEndOfList
+ }
+ break gatherlines
+
+ // a blank line means this should be parsed as a block
+ case containsBlankLine:
+ raw.WriteByte('\n')
+ *flags |= ListItemContainsBlock
+ }
+
+ // if this line was preceded by one or more blanks,
+ // re-introduce the blank into the buffer
+ if containsBlankLine {
+ containsBlankLine = false
+ raw.WriteByte('\n')
+ }
+
+ // add the line into the working buffer without prefix
+ raw.Write(data[line+indentIndex : i])
+
+ line = i
+ }
+
+ rawBytes := raw.Bytes()
+
+ block := p.addBlock(Item, nil)
+ block.ListFlags = *flags
+ block.Tight = false
+ block.BulletChar = bulletChar
+ block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark
+
+ // render the contents of the list item
+ if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 {
+ // intermediate render of block item, except for definition term
+ if sublist > 0 {
+ p.block(rawBytes[:sublist])
+ p.block(rawBytes[sublist:])
+ } else {
+ p.block(rawBytes)
+ }
+ } else {
+ // intermediate render of inline item
+ if sublist > 0 {
+ child := p.addChild(Paragraph, 0)
+ child.content = rawBytes[:sublist]
+ p.block(rawBytes[sublist:])
+ } else {
+ child := p.addChild(Paragraph, 0)
+ child.content = rawBytes
+ }
+ }
+ return line
+}
+
+// render a single paragraph that has already been parsed out
+func (p *Markdown) renderParagraph(data []byte) {
+ if len(data) == 0 {
+ return
+ }
+
+ // trim leading spaces
+ beg := 0
+ for data[beg] == ' ' {
+ beg++
+ }
+
+ end := len(data)
+ // trim trailing newline
+ if data[len(data)-1] == '\n' {
+ end--
+ }
+
+ // trim trailing spaces
+ for end > beg && data[end-1] == ' ' {
+ end--
+ }
+
+ p.addBlock(Paragraph, data[beg:end])
+}
+
+func (p *Markdown) paragraph(data []byte) int {
+ // prev: index of 1st char of previous line
+ // line: index of 1st char of current line
+ // i: index of cursor/end of current line
+ var prev, line, i int
+ tabSize := TabSizeDefault
+ if p.extensions&TabSizeEight != 0 {
+ tabSize = TabSizeDouble
+ }
+ // keep going until we find something to mark the end of the paragraph
+ for i < len(data) {
+ // mark the beginning of the current line
+ prev = line
+ current := data[i:]
+ line = i
+
+ // did we find a reference or a footnote? If so, end a paragraph
+ // preceding it and report that we have consumed up to the end of that
+ // reference:
+ if refEnd := isReference(p, current, tabSize); refEnd > 0 {
+ p.renderParagraph(data[:i])
+ return i + refEnd
+ }
+
+ // did we find a blank line marking the end of the paragraph?
+ if n := p.isEmpty(current); n > 0 {
+ // did this blank line followed by a definition list item?
+ if p.extensions&DefinitionLists != 0 {
+ if i < len(data)-1 && data[i+1] == ':' {
+ return p.list(data[prev:], ListTypeDefinition)
+ }
+ }
+
+ p.renderParagraph(data[:i])
+ return i + n
+ }
+
+ // an underline under some text marks a heading, so our paragraph ended on prev line
+ if i > 0 {
+ if level := p.isUnderlinedHeading(current); level > 0 {
+ // render the paragraph
+ p.renderParagraph(data[:prev])
+
+ // ignore leading and trailing whitespace
+ eol := i - 1
+ for prev < eol && data[prev] == ' ' {
+ prev++
+ }
+ for eol > prev && data[eol-1] == ' ' {
+ eol--
+ }
+
+ id := ""
+ if p.extensions&AutoHeadingIDs != 0 {
+ id = SanitizedAnchorName(string(data[prev:eol]))
+ }
+
+ block := p.addBlock(Heading, data[prev:eol])
+ block.Level = level
+ block.HeadingID = id
+
+ // find the end of the underline
+ for i < len(data) && data[i] != '\n' {
+ i++
+ }
+ return i
+ }
+ }
+
+ // if the next line starts a block of HTML, then the paragraph ends here
+ if p.extensions&LaxHTMLBlocks != 0 {
+ if data[i] == '<' && p.html(current, false) > 0 {
+ // rewind to before the HTML block
+ p.renderParagraph(data[:i])
+ return i
+ }
+ }
+
+ // if there's a prefixed heading or a horizontal rule after this, paragraph is over
+ if p.isPrefixHeading(current) || p.isHRule(current) {
+ p.renderParagraph(data[:i])
+ return i
+ }
+
+ // if there's a fenced code block, paragraph is over
+ if p.extensions&FencedCode != 0 {
+ if p.fencedCodeBlock(current, false) > 0 {
+ p.renderParagraph(data[:i])
+ return i
+ }
+ }
+
+ // if there's a definition list item, prev line is a definition term
+ if p.extensions&DefinitionLists != 0 {
+ if p.dliPrefix(current) != 0 {
+ ret := p.list(data[prev:], ListTypeDefinition)
+ return ret
+ }
+ }
+
+ // if there's a list after this, paragraph is over
+ if p.extensions&NoEmptyLineBeforeBlock != 0 {
+ if p.uliPrefix(current) != 0 ||
+ p.oliPrefix(current) != 0 ||
+ p.quotePrefix(current) != 0 ||
+ p.codePrefix(current) != 0 {
+ p.renderParagraph(data[:i])
+ return i
+ }
+ }
+
+ // otherwise, scan to the beginning of the next line
+ nl := bytes.IndexByte(data[i:], '\n')
+ if nl >= 0 {
+ i += nl + 1
+ } else {
+ i += len(data[i:])
+ }
+ }
+
+ p.renderParagraph(data[:i])
+ return i
+}
+
+func skipChar(data []byte, start int, char byte) int {
+ i := start
+ for i < len(data) && data[i] == char {
+ i++
+ }
+ return i
+}
+
+func skipUntilChar(text []byte, start int, char byte) int {
+ i := start
+ for i < len(text) && text[i] != char {
+ i++
+ }
+ return i
+}
+
+// SanitizedAnchorName returns a sanitized anchor name for the given text.
+//
+// It implements the algorithm specified in the package comment.
+func SanitizedAnchorName(text string) string {
+ var anchorName []rune
+ futureDash := false
+ for _, r := range text {
+ switch {
+ case unicode.IsLetter(r) || unicode.IsNumber(r):
+ if futureDash && len(anchorName) > 0 {
+ anchorName = append(anchorName, '-')
+ }
+ futureDash = false
+ anchorName = append(anchorName, unicode.ToLower(r))
+ default:
+ futureDash = true
+ }
+ }
+ return string(anchorName)
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/doc.go b/vendor/github.com/russross/blackfriday/v2/doc.go
new file mode 100644
index 000000000..57ff152a0
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/doc.go
@@ -0,0 +1,46 @@
+// Package blackfriday is a markdown processor.
+//
+// It translates plain text with simple formatting rules into an AST, which can
+// then be further processed to HTML (provided by Blackfriday itself) or other
+// formats (provided by the community).
+//
+// The simplest way to invoke Blackfriday is to call the Run function. It will
+// take a text input and produce a text output in HTML (or other format).
+//
+// A slightly more sophisticated way to use Blackfriday is to create a Markdown
+// processor and to call Parse, which returns a syntax tree for the input
+// document. You can leverage Blackfriday's parsing for content extraction from
+// markdown documents. You can assign a custom renderer and set various options
+// to the Markdown processor.
+//
+// If you're interested in calling Blackfriday from command line, see
+// https://github.com/russross/blackfriday-tool.
+//
+// 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 AutoHeadingIDs extension is enabled. The
+// algorithm is specified below, so that other packages can create
+// compatible anchor names and links to those anchors.
+//
+// The algorithm iterates over the input text, interpreted as UTF-8,
+// one Unicode code point (rune) at a time. All runes that are letters (category L)
+// or numbers (category N) are considered valid characters. They are mapped to
+// lower case, and included in the output. All other runes are considered
+// invalid characters. Invalid characters that precede the first valid character,
+// as well as invalid character that follow the last valid character
+// are dropped completely. All other sequences of invalid characters
+// between two valid characters are replaced with a single dash character '-'.
+//
+// SanitizedAnchorName exposes this functionality, and can be used to
+// create compatible links to the anchor names generated by blackfriday.
+// This algorithm is also implemented in a small standalone package at
+// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
+// that want a small package and don't need full functionality of blackfriday.
+package blackfriday
+
+// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
+// github.com/shurcooL/sanitized_anchor_name.
+// Otherwise, users of sanitized_anchor_name will get anchor names
+// that are incompatible with those generated by blackfriday.
diff --git a/vendor/github.com/russross/blackfriday/v2/entities.go b/vendor/github.com/russross/blackfriday/v2/entities.go
new file mode 100644
index 000000000..a2c3edb69
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/entities.go
@@ -0,0 +1,2236 @@
+package blackfriday
+
+// Extracted from https://html.spec.whatwg.org/multipage/entities.json
+var entities = map[string]bool{
+ "&AElig": true,
+ "&AElig;": true,
+ "&AMP": true,
+ "&AMP;": true,
+ "&Aacute": true,
+ "&Aacute;": true,
+ "&Abreve;": true,
+ "&Acirc": true,
+ "&Acirc;": true,
+ "&Acy;": true,
+ "&Afr;": true,
+ "&Agrave": true,
+ "&Agrave;": true,
+ "&Alpha;": true,
+ "&Amacr;": true,
+ "&And;": true,
+ "&Aogon;": true,
+ "&Aopf;": true,
+ "&ApplyFunction;": true,
+ "&Aring": true,
+ "&Aring;": true,
+ "&Ascr;": true,
+ "&Assign;": true,
+ "&Atilde": true,
+ "&Atilde;": true,
+ "&Auml": true,
+ "&Auml;": true,
+ "&Backslash;": true,
+ "&Barv;": true,
+ "&Barwed;": true,
+ "&Bcy;": true,
+ "&Because;": true,
+ "&Bernoullis;": true,
+ "&Beta;": true,
+ "&Bfr;": true,
+ "&Bopf;": true,
+ "&Breve;": true,
+ "&Bscr;": true,
+ "&Bumpeq;": true,
+ "&CHcy;": true,
+ "&COPY": true,
+ "&COPY;": true,
+ "&Cacute;": true,
+ "&Cap;": true,
+ "&CapitalDifferentialD;": true,
+ "&Cayleys;": true,
+ "&Ccaron;": true,
+ "&Ccedil": true,
+ "&Ccedil;": true,
+ "&Ccirc;": true,
+ "&Cconint;": true,
+ "&Cdot;": true,
+ "&Cedilla;": true,
+ "&CenterDot;": true,
+ "&Cfr;": true,
+ "&Chi;": true,
+ "&CircleDot;": true,
+ "&CircleMinus;": true,
+ "&CirclePlus;": true,
+ "&CircleTimes;": true,
+ "&ClockwiseContourIntegral;": true,
+ "&CloseCurlyDoubleQuote;": true,
+ "&CloseCurlyQuote;": true,
+ "&Colon;": true,
+ "&Colone;": true,
+ "&Congruent;": true,
+ "&Conint;": true,
+ "&ContourIntegral;": true,
+ "&Copf;": true,
+ "&Coproduct;": true,
+ "&CounterClockwiseContourIntegral;": true,
+ "&Cross;": true,
+ "&Cscr;": true,
+ "&Cup;": true,
+ "&CupCap;": true,
+ "&DD;": true,
+ "&DDotrahd;": true,
+ "&DJcy;": true,
+ "&DScy;": true,
+ "&DZcy;": true,
+ "&Dagger;": true,
+ "&Darr;": true,
+ "&Dashv;": true,
+ "&Dcaron;": true,
+ "&Dcy;": true,
+ "&Del;": true,
+ "&Delta;": true,
+ "&Dfr;": true,
+ "&DiacriticalAcute;": true,
+ "&DiacriticalDot;": true,
+ "&DiacriticalDoubleAcute;": true,
+ "&DiacriticalGrave;": true,
+ "&DiacriticalTilde;": true,
+ "&Diamond;": true,
+ "&DifferentialD;": true,
+ "&Dopf;": true,
+ "&Dot;": true,
+ "&DotDot;": true,
+ "&DotEqual;": true,
+ "&DoubleContourIntegral;": true,
+ "&DoubleDot;": true,
+ "&DoubleDownArrow;": true,
+ "&DoubleLeftArrow;": true,
+ "&DoubleLeftRightArrow;": true,
+ "&DoubleLeftTee;": true,
+ "&DoubleLongLeftArrow;": true,
+ "&DoubleLongLeftRightArrow;": true,
+ "&DoubleLongRightArrow;": true,
+ "&DoubleRightArrow;": true,
+ "&DoubleRightTee;": true,
+ "&DoubleUpArrow;": true,
+ "&DoubleUpDownArrow;": true,
+ "&DoubleVerticalBar;": true,
+ "&DownArrow;": true,
+ "&DownArrowBar;": true,
+ "&DownArrowUpArrow;": true,
+ "&DownBreve;": true,
+ "&DownLeftRightVector;": true,
+ "&DownLeftTeeVector;": true,
+ "&DownLeftVector;": true,
+ "&DownLeftVectorBar;": true,
+ "&DownRightTeeVector;": true,
+ "&DownRightVector;": true,
+ "&DownRightVectorBar;": true,
+ "&DownTee;": true,
+ "&DownTeeArrow;": true,
+ "&Downarrow;": true,
+ "&Dscr;": true,
+ "&Dstrok;": true,
+ "&ENG;": true,
+ "&ETH": true,
+ "&ETH;": true,
+ "&Eacute": true,
+ "&Eacute;": true,
+ "&Ecaron;": true,
+ "&Ecirc": true,
+ "&Ecirc;": true,
+ "&Ecy;": true,
+ "&Edot;": true,
+ "&Efr;": true,
+ "&Egrave": true,
+ "&Egrave;": true,
+ "&Element;": true,
+ "&Emacr;": true,
+ "&EmptySmallSquare;": true,
+ "&EmptyVerySmallSquare;": true,
+ "&Eogon;": true,
+ "&Eopf;": true,
+ "&Epsilon;": true,
+ "&Equal;": true,
+ "&EqualTilde;": true,
+ "&Equilibrium;": true,
+ "&Escr;": true,
+ "&Esim;": true,
+ "&Eta;": true,
+ "&Euml": true,
+ "&Euml;": true,
+ "&Exists;": true,
+ "&ExponentialE;": true,
+ "&Fcy;": true,
+ "&Ffr;": true,
+ "&FilledSmallSquare;": true,
+ "&FilledVerySmallSquare;": true,
+ "&Fopf;": true,
+ "&ForAll;": true,
+ "&Fouriertrf;": true,
+ "&Fscr;": true,
+ "&GJcy;": true,
+ "&GT": true,
+ "&GT;": true,
+ "&Gamma;": true,
+ "&Gammad;": true,
+ "&Gbreve;": true,
+ "&Gcedil;": true,
+ "&Gcirc;": true,
+ "&Gcy;": true,
+ "&Gdot;": true,
+ "&Gfr;": true,
+ "&Gg;": true,
+ "&Gopf;": true,
+ "&GreaterEqual;": true,
+ "&GreaterEqualLess;": true,
+ "&GreaterFullEqual;": true,
+ "&GreaterGreater;": true,
+ "&GreaterLess;": true,
+ "&GreaterSlantEqual;": true,
+ "&GreaterTilde;": true,
+ "&Gscr;": true,
+ "&Gt;": true,
+ "&HARDcy;": true,
+ "&Hacek;": true,
+ "&Hat;": true,
+ "&Hcirc;": true,
+ "&Hfr;": true,
+ "&HilbertSpace;": true,
+ "&Hopf;": true,
+ "&HorizontalLine;": true,
+ "&Hscr;": true,
+ "&Hstrok;": true,
+ "&HumpDownHump;": true,
+ "&HumpEqual;": true,
+ "&IEcy;": true,
+ "&IJlig;": true,
+ "&IOcy;": true,
+ "&Iacute": true,
+ "&Iacute;": true,
+ "&Icirc": true,
+ "&Icirc;": true,
+ "&Icy;": true,
+ "&Idot;": true,
+ "&Ifr;": true,
+ "&Igrave": true,
+ "&Igrave;": true,
+ "&Im;": true,
+ "&Imacr;": true,
+ "&ImaginaryI;": true,
+ "&Implies;": true,
+ "&Int;": true,
+ "&Integral;": true,
+ "&Intersection;": true,
+ "&InvisibleComma;": true,
+ "&InvisibleTimes;": true,
+ "&Iogon;": true,
+ "&Iopf;": true,
+ "&Iota;": true,
+ "&Iscr;": true,
+ "&Itilde;": true,
+ "&Iukcy;": true,
+ "&Iuml": true,
+ "&Iuml;": true,
+ "&Jcirc;": true,
+ "&Jcy;": true,
+ "&Jfr;": true,
+ "&Jopf;": true,
+ "&Jscr;": true,
+ "&Jsercy;": true,
+ "&Jukcy;": true,
+ "&KHcy;": true,
+ "&KJcy;": true,
+ "&Kappa;": true,
+ "&Kcedil;": true,
+ "&Kcy;": true,
+ "&Kfr;": true,
+ "&Kopf;": true,
+ "&Kscr;": true,
+ "&LJcy;": true,
+ "&LT": true,
+ "&LT;": true,
+ "&Lacute;": true,
+ "&Lambda;": true,
+ "&Lang;": true,
+ "&Laplacetrf;": true,
+ "&Larr;": true,
+ "&Lcaron;": true,
+ "&Lcedil;": true,
+ "&Lcy;": true,
+ "&LeftAngleBracket;": true,
+ "&LeftArrow;": true,
+ "&LeftArrowBar;": true,
+ "&LeftArrowRightArrow;": true,
+ "&LeftCeiling;": true,
+ "&LeftDoubleBracket;": true,
+ "&LeftDownTeeVector;": true,
+ "&LeftDownVector;": true,
+ "&LeftDownVectorBar;": true,
+ "&LeftFloor;": true,
+ "&LeftRightArrow;": true,
+ "&LeftRightVector;": true,
+ "&LeftTee;": true,
+ "&LeftTeeArrow;": true,
+ "&LeftTeeVector;": true,
+ "&LeftTriangle;": true,
+ "&LeftTriangleBar;": true,
+ "&LeftTriangleEqual;": true,
+ "&LeftUpDownVector;": true,
+ "&LeftUpTeeVector;": true,
+ "&LeftUpVector;": true,
+ "&LeftUpVectorBar;": true,
+ "&LeftVector;": true,
+ "&LeftVectorBar;": true,
+ "&Leftarrow;": true,
+ "&Leftrightarrow;": true,
+ "&LessEqualGreater;": true,
+ "&LessFullEqual;": true,
+ "&LessGreater;": true,
+ "&LessLess;": true,
+ "&LessSlantEqual;": true,
+ "&LessTilde;": true,
+ "&Lfr;": true,
+ "&Ll;": true,
+ "&Lleftarrow;": true,
+ "&Lmidot;": true,
+ "&LongLeftArrow;": true,
+ "&LongLeftRightArrow;": true,
+ "&LongRightArrow;": true,
+ "&Longleftarrow;": true,
+ "&Longleftrightarrow;": true,
+ "&Longrightarrow;": true,
+ "&Lopf;": true,
+ "&LowerLeftArrow;": true,
+ "&LowerRightArrow;": true,
+ "&Lscr;": true,
+ "&Lsh;": true,
+ "&Lstrok;": true,
+ "&Lt;": true,
+ "&Map;": true,
+ "&Mcy;": true,
+ "&MediumSpace;": true,
+ "&Mellintrf;": true,
+ "&Mfr;": true,
+ "&MinusPlus;": true,
+ "&Mopf;": true,
+ "&Mscr;": true,
+ "&Mu;": true,
+ "&NJcy;": true,
+ "&Nacute;": true,
+ "&Ncaron;": true,
+ "&Ncedil;": true,
+ "&Ncy;": true,
+ "&NegativeMediumSpace;": true,
+ "&NegativeThickSpace;": true,
+ "&NegativeThinSpace;": true,
+ "&NegativeVeryThinSpace;": true,
+ "&NestedGreaterGreater;": true,
+ "&NestedLessLess;": true,
+ "&NewLine;": true,
+ "&Nfr;": true,
+ "&NoBreak;": true,
+ "&NonBreakingSpace;": true,
+ "&Nopf;": true,
+ "&Not;": true,
+ "&NotCongruent;": true,
+ "&NotCupCap;": true,
+ "&NotDoubleVerticalBar;": true,
+ "&NotElement;": true,
+ "&NotEqual;": true,
+ "&NotEqualTilde;": true,
+ "&NotExists;": true,
+ "&NotGreater;": true,
+ "&NotGreaterEqual;": true,
+ "&NotGreaterFullEqual;": true,
+ "&NotGreaterGreater;": true,
+ "&NotGreaterLess;": true,
+ "&NotGreaterSlantEqual;": true,
+ "&NotGreaterTilde;": true,
+ "&NotHumpDownHump;": true,
+ "&NotHumpEqual;": true,
+ "&NotLeftTriangle;": true,
+ "&NotLeftTriangleBar;": true,
+ "&NotLeftTriangleEqual;": true,
+ "&NotLess;": true,
+ "&NotLessEqual;": true,
+ "&NotLessGreater;": true,
+ "&NotLessLess;": true,
+ "&NotLessSlantEqual;": true,
+ "&NotLessTilde;": true,
+ "&NotNestedGreaterGreater;": true,
+ "&NotNestedLessLess;": true,
+ "&NotPrecedes;": true,
+ "&NotPrecedesEqual;": true,
+ "&NotPrecedesSlantEqual;": true,
+ "&NotReverseElement;": true,
+ "&NotRightTriangle;": true,
+ "&NotRightTriangleBar;": true,
+ "&NotRightTriangleEqual;": true,
+ "&NotSquareSubset;": true,
+ "&NotSquareSubsetEqual;": true,
+ "&NotSquareSuperset;": true,
+ "&NotSquareSupersetEqual;": true,
+ "&NotSubset;": true,
+ "&NotSubsetEqual;": true,
+ "&NotSucceeds;": true,
+ "&NotSucceedsEqual;": true,
+ "&NotSucceedsSlantEqual;": true,
+ "&NotSucceedsTilde;": true,
+ "&NotSuperset;": true,
+ "&NotSupersetEqual;": true,
+ "&NotTilde;": true,
+ "&NotTildeEqual;": true,
+ "&NotTildeFullEqual;": true,
+ "&NotTildeTilde;": true,
+ "&NotVerticalBar;": true,
+ "&Nscr;": true,
+ "&Ntilde": true,
+ "&Ntilde;": true,
+ "&Nu;": true,
+ "&OElig;": true,
+ "&Oacute": true,
+ "&Oacute;": true,
+ "&Ocirc": true,
+ "&Ocirc;": true,
+ "&Ocy;": true,
+ "&Odblac;": true,
+ "&Ofr;": true,
+ "&Ograve": true,
+ "&Ograve;": true,
+ "&Omacr;": true,
+ "&Omega;": true,
+ "&Omicron;": true,
+ "&Oopf;": true,
+ "&OpenCurlyDoubleQuote;": true,
+ "&OpenCurlyQuote;": true,
+ "&Or;": true,
+ "&Oscr;": true,
+ "&Oslash": true,
+ "&Oslash;": true,
+ "&Otilde": true,
+ "&Otilde;": true,
+ "&Otimes;": true,
+ "&Ouml": true,
+ "&Ouml;": true,
+ "&OverBar;": true,
+ "&OverBrace;": true,
+ "&OverBracket;": true,
+ "&OverParenthesis;": true,
+ "&PartialD;": true,
+ "&Pcy;": true,
+ "&Pfr;": true,
+ "&Phi;": true,
+ "&Pi;": true,
+ "&PlusMinus;": true,
+ "&Poincareplane;": true,
+ "&Popf;": true,
+ "&Pr;": true,
+ "&Precedes;": true,
+ "&PrecedesEqual;": true,
+ "&PrecedesSlantEqual;": true,
+ "&PrecedesTilde;": true,
+ "&Prime;": true,
+ "&Product;": true,
+ "&Proportion;": true,
+ "&Proportional;": true,
+ "&Pscr;": true,
+ "&Psi;": true,
+ "&QUOT": true,
+ "&QUOT;": true,
+ "&Qfr;": true,
+ "&Qopf;": true,
+ "&Qscr;": true,
+ "&RBarr;": true,
+ "&REG": true,
+ "&REG;": true,
+ "&Racute;": true,
+ "&Rang;": true,
+ "&Rarr;": true,
+ "&Rarrtl;": true,
+ "&Rcaron;": true,
+ "&Rcedil;": true,
+ "&Rcy;": true,
+ "&Re;": true,
+ "&ReverseElement;": true,
+ "&ReverseEquilibrium;": true,
+ "&ReverseUpEquilibrium;": true,
+ "&Rfr;": true,
+ "&Rho;": true,
+ "&RightAngleBracket;": true,
+ "&RightArrow;": true,
+ "&RightArrowBar;": true,
+ "&RightArrowLeftArrow;": true,
+ "&RightCeiling;": true,
+ "&RightDoubleBracket;": true,
+ "&RightDownTeeVector;": true,
+ "&RightDownVector;": true,
+ "&RightDownVectorBar;": true,
+ "&RightFloor;": true,
+ "&RightTee;": true,
+ "&RightTeeArrow;": true,
+ "&RightTeeVector;": true,
+ "&RightTriangle;": true,
+ "&RightTriangleBar;": true,
+ "&RightTriangleEqual;": true,
+ "&RightUpDownVector;": true,
+ "&RightUpTeeVector;": true,
+ "&RightUpVector;": true,
+ "&RightUpVectorBar;": true,
+ "&RightVector;": true,
+ "&RightVectorBar;": true,
+ "&Rightarrow;": true,
+ "&Ropf;": true,
+ "&RoundImplies;": true,
+ "&Rrightarrow;": true,
+ "&Rscr;": true,
+ "&Rsh;": true,
+ "&RuleDelayed;": true,
+ "&SHCHcy;": true,
+ "&SHcy;": true,
+ "&SOFTcy;": true,
+ "&Sacute;": true,
+ "&Sc;": true,
+ "&Scaron;": true,
+ "&Scedil;": true,
+ "&Scirc;": true,
+ "&Scy;": true,
+ "&Sfr;": true,
+ "&ShortDownArrow;": true,
+ "&ShortLeftArrow;": true,
+ "&ShortRightArrow;": true,
+ "&ShortUpArrow;": true,
+ "&Sigma;": true,
+ "&SmallCircle;": true,
+ "&Sopf;": true,
+ "&Sqrt;": true,
+ "&Square;": true,
+ "&SquareIntersection;": true,
+ "&SquareSubset;": true,
+ "&SquareSubsetEqual;": true,
+ "&SquareSuperset;": true,
+ "&SquareSupersetEqual;": true,
+ "&SquareUnion;": true,
+ "&Sscr;": true,
+ "&Star;": true,
+ "&Sub;": true,
+ "&Subset;": true,
+ "&SubsetEqual;": true,
+ "&Succeeds;": true,
+ "&SucceedsEqual;": true,
+ "&SucceedsSlantEqual;": true,
+ "&SucceedsTilde;": true,
+ "&SuchThat;": true,
+ "&Sum;": true,
+ "&Sup;": true,
+ "&Superset;": true,
+ "&SupersetEqual;": true,
+ "&Supset;": true,
+ "&THORN": true,
+ "&THORN;": true,
+ "&TRADE;": true,
+ "&TSHcy;": true,
+ "&TScy;": true,
+ "&Tab;": true,
+ "&Tau;": true,
+ "&Tcaron;": true,
+ "&Tcedil;": true,
+ "&Tcy;": true,
+ "&Tfr;": true,
+ "&Therefore;": true,
+ "&Theta;": true,
+ "&ThickSpace;": true,
+ "&ThinSpace;": true,
+ "&Tilde;": true,
+ "&TildeEqual;": true,
+ "&TildeFullEqual;": true,
+ "&TildeTilde;": true,
+ "&Topf;": true,
+ "&TripleDot;": true,
+ "&Tscr;": true,
+ "&Tstrok;": true,
+ "&Uacute": true,
+ "&Uacute;": true,
+ "&Uarr;": true,
+ "&Uarrocir;": true,
+ "&Ubrcy;": true,
+ "&Ubreve;": true,
+ "&Ucirc": true,
+ "&Ucirc;": true,
+ "&Ucy;": true,
+ "&Udblac;": true,
+ "&Ufr;": true,
+ "&Ugrave": true,
+ "&Ugrave;": true,
+ "&Umacr;": true,
+ "&UnderBar;": true,
+ "&UnderBrace;": true,
+ "&UnderBracket;": true,
+ "&UnderParenthesis;": true,
+ "&Union;": true,
+ "&UnionPlus;": true,
+ "&Uogon;": true,
+ "&Uopf;": true,
+ "&UpArrow;": true,
+ "&UpArrowBar;": true,
+ "&UpArrowDownArrow;": true,
+ "&UpDownArrow;": true,
+ "&UpEquilibrium;": true,
+ "&UpTee;": true,
+ "&UpTeeArrow;": true,
+ "&Uparrow;": true,
+ "&Updownarrow;": true,
+ "&UpperLeftArrow;": true,
+ "&UpperRightArrow;": true,
+ "&Upsi;": true,
+ "&Upsilon;": true,
+ "&Uring;": true,
+ "&Uscr;": true,
+ "&Utilde;": true,
+ "&Uuml": true,
+ "&Uuml;": true,
+ "&VDash;": true,
+ "&Vbar;": true,
+ "&Vcy;": true,
+ "&Vdash;": true,
+ "&Vdashl;": true,
+ "&Vee;": true,
+ "&Verbar;": true,
+ "&Vert;": true,
+ "&VerticalBar;": true,
+ "&VerticalLine;": true,
+ "&VerticalSeparator;": true,
+ "&VerticalTilde;": true,
+ "&VeryThinSpace;": true,
+ "&Vfr;": true,
+ "&Vopf;": true,
+ "&Vscr;": true,
+ "&Vvdash;": true,
+ "&Wcirc;": true,
+ "&Wedge;": true,
+ "&Wfr;": true,
+ "&Wopf;": true,
+ "&Wscr;": true,
+ "&Xfr;": true,
+ "&Xi;": true,
+ "&Xopf;": true,
+ "&Xscr;": true,
+ "&YAcy;": true,
+ "&YIcy;": true,
+ "&YUcy;": true,
+ "&Yacute": true,
+ "&Yacute;": true,
+ "&Ycirc;": true,
+ "&Ycy;": true,
+ "&Yfr;": true,
+ "&Yopf;": true,
+ "&Yscr;": true,
+ "&Yuml;": true,
+ "&ZHcy;": true,
+ "&Zacute;": true,
+ "&Zcaron;": true,
+ "&Zcy;": true,
+ "&Zdot;": true,
+ "&ZeroWidthSpace;": true,
+ "&Zeta;": true,
+ "&Zfr;": true,
+ "&Zopf;": true,
+ "&Zscr;": true,
+ "&aacute": true,
+ "&aacute;": true,
+ "&abreve;": true,
+ "&ac;": true,
+ "&acE;": true,
+ "&acd;": true,
+ "&acirc": true,
+ "&acirc;": true,
+ "&acute": true,
+ "&acute;": true,
+ "&acy;": true,
+ "&aelig": true,
+ "&aelig;": true,
+ "&af;": true,
+ "&afr;": true,
+ "&agrave": true,
+ "&agrave;": true,
+ "&alefsym;": true,
+ "&aleph;": true,
+ "&alpha;": true,
+ "&amacr;": true,
+ "&amalg;": true,
+ "&amp": true,
+ "&amp;": true,
+ "&and;": true,
+ "&andand;": true,
+ "&andd;": true,
+ "&andslope;": true,
+ "&andv;": true,
+ "&ang;": true,
+ "&ange;": true,
+ "&angle;": true,
+ "&angmsd;": true,
+ "&angmsdaa;": true,
+ "&angmsdab;": true,
+ "&angmsdac;": true,
+ "&angmsdad;": true,
+ "&angmsdae;": true,
+ "&angmsdaf;": true,
+ "&angmsdag;": true,
+ "&angmsdah;": true,
+ "&angrt;": true,
+ "&angrtvb;": true,
+ "&angrtvbd;": true,
+ "&angsph;": true,
+ "&angst;": true,
+ "&angzarr;": true,
+ "&aogon;": true,
+ "&aopf;": true,
+ "&ap;": true,
+ "&apE;": true,
+ "&apacir;": true,
+ "&ape;": true,
+ "&apid;": true,
+ "&apos;": true,
+ "&approx;": true,
+ "&approxeq;": true,
+ "&aring": true,
+ "&aring;": true,
+ "&ascr;": true,
+ "&ast;": true,
+ "&asymp;": true,
+ "&asympeq;": true,
+ "&atilde": true,
+ "&atilde;": true,
+ "&auml": true,
+ "&auml;": true,
+ "&awconint;": true,
+ "&awint;": true,
+ "&bNot;": true,
+ "&backcong;": true,
+ "&backepsilon;": true,
+ "&backprime;": true,
+ "&backsim;": true,
+ "&backsimeq;": true,
+ "&barvee;": true,
+ "&barwed;": true,
+ "&barwedge;": true,
+ "&bbrk;": true,
+ "&bbrktbrk;": true,
+ "&bcong;": true,
+ "&bcy;": true,
+ "&bdquo;": true,
+ "&becaus;": true,
+ "&because;": true,
+ "&bemptyv;": true,
+ "&bepsi;": true,
+ "&bernou;": true,
+ "&beta;": true,
+ "&beth;": true,
+ "&between;": true,
+ "&bfr;": true,
+ "&bigcap;": true,
+ "&bigcirc;": true,
+ "&bigcup;": true,
+ "&bigodot;": true,
+ "&bigoplus;": true,
+ "&bigotimes;": true,
+ "&bigsqcup;": true,
+ "&bigstar;": true,
+ "&bigtriangledown;": true,
+ "&bigtriangleup;": true,
+ "&biguplus;": true,
+ "&bigvee;": true,
+ "&bigwedge;": true,
+ "&bkarow;": true,
+ "&blacklozenge;": true,
+ "&blacksquare;": true,
+ "&blacktriangle;": true,
+ "&blacktriangledown;": true,
+ "&blacktriangleleft;": true,
+ "&blacktriangleright;": true,
+ "&blank;": true,
+ "&blk12;": true,
+ "&blk14;": true,
+ "&blk34;": true,
+ "&block;": true,
+ "&bne;": true,
+ "&bnequiv;": true,
+ "&bnot;": true,
+ "&bopf;": true,
+ "&bot;": true,
+ "&bottom;": true,
+ "&bowtie;": true,
+ "&boxDL;": true,
+ "&boxDR;": true,
+ "&boxDl;": true,
+ "&boxDr;": true,
+ "&boxH;": true,
+ "&boxHD;": true,
+ "&boxHU;": true,
+ "&boxHd;": true,
+ "&boxHu;": true,
+ "&boxUL;": true,
+ "&boxUR;": true,
+ "&boxUl;": true,
+ "&boxUr;": true,
+ "&boxV;": true,
+ "&boxVH;": true,
+ "&boxVL;": true,
+ "&boxVR;": true,
+ "&boxVh;": true,
+ "&boxVl;": true,
+ "&boxVr;": true,
+ "&boxbox;": true,
+ "&boxdL;": true,
+ "&boxdR;": true,
+ "&boxdl;": true,
+ "&boxdr;": true,
+ "&boxh;": true,
+ "&boxhD;": true,
+ "&boxhU;": true,
+ "&boxhd;": true,
+ "&boxhu;": true,
+ "&boxminus;": true,
+ "&boxplus;": true,
+ "&boxtimes;": true,
+ "&boxuL;": true,
+ "&boxuR;": true,
+ "&boxul;": true,
+ "&boxur;": true,
+ "&boxv;": true,
+ "&boxvH;": true,
+ "&boxvL;": true,
+ "&boxvR;": true,
+ "&boxvh;": true,
+ "&boxvl;": true,
+ "&boxvr;": true,
+ "&bprime;": true,
+ "&breve;": true,
+ "&brvbar": true,
+ "&brvbar;": true,
+ "&bscr;": true,
+ "&bsemi;": true,
+ "&bsim;": true,
+ "&bsime;": true,
+ "&bsol;": true,
+ "&bsolb;": true,
+ "&bsolhsub;": true,
+ "&bull;": true,
+ "&bullet;": true,
+ "&bump;": true,
+ "&bumpE;": true,
+ "&bumpe;": true,
+ "&bumpeq;": true,
+ "&cacute;": true,
+ "&cap;": true,
+ "&capand;": true,
+ "&capbrcup;": true,
+ "&capcap;": true,
+ "&capcup;": true,
+ "&capdot;": true,
+ "&caps;": true,
+ "&caret;": true,
+ "&caron;": true,
+ "&ccaps;": true,
+ "&ccaron;": true,
+ "&ccedil": true,
+ "&ccedil;": true,
+ "&ccirc;": true,
+ "&ccups;": true,
+ "&ccupssm;": true,
+ "&cdot;": true,
+ "&cedil": true,
+ "&cedil;": true,
+ "&cemptyv;": true,
+ "&cent": true,
+ "&cent;": true,
+ "&centerdot;": true,
+ "&cfr;": true,
+ "&chcy;": true,
+ "&check;": true,
+ "&checkmark;": true,
+ "&chi;": true,
+ "&cir;": true,
+ "&cirE;": true,
+ "&circ;": true,
+ "&circeq;": true,
+ "&circlearrowleft;": true,
+ "&circlearrowright;": true,
+ "&circledR;": true,
+ "&circledS;": true,
+ "&circledast;": true,
+ "&circledcirc;": true,
+ "&circleddash;": true,
+ "&cire;": true,
+ "&cirfnint;": true,
+ "&cirmid;": true,
+ "&cirscir;": true,
+ "&clubs;": true,
+ "&clubsuit;": true,
+ "&colon;": true,
+ "&colone;": true,
+ "&coloneq;": true,
+ "&comma;": true,
+ "&commat;": true,
+ "&comp;": true,
+ "&compfn;": true,
+ "&complement;": true,
+ "&complexes;": true,
+ "&cong;": true,
+ "&congdot;": true,
+ "&conint;": true,
+ "&copf;": true,
+ "&coprod;": true,
+ "&copy": true,
+ "&copy;": true,
+ "&copysr;": true,
+ "&crarr;": true,
+ "&cross;": true,
+ "&cscr;": true,
+ "&csub;": true,
+ "&csube;": true,
+ "&csup;": true,
+ "&csupe;": true,
+ "&ctdot;": true,
+ "&cudarrl;": true,
+ "&cudarrr;": true,
+ "&cuepr;": true,
+ "&cuesc;": true,
+ "&cularr;": true,
+ "&cularrp;": true,
+ "&cup;": true,
+ "&cupbrcap;": true,
+ "&cupcap;": true,
+ "&cupcup;": true,
+ "&cupdot;": true,
+ "&cupor;": true,
+ "&cups;": true,
+ "&curarr;": true,
+ "&curarrm;": true,
+ "&curlyeqprec;": true,
+ "&curlyeqsucc;": true,
+ "&curlyvee;": true,
+ "&curlywedge;": true,
+ "&curren": true,
+ "&curren;": true,
+ "&curvearrowleft;": true,
+ "&curvearrowright;": true,
+ "&cuvee;": true,
+ "&cuwed;": true,
+ "&cwconint;": true,
+ "&cwint;": true,
+ "&cylcty;": true,
+ "&dArr;": true,
+ "&dHar;": true,
+ "&dagger;": true,
+ "&daleth;": true,
+ "&darr;": true,
+ "&dash;": true,
+ "&dashv;": true,
+ "&dbkarow;": true,
+ "&dblac;": true,
+ "&dcaron;": true,
+ "&dcy;": true,
+ "&dd;": true,
+ "&ddagger;": true,
+ "&ddarr;": true,
+ "&ddotseq;": true,
+ "&deg": true,
+ "&deg;": true,
+ "&delta;": true,
+ "&demptyv;": true,
+ "&dfisht;": true,
+ "&dfr;": true,
+ "&dharl;": true,
+ "&dharr;": true,
+ "&diam;": true,
+ "&diamond;": true,
+ "&diamondsuit;": true,
+ "&diams;": true,
+ "&die;": true,
+ "&digamma;": true,
+ "&disin;": true,
+ "&div;": true,
+ "&divide": true,
+ "&divide;": true,
+ "&divideontimes;": true,
+ "&divonx;": true,
+ "&djcy;": true,
+ "&dlcorn;": true,
+ "&dlcrop;": true,
+ "&dollar;": true,
+ "&dopf;": true,
+ "&dot;": true,
+ "&doteq;": true,
+ "&doteqdot;": true,
+ "&dotminus;": true,
+ "&dotplus;": true,
+ "&dotsquare;": true,
+ "&doublebarwedge;": true,
+ "&downarrow;": true,
+ "&downdownarrows;": true,
+ "&downharpoonleft;": true,
+ "&downharpoonright;": true,
+ "&drbkarow;": true,
+ "&drcorn;": true,
+ "&drcrop;": true,
+ "&dscr;": true,
+ "&dscy;": true,
+ "&dsol;": true,
+ "&dstrok;": true,
+ "&dtdot;": true,
+ "&dtri;": true,
+ "&dtrif;": true,
+ "&duarr;": true,
+ "&duhar;": true,
+ "&dwangle;": true,
+ "&dzcy;": true,
+ "&dzigrarr;": true,
+ "&eDDot;": true,
+ "&eDot;": true,
+ "&eacute": true,
+ "&eacute;": true,
+ "&easter;": true,
+ "&ecaron;": true,
+ "&ecir;": true,
+ "&ecirc": true,
+ "&ecirc;": true,
+ "&ecolon;": true,
+ "&ecy;": true,
+ "&edot;": true,
+ "&ee;": true,
+ "&efDot;": true,
+ "&efr;": true,
+ "&eg;": true,
+ "&egrave": true,
+ "&egrave;": true,
+ "&egs;": true,
+ "&egsdot;": true,
+ "&el;": true,
+ "&elinters;": true,
+ "&ell;": true,
+ "&els;": true,
+ "&elsdot;": true,
+ "&emacr;": true,
+ "&empty;": true,
+ "&emptyset;": true,
+ "&emptyv;": true,
+ "&emsp13;": true,
+ "&emsp14;": true,
+ "&emsp;": true,
+ "&eng;": true,
+ "&ensp;": true,
+ "&eogon;": true,
+ "&eopf;": true,
+ "&epar;": true,
+ "&eparsl;": true,
+ "&eplus;": true,
+ "&epsi;": true,
+ "&epsilon;": true,
+ "&epsiv;": true,
+ "&eqcirc;": true,
+ "&eqcolon;": true,
+ "&eqsim;": true,
+ "&eqslantgtr;": true,
+ "&eqslantless;": true,
+ "&equals;": true,
+ "&equest;": true,
+ "&equiv;": true,
+ "&equivDD;": true,
+ "&eqvparsl;": true,
+ "&erDot;": true,
+ "&erarr;": true,
+ "&escr;": true,
+ "&esdot;": true,
+ "&esim;": true,
+ "&eta;": true,
+ "&eth": true,
+ "&eth;": true,
+ "&euml": true,
+ "&euml;": true,
+ "&euro;": true,
+ "&excl;": true,
+ "&exist;": true,
+ "&expectation;": true,
+ "&exponentiale;": true,
+ "&fallingdotseq;": true,
+ "&fcy;": true,
+ "&female;": true,
+ "&ffilig;": true,
+ "&fflig;": true,
+ "&ffllig;": true,
+ "&ffr;": true,
+ "&filig;": true,
+ "&fjlig;": true,
+ "&flat;": true,
+ "&fllig;": true,
+ "&fltns;": true,
+ "&fnof;": true,
+ "&fopf;": true,
+ "&forall;": true,
+ "&fork;": true,
+ "&forkv;": true,
+ "&fpartint;": true,
+ "&frac12": true,
+ "&frac12;": true,
+ "&frac13;": true,
+ "&frac14": true,
+ "&frac14;": true,
+ "&frac15;": true,
+ "&frac16;": true,
+ "&frac18;": true,
+ "&frac23;": true,
+ "&frac25;": true,
+ "&frac34": true,
+ "&frac34;": true,
+ "&frac35;": true,
+ "&frac38;": true,
+ "&frac45;": true,
+ "&frac56;": true,
+ "&frac58;": true,
+ "&frac78;": true,
+ "&frasl;": true,
+ "&frown;": true,
+ "&fscr;": true,
+ "&gE;": true,
+ "&gEl;": true,
+ "&gacute;": true,
+ "&gamma;": true,
+ "&gammad;": true,
+ "&gap;": true,
+ "&gbreve;": true,
+ "&gcirc;": true,
+ "&gcy;": true,
+ "&gdot;": true,
+ "&ge;": true,
+ "&gel;": true,
+ "&geq;": true,
+ "&geqq;": true,
+ "&geqslant;": true,
+ "&ges;": true,
+ "&gescc;": true,
+ "&gesdot;": true,
+ "&gesdoto;": true,
+ "&gesdotol;": true,
+ "&gesl;": true,
+ "&gesles;": true,
+ "&gfr;": true,
+ "&gg;": true,
+ "&ggg;": true,
+ "&gimel;": true,
+ "&gjcy;": true,
+ "&gl;": true,
+ "&glE;": true,
+ "&gla;": true,
+ "&glj;": true,
+ "&gnE;": true,
+ "&gnap;": true,
+ "&gnapprox;": true,
+ "&gne;": true,
+ "&gneq;": true,
+ "&gneqq;": true,
+ "&gnsim;": true,
+ "&gopf;": true,
+ "&grave;": true,
+ "&gscr;": true,
+ "&gsim;": true,
+ "&gsime;": true,
+ "&gsiml;": true,
+ "&gt": true,
+ "&gt;": true,
+ "&gtcc;": true,
+ "&gtcir;": true,
+ "&gtdot;": true,
+ "&gtlPar;": true,
+ "&gtquest;": true,
+ "&gtrapprox;": true,
+ "&gtrarr;": true,
+ "&gtrdot;": true,
+ "&gtreqless;": true,
+ "&gtreqqless;": true,
+ "&gtrless;": true,
+ "&gtrsim;": true,
+ "&gvertneqq;": true,
+ "&gvnE;": true,
+ "&hArr;": true,
+ "&hairsp;": true,
+ "&half;": true,
+ "&hamilt;": true,
+ "&hardcy;": true,
+ "&harr;": true,
+ "&harrcir;": true,
+ "&harrw;": true,
+ "&hbar;": true,
+ "&hcirc;": true,
+ "&hearts;": true,
+ "&heartsuit;": true,
+ "&hellip;": true,
+ "&hercon;": true,
+ "&hfr;": true,
+ "&hksearow;": true,
+ "&hkswarow;": true,
+ "&hoarr;": true,
+ "&homtht;": true,
+ "&hookleftarrow;": true,
+ "&hookrightarrow;": true,
+ "&hopf;": true,
+ "&horbar;": true,
+ "&hscr;": true,
+ "&hslash;": true,
+ "&hstrok;": true,
+ "&hybull;": true,
+ "&hyphen;": true,
+ "&iacute": true,
+ "&iacute;": true,
+ "&ic;": true,
+ "&icirc": true,
+ "&icirc;": true,
+ "&icy;": true,
+ "&iecy;": true,
+ "&iexcl": true,
+ "&iexcl;": true,
+ "&iff;": true,
+ "&ifr;": true,
+ "&igrave": true,
+ "&igrave;": true,
+ "&ii;": true,
+ "&iiiint;": true,
+ "&iiint;": true,
+ "&iinfin;": true,
+ "&iiota;": true,
+ "&ijlig;": true,
+ "&imacr;": true,
+ "&image;": true,
+ "&imagline;": true,
+ "&imagpart;": true,
+ "&imath;": true,
+ "&imof;": true,
+ "&imped;": true,
+ "&in;": true,
+ "&incare;": true,
+ "&infin;": true,
+ "&infintie;": true,
+ "&inodot;": true,
+ "&int;": true,
+ "&intcal;": true,
+ "&integers;": true,
+ "&intercal;": true,
+ "&intlarhk;": true,
+ "&intprod;": true,
+ "&iocy;": true,
+ "&iogon;": true,
+ "&iopf;": true,
+ "&iota;": true,
+ "&iprod;": true,
+ "&iquest": true,
+ "&iquest;": true,
+ "&iscr;": true,
+ "&isin;": true,
+ "&isinE;": true,
+ "&isindot;": true,
+ "&isins;": true,
+ "&isinsv;": true,
+ "&isinv;": true,
+ "&it;": true,
+ "&itilde;": true,
+ "&iukcy;": true,
+ "&iuml": true,
+ "&iuml;": true,
+ "&jcirc;": true,
+ "&jcy;": true,
+ "&jfr;": true,
+ "&jmath;": true,
+ "&jopf;": true,
+ "&jscr;": true,
+ "&jsercy;": true,
+ "&jukcy;": true,
+ "&kappa;": true,
+ "&kappav;": true,
+ "&kcedil;": true,
+ "&kcy;": true,
+ "&kfr;": true,
+ "&kgreen;": true,
+ "&khcy;": true,
+ "&kjcy;": true,
+ "&kopf;": true,
+ "&kscr;": true,
+ "&lAarr;": true,
+ "&lArr;": true,
+ "&lAtail;": true,
+ "&lBarr;": true,
+ "&lE;": true,
+ "&lEg;": true,
+ "&lHar;": true,
+ "&lacute;": true,
+ "&laemptyv;": true,
+ "&lagran;": true,
+ "&lambda;": true,
+ "&lang;": true,
+ "&langd;": true,
+ "&langle;": true,
+ "&lap;": true,
+ "&laquo": true,
+ "&laquo;": true,
+ "&larr;": true,
+ "&larrb;": true,
+ "&larrbfs;": true,
+ "&larrfs;": true,
+ "&larrhk;": true,
+ "&larrlp;": true,
+ "&larrpl;": true,
+ "&larrsim;": true,
+ "&larrtl;": true,
+ "&lat;": true,
+ "&latail;": true,
+ "&late;": true,
+ "&lates;": true,
+ "&lbarr;": true,
+ "&lbbrk;": true,
+ "&lbrace;": true,
+ "&lbrack;": true,
+ "&lbrke;": true,
+ "&lbrksld;": true,
+ "&lbrkslu;": true,
+ "&lcaron;": true,
+ "&lcedil;": true,
+ "&lceil;": true,
+ "&lcub;": true,
+ "&lcy;": true,
+ "&ldca;": true,
+ "&ldquo;": true,
+ "&ldquor;": true,
+ "&ldrdhar;": true,
+ "&ldrushar;": true,
+ "&ldsh;": true,
+ "&le;": true,
+ "&leftarrow;": true,
+ "&leftarrowtail;": true,
+ "&leftharpoondown;": true,
+ "&leftharpoonup;": true,
+ "&leftleftarrows;": true,
+ "&leftrightarrow;": true,
+ "&leftrightarrows;": true,
+ "&leftrightharpoons;": true,
+ "&leftrightsquigarrow;": true,
+ "&leftthreetimes;": true,
+ "&leg;": true,
+ "&leq;": true,
+ "&leqq;": true,
+ "&leqslant;": true,
+ "&les;": true,
+ "&lescc;": true,
+ "&lesdot;": true,
+ "&lesdoto;": true,
+ "&lesdotor;": true,
+ "&lesg;": true,
+ "&lesges;": true,
+ "&lessapprox;": true,
+ "&lessdot;": true,
+ "&lesseqgtr;": true,
+ "&lesseqqgtr;": true,
+ "&lessgtr;": true,
+ "&lesssim;": true,
+ "&lfisht;": true,
+ "&lfloor;": true,
+ "&lfr;": true,
+ "&lg;": true,
+ "&lgE;": true,
+ "&lhard;": true,
+ "&lharu;": true,
+ "&lharul;": true,
+ "&lhblk;": true,
+ "&ljcy;": true,
+ "&ll;": true,
+ "&llarr;": true,
+ "&llcorner;": true,
+ "&llhard;": true,
+ "&lltri;": true,
+ "&lmidot;": true,
+ "&lmoust;": true,
+ "&lmoustache;": true,
+ "&lnE;": true,
+ "&lnap;": true,
+ "&lnapprox;": true,
+ "&lne;": true,
+ "&lneq;": true,
+ "&lneqq;": true,
+ "&lnsim;": true,
+ "&loang;": true,
+ "&loarr;": true,
+ "&lobrk;": true,
+ "&longleftarrow;": true,
+ "&longleftrightarrow;": true,
+ "&longmapsto;": true,
+ "&longrightarrow;": true,
+ "&looparrowleft;": true,
+ "&looparrowright;": true,
+ "&lopar;": true,
+ "&lopf;": true,
+ "&loplus;": true,
+ "&lotimes;": true,
+ "&lowast;": true,
+ "&lowbar;": true,
+ "&loz;": true,
+ "&lozenge;": true,
+ "&lozf;": true,
+ "&lpar;": true,
+ "&lparlt;": true,
+ "&lrarr;": true,
+ "&lrcorner;": true,
+ "&lrhar;": true,
+ "&lrhard;": true,
+ "&lrm;": true,
+ "&lrtri;": true,
+ "&lsaquo;": true,
+ "&lscr;": true,
+ "&lsh;": true,
+ "&lsim;": true,
+ "&lsime;": true,
+ "&lsimg;": true,
+ "&lsqb;": true,
+ "&lsquo;": true,
+ "&lsquor;": true,
+ "&lstrok;": true,
+ "&lt": true,
+ "&lt;": true,
+ "&ltcc;": true,
+ "&ltcir;": true,
+ "&ltdot;": true,
+ "&lthree;": true,
+ "&ltimes;": true,
+ "&ltlarr;": true,
+ "&ltquest;": true,
+ "&ltrPar;": true,
+ "&ltri;": true,
+ "&ltrie;": true,
+ "&ltrif;": true,
+ "&lurdshar;": true,
+ "&luruhar;": true,
+ "&lvertneqq;": true,
+ "&lvnE;": true,
+ "&mDDot;": true,
+ "&macr": true,
+ "&macr;": true,
+ "&male;": true,
+ "&malt;": true,
+ "&maltese;": true,
+ "&map;": true,
+ "&mapsto;": true,
+ "&mapstodown;": true,
+ "&mapstoleft;": true,
+ "&mapstoup;": true,
+ "&marker;": true,
+ "&mcomma;": true,
+ "&mcy;": true,
+ "&mdash;": true,
+ "&measuredangle;": true,
+ "&mfr;": true,
+ "&mho;": true,
+ "&micro": true,
+ "&micro;": true,
+ "&mid;": true,
+ "&midast;": true,
+ "&midcir;": true,
+ "&middot": true,
+ "&middot;": true,
+ "&minus;": true,
+ "&minusb;": true,
+ "&minusd;": true,
+ "&minusdu;": true,
+ "&mlcp;": true,
+ "&mldr;": true,
+ "&mnplus;": true,
+ "&models;": true,
+ "&mopf;": true,
+ "&mp;": true,
+ "&mscr;": true,
+ "&mstpos;": true,
+ "&mu;": true,
+ "&multimap;": true,
+ "&mumap;": true,
+ "&nGg;": true,
+ "&nGt;": true,
+ "&nGtv;": true,
+ "&nLeftarrow;": true,
+ "&nLeftrightarrow;": true,
+ "&nLl;": true,
+ "&nLt;": true,
+ "&nLtv;": true,
+ "&nRightarrow;": true,
+ "&nVDash;": true,
+ "&nVdash;": true,
+ "&nabla;": true,
+ "&nacute;": true,
+ "&nang;": true,
+ "&nap;": true,
+ "&napE;": true,
+ "&napid;": true,
+ "&napos;": true,
+ "&napprox;": true,
+ "&natur;": true,
+ "&natural;": true,
+ "&naturals;": true,
+ "&nbsp": true,
+ "&nbsp;": true,
+ "&nbump;": true,
+ "&nbumpe;": true,
+ "&ncap;": true,
+ "&ncaron;": true,
+ "&ncedil;": true,
+ "&ncong;": true,
+ "&ncongdot;": true,
+ "&ncup;": true,
+ "&ncy;": true,
+ "&ndash;": true,
+ "&ne;": true,
+ "&neArr;": true,
+ "&nearhk;": true,
+ "&nearr;": true,
+ "&nearrow;": true,
+ "&nedot;": true,
+ "&nequiv;": true,
+ "&nesear;": true,
+ "&nesim;": true,
+ "&nexist;": true,
+ "&nexists;": true,
+ "&nfr;": true,
+ "&ngE;": true,
+ "&nge;": true,
+ "&ngeq;": true,
+ "&ngeqq;": true,
+ "&ngeqslant;": true,
+ "&nges;": true,
+ "&ngsim;": true,
+ "&ngt;": true,
+ "&ngtr;": true,
+ "&nhArr;": true,
+ "&nharr;": true,
+ "&nhpar;": true,
+ "&ni;": true,
+ "&nis;": true,
+ "&nisd;": true,
+ "&niv;": true,
+ "&njcy;": true,
+ "&nlArr;": true,
+ "&nlE;": true,
+ "&nlarr;": true,
+ "&nldr;": true,
+ "&nle;": true,
+ "&nleftarrow;": true,
+ "&nleftrightarrow;": true,
+ "&nleq;": true,
+ "&nleqq;": true,
+ "&nleqslant;": true,
+ "&nles;": true,
+ "&nless;": true,
+ "&nlsim;": true,
+ "&nlt;": true,
+ "&nltri;": true,
+ "&nltrie;": true,
+ "&nmid;": true,
+ "&nopf;": true,
+ "&not": true,
+ "&not;": true,
+ "&notin;": true,
+ "&notinE;": true,
+ "&notindot;": true,
+ "&notinva;": true,
+ "&notinvb;": true,
+ "&notinvc;": true,
+ "&notni;": true,
+ "&notniva;": true,
+ "&notnivb;": true,
+ "&notnivc;": true,
+ "&npar;": true,
+ "&nparallel;": true,
+ "&nparsl;": true,
+ "&npart;": true,
+ "&npolint;": true,
+ "&npr;": true,
+ "&nprcue;": true,
+ "&npre;": true,
+ "&nprec;": true,
+ "&npreceq;": true,
+ "&nrArr;": true,
+ "&nrarr;": true,
+ "&nrarrc;": true,
+ "&nrarrw;": true,
+ "&nrightarrow;": true,
+ "&nrtri;": true,
+ "&nrtrie;": true,
+ "&nsc;": true,
+ "&nsccue;": true,
+ "&nsce;": true,
+ "&nscr;": true,
+ "&nshortmid;": true,
+ "&nshortparallel;": true,
+ "&nsim;": true,
+ "&nsime;": true,
+ "&nsimeq;": true,
+ "&nsmid;": true,
+ "&nspar;": true,
+ "&nsqsube;": true,
+ "&nsqsupe;": true,
+ "&nsub;": true,
+ "&nsubE;": true,
+ "&nsube;": true,
+ "&nsubset;": true,
+ "&nsubseteq;": true,
+ "&nsubseteqq;": true,
+ "&nsucc;": true,
+ "&nsucceq;": true,
+ "&nsup;": true,
+ "&nsupE;": true,
+ "&nsupe;": true,
+ "&nsupset;": true,
+ "&nsupseteq;": true,
+ "&nsupseteqq;": true,
+ "&ntgl;": true,
+ "&ntilde": true,
+ "&ntilde;": true,
+ "&ntlg;": true,
+ "&ntriangleleft;": true,
+ "&ntrianglelefteq;": true,
+ "&ntriangleright;": true,
+ "&ntrianglerighteq;": true,
+ "&nu;": true,
+ "&num;": true,
+ "&numero;": true,
+ "&numsp;": true,
+ "&nvDash;": true,
+ "&nvHarr;": true,
+ "&nvap;": true,
+ "&nvdash;": true,
+ "&nvge;": true,
+ "&nvgt;": true,
+ "&nvinfin;": true,
+ "&nvlArr;": true,
+ "&nvle;": true,
+ "&nvlt;": true,
+ "&nvltrie;": true,
+ "&nvrArr;": true,
+ "&nvrtrie;": true,
+ "&nvsim;": true,
+ "&nwArr;": true,
+ "&nwarhk;": true,
+ "&nwarr;": true,
+ "&nwarrow;": true,
+ "&nwnear;": true,
+ "&oS;": true,
+ "&oacute": true,
+ "&oacute;": true,
+ "&oast;": true,
+ "&ocir;": true,
+ "&ocirc": true,
+ "&ocirc;": true,
+ "&ocy;": true,
+ "&odash;": true,
+ "&odblac;": true,
+ "&odiv;": true,
+ "&odot;": true,
+ "&odsold;": true,
+ "&oelig;": true,
+ "&ofcir;": true,
+ "&ofr;": true,
+ "&ogon;": true,
+ "&ograve": true,
+ "&ograve;": true,
+ "&ogt;": true,
+ "&ohbar;": true,
+ "&ohm;": true,
+ "&oint;": true,
+ "&olarr;": true,
+ "&olcir;": true,
+ "&olcross;": true,
+ "&oline;": true,
+ "&olt;": true,
+ "&omacr;": true,
+ "&omega;": true,
+ "&omicron;": true,
+ "&omid;": true,
+ "&ominus;": true,
+ "&oopf;": true,
+ "&opar;": true,
+ "&operp;": true,
+ "&oplus;": true,
+ "&or;": true,
+ "&orarr;": true,
+ "&ord;": true,
+ "&order;": true,
+ "&orderof;": true,
+ "&ordf": true,
+ "&ordf;": true,
+ "&ordm": true,
+ "&ordm;": true,
+ "&origof;": true,
+ "&oror;": true,
+ "&orslope;": true,
+ "&orv;": true,
+ "&oscr;": true,
+ "&oslash": true,
+ "&oslash;": true,
+ "&osol;": true,
+ "&otilde": true,
+ "&otilde;": true,
+ "&otimes;": true,
+ "&otimesas;": true,
+ "&ouml": true,
+ "&ouml;": true,
+ "&ovbar;": true,
+ "&par;": true,
+ "&para": true,
+ "&para;": true,
+ "&parallel;": true,
+ "&parsim;": true,
+ "&parsl;": true,
+ "&part;": true,
+ "&pcy;": true,
+ "&percnt;": true,
+ "&period;": true,
+ "&permil;": true,
+ "&perp;": true,
+ "&pertenk;": true,
+ "&pfr;": true,
+ "&phi;": true,
+ "&phiv;": true,
+ "&phmmat;": true,
+ "&phone;": true,
+ "&pi;": true,
+ "&pitchfork;": true,
+ "&piv;": true,
+ "&planck;": true,
+ "&planckh;": true,
+ "&plankv;": true,
+ "&plus;": true,
+ "&plusacir;": true,
+ "&plusb;": true,
+ "&pluscir;": true,
+ "&plusdo;": true,
+ "&plusdu;": true,
+ "&pluse;": true,
+ "&plusmn": true,
+ "&plusmn;": true,
+ "&plussim;": true,
+ "&plustwo;": true,
+ "&pm;": true,
+ "&pointint;": true,
+ "&popf;": true,
+ "&pound": true,
+ "&pound;": true,
+ "&pr;": true,
+ "&prE;": true,
+ "&prap;": true,
+ "&prcue;": true,
+ "&pre;": true,
+ "&prec;": true,
+ "&precapprox;": true,
+ "&preccurlyeq;": true,
+ "&preceq;": true,
+ "&precnapprox;": true,
+ "&precneqq;": true,
+ "&precnsim;": true,
+ "&precsim;": true,
+ "&prime;": true,
+ "&primes;": true,
+ "&prnE;": true,
+ "&prnap;": true,
+ "&prnsim;": true,
+ "&prod;": true,
+ "&profalar;": true,
+ "&profline;": true,
+ "&profsurf;": true,
+ "&prop;": true,
+ "&propto;": true,
+ "&prsim;": true,
+ "&prurel;": true,
+ "&pscr;": true,
+ "&psi;": true,
+ "&puncsp;": true,
+ "&qfr;": true,
+ "&qint;": true,
+ "&qopf;": true,
+ "&qprime;": true,
+ "&qscr;": true,
+ "&quaternions;": true,
+ "&quatint;": true,
+ "&quest;": true,
+ "&questeq;": true,
+ "&quot": true,
+ "&quot;": true,
+ "&rAarr;": true,
+ "&rArr;": true,
+ "&rAtail;": true,
+ "&rBarr;": true,
+ "&rHar;": true,
+ "&race;": true,
+ "&racute;": true,
+ "&radic;": true,
+ "&raemptyv;": true,
+ "&rang;": true,
+ "&rangd;": true,
+ "&range;": true,
+ "&rangle;": true,
+ "&raquo": true,
+ "&raquo;": true,
+ "&rarr;": true,
+ "&rarrap;": true,
+ "&rarrb;": true,
+ "&rarrbfs;": true,
+ "&rarrc;": true,
+ "&rarrfs;": true,
+ "&rarrhk;": true,
+ "&rarrlp;": true,
+ "&rarrpl;": true,
+ "&rarrsim;": true,
+ "&rarrtl;": true,
+ "&rarrw;": true,
+ "&ratail;": true,
+ "&ratio;": true,
+ "&rationals;": true,
+ "&rbarr;": true,
+ "&rbbrk;": true,
+ "&rbrace;": true,
+ "&rbrack;": true,
+ "&rbrke;": true,
+ "&rbrksld;": true,
+ "&rbrkslu;": true,
+ "&rcaron;": true,
+ "&rcedil;": true,
+ "&rceil;": true,
+ "&rcub;": true,
+ "&rcy;": true,
+ "&rdca;": true,
+ "&rdldhar;": true,
+ "&rdquo;": true,
+ "&rdquor;": true,
+ "&rdsh;": true,
+ "&real;": true,
+ "&realine;": true,
+ "&realpart;": true,
+ "&reals;": true,
+ "&rect;": true,
+ "&reg": true,
+ "&reg;": true,
+ "&rfisht;": true,
+ "&rfloor;": true,
+ "&rfr;": true,
+ "&rhard;": true,
+ "&rharu;": true,
+ "&rharul;": true,
+ "&rho;": true,
+ "&rhov;": true,
+ "&rightarrow;": true,
+ "&rightarrowtail;": true,
+ "&rightharpoondown;": true,
+ "&rightharpoonup;": true,
+ "&rightleftarrows;": true,
+ "&rightleftharpoons;": true,
+ "&rightrightarrows;": true,
+ "&rightsquigarrow;": true,
+ "&rightthreetimes;": true,
+ "&ring;": true,
+ "&risingdotseq;": true,
+ "&rlarr;": true,
+ "&rlhar;": true,
+ "&rlm;": true,
+ "&rmoust;": true,
+ "&rmoustache;": true,
+ "&rnmid;": true,
+ "&roang;": true,
+ "&roarr;": true,
+ "&robrk;": true,
+ "&ropar;": true,
+ "&ropf;": true,
+ "&roplus;": true,
+ "&rotimes;": true,
+ "&rpar;": true,
+ "&rpargt;": true,
+ "&rppolint;": true,
+ "&rrarr;": true,
+ "&rsaquo;": true,
+ "&rscr;": true,
+ "&rsh;": true,
+ "&rsqb;": true,
+ "&rsquo;": true,
+ "&rsquor;": true,
+ "&rthree;": true,
+ "&rtimes;": true,
+ "&rtri;": true,
+ "&rtrie;": true,
+ "&rtrif;": true,
+ "&rtriltri;": true,
+ "&ruluhar;": true,
+ "&rx;": true,
+ "&sacute;": true,
+ "&sbquo;": true,
+ "&sc;": true,
+ "&scE;": true,
+ "&scap;": true,
+ "&scaron;": true,
+ "&sccue;": true,
+ "&sce;": true,
+ "&scedil;": true,
+ "&scirc;": true,
+ "&scnE;": true,
+ "&scnap;": true,
+ "&scnsim;": true,
+ "&scpolint;": true,
+ "&scsim;": true,
+ "&scy;": true,
+ "&sdot;": true,
+ "&sdotb;": true,
+ "&sdote;": true,
+ "&seArr;": true,
+ "&searhk;": true,
+ "&searr;": true,
+ "&searrow;": true,
+ "&sect": true,
+ "&sect;": true,
+ "&semi;": true,
+ "&seswar;": true,
+ "&setminus;": true,
+ "&setmn;": true,
+ "&sext;": true,
+ "&sfr;": true,
+ "&sfrown;": true,
+ "&sharp;": true,
+ "&shchcy;": true,
+ "&shcy;": true,
+ "&shortmid;": true,
+ "&shortparallel;": true,
+ "&shy": true,
+ "&shy;": true,
+ "&sigma;": true,
+ "&sigmaf;": true,
+ "&sigmav;": true,
+ "&sim;": true,
+ "&simdot;": true,
+ "&sime;": true,
+ "&simeq;": true,
+ "&simg;": true,
+ "&simgE;": true,
+ "&siml;": true,
+ "&simlE;": true,
+ "&simne;": true,
+ "&simplus;": true,
+ "&simrarr;": true,
+ "&slarr;": true,
+ "&smallsetminus;": true,
+ "&smashp;": true,
+ "&smeparsl;": true,
+ "&smid;": true,
+ "&smile;": true,
+ "&smt;": true,
+ "&smte;": true,
+ "&smtes;": true,
+ "&softcy;": true,
+ "&sol;": true,
+ "&solb;": true,
+ "&solbar;": true,
+ "&sopf;": true,
+ "&spades;": true,
+ "&spadesuit;": true,
+ "&spar;": true,
+ "&sqcap;": true,
+ "&sqcaps;": true,
+ "&sqcup;": true,
+ "&sqcups;": true,
+ "&sqsub;": true,
+ "&sqsube;": true,
+ "&sqsubset;": true,
+ "&sqsubseteq;": true,
+ "&sqsup;": true,
+ "&sqsupe;": true,
+ "&sqsupset;": true,
+ "&sqsupseteq;": true,
+ "&squ;": true,
+ "&square;": true,
+ "&squarf;": true,
+ "&squf;": true,
+ "&srarr;": true,
+ "&sscr;": true,
+ "&ssetmn;": true,
+ "&ssmile;": true,
+ "&sstarf;": true,
+ "&star;": true,
+ "&starf;": true,
+ "&straightepsilon;": true,
+ "&straightphi;": true,
+ "&strns;": true,
+ "&sub;": true,
+ "&subE;": true,
+ "&subdot;": true,
+ "&sube;": true,
+ "&subedot;": true,
+ "&submult;": true,
+ "&subnE;": true,
+ "&subne;": true,
+ "&subplus;": true,
+ "&subrarr;": true,
+ "&subset;": true,
+ "&subseteq;": true,
+ "&subseteqq;": true,
+ "&subsetneq;": true,
+ "&subsetneqq;": true,
+ "&subsim;": true,
+ "&subsub;": true,
+ "&subsup;": true,
+ "&succ;": true,
+ "&succapprox;": true,
+ "&succcurlyeq;": true,
+ "&succeq;": true,
+ "&succnapprox;": true,
+ "&succneqq;": true,
+ "&succnsim;": true,
+ "&succsim;": true,
+ "&sum;": true,
+ "&sung;": true,
+ "&sup1": true,
+ "&sup1;": true,
+ "&sup2": true,
+ "&sup2;": true,
+ "&sup3": true,
+ "&sup3;": true,
+ "&sup;": true,
+ "&supE;": true,
+ "&supdot;": true,
+ "&supdsub;": true,
+ "&supe;": true,
+ "&supedot;": true,
+ "&suphsol;": true,
+ "&suphsub;": true,
+ "&suplarr;": true,
+ "&supmult;": true,
+ "&supnE;": true,
+ "&supne;": true,
+ "&supplus;": true,
+ "&supset;": true,
+ "&supseteq;": true,
+ "&supseteqq;": true,
+ "&supsetneq;": true,
+ "&supsetneqq;": true,
+ "&supsim;": true,
+ "&supsub;": true,
+ "&supsup;": true,
+ "&swArr;": true,
+ "&swarhk;": true,
+ "&swarr;": true,
+ "&swarrow;": true,
+ "&swnwar;": true,
+ "&szlig": true,
+ "&szlig;": true,
+ "&target;": true,
+ "&tau;": true,
+ "&tbrk;": true,
+ "&tcaron;": true,
+ "&tcedil;": true,
+ "&tcy;": true,
+ "&tdot;": true,
+ "&telrec;": true,
+ "&tfr;": true,
+ "&there4;": true,
+ "&therefore;": true,
+ "&theta;": true,
+ "&thetasym;": true,
+ "&thetav;": true,
+ "&thickapprox;": true,
+ "&thicksim;": true,
+ "&thinsp;": true,
+ "&thkap;": true,
+ "&thksim;": true,
+ "&thorn": true,
+ "&thorn;": true,
+ "&tilde;": true,
+ "&times": true,
+ "&times;": true,
+ "&timesb;": true,
+ "&timesbar;": true,
+ "&timesd;": true,
+ "&tint;": true,
+ "&toea;": true,
+ "&top;": true,
+ "&topbot;": true,
+ "&topcir;": true,
+ "&topf;": true,
+ "&topfork;": true,
+ "&tosa;": true,
+ "&tprime;": true,
+ "&trade;": true,
+ "&triangle;": true,
+ "&triangledown;": true,
+ "&triangleleft;": true,
+ "&trianglelefteq;": true,
+ "&triangleq;": true,
+ "&triangleright;": true,
+ "&trianglerighteq;": true,
+ "&tridot;": true,
+ "&trie;": true,
+ "&triminus;": true,
+ "&triplus;": true,
+ "&trisb;": true,
+ "&tritime;": true,
+ "&trpezium;": true,
+ "&tscr;": true,
+ "&tscy;": true,
+ "&tshcy;": true,
+ "&tstrok;": true,
+ "&twixt;": true,
+ "&twoheadleftarrow;": true,
+ "&twoheadrightarrow;": true,
+ "&uArr;": true,
+ "&uHar;": true,
+ "&uacute": true,
+ "&uacute;": true,
+ "&uarr;": true,
+ "&ubrcy;": true,
+ "&ubreve;": true,
+ "&ucirc": true,
+ "&ucirc;": true,
+ "&ucy;": true,
+ "&udarr;": true,
+ "&udblac;": true,
+ "&udhar;": true,
+ "&ufisht;": true,
+ "&ufr;": true,
+ "&ugrave": true,
+ "&ugrave;": true,
+ "&uharl;": true,
+ "&uharr;": true,
+ "&uhblk;": true,
+ "&ulcorn;": true,
+ "&ulcorner;": true,
+ "&ulcrop;": true,
+ "&ultri;": true,
+ "&umacr;": true,
+ "&uml": true,
+ "&uml;": true,
+ "&uogon;": true,
+ "&uopf;": true,
+ "&uparrow;": true,
+ "&updownarrow;": true,
+ "&upharpoonleft;": true,
+ "&upharpoonright;": true,
+ "&uplus;": true,
+ "&upsi;": true,
+ "&upsih;": true,
+ "&upsilon;": true,
+ "&upuparrows;": true,
+ "&urcorn;": true,
+ "&urcorner;": true,
+ "&urcrop;": true,
+ "&uring;": true,
+ "&urtri;": true,
+ "&uscr;": true,
+ "&utdot;": true,
+ "&utilde;": true,
+ "&utri;": true,
+ "&utrif;": true,
+ "&uuarr;": true,
+ "&uuml": true,
+ "&uuml;": true,
+ "&uwangle;": true,
+ "&vArr;": true,
+ "&vBar;": true,
+ "&vBarv;": true,
+ "&vDash;": true,
+ "&vangrt;": true,
+ "&varepsilon;": true,
+ "&varkappa;": true,
+ "&varnothing;": true,
+ "&varphi;": true,
+ "&varpi;": true,
+ "&varpropto;": true,
+ "&varr;": true,
+ "&varrho;": true,
+ "&varsigma;": true,
+ "&varsubsetneq;": true,
+ "&varsubsetneqq;": true,
+ "&varsupsetneq;": true,
+ "&varsupsetneqq;": true,
+ "&vartheta;": true,
+ "&vartriangleleft;": true,
+ "&vartriangleright;": true,
+ "&vcy;": true,
+ "&vdash;": true,
+ "&vee;": true,
+ "&veebar;": true,
+ "&veeeq;": true,
+ "&vellip;": true,
+ "&verbar;": true,
+ "&vert;": true,
+ "&vfr;": true,
+ "&vltri;": true,
+ "&vnsub;": true,
+ "&vnsup;": true,
+ "&vopf;": true,
+ "&vprop;": true,
+ "&vrtri;": true,
+ "&vscr;": true,
+ "&vsubnE;": true,
+ "&vsubne;": true,
+ "&vsupnE;": true,
+ "&vsupne;": true,
+ "&vzigzag;": true,
+ "&wcirc;": true,
+ "&wedbar;": true,
+ "&wedge;": true,
+ "&wedgeq;": true,
+ "&weierp;": true,
+ "&wfr;": true,
+ "&wopf;": true,
+ "&wp;": true,
+ "&wr;": true,
+ "&wreath;": true,
+ "&wscr;": true,
+ "&xcap;": true,
+ "&xcirc;": true,
+ "&xcup;": true,
+ "&xdtri;": true,
+ "&xfr;": true,
+ "&xhArr;": true,
+ "&xharr;": true,
+ "&xi;": true,
+ "&xlArr;": true,
+ "&xlarr;": true,
+ "&xmap;": true,
+ "&xnis;": true,
+ "&xodot;": true,
+ "&xopf;": true,
+ "&xoplus;": true,
+ "&xotime;": true,
+ "&xrArr;": true,
+ "&xrarr;": true,
+ "&xscr;": true,
+ "&xsqcup;": true,
+ "&xuplus;": true,
+ "&xutri;": true,
+ "&xvee;": true,
+ "&xwedge;": true,
+ "&yacute": true,
+ "&yacute;": true,
+ "&yacy;": true,
+ "&ycirc;": true,
+ "&ycy;": true,
+ "&yen": true,
+ "&yen;": true,
+ "&yfr;": true,
+ "&yicy;": true,
+ "&yopf;": true,
+ "&yscr;": true,
+ "&yucy;": true,
+ "&yuml": true,
+ "&yuml;": true,
+ "&zacute;": true,
+ "&zcaron;": true,
+ "&zcy;": true,
+ "&zdot;": true,
+ "&zeetrf;": true,
+ "&zeta;": true,
+ "&zfr;": true,
+ "&zhcy;": true,
+ "&zigrarr;": true,
+ "&zopf;": true,
+ "&zscr;": true,
+ "&zwj;": true,
+ "&zwnj;": true,
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/esc.go b/vendor/github.com/russross/blackfriday/v2/esc.go
new file mode 100644
index 000000000..6ab60102c
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/esc.go
@@ -0,0 +1,70 @@
+package blackfriday
+
+import (
+ "html"
+ "io"
+)
+
+var htmlEscaper = [256][]byte{
+ '&': []byte("&amp;"),
+ '<': []byte("&lt;"),
+ '>': []byte("&gt;"),
+ '"': []byte("&quot;"),
+}
+
+func escapeHTML(w io.Writer, s []byte) {
+ escapeEntities(w, s, false)
+}
+
+func escapeAllHTML(w io.Writer, s []byte) {
+ escapeEntities(w, s, true)
+}
+
+func escapeEntities(w io.Writer, s []byte, escapeValidEntities bool) {
+ var start, end int
+ for end < len(s) {
+ escSeq := htmlEscaper[s[end]]
+ if escSeq != nil {
+ isEntity, entityEnd := nodeIsEntity(s, end)
+ if isEntity && !escapeValidEntities {
+ w.Write(s[start : entityEnd+1])
+ start = entityEnd + 1
+ } else {
+ 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 nodeIsEntity(s []byte, end int) (isEntity bool, endEntityPos int) {
+ isEntity = false
+ endEntityPos = end + 1
+
+ if s[end] == '&' {
+ for endEntityPos < len(s) {
+ if s[endEntityPos] == ';' {
+ if entities[string(s[end:endEntityPos+1])] {
+ isEntity = true
+ break
+ }
+ }
+ if !isalnum(s[endEntityPos]) && s[endEntityPos] != '&' && s[endEntityPos] != '#' {
+ break
+ }
+ endEntityPos++
+ }
+ }
+
+ return isEntity, endEntityPos
+}
+
+func escLink(w io.Writer, text []byte) {
+ unesc := html.UnescapeString(string(text))
+ escapeHTML(w, []byte(unesc))
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/go.mod b/vendor/github.com/russross/blackfriday/v2/go.mod
new file mode 100644
index 000000000..620b74e0a
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/go.mod
@@ -0,0 +1 @@
+module github.com/russross/blackfriday/v2
diff --git a/vendor/github.com/russross/blackfriday/v2/html.go b/vendor/github.com/russross/blackfriday/v2/html.go
new file mode 100644
index 000000000..cb4f26e30
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/html.go
@@ -0,0 +1,952 @@
+//
+// 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.
+//
+
+//
+//
+// HTML rendering backend
+//
+//
+
+package blackfriday
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+)
+
+// HTMLFlags control optional behavior of HTML renderer.
+type HTMLFlags int
+
+// HTML renderer configuration options.
+const (
+ HTMLFlagsNone HTMLFlags = 0
+ SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks
+ SkipImages // Skip embedded images
+ SkipLinks // Skip all links
+ Safelink // Only link to trusted protocols
+ NofollowLinks // Only link with rel="nofollow"
+ NoreferrerLinks // Only link with rel="noreferrer"
+ NoopenerLinks // Only link with rel="noopener"
+ HrefTargetBlank // Add a blank target
+ CompletePage // Generate a complete HTML page
+ UseXHTML // Generate XHTML output instead of HTML
+ FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source
+ Smartypants // Enable smart punctuation substitutions
+ SmartypantsFractions // Enable smart fractions (with Smartypants)
+ SmartypantsDashes // Enable smart dashes (with Smartypants)
+ SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
+ SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
+ SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
+ TOC // Generate a table of contents
+)
+
+var (
+ htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
+)
+
+const (
+ htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
+ processingInstruction + "|" + declaration + "|" + cdata + ")"
+ closeTag = "</" + tagName + "\\s*[>]"
+ openTag = "<" + tagName + attribute + "*" + "\\s*/?>"
+ attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
+ attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
+ attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
+ attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
+ cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
+ declaration = "<![A-Z]+" + "\\s+[^>]*>"
+ doubleQuotedValue = "\"[^\"]*\""
+ htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
+ processingInstruction = "[<][?].*?[?][>]"
+ singleQuotedValue = "'[^']*'"
+ tagName = "[A-Za-z][A-Za-z0-9-]*"
+ unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
+)
+
+// HTMLRendererParameters is a collection of supplementary parameters tweaking
+// the behavior of various parts of HTML renderer.
+type HTMLRendererParameters struct {
+ // Prepend this text to each relative URL.
+ AbsolutePrefix string
+ // Add this text to each footnote anchor, to ensure uniqueness.
+ FootnoteAnchorPrefix string
+ // Show this text inside the <a> tag for a footnote return link, if the
+ // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
+ // <sup>[return]</sup> is used.
+ FootnoteReturnLinkContents string
+ // If set, add this text to the front of each Heading ID, to ensure
+ // uniqueness.
+ HeadingIDPrefix string
+ // If set, add this text to the back of each Heading ID, to ensure uniqueness.
+ HeadingIDSuffix string
+ // Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
+ // Negative offset is also valid.
+ // Resulting levels are clipped between 1 and 6.
+ HeadingLevelOffset int
+
+ Title string // Document title (used if CompletePage is set)
+ CSS string // Optional CSS file URL (used if CompletePage is set)
+ Icon string // Optional icon file URL (used if CompletePage is set)
+
+ Flags HTMLFlags // Flags allow customizing this renderer's behavior
+}
+
+// HTMLRenderer is a type that implements the Renderer interface for HTML output.
+//
+// Do not create this directly, instead use the NewHTMLRenderer function.
+type HTMLRenderer struct {
+ HTMLRendererParameters
+
+ closeTag string // how to end singleton tags: either " />" or ">"
+
+ // Track heading IDs to prevent ID collision in a single generation.
+ headingIDs map[string]int
+
+ lastOutputLen int
+ disableTags int
+
+ sr *SPRenderer
+}
+
+const (
+ xhtmlClose = " />"
+ htmlClose = ">"
+)
+
+// NewHTMLRenderer creates and configures an HTMLRenderer object, which
+// satisfies the Renderer interface.
+func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
+ // configure the rendering engine
+ closeTag := htmlClose
+ if params.Flags&UseXHTML != 0 {
+ closeTag = xhtmlClose
+ }
+
+ if params.FootnoteReturnLinkContents == "" {
+ // U+FE0E is VARIATION SELECTOR-15.
+ // It suppresses automatic emoji presentation of the preceding
+ // U+21A9 LEFTWARDS ARROW WITH HOOK on iOS and iPadOS.
+ params.FootnoteReturnLinkContents = "<span aria-label='Return'>↩\ufe0e</span>"
+ }
+
+ return &HTMLRenderer{
+ HTMLRendererParameters: params,
+
+ closeTag: closeTag,
+ headingIDs: make(map[string]int),
+
+ sr: NewSmartypantsRenderer(params.Flags),
+ }
+}
+
+func isHTMLTag(tag []byte, tagname string) bool {
+ found, _ := findHTMLTagPos(tag, tagname)
+ return found
+}
+
+// Look for a character, but ignore it when it's in any kind of quotes, it
+// might be JavaScript
+func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
+ inSingleQuote := false
+ inDoubleQuote := false
+ inGraveQuote := false
+ i := start
+ for i < len(html) {
+ switch {
+ case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
+ return i
+ case html[i] == '\'':
+ inSingleQuote = !inSingleQuote
+ case html[i] == '"':
+ inDoubleQuote = !inDoubleQuote
+ case html[i] == '`':
+ inGraveQuote = !inGraveQuote
+ }
+ i++
+ }
+ return start
+}
+
+func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
+ i := 0
+ if i < len(tag) && tag[0] != '<' {
+ return false, -1
+ }
+ i++
+ i = skipSpace(tag, i)
+
+ if i < len(tag) && tag[i] == '/' {
+ i++
+ }
+
+ i = skipSpace(tag, i)
+ j := 0
+ for ; i < len(tag); i, j = i+1, j+1 {
+ if j >= len(tagname) {
+ break
+ }
+
+ if strings.ToLower(string(tag[i]))[0] != tagname[j] {
+ return false, -1
+ }
+ }
+
+ if i == len(tag) {
+ return false, -1
+ }
+
+ rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
+ if rightAngle >= i {
+ return true, rightAngle
+ }
+
+ return false, -1
+}
+
+func skipSpace(tag []byte, i int) int {
+ for i < len(tag) && isspace(tag[i]) {
+ i++
+ }
+ return i
+}
+
+func isRelativeLink(link []byte) (yes bool) {
+ // a tag begin with '#'
+ if link[0] == '#' {
+ return true
+ }
+
+ // link begin with '/' but not '//', the second maybe a protocol relative link
+ if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
+ return true
+ }
+
+ // only the root '/'
+ if len(link) == 1 && link[0] == '/' {
+ return true
+ }
+
+ // current directory : begin with "./"
+ if bytes.HasPrefix(link, []byte("./")) {
+ return true
+ }
+
+ // parent directory : begin with "../"
+ if bytes.HasPrefix(link, []byte("../")) {
+ return true
+ }
+
+ return false
+}
+
+func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
+ for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
+ tmp := fmt.Sprintf("%s-%d", id, count+1)
+
+ if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
+ r.headingIDs[id] = count + 1
+ id = tmp
+ } else {
+ id = id + "-1"
+ }
+ }
+
+ if _, found := r.headingIDs[id]; !found {
+ r.headingIDs[id] = 0
+ }
+
+ return id
+}
+
+func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
+ if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
+ newDest := r.AbsolutePrefix
+ if link[0] != '/' {
+ newDest += "/"
+ }
+ newDest += string(link)
+ return []byte(newDest)
+ }
+ return link
+}
+
+func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
+ if isRelativeLink(link) {
+ return attrs
+ }
+ val := []string{}
+ if flags&NofollowLinks != 0 {
+ val = append(val, "nofollow")
+ }
+ if flags&NoreferrerLinks != 0 {
+ val = append(val, "noreferrer")
+ }
+ if flags&NoopenerLinks != 0 {
+ val = append(val, "noopener")
+ }
+ if flags&HrefTargetBlank != 0 {
+ attrs = append(attrs, "target=\"_blank\"")
+ }
+ if len(val) == 0 {
+ return attrs
+ }
+ attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
+ return append(attrs, attr)
+}
+
+func isMailto(link []byte) bool {
+ return bytes.HasPrefix(link, []byte("mailto:"))
+}
+
+func needSkipLink(flags HTMLFlags, dest []byte) bool {
+ if flags&SkipLinks != 0 {
+ return true
+ }
+ return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
+}
+
+func isSmartypantable(node *Node) bool {
+ pt := node.Parent.Type
+ return pt != Link && pt != CodeBlock && pt != Code
+}
+
+func appendLanguageAttr(attrs []string, info []byte) []string {
+ if len(info) == 0 {
+ return attrs
+ }
+ endOfLang := bytes.IndexAny(info, "\t ")
+ if endOfLang < 0 {
+ endOfLang = len(info)
+ }
+ return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
+}
+
+func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
+ w.Write(name)
+ if len(attrs) > 0 {
+ w.Write(spaceBytes)
+ w.Write([]byte(strings.Join(attrs, " ")))
+ }
+ w.Write(gtBytes)
+ r.lastOutputLen = 1
+}
+
+func footnoteRef(prefix string, node *Node) []byte {
+ urlFrag := prefix + string(slugify(node.Destination))
+ anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
+ return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
+}
+
+func footnoteItem(prefix string, slug []byte) []byte {
+ return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
+}
+
+func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
+ const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
+ return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
+}
+
+func itemOpenCR(node *Node) bool {
+ if node.Prev == nil {
+ return false
+ }
+ ld := node.Parent.ListData
+ return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
+}
+
+func skipParagraphTags(node *Node) bool {
+ grandparent := node.Parent.Parent
+ if grandparent == nil || grandparent.Type != List {
+ return false
+ }
+ tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
+ return grandparent.Type == List && tightOrTerm
+}
+
+func cellAlignment(align CellAlignFlags) string {
+ switch align {
+ case TableAlignmentLeft:
+ return "left"
+ case TableAlignmentRight:
+ return "right"
+ case TableAlignmentCenter:
+ return "center"
+ default:
+ return ""
+ }
+}
+
+func (r *HTMLRenderer) out(w io.Writer, text []byte) {
+ if r.disableTags > 0 {
+ w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
+ } else {
+ w.Write(text)
+ }
+ r.lastOutputLen = len(text)
+}
+
+func (r *HTMLRenderer) cr(w io.Writer) {
+ if r.lastOutputLen > 0 {
+ r.out(w, nlBytes)
+ }
+}
+
+var (
+ nlBytes = []byte{'\n'}
+ gtBytes = []byte{'>'}
+ spaceBytes = []byte{' '}
+)
+
+var (
+ brTag = []byte("<br>")
+ brXHTMLTag = []byte("<br />")
+ emTag = []byte("<em>")
+ emCloseTag = []byte("</em>")
+ strongTag = []byte("<strong>")
+ strongCloseTag = []byte("</strong>")
+ delTag = []byte("<del>")
+ delCloseTag = []byte("</del>")
+ ttTag = []byte("<tt>")
+ ttCloseTag = []byte("</tt>")
+ aTag = []byte("<a")
+ aCloseTag = []byte("</a>")
+ preTag = []byte("<pre>")
+ preCloseTag = []byte("</pre>")
+ codeTag = []byte("<code>")
+ codeCloseTag = []byte("</code>")
+ pTag = []byte("<p>")
+ pCloseTag = []byte("</p>")
+ blockquoteTag = []byte("<blockquote>")
+ blockquoteCloseTag = []byte("</blockquote>")
+ hrTag = []byte("<hr>")
+ hrXHTMLTag = []byte("<hr />")
+ ulTag = []byte("<ul>")
+ ulCloseTag = []byte("</ul>")
+ olTag = []byte("<ol>")
+ olCloseTag = []byte("</ol>")
+ dlTag = []byte("<dl>")
+ dlCloseTag = []byte("</dl>")
+ liTag = []byte("<li>")
+ liCloseTag = []byte("</li>")
+ ddTag = []byte("<dd>")
+ ddCloseTag = []byte("</dd>")
+ dtTag = []byte("<dt>")
+ dtCloseTag = []byte("</dt>")
+ tableTag = []byte("<table>")
+ tableCloseTag = []byte("</table>")
+ tdTag = []byte("<td")
+ tdCloseTag = []byte("</td>")
+ thTag = []byte("<th")
+ thCloseTag = []byte("</th>")
+ theadTag = []byte("<thead>")
+ theadCloseTag = []byte("</thead>")
+ tbodyTag = []byte("<tbody>")
+ tbodyCloseTag = []byte("</tbody>")
+ trTag = []byte("<tr>")
+ trCloseTag = []byte("</tr>")
+ h1Tag = []byte("<h1")
+ h1CloseTag = []byte("</h1>")
+ h2Tag = []byte("<h2")
+ h2CloseTag = []byte("</h2>")
+ h3Tag = []byte("<h3")
+ h3CloseTag = []byte("</h3>")
+ h4Tag = []byte("<h4")
+ h4CloseTag = []byte("</h4>")
+ h5Tag = []byte("<h5")
+ h5CloseTag = []byte("</h5>")
+ h6Tag = []byte("<h6")
+ h6CloseTag = []byte("</h6>")
+
+ footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n")
+ footnotesCloseDivBytes = []byte("\n</div>\n")
+)
+
+func headingTagsFromLevel(level int) ([]byte, []byte) {
+ if level <= 1 {
+ return h1Tag, h1CloseTag
+ }
+ switch level {
+ case 2:
+ return h2Tag, h2CloseTag
+ case 3:
+ return h3Tag, h3CloseTag
+ case 4:
+ return h4Tag, h4CloseTag
+ case 5:
+ return h5Tag, h5CloseTag
+ }
+ return h6Tag, h6CloseTag
+}
+
+func (r *HTMLRenderer) outHRTag(w io.Writer) {
+ if r.Flags&UseXHTML == 0 {
+ r.out(w, hrTag)
+ } else {
+ r.out(w, hrXHTMLTag)
+ }
+}
+
+// RenderNode is a default renderer of a single node of a syntax tree. For
+// block nodes it will be called twice: first time with entering=true, second
+// time with entering=false, so that it could know when it's working on an open
+// tag and when on close. It writes the result to w.
+//
+// The return value is a way to tell the calling walker to adjust its walk
+// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
+// can ask the walker to skip a subtree of this node by returning SkipChildren.
+// The typical behavior is to return GoToNext, which asks for the usual
+// traversal to the next node.
+func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
+ attrs := []string{}
+ switch node.Type {
+ case Text:
+ if r.Flags&Smartypants != 0 {
+ var tmp bytes.Buffer
+ escapeHTML(&tmp, node.Literal)
+ r.sr.Process(w, tmp.Bytes())
+ } else {
+ if node.Parent.Type == Link {
+ escLink(w, node.Literal)
+ } else {
+ escapeHTML(w, node.Literal)
+ }
+ }
+ case Softbreak:
+ r.cr(w)
+ // TODO: make it configurable via out(renderer.softbreak)
+ case Hardbreak:
+ if r.Flags&UseXHTML == 0 {
+ r.out(w, brTag)
+ } else {
+ r.out(w, brXHTMLTag)
+ }
+ r.cr(w)
+ case Emph:
+ if entering {
+ r.out(w, emTag)
+ } else {
+ r.out(w, emCloseTag)
+ }
+ case Strong:
+ if entering {
+ r.out(w, strongTag)
+ } else {
+ r.out(w, strongCloseTag)
+ }
+ case Del:
+ if entering {
+ r.out(w, delTag)
+ } else {
+ r.out(w, delCloseTag)
+ }
+ case HTMLSpan:
+ if r.Flags&SkipHTML != 0 {
+ break
+ }
+ r.out(w, node.Literal)
+ case Link:
+ // mark it but don't link it if it is not a safe link: no smartypants
+ dest := node.LinkData.Destination
+ if needSkipLink(r.Flags, dest) {
+ if entering {
+ r.out(w, ttTag)
+ } else {
+ r.out(w, ttCloseTag)
+ }
+ } else {
+ if entering {
+ dest = r.addAbsPrefix(dest)
+ var hrefBuf bytes.Buffer
+ hrefBuf.WriteString("href=\"")
+ escLink(&hrefBuf, dest)
+ hrefBuf.WriteByte('"')
+ attrs = append(attrs, hrefBuf.String())
+ if node.NoteID != 0 {
+ r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
+ break
+ }
+ attrs = appendLinkAttrs(attrs, r.Flags, dest)
+ if len(node.LinkData.Title) > 0 {
+ var titleBuff bytes.Buffer
+ titleBuff.WriteString("title=\"")
+ escapeHTML(&titleBuff, node.LinkData.Title)
+ titleBuff.WriteByte('"')
+ attrs = append(attrs, titleBuff.String())
+ }
+ r.tag(w, aTag, attrs)
+ } else {
+ if node.NoteID != 0 {
+ break
+ }
+ r.out(w, aCloseTag)
+ }
+ }
+ case Image:
+ if r.Flags&SkipImages != 0 {
+ return SkipChildren
+ }
+ if entering {
+ dest := node.LinkData.Destination
+ dest = r.addAbsPrefix(dest)
+ if r.disableTags == 0 {
+ //if options.safe && potentiallyUnsafe(dest) {
+ //out(w, `<img src="" alt="`)
+ //} else {
+ r.out(w, []byte(`<img src="`))
+ escLink(w, dest)
+ r.out(w, []byte(`" alt="`))
+ //}
+ }
+ r.disableTags++
+ } else {
+ r.disableTags--
+ if r.disableTags == 0 {
+ if node.LinkData.Title != nil {
+ r.out(w, []byte(`" title="`))
+ escapeHTML(w, node.LinkData.Title)
+ }
+ r.out(w, []byte(`" />`))
+ }
+ }
+ case Code:
+ r.out(w, codeTag)
+ escapeAllHTML(w, node.Literal)
+ r.out(w, codeCloseTag)
+ case Document:
+ break
+ case Paragraph:
+ if skipParagraphTags(node) {
+ break
+ }
+ if entering {
+ // TODO: untangle this clusterfuck about when the newlines need
+ // to be added and when not.
+ if node.Prev != nil {
+ switch node.Prev.Type {
+ case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
+ r.cr(w)
+ }
+ }
+ if node.Parent.Type == BlockQuote && node.Prev == nil {
+ r.cr(w)
+ }
+ r.out(w, pTag)
+ } else {
+ r.out(w, pCloseTag)
+ if !(node.Parent.Type == Item && node.Next == nil) {
+ r.cr(w)
+ }
+ }
+ case BlockQuote:
+ if entering {
+ r.cr(w)
+ r.out(w, blockquoteTag)
+ } else {
+ r.out(w, blockquoteCloseTag)
+ r.cr(w)
+ }
+ case HTMLBlock:
+ if r.Flags&SkipHTML != 0 {
+ break
+ }
+ r.cr(w)
+ r.out(w, node.Literal)
+ r.cr(w)
+ case Heading:
+ headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
+ openTag, closeTag := headingTagsFromLevel(headingLevel)
+ if entering {
+ if node.IsTitleblock {
+ attrs = append(attrs, `class="title"`)
+ }
+ if node.HeadingID != "" {
+ id := r.ensureUniqueHeadingID(node.HeadingID)
+ if r.HeadingIDPrefix != "" {
+ id = r.HeadingIDPrefix + id
+ }
+ if r.HeadingIDSuffix != "" {
+ id = id + r.HeadingIDSuffix
+ }
+ attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
+ }
+ r.cr(w)
+ r.tag(w, openTag, attrs)
+ } else {
+ r.out(w, closeTag)
+ if !(node.Parent.Type == Item && node.Next == nil) {
+ r.cr(w)
+ }
+ }
+ case HorizontalRule:
+ r.cr(w)
+ r.outHRTag(w)
+ r.cr(w)
+ case List:
+ openTag := ulTag
+ closeTag := ulCloseTag
+ if node.ListFlags&ListTypeOrdered != 0 {
+ openTag = olTag
+ closeTag = olCloseTag
+ }
+ if node.ListFlags&ListTypeDefinition != 0 {
+ openTag = dlTag
+ closeTag = dlCloseTag
+ }
+ if entering {
+ if node.IsFootnotesList {
+ r.out(w, footnotesDivBytes)
+ r.outHRTag(w)
+ r.cr(w)
+ }
+ r.cr(w)
+ if node.Parent.Type == Item && node.Parent.Parent.Tight {
+ r.cr(w)
+ }
+ r.tag(w, openTag[:len(openTag)-1], attrs)
+ r.cr(w)
+ } else {
+ r.out(w, closeTag)
+ //cr(w)
+ //if node.parent.Type != Item {
+ // cr(w)
+ //}
+ if node.Parent.Type == Item && node.Next != nil {
+ r.cr(w)
+ }
+ if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
+ r.cr(w)
+ }
+ if node.IsFootnotesList {
+ r.out(w, footnotesCloseDivBytes)
+ }
+ }
+ case Item:
+ openTag := liTag
+ closeTag := liCloseTag
+ if node.ListFlags&ListTypeDefinition != 0 {
+ openTag = ddTag
+ closeTag = ddCloseTag
+ }
+ if node.ListFlags&ListTypeTerm != 0 {
+ openTag = dtTag
+ closeTag = dtCloseTag
+ }
+ if entering {
+ if itemOpenCR(node) {
+ r.cr(w)
+ }
+ if node.ListData.RefLink != nil {
+ slug := slugify(node.ListData.RefLink)
+ r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
+ break
+ }
+ r.out(w, openTag)
+ } else {
+ if node.ListData.RefLink != nil {
+ slug := slugify(node.ListData.RefLink)
+ if r.Flags&FootnoteReturnLinks != 0 {
+ r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
+ }
+ }
+ r.out(w, closeTag)
+ r.cr(w)
+ }
+ case CodeBlock:
+ attrs = appendLanguageAttr(attrs, node.Info)
+ r.cr(w)
+ r.out(w, preTag)
+ r.tag(w, codeTag[:len(codeTag)-1], attrs)
+ escapeAllHTML(w, node.Literal)
+ r.out(w, codeCloseTag)
+ r.out(w, preCloseTag)
+ if node.Parent.Type != Item {
+ r.cr(w)
+ }
+ case Table:
+ if entering {
+ r.cr(w)
+ r.out(w, tableTag)
+ } else {
+ r.out(w, tableCloseTag)
+ r.cr(w)
+ }
+ case TableCell:
+ openTag := tdTag
+ closeTag := tdCloseTag
+ if node.IsHeader {
+ openTag = thTag
+ closeTag = thCloseTag
+ }
+ if entering {
+ align := cellAlignment(node.Align)
+ if align != "" {
+ attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
+ }
+ if node.Prev == nil {
+ r.cr(w)
+ }
+ r.tag(w, openTag, attrs)
+ } else {
+ r.out(w, closeTag)
+ r.cr(w)
+ }
+ case TableHead:
+ if entering {
+ r.cr(w)
+ r.out(w, theadTag)
+ } else {
+ r.out(w, theadCloseTag)
+ r.cr(w)
+ }
+ case TableBody:
+ if entering {
+ r.cr(w)
+ r.out(w, tbodyTag)
+ // XXX: this is to adhere to a rather silly test. Should fix test.
+ if node.FirstChild == nil {
+ r.cr(w)
+ }
+ } else {
+ r.out(w, tbodyCloseTag)
+ r.cr(w)
+ }
+ case TableRow:
+ if entering {
+ r.cr(w)
+ r.out(w, trTag)
+ } else {
+ r.out(w, trCloseTag)
+ r.cr(w)
+ }
+ default:
+ panic("Unknown node type " + node.Type.String())
+ }
+ return GoToNext
+}
+
+// RenderHeader writes HTML document preamble and TOC if requested.
+func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
+ r.writeDocumentHeader(w)
+ if r.Flags&TOC != 0 {
+ r.writeTOC(w, ast)
+ }
+}
+
+// RenderFooter writes HTML document footer.
+func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
+ if r.Flags&CompletePage == 0 {
+ return
+ }
+ io.WriteString(w, "\n</body>\n</html>\n")
+}
+
+func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
+ if r.Flags&CompletePage == 0 {
+ return
+ }
+ ending := ""
+ if r.Flags&UseXHTML != 0 {
+ io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
+ io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
+ io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
+ ending = " /"
+ } else {
+ io.WriteString(w, "<!DOCTYPE html>\n")
+ io.WriteString(w, "<html>\n")
+ }
+ io.WriteString(w, "<head>\n")
+ io.WriteString(w, " <title>")
+ if r.Flags&Smartypants != 0 {
+ r.sr.Process(w, []byte(r.Title))
+ } else {
+ escapeHTML(w, []byte(r.Title))
+ }
+ io.WriteString(w, "</title>\n")
+ io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
+ io.WriteString(w, Version)
+ io.WriteString(w, "\"")
+ io.WriteString(w, ending)
+ io.WriteString(w, ">\n")
+ io.WriteString(w, " <meta charset=\"utf-8\"")
+ io.WriteString(w, ending)
+ io.WriteString(w, ">\n")
+ if r.CSS != "" {
+ io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
+ escapeHTML(w, []byte(r.CSS))
+ io.WriteString(w, "\"")
+ io.WriteString(w, ending)
+ io.WriteString(w, ">\n")
+ }
+ if r.Icon != "" {
+ io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
+ escapeHTML(w, []byte(r.Icon))
+ io.WriteString(w, "\"")
+ io.WriteString(w, ending)
+ io.WriteString(w, ">\n")
+ }
+ io.WriteString(w, "</head>\n")
+ io.WriteString(w, "<body>\n\n")
+}
+
+func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
+ buf := bytes.Buffer{}
+
+ inHeading := false
+ tocLevel := 0
+ headingCount := 0
+
+ ast.Walk(func(node *Node, entering bool) WalkStatus {
+ if node.Type == Heading && !node.HeadingData.IsTitleblock {
+ inHeading = entering
+ if entering {
+ node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
+ if node.Level == tocLevel {
+ buf.WriteString("</li>\n\n<li>")
+ } else if node.Level < tocLevel {
+ for node.Level < tocLevel {
+ tocLevel--
+ buf.WriteString("</li>\n</ul>")
+ }
+ buf.WriteString("</li>\n\n<li>")
+ } else {
+ for node.Level > tocLevel {
+ tocLevel++
+ buf.WriteString("\n<ul>\n<li>")
+ }
+ }
+
+ fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
+ headingCount++
+ } else {
+ buf.WriteString("</a>")
+ }
+ return GoToNext
+ }
+
+ if inHeading {
+ return r.RenderNode(&buf, node, entering)
+ }
+
+ return GoToNext
+ })
+
+ for ; tocLevel > 0; tocLevel-- {
+ buf.WriteString("</li>\n</ul>")
+ }
+
+ if buf.Len() > 0 {
+ io.WriteString(w, "<nav>\n")
+ w.Write(buf.Bytes())
+ io.WriteString(w, "\n\n</nav>\n")
+ }
+ r.lastOutputLen = buf.Len()
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/inline.go b/vendor/github.com/russross/blackfriday/v2/inline.go
new file mode 100644
index 000000000..d45bd9417
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/inline.go
@@ -0,0 +1,1228 @@
+//
+// 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.
+//
+
+//
+// Functions to parse inline elements.
+//
+
+package blackfriday
+
+import (
+ "bytes"
+ "regexp"
+ "strconv"
+)
+
+var (
+ urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+`
+ anchorRe = regexp.MustCompile(`^(<a\shref="` + urlRe + `"(\stitle="[^"<>]+")?\s?>` + urlRe + `<\/a>)`)
+
+ // https://www.w3.org/TR/html5/syntax.html#character-references
+ // highest unicode code point in 17 planes (2^20): 1,114,112d =
+ // 7 dec digits or 6 hex digits
+ // named entity references can be 2-31 characters with stuff like &lt;
+ // at one end and &CounterClockwiseContourIntegral; at the other. There
+ // are also sometimes numbers at the end, although this isn't inherent
+ // in the specification; there are never numbers anywhere else in
+ // current character references, though; see &frac34; and &blk12;, etc.
+ // https://www.w3.org/TR/html5/syntax.html#named-character-references
+ //
+ // entity := "&" (named group | number ref) ";"
+ // named group := [a-zA-Z]{2,31}[0-9]{0,2}
+ // number ref := "#" (dec ref | hex ref)
+ // dec ref := [0-9]{1,7}
+ // hex ref := ("x" | "X") [0-9a-fA-F]{1,6}
+ htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`)
+)
+
+// Functions to parse text within a block
+// Each function returns the number of chars taken care of
+// data is the complete block being rendered
+// offset is the number of valid chars before the current cursor
+
+func (p *Markdown) inline(currBlock *Node, data []byte) {
+ // handlers might call us recursively: enforce a maximum depth
+ if p.nesting >= p.maxNesting || len(data) == 0 {
+ return
+ }
+ p.nesting++
+ beg, end := 0, 0
+ for end < len(data) {
+ handler := p.inlineCallback[data[end]]
+ if handler != nil {
+ if consumed, node := handler(p, data, end); consumed == 0 {
+ // No action from the callback.
+ end++
+ } else {
+ // Copy inactive chars into the output.
+ currBlock.AppendChild(text(data[beg:end]))
+ if node != nil {
+ currBlock.AppendChild(node)
+ }
+ // Skip past whatever the callback used.
+ beg = end + consumed
+ end = beg
+ }
+ } else {
+ end++
+ }
+ }
+ if beg < len(data) {
+ if data[end-1] == '\n' {
+ end--
+ }
+ currBlock.AppendChild(text(data[beg:end]))
+ }
+ p.nesting--
+}
+
+// single and double emphasis parsing
+func emphasis(p *Markdown, data []byte, offset int) (int, *Node) {
+ data = data[offset:]
+ c := data[0]
+
+ if len(data) > 2 && data[1] != c {
+ // whitespace cannot follow an opening emphasis;
+ // strikethrough only takes two characters '~~'
+ if c == '~' || isspace(data[1]) {
+ return 0, nil
+ }
+ ret, node := helperEmphasis(p, data[1:], c)
+ if ret == 0 {
+ return 0, nil
+ }
+
+ return ret + 1, node
+ }
+
+ if len(data) > 3 && data[1] == c && data[2] != c {
+ if isspace(data[2]) {
+ return 0, nil
+ }
+ ret, node := helperDoubleEmphasis(p, data[2:], c)
+ if ret == 0 {
+ return 0, nil
+ }
+
+ return ret + 2, node
+ }
+
+ if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c {
+ if c == '~' || isspace(data[3]) {
+ return 0, nil
+ }
+ ret, node := helperTripleEmphasis(p, data, 3, c)
+ if ret == 0 {
+ return 0, nil
+ }
+
+ return ret + 3, node
+ }
+
+ return 0, nil
+}
+
+func codeSpan(p *Markdown, data []byte, offset int) (int, *Node) {
+ data = data[offset:]
+
+ nb := 0
+
+ // count the number of backticks in the delimiter
+ for nb < len(data) && data[nb] == '`' {
+ nb++
+ }
+
+ // find the next delimiter
+ i, end := 0, 0
+ for end = nb; end < len(data) && i < nb; end++ {
+ if data[end] == '`' {
+ i++
+ } else {
+ i = 0
+ }
+ }
+
+ // no matching delimiter?
+ if i < nb && end >= len(data) {
+ return 0, nil
+ }
+
+ // trim outside whitespace
+ fBegin := nb
+ for fBegin < end && data[fBegin] == ' ' {
+ fBegin++
+ }
+
+ fEnd := end - nb
+ for fEnd > fBegin && data[fEnd-1] == ' ' {
+ fEnd--
+ }
+
+ // render the code span
+ if fBegin != fEnd {
+ code := NewNode(Code)
+ code.Literal = data[fBegin:fEnd]
+ return end, code
+ }
+
+ return end, nil
+}
+
+// newline preceded by two spaces becomes <br>
+func maybeLineBreak(p *Markdown, data []byte, offset int) (int, *Node) {
+ origOffset := offset
+ for offset < len(data) && data[offset] == ' ' {
+ offset++
+ }
+
+ if offset < len(data) && data[offset] == '\n' {
+ if offset-origOffset >= 2 {
+ return offset - origOffset + 1, NewNode(Hardbreak)
+ }
+ return offset - origOffset, nil
+ }
+ return 0, nil
+}
+
+// newline without two spaces works when HardLineBreak is enabled
+func lineBreak(p *Markdown, data []byte, offset int) (int, *Node) {
+ if p.extensions&HardLineBreak != 0 {
+ return 1, NewNode(Hardbreak)
+ }
+ return 0, nil
+}
+
+type linkType int
+
+const (
+ linkNormal linkType = iota
+ linkImg
+ linkDeferredFootnote
+ linkInlineFootnote
+)
+
+func isReferenceStyleLink(data []byte, pos int, t linkType) bool {
+ if t == linkDeferredFootnote {
+ return false
+ }
+ return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^'
+}
+
+func maybeImage(p *Markdown, data []byte, offset int) (int, *Node) {
+ if offset < len(data)-1 && data[offset+1] == '[' {
+ return link(p, data, offset)
+ }
+ return 0, nil
+}
+
+func maybeInlineFootnote(p *Markdown, data []byte, offset int) (int, *Node) {
+ if offset < len(data)-1 && data[offset+1] == '[' {
+ return link(p, data, offset)
+ }
+ return 0, nil
+}
+
+// '[': parse a link or an image or a footnote
+func link(p *Markdown, data []byte, offset int) (int, *Node) {
+ // no links allowed inside regular links, footnote, and deferred footnotes
+ if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') {
+ return 0, nil
+ }
+
+ var t linkType
+ switch {
+ // special case: ![^text] == deferred footnote (that follows something with
+ // an exclamation point)
+ case p.extensions&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^':
+ t = linkDeferredFootnote
+ // ![alt] == image
+ case offset >= 0 && data[offset] == '!':
+ t = linkImg
+ offset++
+ // ^[text] == inline footnote
+ // [^refId] == deferred footnote
+ case p.extensions&Footnotes != 0:
+ if offset >= 0 && data[offset] == '^' {
+ t = linkInlineFootnote
+ offset++
+ } else if len(data)-1 > offset && data[offset+1] == '^' {
+ t = linkDeferredFootnote
+ }
+ // [text] == regular link
+ default:
+ t = linkNormal
+ }
+
+ data = data[offset:]
+
+ var (
+ i = 1
+ noteID int
+ title, link, altContent []byte
+ textHasNl = false
+ )
+
+ if t == linkDeferredFootnote {
+ i++
+ }
+
+ // look for the matching closing bracket
+ for level := 1; level > 0 && i < len(data); i++ {
+ switch {
+ case data[i] == '\n':
+ textHasNl = true
+
+ case isBackslashEscaped(data, i):
+ continue
+
+ case data[i] == '[':
+ level++
+
+ case data[i] == ']':
+ level--
+ if level <= 0 {
+ i-- // compensate for extra i++ in for loop
+ }
+ }
+ }
+
+ if i >= len(data) {
+ return 0, nil
+ }
+
+ txtE := i
+ i++
+ var footnoteNode *Node
+
+ // skip any amount of whitespace or newline
+ // (this is much more lax than original markdown syntax)
+ for i < len(data) && isspace(data[i]) {
+ i++
+ }
+
+ // inline style link
+ switch {
+ case i < len(data) && data[i] == '(':
+ // skip initial whitespace
+ i++
+
+ for i < len(data) && isspace(data[i]) {
+ i++
+ }
+
+ linkB := i
+
+ // look for link end: ' " )
+ findlinkend:
+ for i < len(data) {
+ switch {
+ case data[i] == '\\':
+ i += 2
+
+ case data[i] == ')' || data[i] == '\'' || data[i] == '"':
+ break findlinkend
+
+ default:
+ i++
+ }
+ }
+
+ if i >= len(data) {
+ return 0, nil
+ }
+ linkE := i
+
+ // look for title end if present
+ titleB, titleE := 0, 0
+ if data[i] == '\'' || data[i] == '"' {
+ i++
+ titleB = i
+
+ findtitleend:
+ for i < len(data) {
+ switch {
+ case data[i] == '\\':
+ i += 2
+
+ case data[i] == ')':
+ break findtitleend
+
+ default:
+ i++
+ }
+ }
+
+ if i >= len(data) {
+ return 0, nil
+ }
+
+ // skip whitespace after title
+ titleE = i - 1
+ for titleE > titleB && isspace(data[titleE]) {
+ titleE--
+ }
+
+ // check for closing quote presence
+ if data[titleE] != '\'' && data[titleE] != '"' {
+ titleB, titleE = 0, 0
+ linkE = i
+ }
+ }
+
+ // remove whitespace at the end of the link
+ for linkE > linkB && isspace(data[linkE-1]) {
+ linkE--
+ }
+
+ // remove optional angle brackets around the link
+ if data[linkB] == '<' {
+ linkB++
+ }
+ if data[linkE-1] == '>' {
+ linkE--
+ }
+
+ // build escaped link and title
+ if linkE > linkB {
+ link = data[linkB:linkE]
+ }
+
+ if titleE > titleB {
+ title = data[titleB:titleE]
+ }
+
+ i++
+
+ // reference style link
+ case isReferenceStyleLink(data, i, t):
+ var id []byte
+ altContentConsidered := false
+
+ // look for the id
+ i++
+ linkB := i
+ for i < len(data) && data[i] != ']' {
+ i++
+ }
+ if i >= len(data) {
+ return 0, nil
+ }
+ linkE := i
+
+ // find the reference
+ if linkB == linkE {
+ if textHasNl {
+ var b bytes.Buffer
+
+ for j := 1; j < txtE; j++ {
+ switch {
+ case data[j] != '\n':
+ b.WriteByte(data[j])
+ case data[j-1] != ' ':
+ b.WriteByte(' ')
+ }
+ }
+
+ id = b.Bytes()
+ } else {
+ id = data[1:txtE]
+ altContentConsidered = true
+ }
+ } else {
+ id = data[linkB:linkE]
+ }
+
+ // find the reference with matching id
+ lr, ok := p.getRef(string(id))
+ if !ok {
+ return 0, nil
+ }
+
+ // keep link and title from reference
+ link = lr.link
+ title = lr.title
+ if altContentConsidered {
+ altContent = lr.text
+ }
+ i++
+
+ // shortcut reference style link or reference or inline footnote
+ default:
+ var id []byte
+
+ // craft the id
+ if textHasNl {
+ var b bytes.Buffer
+
+ for j := 1; j < txtE; j++ {
+ switch {
+ case data[j] != '\n':
+ b.WriteByte(data[j])
+ case data[j-1] != ' ':
+ b.WriteByte(' ')
+ }
+ }
+
+ id = b.Bytes()
+ } else {
+ if t == linkDeferredFootnote {
+ id = data[2:txtE] // get rid of the ^
+ } else {
+ id = data[1:txtE]
+ }
+ }
+
+ footnoteNode = NewNode(Item)
+ if t == linkInlineFootnote {
+ // create a new reference
+ noteID = len(p.notes) + 1
+
+ var fragment []byte
+ if len(id) > 0 {
+ if len(id) < 16 {
+ fragment = make([]byte, len(id))
+ } else {
+ fragment = make([]byte, 16)
+ }
+ copy(fragment, slugify(id))
+ } else {
+ fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteID))...)
+ }
+
+ ref := &reference{
+ noteID: noteID,
+ hasBlock: false,
+ link: fragment,
+ title: id,
+ footnote: footnoteNode,
+ }
+
+ p.notes = append(p.notes, ref)
+
+ link = ref.link
+ title = ref.title
+ } else {
+ // find the reference with matching id
+ lr, ok := p.getRef(string(id))
+ if !ok {
+ return 0, nil
+ }
+
+ if t == linkDeferredFootnote {
+ lr.noteID = len(p.notes) + 1
+ lr.footnote = footnoteNode
+ p.notes = append(p.notes, lr)
+ }
+
+ // keep link and title from reference
+ link = lr.link
+ // if inline footnote, title == footnote contents
+ title = lr.title
+ noteID = lr.noteID
+ }
+
+ // rewind the whitespace
+ i = txtE + 1
+ }
+
+ var uLink []byte
+ if t == linkNormal || t == linkImg {
+ if len(link) > 0 {
+ var uLinkBuf bytes.Buffer
+ unescapeText(&uLinkBuf, link)
+ uLink = uLinkBuf.Bytes()
+ }
+
+ // links need something to click on and somewhere to go
+ if len(uLink) == 0 || (t == linkNormal && txtE <= 1) {
+ return 0, nil
+ }
+ }
+
+ // call the relevant rendering function
+ var linkNode *Node
+ switch t {
+ case linkNormal:
+ linkNode = NewNode(Link)
+ linkNode.Destination = normalizeURI(uLink)
+ linkNode.Title = title
+ if len(altContent) > 0 {
+ linkNode.AppendChild(text(altContent))
+ } else {
+ // links cannot contain other links, so turn off link parsing
+ // temporarily and recurse
+ insideLink := p.insideLink
+ p.insideLink = true
+ p.inline(linkNode, data[1:txtE])
+ p.insideLink = insideLink
+ }
+
+ case linkImg:
+ linkNode = NewNode(Image)
+ linkNode.Destination = uLink
+ linkNode.Title = title
+ linkNode.AppendChild(text(data[1:txtE]))
+ i++
+
+ case linkInlineFootnote, linkDeferredFootnote:
+ linkNode = NewNode(Link)
+ linkNode.Destination = link
+ linkNode.Title = title
+ linkNode.NoteID = noteID
+ linkNode.Footnote = footnoteNode
+ if t == linkInlineFootnote {
+ i++
+ }
+
+ default:
+ return 0, nil
+ }
+
+ return i, linkNode
+}
+
+func (p *Markdown) inlineHTMLComment(data []byte) int {
+ if len(data) < 5 {
+ return 0
+ }
+ if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' {
+ return 0
+ }
+ i := 5
+ // scan for an end-of-comment marker, across lines if necessary
+ for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') {
+ i++
+ }
+ // no end-of-comment marker
+ if i >= len(data) {
+ return 0
+ }
+ return i + 1
+}
+
+func stripMailto(link []byte) []byte {
+ if bytes.HasPrefix(link, []byte("mailto://")) {
+ return link[9:]
+ } else if bytes.HasPrefix(link, []byte("mailto:")) {
+ return link[7:]
+ } else {
+ return link
+ }
+}
+
+// autolinkType specifies a kind of autolink that gets detected.
+type autolinkType int
+
+// These are the possible flag values for the autolink renderer.
+const (
+ notAutolink autolinkType = iota
+ normalAutolink
+ emailAutolink
+)
+
+// '<' when tags or autolinks are allowed
+func leftAngle(p *Markdown, data []byte, offset int) (int, *Node) {
+ data = data[offset:]
+ altype, end := tagLength(data)
+ if size := p.inlineHTMLComment(data); size > 0 {
+ end = size
+ }
+ if end > 2 {
+ if altype != notAutolink {
+ var uLink bytes.Buffer
+ unescapeText(&uLink, data[1:end+1-2])
+ if uLink.Len() > 0 {
+ link := uLink.Bytes()
+ node := NewNode(Link)
+ node.Destination = link
+ if altype == emailAutolink {
+ node.Destination = append([]byte("mailto:"), link...)
+ }
+ node.AppendChild(text(stripMailto(link)))
+ return end, node
+ }
+ } else {
+ htmlTag := NewNode(HTMLSpan)
+ htmlTag.Literal = data[:end]
+ return end, htmlTag
+ }
+ }
+
+ return end, nil
+}
+
+// '\\' backslash escape
+var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~")
+
+func escape(p *Markdown, data []byte, offset int) (int, *Node) {
+ data = data[offset:]
+
+ if len(data) > 1 {
+ if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' {
+ return 2, NewNode(Hardbreak)
+ }
+ if bytes.IndexByte(escapeChars, data[1]) < 0 {
+ return 0, nil
+ }
+
+ return 2, text(data[1:2])
+ }
+
+ return 2, nil
+}
+
+func unescapeText(ob *bytes.Buffer, src []byte) {
+ i := 0
+ for i < len(src) {
+ org := i
+ for i < len(src) && src[i] != '\\' {
+ i++
+ }
+
+ if i > org {
+ ob.Write(src[org:i])
+ }
+
+ if i+1 >= len(src) {
+ break
+ }
+
+ ob.WriteByte(src[i+1])
+ i += 2
+ }
+}
+
+// '&' escaped when it doesn't belong to an entity
+// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+;
+func entity(p *Markdown, data []byte, offset int) (int, *Node) {
+ data = data[offset:]
+
+ end := 1
+
+ if end < len(data) && data[end] == '#' {
+ end++
+ }
+
+ for end < len(data) && isalnum(data[end]) {
+ end++
+ }
+
+ if end < len(data) && data[end] == ';' {
+ end++ // real entity
+ } else {
+ return 0, nil // lone '&'
+ }
+
+ ent := data[:end]
+ // undo &amp; escaping or it will be converted to &amp;amp; by another
+ // escaper in the renderer
+ if bytes.Equal(ent, []byte("&amp;")) {
+ ent = []byte{'&'}
+ }
+
+ return end, text(ent)
+}
+
+func linkEndsWithEntity(data []byte, linkEnd int) bool {
+ entityRanges := htmlEntityRe.FindAllIndex(data[:linkEnd], -1)
+ return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd
+}
+
+// hasPrefixCaseInsensitive is a custom implementation of
+// strings.HasPrefix(strings.ToLower(s), prefix)
+// we rolled our own because ToLower pulls in a huge machinery of lowercasing
+// anything from Unicode and that's very slow. Since this func will only be
+// used on ASCII protocol prefixes, we can take shortcuts.
+func hasPrefixCaseInsensitive(s, prefix []byte) bool {
+ if len(s) < len(prefix) {
+ return false
+ }
+ delta := byte('a' - 'A')
+ for i, b := range prefix {
+ if b != s[i] && b != s[i]+delta {
+ return false
+ }
+ }
+ return true
+}
+
+var protocolPrefixes = [][]byte{
+ []byte("http://"),
+ []byte("https://"),
+ []byte("ftp://"),
+ []byte("file://"),
+ []byte("mailto:"),
+}
+
+const shortestPrefix = 6 // len("ftp://"), the shortest of the above
+
+func maybeAutoLink(p *Markdown, data []byte, offset int) (int, *Node) {
+ // quick check to rule out most false hits
+ if p.insideLink || len(data) < offset+shortestPrefix {
+ return 0, nil
+ }
+ for _, prefix := range protocolPrefixes {
+ endOfHead := offset + 8 // 8 is the len() of the longest prefix
+ if endOfHead > len(data) {
+ endOfHead = len(data)
+ }
+ if hasPrefixCaseInsensitive(data[offset:endOfHead], prefix) {
+ return autoLink(p, data, offset)
+ }
+ }
+ return 0, nil
+}
+
+func autoLink(p *Markdown, data []byte, offset int) (int, *Node) {
+ // Now a more expensive check to see if we're not inside an anchor element
+ anchorStart := offset
+ offsetFromAnchor := 0
+ for anchorStart > 0 && data[anchorStart] != '<' {
+ anchorStart--
+ offsetFromAnchor++
+ }
+
+ anchorStr := anchorRe.Find(data[anchorStart:])
+ if anchorStr != nil {
+ anchorClose := NewNode(HTMLSpan)
+ anchorClose.Literal = anchorStr[offsetFromAnchor:]
+ return len(anchorStr) - offsetFromAnchor, anchorClose
+ }
+
+ // scan backward for a word boundary
+ rewind := 0
+ for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) {
+ rewind++
+ }
+ if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters
+ return 0, nil
+ }
+
+ origData := data
+ data = data[offset-rewind:]
+
+ if !isSafeLink(data) {
+ return 0, nil
+ }
+
+ linkEnd := 0
+ for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) {
+ linkEnd++
+ }
+
+ // Skip punctuation at the end of the link
+ if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' {
+ linkEnd--
+ }
+
+ // But don't skip semicolon if it's a part of escaped entity:
+ if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) {
+ linkEnd--
+ }
+
+ // See if the link finishes with a punctuation sign that can be closed.
+ var copen byte
+ switch data[linkEnd-1] {
+ case '"':
+ copen = '"'
+ case '\'':
+ copen = '\''
+ case ')':
+ copen = '('
+ case ']':
+ copen = '['
+ case '}':
+ copen = '{'
+ default:
+ copen = 0
+ }
+
+ if copen != 0 {
+ bufEnd := offset - rewind + linkEnd - 2
+
+ openDelim := 1
+
+ /* Try to close the final punctuation sign in this same line;
+ * if we managed to close it outside of the URL, that means that it's
+ * not part of the URL. If it closes inside the URL, that means it
+ * is part of the URL.
+ *
+ * Examples:
+ *
+ * foo http://www.pokemon.com/Pikachu_(Electric) bar
+ * => http://www.pokemon.com/Pikachu_(Electric)
+ *
+ * foo (http://www.pokemon.com/Pikachu_(Electric)) bar
+ * => http://www.pokemon.com/Pikachu_(Electric)
+ *
+ * foo http://www.pokemon.com/Pikachu_(Electric)) bar
+ * => http://www.pokemon.com/Pikachu_(Electric))
+ *
+ * (foo http://www.pokemon.com/Pikachu_(Electric)) bar
+ * => foo http://www.pokemon.com/Pikachu_(Electric)
+ */
+
+ for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 {
+ if origData[bufEnd] == data[linkEnd-1] {
+ openDelim++
+ }
+
+ if origData[bufEnd] == copen {
+ openDelim--
+ }
+
+ bufEnd--
+ }
+
+ if openDelim == 0 {
+ linkEnd--
+ }
+ }
+
+ var uLink bytes.Buffer
+ unescapeText(&uLink, data[:linkEnd])
+
+ if uLink.Len() > 0 {
+ node := NewNode(Link)
+ node.Destination = uLink.Bytes()
+ node.AppendChild(text(uLink.Bytes()))
+ return linkEnd, node
+ }
+
+ return linkEnd, nil
+}
+
+func isEndOfLink(char byte) bool {
+ return isspace(char) || char == '<'
+}
+
+var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")}
+var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")}
+
+func isSafeLink(link []byte) bool {
+ for _, path := range validPaths {
+ if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) {
+ if len(link) == len(path) {
+ return true
+ } else if isalnum(link[len(path)]) {
+ return true
+ }
+ }
+ }
+
+ for _, prefix := range validUris {
+ // TODO: handle unicode here
+ // case-insensitive prefix test
+ if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// return the length of the given tag, or 0 is it's not valid
+func tagLength(data []byte) (autolink autolinkType, end int) {
+ var i, j int
+
+ // a valid tag can't be shorter than 3 chars
+ if len(data) < 3 {
+ return notAutolink, 0
+ }
+
+ // begins with a '<' optionally followed by '/', followed by letter or number
+ if data[0] != '<' {
+ return notAutolink, 0
+ }
+ if data[1] == '/' {
+ i = 2
+ } else {
+ i = 1
+ }
+
+ if !isalnum(data[i]) {
+ return notAutolink, 0
+ }
+
+ // scheme test
+ autolink = notAutolink
+
+ // try to find the beginning of an URI
+ for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') {
+ i++
+ }
+
+ if i > 1 && i < len(data) && data[i] == '@' {
+ if j = isMailtoAutoLink(data[i:]); j != 0 {
+ return emailAutolink, i + j
+ }
+ }
+
+ if i > 2 && i < len(data) && data[i] == ':' {
+ autolink = normalAutolink
+ i++
+ }
+
+ // complete autolink test: no whitespace or ' or "
+ switch {
+ case i >= len(data):
+ autolink = notAutolink
+ case autolink != notAutolink:
+ j = i
+
+ for i < len(data) {
+ if data[i] == '\\' {
+ i += 2
+ } else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) {
+ break
+ } else {
+ i++
+ }
+
+ }
+
+ if i >= len(data) {
+ return autolink, 0
+ }
+ if i > j && data[i] == '>' {
+ return autolink, i + 1
+ }
+
+ // one of the forbidden chars has been found
+ autolink = notAutolink
+ }
+ i += bytes.IndexByte(data[i:], '>')
+ if i < 0 {
+ return autolink, 0
+ }
+ return autolink, i + 1
+}
+
+// look for the address part of a mail autolink and '>'
+// this is less strict than the original markdown e-mail address matching
+func isMailtoAutoLink(data []byte) int {
+ nb := 0
+
+ // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@'
+ for i := 0; i < len(data); i++ {
+ if isalnum(data[i]) {
+ continue
+ }
+
+ switch data[i] {
+ case '@':
+ nb++
+
+ case '-', '.', '_':
+ break
+
+ case '>':
+ if nb == 1 {
+ return i + 1
+ }
+ return 0
+ default:
+ return 0
+ }
+ }
+
+ return 0
+}
+
+// look for the next emph char, skipping other constructs
+func helperFindEmphChar(data []byte, c byte) int {
+ i := 0
+
+ for i < len(data) {
+ for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' {
+ i++
+ }
+ if i >= len(data) {
+ return 0
+ }
+ // do not count escaped chars
+ if i != 0 && data[i-1] == '\\' {
+ i++
+ continue
+ }
+ if data[i] == c {
+ return i
+ }
+
+ if data[i] == '`' {
+ // skip a code span
+ tmpI := 0
+ i++
+ for i < len(data) && data[i] != '`' {
+ if tmpI == 0 && data[i] == c {
+ tmpI = i
+ }
+ i++
+ }
+ if i >= len(data) {
+ return tmpI
+ }
+ i++
+ } else if data[i] == '[' {
+ // skip a link
+ tmpI := 0
+ i++
+ for i < len(data) && data[i] != ']' {
+ if tmpI == 0 && data[i] == c {
+ tmpI = i
+ }
+ i++
+ }
+ i++
+ for i < len(data) && (data[i] == ' ' || data[i] == '\n') {
+ i++
+ }
+ if i >= len(data) {
+ return tmpI
+ }
+ if data[i] != '[' && data[i] != '(' { // not a link
+ if tmpI > 0 {
+ return tmpI
+ }
+ continue
+ }
+ cc := data[i]
+ i++
+ for i < len(data) && data[i] != cc {
+ if tmpI == 0 && data[i] == c {
+ return i
+ }
+ i++
+ }
+ if i >= len(data) {
+ return tmpI
+ }
+ i++
+ }
+ }
+ return 0
+}
+
+func helperEmphasis(p *Markdown, data []byte, c byte) (int, *Node) {
+ i := 0
+
+ // skip one symbol if coming from emph3
+ if len(data) > 1 && data[0] == c && data[1] == c {
+ i = 1
+ }
+
+ for i < len(data) {
+ length := helperFindEmphChar(data[i:], c)
+ if length == 0 {
+ return 0, nil
+ }
+ i += length
+ if i >= len(data) {
+ return 0, nil
+ }
+
+ if i+1 < len(data) && data[i+1] == c {
+ i++
+ continue
+ }
+
+ if data[i] == c && !isspace(data[i-1]) {
+
+ if p.extensions&NoIntraEmphasis != 0 {
+ if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) {
+ continue
+ }
+ }
+
+ emph := NewNode(Emph)
+ p.inline(emph, data[:i])
+ return i + 1, emph
+ }
+ }
+
+ return 0, nil
+}
+
+func helperDoubleEmphasis(p *Markdown, data []byte, c byte) (int, *Node) {
+ i := 0
+
+ for i < len(data) {
+ length := helperFindEmphChar(data[i:], c)
+ if length == 0 {
+ return 0, nil
+ }
+ i += length
+
+ if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) {
+ nodeType := Strong
+ if c == '~' {
+ nodeType = Del
+ }
+ node := NewNode(nodeType)
+ p.inline(node, data[:i])
+ return i + 2, node
+ }
+ i++
+ }
+ return 0, nil
+}
+
+func helperTripleEmphasis(p *Markdown, data []byte, offset int, c byte) (int, *Node) {
+ i := 0
+ origData := data
+ data = data[offset:]
+
+ for i < len(data) {
+ length := helperFindEmphChar(data[i:], c)
+ if length == 0 {
+ return 0, nil
+ }
+ i += length
+
+ // skip whitespace preceded symbols
+ if data[i] != c || isspace(data[i-1]) {
+ continue
+ }
+
+ switch {
+ case i+2 < len(data) && data[i+1] == c && data[i+2] == c:
+ // triple symbol found
+ strong := NewNode(Strong)
+ em := NewNode(Emph)
+ strong.AppendChild(em)
+ p.inline(em, data[:i])
+ return i + 3, strong
+ case (i+1 < len(data) && data[i+1] == c):
+ // double symbol found, hand over to emph1
+ length, node := helperEmphasis(p, origData[offset-2:], c)
+ if length == 0 {
+ return 0, nil
+ }
+ return length - 2, node
+ default:
+ // single symbol found, hand over to emph2
+ length, node := helperDoubleEmphasis(p, origData[offset-1:], c)
+ if length == 0 {
+ return 0, nil
+ }
+ return length - 1, node
+ }
+ }
+ return 0, nil
+}
+
+func text(s []byte) *Node {
+ node := NewNode(Text)
+ node.Literal = s
+ return node
+}
+
+func normalizeURI(s []byte) []byte {
+ return s // TODO: implement
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/markdown.go b/vendor/github.com/russross/blackfriday/v2/markdown.go
new file mode 100644
index 000000000..58d2e4538
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/markdown.go
@@ -0,0 +1,950 @@
+// 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.
+
+package blackfriday
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "strings"
+ "unicode/utf8"
+)
+
+//
+// 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.
+// OR these values together to select multiple extensions.
+const (
+ NoExtensions Extensions = 0
+ NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
+ Tables // Render tables
+ FencedCode // Render fenced code blocks
+ Autolink // Detect embedded URLs that are not explicitly marked
+ Strikethrough // Strikethrough text using ~~test~~
+ LaxHTMLBlocks // Loosen up HTML block parsing rules
+ SpaceHeadings // Be strict about prefix heading rules
+ HardLineBreak // Translate newlines into line breaks
+ TabSizeEight // Expand tabs to eight spaces instead of four
+ Footnotes // Pandoc-style footnotes
+ NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
+ HeadingIDs // specify heading IDs with {#id}
+ Titleblock // Titleblock ala pandoc
+ AutoHeadingIDs // Create the heading ID from the text
+ BackslashLineBreak // Translate trailing backslashes into line breaks
+ DefinitionLists // Render definition lists
+
+ CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
+ SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
+
+ CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
+ Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
+ BackslashLineBreak | DefinitionLists
+)
+
+// ListType contains bitwise or'ed flags for list and list item objects.
+type ListType int
+
+// These are the possible flag values for the ListItem renderer.
+// Multiple flag values may be ORed together.
+// These are mostly of interest if you are writing a new output format.
+const (
+ ListTypeOrdered ListType = 1 << iota
+ ListTypeDefinition
+ ListTypeTerm
+
+ ListItemContainsBlock
+ ListItemBeginningOfList // TODO: figure out if this is of any use now
+ ListItemEndOfList
+)
+
+// CellAlignFlags holds a type of alignment in a table cell.
+type CellAlignFlags int
+
+// These are the possible flag values for the table cell renderer.
+// 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 (
+ TableAlignmentLeft CellAlignFlags = 1 << iota
+ TableAlignmentRight
+ TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
+)
+
+// The size of a tab stop.
+const (
+ TabSizeDefault = 4
+ TabSizeDouble = 8
+)
+
+// blockTags is a set of tags that are recognized as HTML block tags.
+// Any of these can be included in markdown text without special escaping.
+var blockTags = map[string]struct{}{
+ "blockquote": {},
+ "del": {},
+ "div": {},
+ "dl": {},
+ "fieldset": {},
+ "form": {},
+ "h1": {},
+ "h2": {},
+ "h3": {},
+ "h4": {},
+ "h5": {},
+ "h6": {},
+ "iframe": {},
+ "ins": {},
+ "math": {},
+ "noscript": {},
+ "ol": {},
+ "pre": {},
+ "p": {},
+ "script": {},
+ "style": {},
+ "table": {},
+ "ul": {},
+
+ // HTML5
+ "address": {},
+ "article": {},
+ "aside": {},
+ "canvas": {},
+ "figcaption": {},
+ "figure": {},
+ "footer": {},
+ "header": {},
+ "hgroup": {},
+ "main": {},
+ "nav": {},
+ "output": {},
+ "progress": {},
+ "section": {},
+ "video": {},
+}
+
+// Renderer is the rendering interface. 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
+// for external implementations.
+type Renderer interface {
+ // RenderNode is the main rendering method. It will be called once for
+ // every leaf node and twice for every non-leaf node (first with
+ // entering=true, then with entering=false). The method should write its
+ // rendition of the node to the supplied writer w.
+ RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
+
+ // RenderHeader is a method that allows the renderer to produce some
+ // content preceding the main body of the output document. The header is
+ // understood in the broad sense here. For example, the default HTML
+ // renderer will write not only the HTML document preamble, but also the
+ // table of contents if it was requested.
+ //
+ // The method will be passed an entire document tree, in case a particular
+ // implementation needs to inspect it to produce output.
+ //
+ // The output should be written to the supplied writer w. If your
+ // implementation has no header to write, supply an empty implementation.
+ RenderHeader(w io.Writer, ast *Node)
+
+ // RenderFooter is a symmetric counterpart of RenderHeader.
+ RenderFooter(w io.Writer, ast *Node)
+}
+
+// Callback functions for inline parsing. One such function is defined
+// for each character that triggers a response when parsing inline data.
+type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
+
+// Markdown is a type that holds extensions and the runtime state used by
+// Parse, and the renderer. You can not use it directly, construct it with New.
+type Markdown struct {
+ renderer Renderer
+ referenceOverride ReferenceOverrideFunc
+ refs map[string]*reference
+ inlineCallback [256]inlineParser
+ extensions Extensions
+ nesting int
+ maxNesting int
+ insideLink bool
+
+ // Footnotes need to be ordered as well as available to quickly check for
+ // presence. If a ref is also a footnote, it's stored both in refs and here
+ // in notes. Slice is nil if footnotes not enabled.
+ notes []*reference
+
+ doc *Node
+ tip *Node // = doc
+ oldTip *Node
+ lastMatchedContainer *Node // = doc
+ allClosed bool
+}
+
+func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
+ if p.referenceOverride != nil {
+ r, overridden := p.referenceOverride(refid)
+ if overridden {
+ if r == nil {
+ return nil, false
+ }
+ return &reference{
+ link: []byte(r.Link),
+ title: []byte(r.Title),
+ noteID: 0,
+ hasBlock: false,
+ text: []byte(r.Text)}, true
+ }
+ }
+ // refs are case insensitive
+ ref, found = p.refs[strings.ToLower(refid)]
+ return ref, found
+}
+
+func (p *Markdown) finalize(block *Node) {
+ above := block.Parent
+ block.open = false
+ p.tip = above
+}
+
+func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
+ return p.addExistingChild(NewNode(node), offset)
+}
+
+func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
+ for !p.tip.canContain(node.Type) {
+ p.finalize(p.tip)
+ }
+ p.tip.AppendChild(node)
+ p.tip = node
+ return node
+}
+
+func (p *Markdown) closeUnmatchedBlocks() {
+ if !p.allClosed {
+ for p.oldTip != p.lastMatchedContainer {
+ parent := p.oldTip.Parent
+ p.finalize(p.oldTip)
+ p.oldTip = parent
+ }
+ p.allClosed = true
+ }
+}
+
+//
+//
+// Public interface
+//
+//
+
+// Reference represents the details of a link.
+// See the documentation in Options for more details on use-case.
+type Reference struct {
+ // Link is usually the URL the reference points to.
+ Link string
+ // Title is the alternate text describing the link in more detail.
+ Title string
+ // Text is the optional text to override the ref with if the syntax used was
+ // [refid][]
+ Text string
+}
+
+// ReferenceOverrideFunc is expected to be called with a reference string and
+// return either a valid Reference type that the reference string maps to or
+// nil. If overridden is false, the default reference logic will be executed.
+// See the documentation in Options for more details on use-case.
+type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
+
+// New constructs a Markdown processor. You can use the same With* functions as
+// for Run() to customize parser's behavior and the renderer.
+func New(opts ...Option) *Markdown {
+ var p Markdown
+ for _, opt := range opts {
+ opt(&p)
+ }
+ p.refs = make(map[string]*reference)
+ p.maxNesting = 16
+ p.insideLink = false
+ docNode := NewNode(Document)
+ p.doc = docNode
+ p.tip = docNode
+ p.oldTip = docNode
+ p.lastMatchedContainer = docNode
+ p.allClosed = true
+ // register inline parsers
+ p.inlineCallback[' '] = maybeLineBreak
+ p.inlineCallback['*'] = emphasis
+ p.inlineCallback['_'] = emphasis
+ if p.extensions&Strikethrough != 0 {
+ p.inlineCallback['~'] = emphasis
+ }
+ p.inlineCallback['`'] = codeSpan
+ p.inlineCallback['\n'] = lineBreak
+ p.inlineCallback['['] = link
+ p.inlineCallback['<'] = leftAngle
+ p.inlineCallback['\\'] = escape
+ p.inlineCallback['&'] = entity
+ p.inlineCallback['!'] = maybeImage
+ p.inlineCallback['^'] = maybeInlineFootnote
+ if p.extensions&Autolink != 0 {
+ p.inlineCallback['h'] = maybeAutoLink
+ p.inlineCallback['m'] = maybeAutoLink
+ p.inlineCallback['f'] = maybeAutoLink
+ p.inlineCallback['H'] = maybeAutoLink
+ p.inlineCallback['M'] = maybeAutoLink
+ p.inlineCallback['F'] = maybeAutoLink
+ }
+ if p.extensions&Footnotes != 0 {
+ p.notes = make([]*reference, 0)
+ }
+ return &p
+}
+
+// Option customizes the Markdown processor's default behavior.
+type Option func(*Markdown)
+
+// WithRenderer allows you to override the default renderer.
+func WithRenderer(r Renderer) Option {
+ return func(p *Markdown) {
+ p.renderer = r
+ }
+}
+
+// WithExtensions allows you to pick some of the many extensions provided by
+// Blackfriday. You can bitwise OR them.
+func WithExtensions(e Extensions) Option {
+ return func(p *Markdown) {
+ p.extensions = e
+ }
+}
+
+// WithNoExtensions turns off all extensions and custom behavior.
+func WithNoExtensions() Option {
+ return func(p *Markdown) {
+ p.extensions = NoExtensions
+ p.renderer = NewHTMLRenderer(HTMLRendererParameters{
+ Flags: HTMLFlagsNone,
+ })
+ }
+}
+
+// WithRefOverride sets 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.
+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 {
+ p.inline(block, ref.title)
+ }
+ flags &^= ListItemBeginningOfList | ListItemContainsBlock
+ }
+ above := block.Parent
+ finalizeList(block)
+ p.tip = above
+ block.Walk(func(node *Node, entering bool) WalkStatus {
+ if node.Type == Paragraph || node.Type == Heading {
+ p.inline(node, node.content)
+ node.content = nil
+ }
+ return GoToNext
+ })
+}
+
+//
+// Link references
+//
+// This section implements support for references that (usually) appear
+// as footnotes in a document, and can be referenced anywhere in the document.
+// The basic format is:
+//
+// [1]: http://www.google.com/ "Google"
+// [2]: http://www.github.com/ "Github"
+//
+// Anywhere in the document, the reference can be linked by referring to its
+// label, i.e., 1 and 2 in this example, as in:
+//
+// This library is hosted on [Github][2], a git hosting site.
+//
+// Actual footnotes as specified in Pandoc and supported by some other Markdown
+// libraries such as php-markdown are also taken care of. They look like this:
+//
+// This sentence needs a bit of further explanation.[^note]
+//
+// [^note]: This is the explanation.
+//
+// Footnotes should be placed at the end of the document in an ordered list.
+// Finally, there are inline footnotes such as:
+//
+// Inline footnotes^[Also supported.] provide a quick inline explanation,
+// but are rendered at the bottom of the document.
+//
+
+// reference holds all information necessary for a reference-style links or
+// footnotes.
+//
+// Consider this markdown with reference-style links:
+//
+// [link][ref]
+//
+// [ref]: /url/ "tooltip title"
+//
+// It will be ultimately converted to this HTML:
+//
+// <p><a href=\"/url/\" title=\"title\">link</a></p>
+//
+// And a reference structure will be populated as follows:
+//
+// p.refs["ref"] = &reference{
+// link: "/url/",
+// title: "tooltip title",
+// }
+//
+// Alternatively, reference can contain information about a footnote. Consider
+// this markdown:
+//
+// Text needing a footnote.[^a]
+//
+// [^a]: This is the note
+//
+// A reference structure will be populated as follows:
+//
+// p.refs["a"] = &reference{
+// link: "a",
+// title: "This is the note",
+// noteID: <some positive int>,
+// }
+//
+// TODO: As you can see, it begs for splitting into two dedicated structures
+// for refs and for footnotes.
+type reference struct {
+ link []byte
+ title []byte
+ noteID int // 0 if not a footnote ref
+ hasBlock bool
+ footnote *Node // a link to the Item node within a list of footnotes
+
+ text []byte // only gets populated by refOverride feature with Reference.Text
+}
+
+func (r *reference) String() string {
+ return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
+ r.link, r.title, r.text, r.noteID, r.hasBlock)
+}
+
+// Check whether or not data starts with a reference link.
+// If so, it is parsed and stored in the list of references
+// (in the render struct).
+// Returns the number of bytes to skip to move past it,
+// or zero if the first line is not a reference.
+func isReference(p *Markdown, data []byte, tabSize int) int {
+ // up to 3 optional leading spaces
+ if len(data) < 4 {
+ return 0
+ }
+ i := 0
+ for i < 3 && data[i] == ' ' {
+ i++
+ }
+
+ noteID := 0
+
+ // id part: anything but a newline between brackets
+ if data[i] != '[' {
+ return 0
+ }
+ i++
+ if p.extensions&Footnotes != 0 {
+ if i < len(data) && data[i] == '^' {
+ // 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
+ noteID = 1
+ i++
+ }
+ }
+ idOffset := i
+ for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
+ i++
+ }
+ if i >= len(data) || data[i] != ']' {
+ return 0
+ }
+ 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)*
+ i++
+ if i >= len(data) || data[i] != ':' {
+ return 0
+ }
+ i++
+ for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+ i++
+ }
+ if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
+ i++
+ if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
+ i++
+ }
+ }
+ for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+ i++
+ }
+ if i >= len(data) {
+ return 0
+ }
+
+ var (
+ linkOffset, linkEnd int
+ titleOffset, titleEnd int
+ lineEnd int
+ raw []byte
+ hasBlock bool
+ )
+
+ if p.extensions&Footnotes != 0 && noteID != 0 {
+ linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
+ lineEnd = linkEnd
+ } else {
+ linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
+ }
+ if lineEnd == 0 {
+ return 0
+ }
+
+ // a valid ref has been found
+
+ ref := &reference{
+ noteID: noteID,
+ hasBlock: hasBlock,
+ }
+
+ if noteID > 0 {
+ // reusing the link field for the id since footnotes don't have links
+ ref.link = data[idOffset:idEnd]
+ // if footnote, it's not really a title, it's the contained text
+ ref.title = raw
+ } else {
+ ref.link = data[linkOffset:linkEnd]
+ ref.title = data[titleOffset:titleEnd]
+ }
+
+ // id matches are case-insensitive
+ id := string(bytes.ToLower(data[idOffset:idEnd]))
+
+ p.refs[id] = ref
+
+ return lineEnd
+}
+
+func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
+ // link: whitespace-free sequence, optionally between angle brackets
+ if data[i] == '<' {
+ i++
+ }
+ linkOffset = i
+ for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
+ i++
+ }
+ linkEnd = i
+ if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
+ linkOffset++
+ linkEnd--
+ }
+
+ // optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
+ for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+ i++
+ }
+ if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
+ return
+ }
+
+ // compute end-of-line
+ if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
+ lineEnd = i
+ }
+ if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
+ lineEnd++
+ }
+
+ // optional (space|tab)* spacer after a newline
+ if lineEnd > 0 {
+ i = lineEnd + 1
+ for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
+ i++
+ }
+ }
+
+ // optional title: any non-newline sequence enclosed in '"() alone on its line
+ if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
+ i++
+ titleOffset = i
+
+ // look for EOL
+ for i < len(data) && data[i] != '\n' && data[i] != '\r' {
+ i++
+ }
+ if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
+ titleEnd = i + 1
+ } else {
+ titleEnd = i
+ }
+
+ // step back
+ i--
+ for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
+ i--
+ }
+ if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
+ lineEnd = titleEnd
+ titleEnd = i
+ }
+ }
+
+ return
+}
+
+// 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
+// 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
+// extracted text that was shifted over one tab. It will need to be rendered at
+// the end of the document.
+func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
+ if i == 0 || len(data) == 0 {
+ return
+ }
+
+ // skip leading whitespace on first line
+ for i < len(data) && data[i] == ' ' {
+ i++
+ }
+
+ blockStart = i
+
+ // find the end of the line
+ blockEnd = i
+ for i < len(data) && data[i-1] != '\n' {
+ i++
+ }
+
+ // get working buffer
+ var raw bytes.Buffer
+
+ // put the first line into the working buffer
+ raw.Write(data[blockEnd:i])
+ blockEnd = i
+
+ // process the following lines
+ containsBlankLine := false
+
+gatherLines:
+ for blockEnd < len(data) {
+ i++
+
+ // find the end of this line
+ for i < len(data) && 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[blockEnd:i]) > 0 {
+ containsBlankLine = true
+ blockEnd = i
+ continue
+ }
+
+ n := 0
+ if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
+ // this is the end of the block.
+ // we don't want to include this last line in the index.
+ break gatherLines
+ }
+
+ // if there were blank lines before this one, insert a new one now
+ if containsBlankLine {
+ raw.WriteByte('\n')
+ containsBlankLine = false
+ }
+
+ // get rid of that first tab, write to buffer
+ raw.Write(data[blockEnd+n : i])
+ hasBlock = true
+
+ blockEnd = i
+ }
+
+ if data[blockEnd-1] != '\n' {
+ raw.WriteByte('\n')
+ }
+
+ contents = raw.Bytes()
+
+ return
+}
+
+//
+//
+// Miscellaneous helper functions
+//
+//
+
+// Test if a character is a punctuation symbol.
+// Taken from a private function in regexp in the stdlib.
+func ispunct(c byte) bool {
+ for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
+ if c == r {
+ return true
+ }
+ }
+ return false
+}
+
+// Test if a character is a whitespace character.
+func isspace(c byte) bool {
+ 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 character.
+func isverticalspace(c byte) bool {
+ return c == '\n' || c == '\r' || c == '\f' || c == '\v'
+}
+
+// Test if a character is letter.
+func isletter(c byte) bool {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
+}
+
+// Test if a character is a letter or a digit.
+// TODO: check when this is looking for ASCII alnum and when it should use unicode
+func isalnum(c byte) bool {
+ return (c >= '0' && c <= '9') || isletter(c)
+}
+
+// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
+// always ends output with a newline
+func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
+ // first, check for common cases: no tabs, or only tabs at beginning of line
+ i, prefix := 0, 0
+ slowcase := false
+ for i = 0; i < len(line); i++ {
+ if line[i] == '\t' {
+ if prefix == i {
+ prefix++
+ } else {
+ slowcase = true
+ break
+ }
+ }
+ }
+
+ // no need to decode runes if all tabs are at the beginning of the line
+ if !slowcase {
+ for i = 0; i < prefix*tabSize; i++ {
+ out.WriteByte(' ')
+ }
+ out.Write(line[prefix:])
+ return
+ }
+
+ // the slow case: we need to count runes to figure out how
+ // many spaces to insert for each tab
+ column := 0
+ i = 0
+ for i < len(line) {
+ start := i
+ for i < len(line) && line[i] != '\t' {
+ _, size := utf8.DecodeRune(line[i:])
+ i += size
+ column++
+ }
+
+ if i > start {
+ out.Write(line[start:i])
+ }
+
+ if i >= len(line) {
+ break
+ }
+
+ for {
+ out.WriteByte(' ')
+ column++
+ if column%tabSize == 0 {
+ break
+ }
+ }
+
+ i++
+ }
+}
+
+// Find if a line counts as indented or not.
+// Returns number of characters the indent is (0 = not indented).
+func isIndented(data []byte, indentSize int) int {
+ if len(data) == 0 {
+ return 0
+ }
+ if data[0] == '\t' {
+ return 1
+ }
+ if len(data) < indentSize {
+ return 0
+ }
+ for i := 0; i < indentSize; i++ {
+ if data[i] != ' ' {
+ return 0
+ }
+ }
+ return indentSize
+}
+
+// Create a url-safe slug for fragments
+func slugify(in []byte) []byte {
+ if len(in) == 0 {
+ return in
+ }
+ out := make([]byte, 0, len(in))
+ sym := false
+
+ for _, ch := range in {
+ if isalnum(ch) {
+ sym = false
+ out = append(out, ch)
+ } else if sym {
+ continue
+ } else {
+ out = append(out, '-')
+ sym = true
+ }
+ }
+ var a, b int
+ var ch byte
+ for a, ch = range out {
+ if ch != '-' {
+ break
+ }
+ }
+ for b = len(out) - 1; b > 0; b-- {
+ if out[b] != '-' {
+ break
+ }
+ }
+ return out[a : b+1]
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/node.go b/vendor/github.com/russross/blackfriday/v2/node.go
new file mode 100644
index 000000000..04e6050ce
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/node.go
@@ -0,0 +1,360 @@
+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
+ }
+}
+
+// IsContainer returns true if 'n' can contain children.
+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
+ }
+}
+
+// IsLeaf returns true if 'n' is a leaf node.
+func (n *Node) IsLeaf() bool {
+ return !n.IsContainer()
+}
+
+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)
+}
diff --git a/vendor/github.com/russross/blackfriday/v2/smartypants.go b/vendor/github.com/russross/blackfriday/v2/smartypants.go
new file mode 100644
index 000000000..3a220e942
--- /dev/null
+++ b/vendor/github.com/russross/blackfriday/v2/smartypants.go
@@ -0,0 +1,457 @@
+//
+// 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.
+//
+
+//
+//
+// SmartyPants rendering
+//
+//
+
+package blackfriday
+
+import (
+ "bytes"
+ "io"
+)
+
+// SPRenderer is a struct containing state of a Smartypants renderer.
+type SPRenderer struct {
+ inSingleQuote bool
+ inDoubleQuote bool
+ callbacks [256]smartCallback
+}
+
+func wordBoundary(c byte) bool {
+ return c == 0 || isspace(c) || ispunct(c)
+}
+
+func tolower(c byte) byte {
+ if c >= 'A' && c <= 'Z' {
+ return c - 'A' + 'a'
+ }
+ return c
+}
+
+func isdigit(c byte) bool {
+ return c >= '0' && c <= '9'
+}
+
+func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
+ // edge of the buffer is likely to be a tag that we don't get to see,
+ // so we treat it like text sometimes
+
+ // enumerate all sixteen possibilities for (previousChar, nextChar)
+ // each can be one of {0, space, punct, other}
+ switch {
+ case previousChar == 0 && nextChar == 0:
+ // context is not any help here, so toggle
+ *isOpen = !*isOpen
+ case isspace(previousChar) && nextChar == 0:
+ // [ "] might be [ "<code>foo...]
+ *isOpen = true
+ case ispunct(previousChar) && nextChar == 0:
+ // [!"] hmm... could be [Run!"] or [("<code>...]
+ *isOpen = false
+ case /* isnormal(previousChar) && */ nextChar == 0:
+ // [a"] is probably a close
+ *isOpen = false
+ case previousChar == 0 && isspace(nextChar):
+ // [" ] might be [...foo</code>" ]
+ *isOpen = false
+ case isspace(previousChar) && isspace(nextChar):
+ // [ " ] context is not any help here, so toggle
+ *isOpen = !*isOpen
+ case ispunct(previousChar) && isspace(nextChar):
+ // [!" ] is probably a close
+ *isOpen = false
+ case /* isnormal(previousChar) && */ isspace(nextChar):
+ // [a" ] this is one of the easy cases
+ *isOpen = false
+ case previousChar == 0 && ispunct(nextChar):
+ // ["!] hmm... could be ["$1.95] or [</code>"!...]
+ *isOpen = false
+ case isspace(previousChar) && ispunct(nextChar):
+ // [ "!] looks more like [ "$1.95]
+ *isOpen = true
+ case ispunct(previousChar) && ispunct(nextChar):
+ // [!"!] context is not any help here, so toggle
+ *isOpen = !*isOpen
+ case /* isnormal(previousChar) && */ ispunct(nextChar):
+ // [a"!] is probably a close
+ *isOpen = false
+ case previousChar == 0 /* && isnormal(nextChar) */ :
+ // ["a] is probably an open
+ *isOpen = true
+ case isspace(previousChar) /* && isnormal(nextChar) */ :
+ // [ "a] this is one of the easy cases
+ *isOpen = true
+ case ispunct(previousChar) /* && isnormal(nextChar) */ :
+ // [!"a] is probably an open
+ *isOpen = true
+ default:
+ // [a'b] maybe a contraction?
+ *isOpen = false
+ }
+
+ // Note that with the limited lookahead, this non-breaking
+ // space will also be appended to single double quotes.
+ if addNBSP && !*isOpen {
+ out.WriteString("&nbsp;")
+ }
+
+ out.WriteByte('&')
+ if *isOpen {
+ out.WriteByte('l')
+ } else {
+ out.WriteByte('r')
+ }
+ out.WriteByte(quote)
+ out.WriteString("quo;")
+
+ if addNBSP && *isOpen {
+ out.WriteString("&nbsp;")
+ }
+
+ return true
+}
+
+func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if len(text) >= 2 {
+ t1 := tolower(text[1])
+
+ if t1 == '\'' {
+ nextChar := byte(0)
+ if len(text) >= 3 {
+ nextChar = text[2]
+ }
+ if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
+ return 1
+ }
+ }
+
+ if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
+ out.WriteString("&rsquo;")
+ return 0
+ }
+
+ if len(text) >= 3 {
+ t2 := tolower(text[2])
+
+ if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
+ (len(text) < 4 || wordBoundary(text[3])) {
+ out.WriteString("&rsquo;")
+ return 0
+ }
+ }
+ }
+
+ nextChar := byte(0)
+ if len(text) > 1 {
+ nextChar = text[1]
+ }
+ if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) {
+ return 0
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if len(text) >= 3 {
+ t1 := tolower(text[1])
+ t2 := tolower(text[2])
+
+ if t1 == 'c' && t2 == ')' {
+ out.WriteString("&copy;")
+ return 2
+ }
+
+ if t1 == 'r' && t2 == ')' {
+ out.WriteString("&reg;")
+ return 2
+ }
+
+ if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
+ out.WriteString("&trade;")
+ return 3
+ }
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if len(text) >= 2 {
+ if text[1] == '-' {
+ out.WriteString("&mdash;")
+ return 1
+ }
+
+ if wordBoundary(previousChar) && wordBoundary(text[1]) {
+ out.WriteString("&ndash;")
+ return 0
+ }
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
+ out.WriteString("&mdash;")
+ return 2
+ }
+ if len(text) >= 2 && text[1] == '-' {
+ out.WriteString("&ndash;")
+ return 1
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int {
+ if bytes.HasPrefix(text, []byte("&quot;")) {
+ nextChar := byte(0)
+ if len(text) >= 7 {
+ nextChar = text[6]
+ }
+ if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) {
+ return 5
+ }
+ }
+
+ if bytes.HasPrefix(text, []byte("&#0;")) {
+ return 3
+ }
+
+ out.WriteByte('&')
+ return 0
+}
+
+func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int {
+ var quote byte = 'd'
+ if angledQuotes {
+ quote = 'a'
+ }
+
+ return func(out *bytes.Buffer, previousChar byte, text []byte) int {
+ return r.smartAmpVariant(out, previousChar, text, quote, addNBSP)
+ }
+}
+
+func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
+ out.WriteString("&hellip;")
+ return 2
+ }
+
+ if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
+ out.WriteString("&hellip;")
+ return 4
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if len(text) >= 2 && text[1] == '`' {
+ nextChar := byte(0)
+ if len(text) >= 3 {
+ nextChar = text[2]
+ }
+ if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) {
+ return 1
+ }
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
+ // 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)
+ // and avoid changing dates like 1/23/2005 into fractions.
+ numEnd := 0
+ for len(text) > numEnd && isdigit(text[numEnd]) {
+ numEnd++
+ }
+ if numEnd == 0 {
+ out.WriteByte(text[0])
+ return 0
+ }
+ denStart := numEnd + 1
+ if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
+ denStart = numEnd + 3
+ } else if len(text) < numEnd+2 || text[numEnd] != '/' {
+ out.WriteByte(text[0])
+ return 0
+ }
+ denEnd := denStart
+ for len(text) > denEnd && isdigit(text[denEnd]) {
+ denEnd++
+ }
+ if denEnd == denStart {
+ out.WriteByte(text[0])
+ return 0
+ }
+ if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
+ out.WriteString("<sup>")
+ out.Write(text[:numEnd])
+ out.WriteString("</sup>&frasl;<sub>")
+ out.Write(text[denStart:denEnd])
+ out.WriteString("</sub>")
+ return denEnd - 1
+ }
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int {
+ if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
+ if text[0] == '1' && text[1] == '/' && text[2] == '2' {
+ if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
+ out.WriteString("&frac12;")
+ return 2
+ }
+ }
+
+ if text[0] == '1' && text[1] == '/' && text[2] == '4' {
+ if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
+ out.WriteString("&frac14;")
+ return 2
+ }
+ }
+
+ if text[0] == '3' && text[1] == '/' && text[2] == '4' {
+ if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
+ out.WriteString("&frac34;")
+ return 2
+ }
+ }
+ }
+
+ out.WriteByte(text[0])
+ return 0
+}
+
+func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
+ nextChar := byte(0)
+ if len(text) > 1 {
+ nextChar = text[1]
+ }
+ if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) {
+ out.WriteString("&quot;")
+ }
+
+ return 0
+}
+
+func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
+ return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
+}
+
+func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
+ return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
+}
+
+func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
+ i := 0
+
+ for i < len(text) && text[i] != '>' {
+ i++
+ }
+
+ out.Write(text[:i+1])
+ return i
+}
+
+type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
+
+// NewSmartypantsRenderer constructs a Smartypants renderer object.
+func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
+ var (
+ r SPRenderer
+
+ smartAmpAngled = r.smartAmp(true, false)
+ smartAmpAngledNBSP = r.smartAmp(true, true)
+ smartAmpRegular = r.smartAmp(false, false)
+ smartAmpRegularNBSP = r.smartAmp(false, true)
+
+ addNBSP = flags&SmartypantsQuotesNBSP != 0
+ )
+
+ if flags&SmartypantsAngledQuotes == 0 {
+ r.callbacks['"'] = r.smartDoubleQuote
+ if !addNBSP {
+ r.callbacks['&'] = smartAmpRegular
+ } else {
+ r.callbacks['&'] = smartAmpRegularNBSP
+ }
+ } else {
+ r.callbacks['"'] = r.smartAngledDoubleQuote
+ if !addNBSP {
+ r.callbacks['&'] = smartAmpAngled
+ } else {
+ r.callbacks['&'] = smartAmpAngledNBSP
+ }
+ }
+ r.callbacks['\''] = r.smartSingleQuote
+ r.callbacks['('] = r.smartParens
+ if flags&SmartypantsDashes != 0 {
+ if flags&SmartypantsLatexDashes == 0 {
+ r.callbacks['-'] = r.smartDash
+ } else {
+ r.callbacks['-'] = r.smartDashLatex
+ }
+ }
+ r.callbacks['.'] = r.smartPeriod
+ if flags&SmartypantsFractions == 0 {
+ r.callbacks['1'] = r.smartNumber
+ r.callbacks['3'] = r.smartNumber
+ } else {
+ for ch := '1'; ch <= '9'; ch++ {
+ r.callbacks[ch] = r.smartNumberGeneric
+ }
+ }
+ r.callbacks['<'] = r.smartLeftAngle
+ r.callbacks['`'] = r.smartBacktick
+ return &r
+}
+
+// Process is the entry point of the Smartypants renderer.
+func (r *SPRenderer) Process(w io.Writer, text []byte) {
+ mark := 0
+ for i := 0; i < len(text); i++ {
+ if action := r.callbacks[text[i]]; action != nil {
+ if i > mark {
+ w.Write(text[mark:i])
+ }
+ previousChar := byte(0)
+ if i > 0 {
+ previousChar = text[i-1]
+ }
+ var tmp bytes.Buffer
+ i += action(&tmp, previousChar, text[i:])
+ w.Write(tmp.Bytes())
+ mark = i + 1
+ }
+ }
+ if mark < len(text) {
+ w.Write(text[mark:])
+ }
+}
diff --git a/vendor/github.com/vbatts/git-validation/.gitignore b/vendor/github.com/vbatts/git-validation/.gitignore
new file mode 100644
index 000000000..265db6acb
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/.gitignore
@@ -0,0 +1,2 @@
+*~
+git-validation
diff --git a/vendor/github.com/vbatts/git-validation/.travis.yml b/vendor/github.com/vbatts/git-validation/.travis.yml
new file mode 100644
index 000000000..27cd83c47
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/.travis.yml
@@ -0,0 +1,37 @@
+language: go
+
+go_import_path: github.com/vbatts/git-validation
+
+go:
+ - "tip"
+ - "1.x"
+ - "1.11.x"
+ - "1.10.x"
+ - "1.9.x"
+
+env:
+
+matrix:
+
+sudo: false
+
+install: true
+
+notifications:
+ email:
+ on_success: change
+ on_failure: always
+
+before_script:
+ - env
+
+before_install:
+ - go get ./...
+ - if [[ "$(go version |awk '{ print $3 }')" =~ ^go1\.11\. ]] ; then go get -u golang.org/x/lint/golint ; fi
+
+script:
+ - if [[ "$(go version |awk '{ print $3 }')" =~ ^go1\.11\. ]] ; then golint -set_exit_status ./... ; fi
+ - go vet -x ./...
+ - go build .
+ - go test -v ./...
+ - ./git-validation -run DCO,short-subject,dangling-whitespace -v
diff --git a/vendor/github.com/vbatts/git-validation/LICENSE b/vendor/github.com/vbatts/git-validation/LICENSE
new file mode 100644
index 000000000..8efd59c71
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Vincent Batts
+
+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.
diff --git a/vendor/github.com/vbatts/git-validation/README.md b/vendor/github.com/vbatts/git-validation/README.md
new file mode 100644
index 000000000..354276e02
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/README.md
@@ -0,0 +1,106 @@
+# git-validation
+
+A way to do validation on git commits.
+[![Build Status](https://travis-ci.org/vbatts/git-validation.svg?branch=master)](https://travis-ci.org/vbatts/git-validation)
+
+## install
+
+```console
+vbatts@valse ~ (master) $ go get -u github.com/vbatts/git-validation
+```
+
+## usage
+
+The flags
+```console
+vbatts@valse ~/src/vb/git-validation (master *) $ git-validation -h
+Usage of git-validation:
+ -D debug output
+ -d string
+ git directory to validate from (default ".")
+ -list-rules
+ list the rules registered
+ -range string
+ use this commit range instead
+ -run string
+ comma delimited list of rules to run. Defaults to all.
+ -v verbose
+```
+
+The entire default rule set is run by default:
+```console
+vbatts@valse ~/src/vb/git-validation (master) $ git-validation -list-rules
+"dangling-whitespace" -- checking the presence of dangling whitespaces on line endings
+"DCO" -- makes sure the commits are signed
+"message_regexp" -- checks the commit message for a user provided regular expression
+"short-subject" -- commit subjects are strictly less than 90 (github ellipsis length)
+```
+
+Or, specify comma-delimited rules to run:
+```console
+vbatts@valse ~/src/vb/git-validation (master) $ git-validation -run DCO,short-subject
+ * b243ca4 "README: adding install and usage" ... PASS
+ * d614ccf "*: run tests in a runner" ... PASS
+ * b9413c6 "shortsubject: add a subject length check" ... PASS
+ * 5e74abd "*: comments and golint" ... PASS
+ * 07a982f "git: add verbose output of the commands run" ... PASS
+ * 03bda4b "main: add filtering of rules to run" ... PASS
+ * c10ba9c "Initial commit" ... PASS
+```
+
+Verbosity shows each rule's output:
+```console
+vbatts@valse ~/src/vb/git-validation (master) $ git-validation -v
+ * d614ccf "*: run tests in a runner" ... PASS
+ - PASS - has a valid DCO
+ - PASS - commit subject is 72 characters or less! *yay*
+ * b9413c6 "shortsubject: add a subject length check" ... PASS
+ - PASS - has a valid DCO
+ - PASS - commit subject is 72 characters or less! *yay*
+ * 5e74abd "*: comments and golint" ... PASS
+ - PASS - has a valid DCO
+ - PASS - commit subject is 72 characters or less! *yay*
+ * 07a982f "git: add verbose output of the commands run" ... PASS
+ - PASS - has a valid DCO
+ - PASS - commit subject is 72 characters or less! *yay*
+ * 03bda4b "main: add filtering of rules to run" ... PASS
+ - PASS - has a valid DCO
+ - PASS - commit subject is 72 characters or less! *yay*
+ * c10ba9c "Initial commit" ... PASS
+ - PASS - has a valid DCO
+ - PASS - commit subject is 72 characters or less! *yay*
+```
+
+Here's a failure:
+```console
+vbatts@valse ~/src/vb/git-validation (master) $ git-validation
+ * 49f51a8 "README: adding install and usage" ... FAIL
+ - FAIL - does not have a valid DCO
+ * d614ccf "*: run tests in a runner" ... PASS
+ * b9413c6 "shortsubject: add a subject length check" ... PASS
+ * 5e74abd "*: comments and golint" ... PASS
+ * 07a982f "git: add verbose output of the commands run" ... PASS
+ * 03bda4b "main: add filtering of rules to run" ... PASS
+ * c10ba9c "Initial commit" ... PASS
+1 issues to fix
+vbatts@valse ~/src/vb/git-validation (master) $ echo $?
+1
+```
+
+Excluding paths that are out of the scope of your project:
+```console
+vbatts@valse ~/src/vb/git-validation (master) $ GIT_CHECK_EXCLUDE="./vendor:./git/testdata" git-validation -q -run dangling-whitespace
+...
+```
+using the `GIT_CHECK_EXCLUDE` environment variable. Multiple paths should be separated by colon(`:`)
+
+
+## Rules
+
+Default rules are added by registering them to the `validate` package.
+Usually by putting them in their own package.
+See [`./rules/`](./rules/).
+Feel free to contribute more.
+
+Otherwise, by using `validate` package API directly, rules can be handed directly to the `validate.Runner`.
+
diff --git a/vendor/github.com/vbatts/git-validation/git/commits.go b/vendor/github.com/vbatts/git-validation/git/commits.go
new file mode 100644
index 000000000..52af99789
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/git/commits.go
@@ -0,0 +1,192 @@
+package git
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+
+ version "github.com/hashicorp/go-version"
+ "github.com/sirupsen/logrus"
+)
+
+// Commits returns a set of commits.
+// If commitrange is a git still range 12345...54321, then it will be isolated set of commits.
+// If commitrange is a single commit, all ancestor commits up through the hash provided.
+// If commitrange is an empty commit range, then nil is returned.
+func Commits(commitrange string) ([]CommitEntry, error) {
+ cmdArgs := []string{"git", "--no-pager", "log", `--pretty=format:%H`, commitrange}
+ if debug() {
+ logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
+ }
+ output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
+ if err != nil {
+ logrus.Errorf("mm[git] cmd: %q", strings.Join(cmdArgs, " "))
+ return nil, err
+ }
+ if len(output) == 0 {
+ return nil, nil
+ }
+ commitHashes := strings.Split(strings.TrimSpace(string(output)), "\n")
+ commits := make([]CommitEntry, len(commitHashes))
+ for i, commitHash := range commitHashes {
+ c, err := LogCommit(commitHash)
+ if err != nil {
+ return commits, err
+ }
+ commits[i] = *c
+ }
+ return commits, nil
+}
+
+// FieldNames are for the formating and rendering of the CommitEntry structs.
+// Keys here are from git log pretty format "format:..."
+var FieldNames = map[string]string{
+ "%h": "abbreviated_commit",
+ "%p": "abbreviated_parent",
+ "%t": "abbreviated_tree",
+ "%aD": "author_date",
+ "%aE": "author_email",
+ "%aN": "author_name",
+ "%b": "body",
+ "%H": "commit",
+ "%N": "commit_notes",
+ "%cD": "committer_date",
+ "%cE": "committer_email",
+ "%cN": "committer_name",
+ "%e": "encoding",
+ "%P": "parent",
+ "%D": "refs",
+ "%f": "sanitized_subject_line",
+ "%GS": "signer",
+ "%GK": "signer_key",
+ "%s": "subject",
+ "%G?": "verification_flag",
+}
+
+func gitVersion() (string, error) {
+ cmd := exec.Command("git", "version")
+ cmd.Stderr = os.Stderr
+ buf, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ return strings.Fields(string(buf))[2], nil
+}
+
+// https://github.com/vbatts/git-validation/issues/37
+var versionWithExcludes = "1.9.5"
+
+func gitVersionNewerThan(otherV string) (bool, error) {
+ gv, err := gitVersion()
+ if err != nil {
+ return false, err
+ }
+ v1, err := version.NewVersion(gv)
+ if err != nil {
+ return false, err
+ }
+ v2, err := version.NewVersion(otherV)
+ if err != nil {
+ return false, err
+ }
+ return v2.Equal(v1) || v2.LessThan(v1), nil
+}
+
+// Check warns if changes introduce whitespace errors.
+// Returns non-zero if any issues are found.
+func Check(commit string) ([]byte, error) {
+ args := []string{
+ "--no-pager", "log", "--check",
+ fmt.Sprintf("%s^..%s", commit, commit),
+ }
+ if excludeEnvList := os.Getenv("GIT_CHECK_EXCLUDE"); excludeEnvList != "" {
+ gitNewEnough, err := gitVersionNewerThan(versionWithExcludes)
+ if err != nil {
+ return nil, err
+ }
+ if gitNewEnough {
+ excludeList := strings.Split(excludeEnvList, ":")
+ for _, exclude := range excludeList {
+ if exclude == "" {
+ continue
+ }
+ args = append(args, "--", ".", fmt.Sprintf(":(exclude)%s", exclude))
+ }
+ }
+ }
+ cmd := exec.Command("git", args...)
+ if debug() {
+ logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
+ }
+ cmd.Stderr = os.Stderr
+ return cmd.Output()
+}
+
+// Show returns the diff of a commit.
+//
+// NOTE: This could be expensive for very large commits.
+func Show(commit string) ([]byte, error) {
+ cmd := exec.Command("git", "--no-pager", "show", commit)
+ if debug() {
+ logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
+ }
+ cmd.Stderr = os.Stderr
+ return cmd.Output()
+}
+
+// CommitEntry represents a single commit's information from `git`.
+// See also FieldNames
+type CommitEntry map[string]string
+
+// LogCommit assembles the full information on a commit from its commit hash
+func LogCommit(commit string) (*CommitEntry, error) {
+ c := CommitEntry{}
+ for k, v := range FieldNames {
+ cmd := exec.Command("git", "--no-pager", "log", "-1", `--pretty=format:`+k+``, commit)
+ if debug() {
+ logrus.Infof("[git] cmd: %q", strings.Join(cmd.Args, " "))
+ }
+ cmd.Stderr = os.Stderr
+ out, err := cmd.Output()
+ if err != nil {
+ logrus.Errorf("[git] cmd: %q", strings.Join(cmd.Args, " "))
+ return nil, err
+ }
+ c[v] = strings.TrimSpace(string(out))
+ }
+
+ return &c, nil
+}
+
+func debug() bool {
+ return len(os.Getenv("DEBUG")) > 0
+}
+
+// FetchHeadCommit returns the hash of FETCH_HEAD
+func FetchHeadCommit() (string, error) {
+ cmdArgs := []string{"git", "--no-pager", "rev-parse", "--verify", "FETCH_HEAD"}
+ if debug() {
+ logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
+ }
+ output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
+ if err != nil {
+ logrus.Errorf("[git] cmd: %q", strings.Join(cmdArgs, " "))
+ return "", err
+ }
+ return strings.TrimSpace(string(output)), nil
+}
+
+// HeadCommit returns the hash of HEAD
+func HeadCommit() (string, error) {
+ cmdArgs := []string{"git", "--no-pager", "rev-parse", "--verify", "HEAD"}
+ if debug() {
+ logrus.Infof("[git] cmd: %q", strings.Join(cmdArgs, " "))
+ }
+ output, err := exec.Command(cmdArgs[0], cmdArgs[1:]...).Output()
+ if err != nil {
+ logrus.Errorf("[git] cmd: %q", strings.Join(cmdArgs, " "))
+ return "", err
+ }
+ return strings.TrimSpace(string(output)), nil
+}
diff --git a/vendor/github.com/vbatts/git-validation/go.mod b/vendor/github.com/vbatts/git-validation/go.mod
new file mode 100644
index 000000000..14b7069e7
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/go.mod
@@ -0,0 +1,8 @@
+module github.com/vbatts/git-validation
+
+go 1.12
+
+require (
+ github.com/hashicorp/go-version v1.2.0
+ github.com/sirupsen/logrus v1.4.1
+)
diff --git a/vendor/github.com/vbatts/git-validation/go.sum b/vendor/github.com/vbatts/git-validation/go.sum
new file mode 100644
index 000000000..265941fe3
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/go.sum
@@ -0,0 +1,15 @@
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
+github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
diff --git a/vendor/github.com/vbatts/git-validation/main.go b/vendor/github.com/vbatts/git-validation/main.go
new file mode 100644
index 000000000..cd5f271a4
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/main.go
@@ -0,0 +1,92 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ _ "github.com/vbatts/git-validation/rules/danglingwhitespace"
+ _ "github.com/vbatts/git-validation/rules/dco"
+ _ "github.com/vbatts/git-validation/rules/messageregexp"
+ _ "github.com/vbatts/git-validation/rules/shortsubject"
+ "github.com/vbatts/git-validation/validate"
+)
+
+var (
+ flCommitRange = flag.String("range", "", "use this commit range instead (implies -no-travis)")
+ flListRules = flag.Bool("list-rules", false, "list the rules registered")
+ flRun = flag.String("run", "", "comma delimited list of rules to run. Defaults to all.")
+ flVerbose = flag.Bool("v", false, "verbose")
+ flDebug = flag.Bool("D", false, "debug output")
+ flQuiet = flag.Bool("q", false, "less output")
+ flDir = flag.String("d", ".", "git directory to validate from")
+ flNoTravis = flag.Bool("no-travis", false, "disables travis environment checks (when env TRAVIS=true is set)")
+ flTravisPROnly = flag.Bool("travis-pr-only", true, "when on travis, only run validations if the CI-Build is checking pull-request build")
+)
+
+func main() {
+ flag.Parse()
+
+ if *flDebug {
+ os.Setenv("DEBUG", "1")
+ }
+ if *flQuiet {
+ os.Setenv("QUIET", "1")
+ }
+
+ if *flListRules {
+ for _, r := range validate.RegisteredRules {
+ fmt.Printf("%q -- %s\n", r.Name, r.Description)
+ }
+ return
+ }
+
+ if *flTravisPROnly && strings.ToLower(os.Getenv("TRAVIS_PULL_REQUEST")) == "false" {
+ fmt.Printf("only to check travis PR builds and this not a PR build. yielding.\n")
+ return
+ }
+
+ // rules to be used
+ var rules []validate.Rule
+ for _, r := range validate.RegisteredRules {
+ // only those that are Default
+ if r.Default {
+ rules = append(rules, r)
+ }
+ }
+ // or reduce the set being run to what the user provided
+ if *flRun != "" {
+ rules = validate.FilterRules(validate.RegisteredRules, validate.SanitizeFilters(*flRun))
+ }
+ if os.Getenv("DEBUG") != "" {
+ log.Printf("%#v", rules) // XXX maybe reduce this list
+ }
+
+ var commitRange = *flCommitRange
+ if commitRange == "" {
+ if strings.ToLower(os.Getenv("TRAVIS")) == "true" && !*flNoTravis {
+ if os.Getenv("TRAVIS_COMMIT_RANGE") != "" {
+ commitRange = strings.Replace(os.Getenv("TRAVIS_COMMIT_RANGE"), "...", "..", 1)
+ } else if os.Getenv("TRAVIS_COMMIT") != "" {
+ commitRange = os.Getenv("TRAVIS_COMMIT")
+ }
+ }
+ }
+
+ runner, err := validate.NewRunner(*flDir, rules, commitRange, *flVerbose)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if err := runner.Run(); err != nil {
+ log.Fatal(err)
+ }
+ _, fail := runner.Results.PassFail()
+ if fail > 0 {
+ fmt.Printf("%d commits to fix\n", fail)
+ os.Exit(1)
+ }
+
+}
diff --git a/vendor/github.com/vbatts/git-validation/rules/danglingwhitespace/rule.go b/vendor/github.com/vbatts/git-validation/rules/danglingwhitespace/rule.go
new file mode 100644
index 000000000..dab3a984b
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/rules/danglingwhitespace/rule.go
@@ -0,0 +1,39 @@
+package danglingwhitespace
+
+import (
+ "github.com/vbatts/git-validation/git"
+ "github.com/vbatts/git-validation/validate"
+)
+
+var (
+ // DanglingWhitespace is the rule for checking the presence of dangling
+ // whitespaces on line endings.
+ DanglingWhitespace = validate.Rule{
+ Name: "dangling-whitespace",
+ Description: "checking the presence of dangling whitespaces on line endings",
+ Run: ValidateDanglingWhitespace,
+ Default: true,
+ }
+)
+
+func init() {
+ validate.RegisterRule(DanglingWhitespace)
+}
+
+// ValidateDanglingWhitespace runs Git's check to look for whitespace errors.
+func ValidateDanglingWhitespace(r validate.Rule, c git.CommitEntry) (vr validate.Result) {
+ vr.CommitEntry = c
+ vr.Msg = "commit does not have any whitespace errors"
+ vr.Pass = true
+
+ _, err := git.Check(c["commit"])
+ if err != nil {
+ vr.Pass = false
+ if err.Error() == "exit status 2" {
+ vr.Msg = "has whitespace errors. See `git show --check " + c["commit"] + "`."
+ } else {
+ vr.Msg = "errored with: " + err.Error()
+ }
+ }
+ return
+}
diff --git a/vendor/github.com/vbatts/git-validation/rules/dco/dco.go b/vendor/github.com/vbatts/git-validation/rules/dco/dco.go
new file mode 100644
index 000000000..a42ea06ac
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/rules/dco/dco.go
@@ -0,0 +1,51 @@
+package dco
+
+import (
+ "regexp"
+ "strings"
+
+ "github.com/vbatts/git-validation/git"
+ "github.com/vbatts/git-validation/validate"
+)
+
+func init() {
+ validate.RegisterRule(DcoRule)
+}
+
+var (
+ // ValidDCO is the regexp for signed off DCO
+ ValidDCO = regexp.MustCompile(`^Signed-off-by: ([^<]+) <([^<>@]+@[^<>]+)>$`)
+ // DcoRule is the rule being registered
+ DcoRule = validate.Rule{
+ Name: "DCO",
+ Description: "makes sure the commits are signed",
+ Run: ValidateDCO,
+ Default: true,
+ }
+)
+
+// ValidateDCO checks that the commit has been signed off, per the DCO process
+func ValidateDCO(r validate.Rule, c git.CommitEntry) (vr validate.Result) {
+ vr.CommitEntry = c
+ if len(strings.Split(c["parent"], " ")) > 1 {
+ vr.Pass = true
+ vr.Msg = "merge commits do not require DCO"
+ return vr
+ }
+
+ hasValid := false
+ for _, line := range strings.Split(c["body"], "\n") {
+ if ValidDCO.MatchString(line) {
+ hasValid = true
+ }
+ }
+ if !hasValid {
+ vr.Pass = false
+ vr.Msg = "does not have a valid DCO"
+ } else {
+ vr.Pass = true
+ vr.Msg = "has a valid DCO"
+ }
+
+ return vr
+}
diff --git a/vendor/github.com/vbatts/git-validation/rules/messageregexp/rule.go b/vendor/github.com/vbatts/git-validation/rules/messageregexp/rule.go
new file mode 100644
index 000000000..98587f262
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/rules/messageregexp/rule.go
@@ -0,0 +1,61 @@
+package messageregexp
+
+import (
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/vbatts/git-validation/git"
+ "github.com/vbatts/git-validation/validate"
+)
+
+func init() {
+ validate.RegisterRule(RegexpRule)
+}
+
+var (
+ // RegexpRule for validating a user provided regex on the commit messages
+ RegexpRule = validate.Rule{
+ Name: "message_regexp",
+ Description: "checks the commit message for a user provided regular expression",
+ Run: ValidateMessageRegexp,
+ Default: false, // only for users specifically calling it through -run ...
+ }
+)
+
+// ValidateMessageRegexp is the message regex func to run
+func ValidateMessageRegexp(r validate.Rule, c git.CommitEntry) (vr validate.Result) {
+ if r.Value == "" {
+ vr.Pass = true
+ vr.Msg = "noop: message_regexp value is blank"
+ return vr
+ }
+
+ re := regexp.MustCompile(r.Value)
+ vr.CommitEntry = c
+ if len(strings.Split(c["parent"], " ")) > 1 {
+ vr.Pass = true
+ vr.Msg = "merge commits are not checked for message_regexp"
+ return vr
+ }
+
+ hasValid := false
+ for _, line := range strings.Split(c["subject"], "\n") {
+ if re.MatchString(line) {
+ hasValid = true
+ }
+ }
+ for _, line := range strings.Split(c["body"], "\n") {
+ if re.MatchString(line) {
+ hasValid = true
+ }
+ }
+ if !hasValid {
+ vr.Pass = false
+ vr.Msg = fmt.Sprintf("commit message does not match %q", r.Value)
+ } else {
+ vr.Pass = true
+ vr.Msg = fmt.Sprintf("commit message matches %q", r.Value)
+ }
+ return vr
+}
diff --git a/vendor/github.com/vbatts/git-validation/rules/shortsubject/shortsubject.go b/vendor/github.com/vbatts/git-validation/rules/shortsubject/shortsubject.go
new file mode 100644
index 000000000..8fd033601
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/rules/shortsubject/shortsubject.go
@@ -0,0 +1,44 @@
+package shortsubject
+
+import (
+ "strings"
+
+ "github.com/vbatts/git-validation/git"
+ "github.com/vbatts/git-validation/validate"
+)
+
+var (
+ // ShortSubjectRule is the rule being registered
+ ShortSubjectRule = validate.Rule{
+ Name: "short-subject",
+ Description: "commit subjects are strictly less than 90 (github ellipsis length)",
+ Run: ValidateShortSubject,
+ Default: true,
+ }
+)
+
+func init() {
+ validate.RegisterRule(ShortSubjectRule)
+}
+
+// ValidateShortSubject checks that the commit's subject is strictly less than
+// 90 characters (preferably not more than 72 chars).
+func ValidateShortSubject(r validate.Rule, c git.CommitEntry) (vr validate.Result) {
+ if len(strings.Split(c["parent"], " ")) > 1 {
+ vr.Pass = true
+ vr.Msg = "merge commits do not require length check"
+ return vr
+ }
+ if len(c["subject"]) >= 90 {
+ vr.Pass = false
+ vr.Msg = "commit subject exceeds 90 characters"
+ return
+ }
+ vr.Pass = true
+ if len(c["subject"]) > 72 {
+ vr.Msg = "commit subject is under 90 characters, but is still more than 72 chars"
+ } else {
+ vr.Msg = "commit subject is 72 characters or less! *yay*"
+ }
+ return
+}
diff --git a/vendor/github.com/vbatts/git-validation/validate/rules.go b/vendor/github.com/vbatts/git-validation/validate/rules.go
new file mode 100644
index 000000000..38126a4f0
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/validate/rules.go
@@ -0,0 +1,134 @@
+package validate
+
+import (
+ "sort"
+ "strings"
+ "sync"
+
+ "github.com/vbatts/git-validation/git"
+)
+
+var (
+ // RegisteredRules are the avaible validation to perform on git commits
+ RegisteredRules = []Rule{}
+ registerRuleLock = sync.Mutex{}
+)
+
+// RegisterRule includes the Rule in the avaible set to use
+func RegisterRule(vr Rule) {
+ registerRuleLock.Lock()
+ defer registerRuleLock.Unlock()
+ RegisteredRules = append(RegisteredRules, vr)
+}
+
+// Rule will operate over a provided git.CommitEntry, and return a result.
+type Rule struct {
+ Name string // short name for reference in in the `-run=...` flag
+ Value string // value to configure for the rule (i.e. a regexp to check for in the commit message)
+ Description string // longer Description for readability
+ Run func(Rule, git.CommitEntry) Result
+ Default bool // whether the registered rule is run by default
+}
+
+// Commit processes the given rules on the provided commit, and returns the result set.
+func Commit(c git.CommitEntry, rules []Rule) Results {
+ results := Results{}
+ for _, r := range rules {
+ results = append(results, r.Run(r, c))
+ }
+ return results
+}
+
+// Result is the result for a single validation of a commit.
+type Result struct {
+ CommitEntry git.CommitEntry
+ Pass bool
+ Msg string
+}
+
+// Results is a set of results. This is type makes it easy for the following function.
+type Results []Result
+
+// PassFail gives a quick over/under of passes and failures of the results in this set
+func (vr Results) PassFail() (pass int, fail int) {
+ for _, res := range vr {
+ if res.Pass {
+ pass++
+ } else {
+ fail++
+ }
+ }
+ return pass, fail
+}
+
+// SanitizeFilters takes a comma delimited list and returns the trimmend and
+// split (on ",") items in the list
+func SanitizeFilters(filtStr string) (filters []string) {
+ for _, item := range strings.Split(filtStr, ",") {
+ filters = append(filters, strings.TrimSpace(item))
+ }
+ return
+}
+
+// FilterRules takes a set of rules and a list of short names to include, and
+// returns the reduced set. The comparison is case insensitive.
+//
+// Some `includes` rules have values assigned to them.
+// i.e. -run "dco,message_regexp='^JIRA-[0-9]+ [A-Z].*$'"
+//
+func FilterRules(rules []Rule, includes []string) []Rule {
+ ret := []Rule{}
+
+ for _, r := range rules {
+ for i := range includes {
+ if strings.Contains(includes[i], "=") {
+ chunks := strings.SplitN(includes[i], "=", 2)
+ if strings.ToLower(r.Name) == strings.ToLower(chunks[0]) {
+ // for these rules, the Name won't be unique per se. There may be
+ // multiple "regexp=" with different values. We'll need to set the
+ // .Value = chunk[1] and ensure r is dup'ed so they don't clobber
+ // each other.
+ newR := Rule(r)
+ newR.Value = chunks[1]
+ ret = append(ret, newR)
+ }
+ } else {
+ if strings.ToLower(r.Name) == strings.ToLower(includes[i]) {
+ ret = append(ret, r)
+ }
+ }
+ }
+ }
+
+ return ret
+}
+
+// StringsSliceEqual compares two string arrays for equality
+func StringsSliceEqual(a, b []string) bool {
+ if !sort.StringsAreSorted(a) {
+ sort.Strings(a)
+ }
+ if !sort.StringsAreSorted(b) {
+ sort.Strings(b)
+ }
+ for i := range b {
+ if !StringsSliceContains(a, b[i]) {
+ return false
+ }
+ }
+ for i := range a {
+ if !StringsSliceContains(b, a[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// StringsSliceContains checks for the presence of a word in string array
+func StringsSliceContains(a []string, b string) bool {
+ if !sort.StringsAreSorted(a) {
+ sort.Strings(a)
+ }
+ i := sort.SearchStrings(a, b)
+ return i < len(a) && a[i] == b
+}
diff --git a/vendor/github.com/vbatts/git-validation/validate/runner.go b/vendor/github.com/vbatts/git-validation/validate/runner.go
new file mode 100644
index 000000000..eea61fba1
--- /dev/null
+++ b/vendor/github.com/vbatts/git-validation/validate/runner.go
@@ -0,0 +1,109 @@
+package validate
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/vbatts/git-validation/git"
+)
+
+// Runner is the for processing a set of rules against a range of commits
+type Runner struct {
+ Root string
+ Rules []Rule
+ Results Results
+ Verbose bool
+ CommitRange string // if this is empty, then it will default to FETCH_HEAD, then HEAD
+}
+
+// NewRunner returns an initiallized Runner.
+func NewRunner(root string, rules []Rule, commitrange string, verbose bool) (*Runner, error) {
+ newroot, err := filepath.Abs(root)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get absolute path of %q: %s", root, err)
+ }
+ if commitrange == "" {
+ var err error
+ cwd, err := os.Getwd()
+ if err != nil {
+ return nil, err
+ }
+ defer os.Chdir(cwd)
+
+ if err := os.Chdir(newroot); err != nil {
+ return nil, err
+ }
+ commitrange, err = git.FetchHeadCommit()
+ if err != nil {
+ commitrange, err = git.HeadCommit()
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ return &Runner{
+ Root: newroot,
+ Rules: rules,
+ CommitRange: commitrange,
+ Verbose: verbose,
+ }, nil
+}
+
+// Run processes the rules for each commit in the range provided
+func (r *Runner) Run() error {
+ cwd, err := os.Getwd()
+ if err != nil {
+ return err
+ }
+ defer os.Chdir(cwd)
+
+ if err := os.Chdir(r.Root); err != nil {
+ return err
+ }
+
+ // collect the entries
+ c, err := git.Commits(r.CommitRange)
+ if err != nil {
+ return err
+ }
+
+ // run them and show results
+ for _, commit := range c {
+ if os.Getenv("QUIET") == "" {
+ fmt.Printf(" * %s %q ... ", commit["abbreviated_commit"], commit["subject"])
+ }
+ vr := Commit(commit, r.Rules)
+ r.Results = append(r.Results, vr...)
+ _, fail := vr.PassFail()
+ if os.Getenv("QUIET") != "" {
+ if fail != 0 {
+ for _, res := range vr {
+ if !res.Pass {
+ fmt.Printf(" %s - FAIL - %s\n", commit["abbreviated_commit"], res.Msg)
+ }
+ }
+ }
+ // everything else in the loop is printing output.
+ // If we're quiet, then just continue
+ continue
+ }
+ if fail == 0 {
+ fmt.Println("PASS")
+ } else {
+ fmt.Println("FAIL")
+ }
+ for _, res := range vr {
+ if r.Verbose {
+ if res.Pass {
+ fmt.Printf(" - PASS - %s\n", res.Msg)
+ } else {
+ fmt.Printf(" - FAIL - %s\n", res.Msg)
+ }
+ } else if !res.Pass {
+ fmt.Printf(" - FAIL - %s\n", res.Msg)
+ }
+ }
+ }
+ return nil
+}