init - add project files

This commit is contained in:
2025-03-06 23:54:11 -05:00
commit e724ff1120
1363 changed files with 897467 additions and 0 deletions

13
vendor/github.com/gomarkdown/markdown/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,13 @@
*.out
*.swp
*.8
*.6
_obj
_test*
markdown
tags
fuzz-workdir/
markdown-fuzz.zip
coverage.txt
testdata/*_got.md
testdata/*_ast.txt

31
vendor/github.com/gomarkdown/markdown/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,31 @@
Markdown is distributed under the Simplified BSD License:
Copyright © 2011 Russ Ross
Copyright © 2018 Krzysztof Kowalczyk
Copyright © 2018 Authors
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.

300
vendor/github.com/gomarkdown/markdown/README.md generated vendored Normal file
View File

@@ -0,0 +1,300 @@
# Markdown Parser and HTML Renderer for Go
[![pkg.go.dev](https://pkg.go.dev/badge/github.com/gomarkdown/markdown)](https://pkg.go.dev/github.com/gomarkdown/markdown)
Package `github.com/gomarkdown/markdown` is a Go library for parsing Markdown text and rendering as HTML.
It's very fast and supports common extensions.
Tutorial: https://blog.kowalczyk.info/article/cxn3/advanced-markdown-processing-in-go.html
Code examples:
* https://tools.arslexis.io/goplayground/#txO7hJ-ibeU : basic markdown => HTML
* https://tools.arslexis.io/goplayground/#yFRIWRiu-KL : customize HTML renderer
* https://tools.arslexis.io/goplayground/#2yV5-HDKBUV : modify AST
* https://tools.arslexis.io/goplayground/#9fqKwRbuJ04 : customize parser
* https://tools.arslexis.io/goplayground/#Bk0zTvrzUDR : syntax highlight
Those examples are also in [examples](./examples) directory.
## API Docs:
- https://pkg.go.dev/github.com/gomarkdown/markdown : top level package
- https://pkg.go.dev/github.com/gomarkdown/markdown/ast : defines abstract syntax tree of parsed markdown document
- https://pkg.go.dev/github.com/gomarkdown/markdown/parser : parser
- https://pkg.go.dev/github.com/gomarkdown/markdown/html : html renderer
## Usage
To convert markdown text to HTML using reasonable defaults:
```go
package main
import (
"os"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
"fmt"
)
var mds = `# header
Sample text.
[link](http://example.com)
`
func mdToHTML(md []byte) []byte {
// create markdown parser with extensions
extensions := parser.CommonExtensions | parser.AutoHeadingIDs | parser.NoEmptyLineBeforeBlock
p := parser.NewWithExtensions(extensions)
doc := p.Parse(md)
// create HTML renderer with extensions
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
return markdown.Render(doc, renderer)
}
func main() {
md := []byte(mds)
html := mdToHTML(md)
fmt.Printf("--- Markdown:\n%s\n\n--- HTML:\n%s\n", md, html)
}
```
Try it online: https://onlinetool.io/goplayground/#txO7hJ-ibeU
For more documentation read [this guide](https://blog.kowalczyk.info/article/cxn3/advanced-markdown-processing-in-go.html)
Comparing to other markdown parsers: https://babelmark.github.io/
## Sanitize untrusted content
We don't protect against malicious content. When dealing with user-provided
markdown, run renderer HTML through HTML sanitizer such as [Bluemonday](https://github.com/microcosm-cc/bluemonday).
Here's an example of simple usage with Bluemonday:
```go
import (
"github.com/microcosm-cc/bluemonday"
"github.com/gomarkdown/markdown"
)
// ...
maybeUnsafeHTML := markdown.ToHTML(md, nil, nil)
html := bluemonday.UGCPolicy().SanitizeBytes(maybeUnsafeHTML)
```
## mdtohtml command-line tool
https://github.com/gomarkdown/mdtohtml is a command-line markdown to html
converter built using this library.
You can also use it as an example of how to use the library.
You can install it with:
go get -u github.com/gomarkdown/mdtohtml
To run: `mdtohtml input-file [output-file]`
## Features
- **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 this package is
more consistent and cleaner.
- **Common extensions**, including table support, fenced code
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
- **Safety**. Markdown 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/gomarkdown/markdown#sanitize-untrusted-content).
- **Fast**. 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**. Only depends on standard library packages in Go.
- **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. We let 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
```
Table footers are supported as well and can be added with equal signs (`=`):
```
Name | Age
--------|------
Bob | 27
Alice | 23
========|======
Total | 50
```
A cell spanning multiple columns (colspan) is supported, just repeat the pipe symbol:
```
Name | Age
--------|------
Bob ||
Alice | 23
========|======
Total | 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.
- **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**. We 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
translates into line breaks in the output. This extension is off by default.
- **Non blocking space**. With this extension enabled spaces preceded by a backslash
in the input translates non-blocking spaces 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 `–`, and `---` is translated into
`—`. 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>.
- **MathJaX Support** is an additional feature which is supported by
many markdown editor. It translates inline math equations quoted by `$`
and displays math blocks quoted by `$$` into MathJax compatible format.
Hyphens (`_`) won't break LaTeX render within a math element any more.
```
$$
\left[ \begin{array}{a} a^l_1 \\ ⋮ \\ a^l_{d_l} \end{array}\right]
= \sigma(
\left[ \begin{matrix}
w^l_{1,1} & ⋯ & w^l_{1,d_{l-1}} \\
⋮ & ⋱ & ⋮ \\
w^l_{d_l,1} & ⋯ & w^l_{d_l,d_{l-1}} \\
\end{matrix}\right] ·
\left[ \begin{array}{x} a^{l-1}_1 \\ ⋮ \\ ⋮ \\ a^{l-1}_{d_{l-1}} \end{array}\right] +
\left[ \begin{array}{b} b^l_1 \\ ⋮ \\ b^l_{d_l} \end{array}\right])
$$
```
- **Ordered list start number**. With this extension enabled an ordered list will start with
the number that was used to start it.
- **Super and subscript**. With this extension enabled sequences between ^ will indicate
superscript and ~ will become a subscript. For example: H~2~O is a liquid, 2^10^ is 1024.
- **Block level attributes** allow setting attributes (ID, classes and key/value pairs) on block
level elements. The attribute must be enclosed with braces and be put on a line before the
element.
```
{#id3 .myclass fontsize="tiny"}
# Header 1
```
Will convert into `<h1 id="id3" class="myclass" fontsize="tiny">Header 1</h1>`.
- **Mmark support**, see <https://mmark.miek.nl/post/syntax/> for all new syntax elements this adds.
## Users
Some tools using this package: https://pkg.go.dev/github.com/gomarkdown/markdown?tab=importedby
## History
markdown is a fork of v2 of https://github.com/russross/blackfriday.
I refactored the API (split into ast/parser/html sub-packages).
Blackfriday itself was based on C implementation [sundown](https://github.com/vmg/sundown) which in turn was based on [libsoldout](http://fossil.instinctive.eu/libsoldout/home).
## License
[Simplified BSD License](LICENSE.txt)

4
vendor/github.com/gomarkdown/markdown/ast/doc.go generated vendored Normal file
View File

@@ -0,0 +1,4 @@
/*
Package ast defines tree representation of a parsed markdown document.
*/
package ast

581
vendor/github.com/gomarkdown/markdown/ast/node.go generated vendored Normal file
View File

@@ -0,0 +1,581 @@
package ast
// An attribute can be attached to block elements. They are specified as
// {#id .classs key="value"} where quotes for values are mandatory, multiple
// key/value pairs are separated by whitespace.
type Attribute struct {
ID []byte
Classes [][]byte
Attrs map[string][]byte
}
// 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)
)
func (a CellAlignFlags) String() string {
switch a {
case TableAlignmentLeft:
return "left"
case TableAlignmentRight:
return "right"
case TableAlignmentCenter:
return "center"
default:
return ""
}
}
// DocumentMatters holds the type of a {front,main,back}matter in the document
type DocumentMatters int
// These are all possible Document divisions.
const (
DocumentMatterNone DocumentMatters = iota
DocumentMatterFront
DocumentMatterMain
DocumentMatterBack
)
// CitationTypes holds the type of a citation, informative, normative or suppressed
type CitationTypes int
const (
CitationTypeNone CitationTypes = iota
CitationTypeSuppressed
CitationTypeInformative
CitationTypeNormative
)
// Node defines an ast node
type Node interface {
AsContainer() *Container
AsLeaf() *Leaf
GetParent() Node
SetParent(newParent Node)
GetChildren() []Node
SetChildren(newChildren []Node)
}
// Container is a type of node that can contain children
type Container struct {
Parent Node
Children []Node
Literal []byte // Text contents of the leaf nodes
Content []byte // Markdown content of the block nodes
*Attribute // Block level attribute
}
// return true if can contain children of a given node type
// used by custom nodes to over-ride logic in canNodeContain
type CanContain interface {
CanContain(Node) bool
}
// AsContainer returns itself as *Container
func (c *Container) AsContainer() *Container {
return c
}
// AsLeaf returns nil
func (c *Container) AsLeaf() *Leaf {
return nil
}
// GetParent returns parent node
func (c *Container) GetParent() Node {
return c.Parent
}
// SetParent sets the parent node
func (c *Container) SetParent(newParent Node) {
c.Parent = newParent
}
// GetChildren returns children nodes
func (c *Container) GetChildren() []Node {
return c.Children
}
// SetChildren sets children node
func (c *Container) SetChildren(newChildren []Node) {
c.Children = newChildren
}
// Leaf is a type of node that cannot have children
type Leaf struct {
Parent Node
Literal []byte // Text contents of the leaf nodes
Content []byte // Markdown content of the block nodes
*Attribute // Block level attribute
}
// AsContainer returns nil
func (l *Leaf) AsContainer() *Container {
return nil
}
// AsLeaf returns itself as *Leaf
func (l *Leaf) AsLeaf() *Leaf {
return l
}
// GetParent returns parent node
func (l *Leaf) GetParent() Node {
return l.Parent
}
// SetParent sets the parent nodd
func (l *Leaf) SetParent(newParent Node) {
l.Parent = newParent
}
// GetChildren returns nil because Leaf cannot have children
func (l *Leaf) GetChildren() []Node {
return nil
}
// SetChildren will panic if trying to set non-empty children
// because Leaf cannot have children
func (l *Leaf) SetChildren(newChildren []Node) {
if len(newChildren) != 0 {
panic("leaf node cannot have children")
}
}
// Document represents markdown document node, a root of ast
type Document struct {
Container
}
// DocumentMatter represents markdown node that signals a document
// division: frontmatter, mainmatter or backmatter.
type DocumentMatter struct {
Container
Matter DocumentMatters
}
// BlockQuote represents markdown block quote node
type BlockQuote struct {
Container
}
// Aside represents an markdown aside node.
type Aside struct {
Container
}
// List represents markdown list node
type List struct {
Container
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
Start int // for ordered lists this indicates the starting number if > 0
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
}
// ListItem represents markdown list item node
type ListItem struct {
Container
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
}
// Paragraph represents markdown paragraph node
type Paragraph struct {
Container
}
// Math represents markdown MathAjax inline node
type Math struct {
Leaf
}
// MathBlock represents markdown MathAjax block node
type MathBlock struct {
Container
}
// Heading represents markdown heading node
type Heading struct {
Container
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
IsSpecial bool // We are a special heading (starts with .#)
}
// HorizontalRule represents markdown horizontal rule node
type HorizontalRule struct {
Leaf
}
// Emph represents markdown emphasis node
type Emph struct {
Container
}
// Strong represents markdown strong node
type Strong struct {
Container
}
// Del represents markdown del node
type Del struct {
Container
}
// Link represents markdown link node
type Link struct {
Container
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.
DeferredID []byte // If a deferred link this holds the original ID.
AdditionalAttributes []string // Defines additional attributes to use during rendering.
}
// CrossReference is a reference node.
type CrossReference struct {
Container
Destination []byte // Destination is where the reference points to
Suffix []byte // Potential citation suffix, i.e. (#myid, text)
}
// Citation is a citation node.
type Citation struct {
Leaf
Destination [][]byte // Destination is where the citation points to. Multiple ones are allowed.
Type []CitationTypes // 1:1 mapping of destination and citation type
Suffix [][]byte // Potential citation suffix, i.e. [@!RFC1035, p. 144]
}
// Image represents markdown image node
type Image struct {
Container
Destination []byte // Destination is what goes into a href
Title []byte // Title is the tooltip thing that goes in a title attribute
}
// Text represents markdown text node
type Text struct {
Leaf
}
// HTMLBlock represents markdown html node
type HTMLBlock struct {
Leaf
}
// CodeBlock represents markdown code block node
type CodeBlock struct {
Leaf
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
}
// Softbreak represents markdown softbreak node
// Note: not used currently
type Softbreak struct {
Leaf
}
// Hardbreak represents markdown hard break node
type Hardbreak struct {
Leaf
}
// NonBlockingSpace represents markdown non-blocking space node
type NonBlockingSpace struct {
Leaf
}
// Code represents markdown code node
type Code struct {
Leaf
}
// HTMLSpan represents markdown html span node
type HTMLSpan struct {
Leaf
}
// Table represents markdown table node
type Table struct {
Container
}
// TableCell represents markdown table cell node
type TableCell struct {
Container
IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute
ColSpan int // How many columns to span
}
// TableHeader represents markdown table head node
type TableHeader struct {
Container
}
// TableBody represents markdown table body node
type TableBody struct {
Container
}
// TableRow represents markdown table row node
type TableRow struct {
Container
}
// TableFooter represents markdown table foot node
type TableFooter struct {
Container
}
// Caption represents a figure, code or quote caption
type Caption struct {
Container
}
// CaptionFigure is a node (blockquote or codeblock) that has a caption
type CaptionFigure struct {
Container
HeadingID string // This might hold heading ID, if present
}
// Callout is a node that can exist both in text (where it is an actual node) and in a code block.
type Callout struct {
Leaf
ID []byte // number of this callout
}
// Index is a node that contains an Index item and an optional, subitem.
type Index struct {
Leaf
Primary bool
Item []byte
Subitem []byte
ID string // ID of the index
}
// Subscript is a subscript node
type Subscript struct {
Leaf
}
// Subscript is a superscript node
type Superscript struct {
Leaf
}
// Footnotes is a node that contains all footnotes
type Footnotes struct {
Container
}
func removeNodeFromArray(a []Node, node Node) []Node {
n := len(a)
for i := 0; i < n; i++ {
if a[i] == node {
return append(a[:i], a[i+1:]...)
}
}
return nil
}
// AppendChild appends child to children of parent
// It panics if either node is nil.
func AppendChild(parent Node, child Node) {
RemoveFromTree(child)
child.SetParent(parent)
newChildren := append(parent.GetChildren(), child)
parent.SetChildren(newChildren)
}
// RemoveFromTree removes this node from tree
func RemoveFromTree(n Node) {
if n.GetParent() == nil {
return
}
// important: don't clear n.Children if n has no parent
// we're called from AppendChild and that might happen on a node
// that accumulated Children but hasn't been inserted into the tree
n.SetChildren(nil)
p := n.GetParent()
newChildren := removeNodeFromArray(p.GetChildren(), n)
if newChildren != nil {
p.SetChildren(newChildren)
}
}
// GetLastChild returns last child of node n
// It's implemented as stand-alone function to keep Node interface small
func GetLastChild(n Node) Node {
a := n.GetChildren()
if len(a) > 0 {
return a[len(a)-1]
}
return nil
}
// GetFirstChild returns first child of node n
// It's implemented as stand-alone function to keep Node interface small
func GetFirstChild(n Node) Node {
a := n.GetChildren()
if len(a) > 0 {
return a[0]
}
return nil
}
// GetNextNode returns next sibling of node n (node after n)
// We can't make it part of Container or Leaf because we loose Node identity
func GetNextNode(n Node) Node {
parent := n.GetParent()
if parent == nil {
return nil
}
a := parent.GetChildren()
len := len(a) - 1
for i := 0; i < len; i++ {
if a[i] == n {
return a[i+1]
}
}
return nil
}
// GetPrevNode returns previous sibling of node n (node before n)
// We can't make it part of Container or Leaf because we loose Node identity
func GetPrevNode(n Node) Node {
parent := n.GetParent()
if parent == nil {
return nil
}
a := parent.GetChildren()
len := len(a)
for i := 1; i < len; i++ {
if a[i] == n {
return a[i-1]
}
}
return nil
}
// 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 interface {
Visit(node Node, entering bool) WalkStatus
}
// NodeVisitorFunc casts a function to match NodeVisitor interface
type NodeVisitorFunc func(node Node, entering bool) WalkStatus
// Walk traverses tree recursively
func Walk(n Node, visitor NodeVisitor) WalkStatus {
isContainer := n.AsContainer() != nil
status := visitor.Visit(n, true) // entering
if status == Terminate {
// even if terminating, close container node
if isContainer {
visitor.Visit(n, false)
}
return status
}
if isContainer && status != SkipChildren {
children := n.GetChildren()
for _, n := range children {
status = Walk(n, visitor)
if status == Terminate {
return status
}
}
}
if isContainer {
status = visitor.Visit(n, false) // exiting
if status == Terminate {
return status
}
}
return GoToNext
}
// Visit calls visitor function
func (f NodeVisitorFunc) Visit(node Node, entering bool) WalkStatus {
return f(node, entering)
}
// WalkFunc is like Walk but accepts just a callback function
func WalkFunc(n Node, f NodeVisitorFunc) {
visitor := NodeVisitorFunc(f)
Walk(n, visitor)
}

168
vendor/github.com/gomarkdown/markdown/ast/print.go generated vendored Normal file
View File

@@ -0,0 +1,168 @@
package ast
import (
"bytes"
"fmt"
"io"
"strings"
"unicode/utf8"
)
// Print is for debugging. It prints a string representation of parsed
// markdown doc (result of parser.Parse()) to dst.
//
// To make output readable, it shortens text output.
func Print(dst io.Writer, doc Node) {
PrintWithPrefix(dst, doc, " ")
}
// PrintWithPrefix is like Print but allows customizing prefix used for
// indentation. By default it's 2 spaces. You can change it to e.g. tab
// by passing "\t"
func PrintWithPrefix(w io.Writer, doc Node, prefix string) {
// for more compact output, don't print outer Document
if _, ok := doc.(*Document); ok {
for _, c := range doc.GetChildren() {
printRecur(w, c, prefix, 0)
}
} else {
printRecur(w, doc, prefix, 0)
}
}
// ToString is like Dump but returns result as a string
func ToString(doc Node) string {
var buf bytes.Buffer
Print(&buf, doc)
return buf.String()
}
func contentToString(d1 []byte, d2 []byte) string {
if d1 != nil {
return string(d1)
}
if d2 != nil {
return string(d2)
}
return ""
}
func getContent(node Node) string {
if c := node.AsContainer(); c != nil {
return contentToString(c.Literal, c.Content)
}
leaf := node.AsLeaf()
return contentToString(leaf.Literal, leaf.Content)
}
func shortenString(s string, maxLen int) string {
// for cleaner, one-line ouput, replace some white-space chars
// with their escaped version
s = strings.Replace(s, "\n", `\n`, -1)
s = strings.Replace(s, "\r", `\r`, -1)
s = strings.Replace(s, "\t", `\t`, -1)
if maxLen < 0 {
return s
}
if utf8.RuneCountInString(s) < maxLen {
return s
}
// add "…" to indicate truncation
return string(append([]rune(s)[:maxLen-3], '…'))
}
// get a short name of the type of v which excludes package name
// and strips "()" from the end
func getNodeType(node Node) string {
s := fmt.Sprintf("%T", node)
s = strings.TrimSuffix(s, "()")
if idx := strings.Index(s, "."); idx != -1 {
return s[idx+1:]
}
return s
}
func printDefault(w io.Writer, indent string, typeName string, content string) {
content = strings.TrimSpace(content)
if len(content) > 0 {
fmt.Fprintf(w, "%s%s '%s'\n", indent, typeName, content)
} else {
fmt.Fprintf(w, "%s%s\n", indent, typeName)
}
}
func getListFlags(f ListType) string {
var s string
if f&ListTypeOrdered != 0 {
s += "ordered "
}
if f&ListTypeDefinition != 0 {
s += "definition "
}
if f&ListTypeTerm != 0 {
s += "term "
}
if f&ListItemContainsBlock != 0 {
s += "has_block "
}
if f&ListItemBeginningOfList != 0 {
s += "start "
}
if f&ListItemEndOfList != 0 {
s += "end "
}
s = strings.TrimSpace(s)
return s
}
func printRecur(w io.Writer, node Node, prefix string, depth int) {
if node == nil {
return
}
indent := strings.Repeat(prefix, depth)
content := shortenString(getContent(node), 40)
typeName := getNodeType(node)
switch v := node.(type) {
case *Link:
content := "url=" + string(v.Destination)
printDefault(w, indent, typeName, content)
case *Image:
content := "url=" + string(v.Destination)
printDefault(w, indent, typeName, content)
case *List:
if v.Start > 1 {
content += fmt.Sprintf("start=%d ", v.Start)
}
if v.Tight {
content += "tight "
}
if v.IsFootnotesList {
content += "footnotes "
}
flags := getListFlags(v.ListFlags)
if len(flags) > 0 {
content += "flags=" + flags + " "
}
printDefault(w, indent, typeName, content)
case *ListItem:
if v.Tight {
content += "tight "
}
if v.IsFootnotesList {
content += "footnotes "
}
flags := getListFlags(v.ListFlags)
if len(flags) > 0 {
content += "flags=" + flags + " "
}
printDefault(w, indent, typeName, content)
case *CodeBlock:
printDefault(w, indent, typeName + ":" + string(v.Info), content)
default:
printDefault(w, indent, typeName, content)
}
for _, child := range node.GetChildren() {
printRecur(w, child, prefix, depth+1)
}
}

View File

@@ -0,0 +1,27 @@
## Changes from blackfriday
This library is derived from blackfriday library. Here's a list of changes.
**Redesigned API**
- split into 3 separate packages: ast, parser and html (for html renderer). This makes the API more manageable. It also separates e.g. parser option from renderer options
- changed how AST node is represented from union-like representation (manually keeping track of the type of the node) to using interface{} (which is a Go way to combine an arbitrary value with its type)
**Allow re-using most of html renderer logic**
You can implement your own renderer by implementing `Renderer` interface.
Implementing a full renderer is a lot of work and often you just want to tweak html rendering of few node typs.
I've added a way to hook `Renderer.Render` function in html renderer with a custom function that can take over rendering of specific nodes.
I use it myself to do syntax-highlighting of code snippets.
**Speed up go test**
Running `go test` was really slow (17 secs) because it did a poor man's version of fuzzing by feeding the parser all subsets of test strings in order to find panics
due to incorrect parsing logic.
I've moved that logic to `cmd/crashtest`, so that it can be run on CI but not slow down regular development.
Now `go test` is blazing fast.

35
vendor/github.com/gomarkdown/markdown/doc.go generated vendored Normal file
View File

@@ -0,0 +1,35 @@
/*
Package markdown implements markdown parser and HTML renderer.
It parses markdown into AST format which can be serialized to HTML
(using html.Renderer) or possibly other formats (using alternate renderers).
Convert markdown to HTML
The simplest way to convert markdown document to HTML
md := []byte("## markdown document")
html := markdown.ToHTML(md, nil, nil)
Customizing parsing and HTML rendering
You can customize parser and HTML renderer:
import (
"github.com/gomarkdown/markdown/parser"
"github.com/gomarkdown/markdown/renderer"
"github.com/gomarkdown/markdown"
)
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
p := parser.NewWithExtensions(extensions)
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
md := []byte("markdown text")
html := markdown.ToHTML(md, p, renderer)
For a cmd-line tool see https://github.com/gomarkdown/mdtohtml
*/
package markdown

10
vendor/github.com/gomarkdown/markdown/fuzz.go generated vendored Normal file
View File

@@ -0,0 +1,10 @@
//go:build gofuzz
// +build gofuzz
package markdown
// Fuzz is to be used by https://github.com/dvyukov/go-fuzz
func Fuzz(data []byte) int {
Parse(data, nil)
return 0
}

43
vendor/github.com/gomarkdown/markdown/html/doc.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
/*
Package html implements HTML renderer of parsed markdown document.
Configuring and customizing a renderer
A renderer can be configured with multiple options:
import "github.com/gomarkdown/markdown/html"
flags := html.CommonFlags | html.CompletePage | html.HrefTargetBlank
opts := html.RendererOptions{
Title: "A custom title",
Flags: flags,
}
renderer := html.NewRenderer(opts)
You can also re-use most of the logic and customize rendering of selected nodes
by providing node render hook.
This is most useful for rendering nodes that allow for design choices, like
links or code blocks.
import (
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/ast"
)
// a very dummy render hook that will output "code_replacements" instead of
// <code>${content}</code> emitted by html.Renderer
func renderHookCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
_, ok := node.(*ast.CodeBlock)
if !ok {
return ast.GoToNext, false
}
io.WriteString(w, "code_replacement")
return ast.GoToNext, true
}
opts := html.RendererOptions{
RenderNodeHook: renderHookCodeBlock,
}
renderer := html.NewRenderer(opts)
*/
package html

1348
vendor/github.com/gomarkdown/markdown/html/renderer.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,452 @@
package html
import (
"bytes"
"io"
"github.com/gomarkdown/markdown/parser"
)
// SmartyPants rendering
var (
isSpace = parser.IsSpace
isAlnum = parser.IsAlnum
isPunctuation = parser.IsPunctuation
)
// 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) || isPunctuation(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 isPunctuation(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 isPunctuation(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 && isPunctuation(nextChar):
// ["!] hmm... could be ["$1.95] or [</code>"!...]
*isOpen = false
case isSpace(previousChar) && isPunctuation(nextChar):
// [ "!] looks more like [ "$1.95]
*isOpen = true
case isPunctuation(previousChar) && isPunctuation(nextChar):
// [!"!] context is not any help here, so toggle
*isOpen = !*isOpen
case /* isnormal(previousChar) && */ isPunctuation(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 isPunctuation(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 Flags) *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:])
}
}

90
vendor/github.com/gomarkdown/markdown/markdown.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
package markdown
import (
"bytes"
"io"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
// Renderer is an interface for implementing custom renderers.
type Renderer interface {
// RenderNode renders markdown node to w.
// It's called once for a leaf node.
// It's called twice for non-leaf nodes:
// * first with entering=true
// * then with entering=false
//
// 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.
RenderNode(w io.Writer, node ast.Node, entering bool) ast.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 ast.Node)
// RenderFooter is a symmetric counterpart of RenderHeader.
RenderFooter(w io.Writer, ast ast.Node)
}
// Parse parsers a markdown document using provided parser. If parser is nil,
// we use parser configured with parser.CommonExtensions.
//
// It returns AST (abstract syntax tree) that can be converted to another
// format using Render function.
func Parse(markdown []byte, p *parser.Parser) ast.Node {
if p == nil {
p = parser.New()
}
return p.Parse(markdown)
}
// Render uses renderer to convert parsed markdown document into a different format.
//
// To convert to HTML, pass html.Renderer
func Render(doc ast.Node, renderer Renderer) []byte {
var buf bytes.Buffer
renderer.RenderHeader(&buf, doc)
ast.WalkFunc(doc, func(node ast.Node, entering bool) ast.WalkStatus {
return renderer.RenderNode(&buf, node, entering)
})
renderer.RenderFooter(&buf, doc)
return buf.Bytes()
}
// ToHTML converts markdownDoc to HTML.
//
// You can optionally pass a parser and renderer. This allows to customize
// a parser, use a customized html render or use completely custom renderer.
//
// If you pass nil for both, we use parser configured with parser.CommonExtensions
// and html.Renderer configured with html.CommonFlags.
func ToHTML(markdown []byte, p *parser.Parser, renderer Renderer) []byte {
doc := Parse(markdown, p)
if renderer == nil {
opts := html.RendererOptions{
Flags: html.CommonFlags,
}
renderer = html.NewRenderer(opts)
}
return Render(doc, renderer)
}
// NormalizeNewlines converts Windows and Mac newlines to Unix newlines.
// The parser only supports Unix newlines. If your markdown content
// might contain Windows or Mac newlines, use this function to convert to Unix newlines
var NormalizeNewlines = parser.NormalizeNewlines

73
vendor/github.com/gomarkdown/markdown/parser/aside.go generated vendored Normal file
View File

@@ -0,0 +1,73 @@
package parser
import (
"bytes"
"github.com/gomarkdown/markdown/ast"
)
// returns aisde prefix length
func (p *Parser) asidePrefix(data []byte) int {
i := 0
n := len(data)
for i < 3 && i < n && data[i] == ' ' {
i++
}
if i+1 < n && data[i] == 'A' && data[i+1] == '>' {
if i+2 < n && data[i+2] == ' ' {
return i + 3
}
return i + 2
}
return 0
}
// aside ends with at least one blank line
// followed by something without a aside prefix
func (p *Parser) terminateAside(data []byte, beg, end int) bool {
if IsEmpty(data[beg:]) <= 0 {
return false
}
if end >= len(data) {
return true
}
return p.asidePrefix(data[end:]) == 0 && IsEmpty(data[end:]) == 0
}
// parse a aside fragment
func (p *Parser) aside(data []byte) int {
var raw bytes.Buffer
beg, end := 0, 0
// identical to quote
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++
}
end = skipCharN(data, end, '\n', 1)
if pre := p.asidePrefix(data[beg:]); pre > 0 {
// skip the prefix
beg += pre
} else if p.terminateAside(data, beg, end) {
break
}
// this line is part of the aside
raw.Write(data[beg:end])
beg = end
}
block := p.AddBlock(&ast.Aside{})
p.Block(raw.Bytes())
p.Finalize(block)
return end
}

View File

@@ -0,0 +1,116 @@
package parser
import (
"bytes"
"github.com/gomarkdown/markdown/ast"
)
// attribute parses a (potential) block attribute and adds it to p.
func (p *Parser) attribute(data []byte) []byte {
if len(data) < 3 {
return data
}
i := 0
if data[i] != '{' {
return data
}
i++
// last character must be a } otherwise it's not an attribute
end := skipUntilChar(data, i, '\n')
if data[end-1] != '}' {
return data
}
i = skipSpace(data, i)
b := &ast.Attribute{Attrs: make(map[string][]byte)}
esc := false
quote := false
trail := 0
Loop:
for ; i < len(data); i++ {
switch data[i] {
case ' ', '\t', '\f', '\v':
if quote {
continue
}
chunk := data[trail+1 : i]
if len(chunk) == 0 {
trail = i
continue
}
switch {
case chunk[0] == '.':
b.Classes = append(b.Classes, chunk[1:])
case chunk[0] == '#':
b.ID = chunk[1:]
default:
k, v := keyValue(chunk)
if k != nil && v != nil {
b.Attrs[string(k)] = v
} else {
// this is illegal in an attribute
return data
}
}
trail = i
case '"':
if esc {
esc = !esc
continue
}
quote = !quote
case '\\':
esc = !esc
case '}':
if esc {
esc = !esc
continue
}
chunk := data[trail+1 : i]
if len(chunk) == 0 {
return data
}
switch {
case chunk[0] == '.':
b.Classes = append(b.Classes, chunk[1:])
case chunk[0] == '#':
b.ID = chunk[1:]
default:
k, v := keyValue(chunk)
if k != nil && v != nil {
b.Attrs[string(k)] = v
} else {
return data
}
}
i++
break Loop
default:
esc = false
}
}
p.attr = b
return data[i:]
}
// key="value" quotes are mandatory.
func keyValue(data []byte) ([]byte, []byte) {
chunk := bytes.SplitN(data, []byte{'='}, 2)
if len(chunk) != 2 {
return nil, nil
}
key := chunk[0]
value := chunk[1]
if len(value) < 3 || len(key) == 0 {
return nil, nil
}
if value[0] != '"' || value[len(value)-1] != '"' {
return key, nil
}
return key, value[1 : len(value)-1]
}

1830
vendor/github.com/gomarkdown/markdown/parser/block.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,328 @@
package parser
import "github.com/gomarkdown/markdown/ast"
// 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 *Parser) tableRow(data []byte, columns []ast.CellAlignFlags, header bool) {
p.AddBlock(&ast.TableRow{})
col := 0
i := skipChar(data, 0, '|')
n := len(data)
colspans := 0 // keep track of total colspan in this row.
for col = 0; col < len(columns) && i < n; col++ {
colspan := 0
i = skipChar(data, i, ' ')
cellStart := i
// If we are in a codespan we should discount any | we see, check for that here and skip ahead.
if isCode, _ := codeSpan(p, data[i:], 0); isCode > 0 {
i += isCode - 1
}
for i < n && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' {
i++
}
cellEnd := i
// skip the end-of-cell marker, possibly taking us past end of buffer
// each _extra_ | means a colspan
for i < len(data) && data[i] == '|' && !isBackslashEscaped(data, i) {
i++
colspan++
}
// only colspan > 1 make sense.
if colspan < 2 {
colspan = 0
}
for cellEnd > cellStart && cellEnd-1 < n && data[cellEnd-1] == ' ' {
cellEnd--
}
block := &ast.TableCell{
IsHeader: header,
Align: columns[col],
ColSpan: colspan,
}
block.Content = data[cellStart:cellEnd]
if cellStart == cellEnd && colspans > 0 {
// an empty cell that we should ignore, it exists because of colspan
colspans--
} else {
p.AddBlock(block)
}
if colspan > 0 {
colspans += colspan - 1
}
}
// pad it out with empty columns to get the right number
for ; col < len(columns); col++ {
block := &ast.TableCell{
IsHeader: header,
Align: columns[col],
}
p.AddBlock(block)
}
// silently ignore rows with too many cells
}
// tableFooter parses the (optional) table footer.
func (p *Parser) tableFooter(data []byte) bool {
colCount := 1
// ignore up to 3 spaces
n := len(data)
i := skipCharN(data, 0, ' ', 3)
for ; i < n && data[i] != '\n'; i++ {
// If we are in a codespan we should discount any | we see, check for that here and skip ahead.
if isCode, _ := codeSpan(p, data[i:], 0); isCode > 0 {
i += isCode - 1
}
if data[i] == '|' && !isBackslashEscaped(data, i) {
colCount++
continue
}
// remaining data must be the = character
if data[i] != '=' {
return false
}
}
// doesn't look like a table footer
if colCount == 1 {
return false
}
p.AddBlock(&ast.TableFooter{})
return true
}
// tableHeaders parses the header. If recognized it will also add a table.
func (p *Parser) tableHeader(data []byte, doRender bool) (size int, columns []ast.CellAlignFlags, table ast.Node) {
i := 0
colCount := 1
headerIsUnderline := true
headerIsWithEmptyFields := true
for i = 0; i < len(data) && data[i] != '\n'; i++ {
// If we are in a codespan we should discount any | we see, check for that here and skip ahead.
if isCode, _ := codeSpan(p, data[i:], 0); isCode > 0 {
i += isCode - 1
}
if data[i] == '|' && !isBackslashEscaped(data, i) {
colCount++
}
if data[i] != '-' && data[i] != ' ' && data[i] != ':' && data[i] != '|' {
headerIsUnderline = false
}
if data[i] != ' ' && data[i] != '|' {
headerIsWithEmptyFields = false
}
}
// doesn't look like a table header
if colCount == 1 {
return
}
// include the newline in the data sent to tableRow
j := skipCharN(data, i, '\n', 1)
header := data[:j]
// column count ignores pipes at beginning or end of line
if data[0] == '|' {
colCount--
}
{
tmp := header
// remove whitespace from the end
for len(tmp) > 0 {
lastIdx := len(tmp) - 1
if tmp[lastIdx] == '\n' || tmp[lastIdx] == ' ' {
tmp = tmp[:lastIdx]
} else {
break
}
}
n := len(tmp)
if n > 2 && tmp[n-1] == '|' && !isBackslashEscaped(tmp, n-1) {
colCount--
}
}
// if the header looks like a underline, then we omit the header
// and parse the first line again as underline
if headerIsUnderline && !headerIsWithEmptyFields {
header = nil
i = 0
} else {
i++ // move past newline
}
columns = make([]ast.CellAlignFlags, colCount)
// move on to the header underline
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
n := len(data)
for i < n && data[i] != '\n' {
dashes := 0
if data[i] == ':' {
i++
columns[col] |= ast.TableAlignmentLeft
dashes++
}
for i < n && data[i] == '-' {
i++
dashes++
}
if i < n && data[i] == ':' {
i++
columns[col] |= ast.TableAlignmentRight
dashes++
}
for i < n && data[i] == ' ' {
i++
}
if i == n {
return
}
// end of column test is messy
switch {
case dashes < 1:
// not a valid column
return
case data[i] == '|' && !isBackslashEscaped(data, i):
// marker found, now skip past trailing whitespace
col++
i++
for i < n && 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
}
if doRender {
table = &ast.Table{}
p.AddBlock(table)
if header != nil {
p.AddBlock(&ast.TableHeader{})
p.tableRow(header, columns, true)
}
}
size = skipCharN(data, i, '\n', 1)
return
}
/*
Table:
Name | Age | Phone
------|-----|---------
Bob | 31 | 555-1234
Alice | 27 | 555-4321
*/
func (p *Parser) table(data []byte) int {
i, columns, table := p.tableHeader(data, true)
if i == 0 {
return 0
}
p.AddBlock(&ast.TableBody{})
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
i = skipCharN(data, i, '\n', 1)
if p.tableFooter(data[rowStart:i]) {
continue
}
p.tableRow(data[rowStart:i], columns, false)
}
if captionContent, id, consumed := p.caption(data[i:], []byte(captionTable)); consumed > 0 {
caption := &ast.Caption{}
p.Inline(caption, captionContent)
// Some switcheroo to re-insert the parsed table as a child of the captionfigure.
figure := &ast.CaptionFigure{}
figure.HeadingID = id
table2 := &ast.Table{}
// Retain any block level attributes.
table2.AsContainer().Attribute = table.AsContainer().Attribute
children := table.GetChildren()
ast.RemoveFromTree(table)
table2.SetChildren(children)
ast.AppendChild(figure, table2)
ast.AppendChild(figure, caption)
p.addChild(figure)
p.Finalize(figure)
i += consumed
}
return i
}

View File

@@ -0,0 +1,29 @@
package parser
import (
"bytes"
"strconv"
)
// IsCallout detects a callout in the following format: <<N>> Where N is a integer > 0.
func IsCallout(data []byte) (id []byte, consumed int) {
if !bytes.HasPrefix(data, []byte("<<")) {
return nil, 0
}
start := 2
end := bytes.Index(data[start:], []byte(">>"))
if end < 0 {
return nil, 0
}
b := data[start : start+end]
b = bytes.TrimSpace(b)
i, err := strconv.Atoi(string(b))
if err != nil {
return nil, 0
}
if i <= 0 {
return nil, 0
}
return b, start + end + 2 // 2 for >>
}

View File

@@ -0,0 +1,70 @@
package parser
import (
"bytes"
)
// caption checks for a caption, it returns the caption data and a potential "headingID".
func (p *Parser) caption(data, caption []byte) ([]byte, string, int) {
if !bytes.HasPrefix(data, caption) {
return nil, "", 0
}
j := len(caption)
data = data[j:]
end := LinesUntilEmpty(data)
data = data[:end]
id, start := captionID(data)
if id != "" {
return data[:start], id, end + j
}
return data, "", end + j
}
// LinesUntilEmpty scans lines up to the first empty line.
func LinesUntilEmpty(data []byte) int {
line, i := 0, 0
for line < len(data) {
i++
// find the end of this line
for i < len(data) && data[i-1] != '\n' {
i++
}
if IsEmpty(data[line:i]) == 0 {
line = i
continue
}
break
}
return i
}
// captionID checks if the caption *ends* in {#....}. If so the text after {# is taken to be
// the ID/anchor of the entire figure block.
func captionID(data []byte) (string, int) {
end := len(data)
j, k := 0, 0
// find start/end of heading id
for j = 0; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ {
}
for k = j + 1; k < end && data[k] != '}'; k++ {
}
// remains must be whitespace.
for l := k + 1; l < end; l++ {
if !IsSpace(data[l]) {
return "", 0
}
}
if j > 0 && k > 0 && j+2 < k {
return string(data[j+2 : k]), j
}
return "", 0
}

View File

@@ -0,0 +1,91 @@
package parser
import (
"bytes"
"github.com/gomarkdown/markdown/ast"
)
// citation parses a citation. In its most simple form [@ref], we allow multiple
// being separated by semicolons and a sub reference inside ala pandoc: [@ref, p. 23].
// Each citation can have a modifier: !, ? or - wich mean:
//
// ! - normative
// ? - formative
// - - suppressed
//
// The suffix starts after a comma, we strip any whitespace before and after. If the output
// allows for it, this can be rendered.
func citation(p *Parser, data []byte, offset int) (int, ast.Node) {
// look for the matching closing bracket
i := offset + 1
for level := 1; level > 0 && i < len(data); i++ {
switch {
case data[i] == '\n':
// no newlines allowed.
return 0, nil
case data[i-1] == '\\':
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
}
node := &ast.Citation{}
citations := bytes.Split(data[1:i], []byte(";"))
for _, citation := range citations {
var suffix []byte
citation = bytes.TrimSpace(citation)
j := 0
if citation[j] != '@' {
// not a citation, drop out entirely.
return 0, nil
}
if c := bytes.Index(citation, []byte(",")); c > 0 {
part := citation[:c]
suff := citation[c+1:]
part = bytes.TrimSpace(part)
suff = bytes.TrimSpace(suff)
citation = part
suffix = suff
}
citeType := ast.CitationTypeInformative
if len(citation) < 2 {
continue
}
j = 1
switch citation[j] {
case '!':
citeType = ast.CitationTypeNormative
j++
case '?':
citeType = ast.CitationTypeInformative
j++
case '-':
citeType = ast.CitationTypeSuppressed
j++
}
node.Destination = append(node.Destination, citation[j:])
node.Type = append(node.Type, citeType)
node.Suffix = append(node.Suffix, suffix)
}
return i + 1, node
}

20
vendor/github.com/gomarkdown/markdown/parser/esc.go generated vendored Normal file
View File

@@ -0,0 +1,20 @@
package parser
// isEscape returns true if byte i is prefixed by an odd number of backslahses.
func isEscape(data []byte, i int) bool {
if i == 0 {
return false
}
if i == 1 {
return data[0] == '\\'
}
j := i - 1
for ; j >= 0; j-- {
if data[j] != '\\' {
break
}
}
j++
// odd number of backslahes means escape
return (i-j)%2 != 0
}

117
vendor/github.com/gomarkdown/markdown/parser/figures.go generated vendored Normal file
View File

@@ -0,0 +1,117 @@
package parser
import (
"bytes"
"github.com/gomarkdown/markdown/ast"
)
// sFigureLine checks if there's a figure line (e.g., !--- ) at the beginning of data,
// and returns the end index if so, or 0 otherwise.
func sFigureLine(data []byte, oldmarker string) (end int, marker string) {
i, size := 0, 0
n := len(data)
// skip up to three spaces
for i < n && i < 3 && data[i] == ' ' {
i++
}
// check for the marker characters: !
if i+1 >= n {
return 0, ""
}
if data[i] != '!' || data[i+1] != '-' {
return 0, ""
}
i++
c := data[i] // i.e. the -
// the whole line must be the same char or whitespace
for i < n && 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, ""
}
// there is no syntax modifier although it might be an idea to re-use this space for something?
i = skipChar(data, i, ' ')
if i >= n || data[i] != '\n' {
if i == n {
return i, marker
}
return 0, ""
}
return i + 1, marker // Take newline into account.
}
// figureBlock returns the end index if data contains a figure 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 figure block.
func (p *Parser) figureBlock(data []byte, doRender bool) int {
beg, marker := sFigureLine(data, "")
if beg == 0 || beg >= len(data) {
return 0
}
var raw bytes.Buffer
for {
// safe to assume beg < len(data)
// check for the end of the code block
figEnd, _ := sFigureLine(data[beg:], marker)
if figEnd != 0 {
beg += figEnd
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 {
raw.Write(data[beg:end])
}
beg = end
}
if !doRender {
return beg
}
figure := &ast.CaptionFigure{}
p.AddBlock(figure)
p.Block(raw.Bytes())
defer p.Finalize(figure)
if captionContent, id, consumed := p.caption(data[beg:], []byte("Figure: ")); consumed > 0 {
caption := &ast.Caption{}
p.Inline(caption, captionContent)
figure.HeadingID = id
p.addChild(caption)
beg += consumed
}
return beg
}

129
vendor/github.com/gomarkdown/markdown/parser/include.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
package parser
import (
"bytes"
"path"
"path/filepath"
)
// isInclude parses {{...}}[...], that contains a path between the {{, the [...] syntax contains
// an address to select which lines to include. It is treated as an opaque string and just given
// to readInclude.
func (p *Parser) isInclude(data []byte) (filename string, address []byte, consumed int) {
i := skipCharN(data, 0, ' ', 3) // start with up to 3 spaces
if len(data[i:]) < 3 {
return "", nil, 0
}
if data[i] != '{' || data[i+1] != '{' {
return "", nil, 0
}
start := i + 2
// find the end delimiter
i = skipUntilChar(data, i, '}')
if i+1 >= len(data) {
return "", nil, 0
}
end := i
i++
if data[i] != '}' {
return "", nil, 0
}
filename = string(data[start:end])
if i+1 < len(data) && data[i+1] == '[' { // potential address specification
start := i + 2
end = skipUntilChar(data, start, ']')
if end >= len(data) {
return "", nil, 0
}
address = data[start:end]
return filename, address, end + 1
}
return filename, address, i + 1
}
func (p *Parser) readInclude(from, file string, address []byte) []byte {
if p.Opts.ReadIncludeFn != nil {
return p.Opts.ReadIncludeFn(from, file, address)
}
return nil
}
// isCodeInclude parses <{{...}} which is similar to isInclude the returned bytes are, however wrapped in a code block.
func (p *Parser) isCodeInclude(data []byte) (filename string, address []byte, consumed int) {
i := skipCharN(data, 0, ' ', 3) // start with up to 3 spaces
if len(data[i:]) < 3 {
return "", nil, 0
}
if data[i] != '<' {
return "", nil, 0
}
start := i
filename, address, consumed = p.isInclude(data[i+1:])
if consumed == 0 {
return "", nil, 0
}
return filename, address, start + consumed + 1
}
// readCodeInclude acts like include except the returned bytes are wrapped in a fenced code block.
func (p *Parser) readCodeInclude(from, file string, address []byte) []byte {
data := p.readInclude(from, file, address)
if data == nil {
return nil
}
ext := path.Ext(file)
buf := &bytes.Buffer{}
buf.Write([]byte("```"))
if ext != "" { // starts with a dot
buf.WriteString(" " + ext[1:] + "\n")
} else {
buf.WriteByte('\n')
}
buf.Write(data)
buf.WriteString("```\n")
return buf.Bytes()
}
// incStack hold the current stack of chained includes. Each value is the containing
// path of the file being parsed.
type incStack struct {
stack []string
}
func newIncStack() *incStack {
return &incStack{stack: []string{}}
}
// Push updates i with new.
func (i *incStack) Push(new string) {
if path.IsAbs(new) {
i.stack = append(i.stack, path.Dir(new))
return
}
last := ""
if len(i.stack) > 0 {
last = i.stack[len(i.stack)-1]
}
i.stack = append(i.stack, path.Dir(filepath.Join(last, new)))
}
// Pop pops the last value.
func (i *incStack) Pop() {
if len(i.stack) == 0 {
return
}
i.stack = i.stack[:len(i.stack)-1]
}
func (i *incStack) Last() string {
if len(i.stack) == 0 {
return ""
}
return i.stack[len(i.stack)-1]
}

1328
vendor/github.com/gomarkdown/markdown/parser/inline.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

36
vendor/github.com/gomarkdown/markdown/parser/matter.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
package parser
import (
"bytes"
"github.com/gomarkdown/markdown/ast"
)
func (p *Parser) documentMatter(data []byte) int {
if data[0] != '{' {
return 0
}
consumed := 0
matter := ast.DocumentMatterNone
if bytes.HasPrefix(data, []byte("{frontmatter}")) {
consumed = len("{frontmatter}")
matter = ast.DocumentMatterFront
}
if bytes.HasPrefix(data, []byte("{mainmatter}")) {
consumed = len("{mainmatter}")
matter = ast.DocumentMatterMain
}
if bytes.HasPrefix(data, []byte("{backmatter}")) {
consumed = len("{backmatter}")
matter = ast.DocumentMatterBack
}
if consumed == 0 {
return 0
}
node := &ast.DocumentMatter{Matter: matter}
p.AddBlock(node)
p.Finalize(node)
return consumed
}

View File

@@ -0,0 +1,32 @@
package parser
import (
"github.com/gomarkdown/markdown/ast"
)
// Flags control optional behavior of parser.
type Flags int
// Options is a collection of supplementary parameters tweaking the behavior of various parts of the parser.
type Options struct {
ParserHook BlockFunc
ReadIncludeFn ReadIncludeFunc
Flags Flags // Flags allow customizing parser's behavior
}
// Parser renderer configuration options.
const (
FlagsNone Flags = 0
SkipFootnoteList Flags = 1 << iota // Skip adding the footnote list (regardless if they are parsed)
)
// BlockFunc allows to registration of a parser function. If successful it
// returns an ast.Node, a buffer that should be parsed as a block and the the number of bytes consumed.
type BlockFunc func(data []byte) (ast.Node, []byte, int)
// ReadIncludeFunc should read the file under path and returns the read bytes,
// from will be set to the name of the current file being parsed. Initially
// this will be empty. address is the optional address specifier of which lines
// of the file to return. If this function is not set no data will be read.
type ReadIncludeFunc func(from, path string, address []byte) []byte

958
vendor/github.com/gomarkdown/markdown/parser/parser.go generated vendored Normal file
View File

@@ -0,0 +1,958 @@
/*
Package parser implements parser for markdown text that generates AST (abstract syntax tree).
*/
package parser
import (
"bytes"
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"github.com/gomarkdown/markdown/ast"
)
// Extensions is a bitmask of enabled parser extensions.
type Extensions int
// Bit flags representing markdown parsing extensions.
// Use | (or) to specify multiple extensions.
const (
NoExtensions Extensions = 0
NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words
Tables // Parse tables
FencedCode // Parse 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
NonBlockingSpace // Translate backspace spaces into line non-blocking spaces
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 // Parse definition lists
MathJax // Parse MathJax
OrderedListStart // Keep track of the first number used when starting an ordered list.
Attributes // Block Attributes
SuperSubscript // Super- and subscript support: 2^10^, H~2~O.
EmptyLinesBreakList // 2 empty lines break out of list
Includes // Support including other files.
Mmark // Support Mmark syntax, see https://mmark.miek.nl/post/syntax/
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
BackslashLineBreak | DefinitionLists | MathJax
)
// The size of a tab stop.
const (
tabSizeDefault = 4
tabSizeDouble = 8
)
// for each character that triggers a response when parsing inline data.
type InlineParser func(p *Parser, data []byte, offset int) (int, ast.Node)
// 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)
// Parser 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 Parser struct {
// ReferenceOverride is an optional function callback that is called every
// time a reference is resolved. It can be set before starting parsing.
//
// In Markdown, the link reference syntax can be made to resolve a link to
// a reference instead of an inline URL, in one of the following ways:
//
// * [link text][refid]
// * [refid][]
//
// Usually, the refid is defined at the bottom of the Markdown document. If
// this override function is provided, the refid is passed to the override
// function first, before consulting the defined refids at the bottom. If
// the override function indicates an override did not occur, the refids at
// the bottom will be used to fill in the link details.
ReferenceOverride ReferenceOverrideFunc
// IsSafeURLOverride allows overriding the default URL matcher. URL is
// safe if the overriding function returns true. Can be used to extend
// the default list of safe URLs.
IsSafeURLOverride func(url []byte) bool
Opts Options
// after parsing, this is AST root of parsed markdown text
Doc ast.Node
extensions Extensions
refs map[string]*reference
refsRecord map[string]struct{}
inlineCallback [256]InlineParser
nesting int
maxNesting int
insideLink bool
indexCnt int // incremented after every index
// 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
tip ast.Node // = doc
oldTip ast.Node
lastMatchedContainer ast.Node // = doc
allClosed bool
// Attributes are attached to block level elements.
attr *ast.Attribute
includeStack *incStack
// collect headings where we auto-generated id so that we can
// ensure they are unique at the end
allHeadingsWithAutoID []*ast.Heading
didParse bool
}
// New creates a markdown parser with CommonExtensions.
//
// You can then call `doc := p.Parse(markdown)` to parse markdown document
// and `markdown.Render(doc, renderer)` to convert it to another format with
// a renderer.
func New() *Parser {
return NewWithExtensions(CommonExtensions)
}
// NewWithExtensions creates a markdown parser with given extensions.
func NewWithExtensions(extension Extensions) *Parser {
p := Parser{
refs: make(map[string]*reference),
refsRecord: make(map[string]struct{}),
maxNesting: 64,
insideLink: false,
Doc: &ast.Document{},
extensions: extension,
allClosed: true,
includeStack: newIncStack(),
}
p.tip = p.Doc
p.oldTip = p.Doc
p.lastMatchedContainer = p.Doc
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
if p.extensions&Mmark != 0 {
p.inlineCallback['('] = maybeShortRefOrIndex
}
p.inlineCallback['^'] = maybeInlineFootnoteOrSuper
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&MathJax != 0 {
p.inlineCallback['$'] = math
}
return &p
}
func (p *Parser) RegisterInline(n byte, fn InlineParser) InlineParser {
prev := p.inlineCallback[n]
p.inlineCallback[n] = fn
return prev
}
func (p *Parser) 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 *Parser) isFootnote(ref *reference) bool {
_, ok := p.refsRecord[string(ref.link)]
return ok
}
func (p *Parser) Finalize(block ast.Node) {
p.tip = block.GetParent()
}
func (p *Parser) addChild(node ast.Node) ast.Node {
for !canNodeContain(p.tip, node) {
p.Finalize(p.tip)
}
ast.AppendChild(p.tip, node)
p.tip = node
return node
}
func canNodeContain(n ast.Node, v ast.Node) bool {
switch n.(type) {
case *ast.List:
return isListItem(v)
case *ast.Document, *ast.BlockQuote, *ast.Aside, *ast.ListItem, *ast.CaptionFigure:
return !isListItem(v)
case *ast.Table:
switch v.(type) {
case *ast.TableHeader, *ast.TableBody, *ast.TableFooter:
return true
default:
return false
}
case *ast.TableHeader, *ast.TableBody, *ast.TableFooter:
_, ok := v.(*ast.TableRow)
return ok
case *ast.TableRow:
_, ok := v.(*ast.TableCell)
return ok
}
// for nodes implemented outside of ast package, allow them
// to implement this logic via CanContain interface
if o, ok := n.(ast.CanContain); ok {
return o.CanContain(v)
}
// for container nodes outside of ast package default to true
// because false is a bad default
typ := fmt.Sprintf("%T", n)
customNode := !strings.HasPrefix(typ, "*ast.")
if customNode {
return n.AsLeaf() == nil
}
return false
}
func (p *Parser) closeUnmatchedBlocks() {
if p.allClosed {
return
}
for p.oldTip != p.lastMatchedContainer {
parent := p.oldTip.GetParent()
p.Finalize(p.oldTip)
p.oldTip = parent
}
p.allClosed = true
}
// 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
}
// Parse generates AST (abstract syntax tree) representing markdown document.
//
// The result is a root of the tree whose underlying type is *ast.Document
//
// You can then convert AST to html using html.Renderer, to some other format
// using a custom renderer or transform the tree.
//
// Parser is not reusable. Create a new Parser for each Parse() call.
func (p *Parser) Parse(input []byte) ast.Node {
if p.didParse {
panic("Parser is not reusable. Must create new Parser for each Parse() call.")
}
p.didParse = true
// the code only works with Unix CR newlines so to make life easy for
// callers normalize newlines
input = NormalizeNewlines(input)
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
ast.WalkFunc(p.Doc, func(node ast.Node, entering bool) ast.WalkStatus {
switch node.(type) {
case *ast.Paragraph, *ast.Heading, *ast.TableCell:
p.Inline(node, node.AsContainer().Content)
node.AsContainer().Content = nil
}
return ast.GoToNext
})
if p.Opts.Flags&SkipFootnoteList == 0 {
p.parseRefsToAST()
}
// ensure HeadingIDs generated with AutoHeadingIDs are unique
// this is delayed here (as opposed to done when we create the id)
// so that we can preserve more original ids when there are conflicts
taken := map[string]bool{}
for _, h := range p.allHeadingsWithAutoID {
id := h.HeadingID
if id == "" {
continue
}
n := 0
for taken[id] {
n++
id = h.HeadingID + "-" + strconv.Itoa(n)
}
h.HeadingID = id
taken[id] = true
}
return p.Doc
}
func (p *Parser) parseRefsToAST() {
if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
return
}
p.tip = p.Doc
list := &ast.List{
IsFootnotesList: true,
ListFlags: ast.ListTypeOrdered,
}
p.AddBlock(&ast.Footnotes{})
block := p.AddBlock(list)
flags := ast.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.addChild(ref.footnote)
block := ref.footnote
listItem := block.(*ast.ListItem)
listItem.ListFlags = flags | ast.ListTypeOrdered
listItem.RefLink = ref.link
if ref.hasBlock {
flags |= ast.ListItemContainsBlock
p.Block(ref.title)
} else {
p.Inline(block, ref.title)
}
flags &^= ast.ListItemBeginningOfList | ast.ListItemContainsBlock
}
above := list.Parent
finalizeList(list)
p.tip = above
ast.WalkFunc(block, func(node ast.Node, entering bool) ast.WalkStatus {
switch node.(type) {
case *ast.Paragraph, *ast.Heading:
p.Inline(node, node.AsContainer().Content)
node.AsContainer().Content = nil
}
return ast.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.
// Inline footnotes such as:
//
// Inline footnotes^[Not supported.] also exist.
//
// are not yet supported.
// 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 ast.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 *Parser, 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 *Parser, 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 linkEnd < len(data) && 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 *Parser, 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 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
}
// IsPunctuation returns true if c is a punctuation symbol.
func IsPunctuation(c byte) bool {
for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
if c == r {
return true
}
}
return false
}
func IsPunctuation2(d []byte) bool {
if len(d) == 0 {
return false
}
if IsPunctuation(d[0]) {
return true
}
r, _ := utf8.DecodeRune(d)
if r == utf8.RuneError {
return false
}
return unicode.IsPunct(r)
}
// IsSpace returns true if c is a white-space character
func IsSpace(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
}
// IsLetter returns true if c is ascii letter
func IsLetter(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}
// IsAlnum returns true if c is a digit or letter
// 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)
}
var URIs = [][]byte{
[]byte("http://"),
[]byte("https://"),
[]byte("ftp://"),
[]byte("mailto:"),
}
var Paths = [][]byte{
[]byte("/"),
[]byte("./"),
[]byte("../"),
}
// IsSafeURL returns true if url starts with one of the valid schemes or is a relative path.
func IsSafeURL(url []byte) bool {
nLink := len(url)
for _, path := range Paths {
nPath := len(path)
linkPrefix := url[:nPath]
if nLink >= nPath && bytes.Equal(linkPrefix, path) {
if nLink == nPath {
return true
} else if IsAlnum(url[nPath]) {
return true
}
}
}
for _, prefix := range URIs {
// TODO: handle unicode here
// case-insensitive prefix test
nPrefix := len(prefix)
if nLink > nPrefix {
linkPrefix := bytes.ToLower(url[:nPrefix])
if bytes.Equal(linkPrefix, prefix) && IsAlnum(url[nPrefix]) {
return true
}
}
}
return false
}
// TODO: this is not used
// 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]
}
func isListItem(d ast.Node) bool {
_, ok := d.(*ast.ListItem)
return ok
}
func NormalizeNewlines(d []byte) []byte {
res := make([]byte, len(d))
copy(res, d)
d = res
wi := 0
n := len(d)
for i := 0; i < n; i++ {
c := d[i]
// 13 is CR
if c != 13 {
d[wi] = c
wi++
continue
}
// replace CR (mac / win) with LF (unix)
d[wi] = 10
wi++
if i < n-1 && d[i+1] == 10 {
// this was CRLF, so skip the LF
i++
}
}
return d[:wi]
}

104
vendor/github.com/gomarkdown/markdown/parser/ref.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
package parser
import (
"bytes"
"fmt"
"github.com/gomarkdown/markdown/ast"
)
// parse '(#r, text)', where r does not contain spaces, but text may (similar to a citation). Or. (!item) (!item,
// subitem), for an index, (!!item) signals primary.
func maybeShortRefOrIndex(p *Parser, data []byte, offset int) (int, ast.Node) {
if len(data[offset:]) < 4 {
return 0, nil
}
// short ref first
data = data[offset:]
i := 1
switch data[i] {
case '#': // cross ref
i++
Loop:
for i < len(data) {
c := data[i]
switch {
case c == ')':
break Loop
case !IsAlnum(c):
if c == '_' || c == '-' || c == ':' || c == ' ' || c == ',' {
i++
continue
}
i = 0
break Loop
}
i++
}
if i >= len(data) {
return 0, nil
}
if data[i] != ')' {
return 0, nil
}
id := data[2:i]
node := &ast.CrossReference{}
node.Destination = id
if c := bytes.Index(id, []byte(",")); c > 0 {
idpart := id[:c]
suff := id[c+1:]
suff = bytes.TrimSpace(suff)
node.Destination = idpart
node.Suffix = suff
}
if bytes.Index(node.Destination, []byte(" ")) > 0 {
// no spaces allowed in id
return 0, nil
}
if bytes.Index(node.Destination, []byte(",")) > 0 {
// nor comma
return 0, nil
}
return i + 1, node
case '!': // index
i++
start := i
i = skipUntilChar(data, start, ')')
// did we reach the end of the buffer without a closing marker?
if i >= len(data) {
return 0, nil
}
if len(data[start:i]) < 1 {
return 0, nil
}
idx := &ast.Index{}
idx.ID = fmt.Sprintf("idxref:%d", p.indexCnt)
p.indexCnt++
idx.Primary = data[start] == '!'
buf := data[start:i]
if idx.Primary {
buf = buf[1:]
}
items := bytes.Split(buf, []byte(","))
switch len(items) {
case 1:
idx.Item = bytes.TrimSpace(items[0])
return i + 1, idx
case 2:
idx.Item = bytes.TrimSpace(items[0])
idx.Subitem = bytes.TrimSpace(items[1])
return i + 1, idx
}
}
return 0, nil
}