5
0
mirror of https://github.com/cwinfo/matterbridge.git synced 2024-12-24 03:25:40 +00:00

Update markdown parsing library to github.com/gomarkdown/markdown (#944)

This commit is contained in:
Benjamin 2019-11-18 06:18:01 +10:00 committed by Wim
parent aba86855b5
commit 0917dc8766
120 changed files with 8282 additions and 47873 deletions

View File

@ -14,8 +14,9 @@ import (
"golang.org/x/image/webp"
"github.com/42wim/matterbridge/bridge/config"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/parser"
"github.com/sirupsen/logrus"
"gitlab.com/golang-commonmark/markdown"
)
// DownloadFile downloads the given non-authenticated URL.
@ -176,9 +177,12 @@ func ClipMessage(text string, length int) string {
return text
}
// ParseMarkdown takes in an input string as markdown and parses it to html
func ParseMarkdown(input string) string {
md := markdown.New(markdown.XHTMLOutput(true), markdown.Breaks(true))
res := md.RenderToString([]byte(input))
extensions := parser.HardLineBreak
markdownParser := parser.NewWithExtensions(extensions)
parsedMarkdown := markdown.ToHTML([]byte(input), markdownParser, nil)
res := string(parsedMarkdown)
res = strings.TrimPrefix(res, "<p>")
res = strings.TrimSuffix(res, "</p>\n")
return res

9
go.mod
View File

@ -12,6 +12,7 @@ require (
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
github.com/fsnotify/fsnotify v1.4.7
github.com/go-telegram-bot-api/telegram-bot-api v4.6.5-0.20181225215658-ec221ba9ea45+incompatible
github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83
github.com/google/gops v0.3.6
github.com/gopackage/ddp v0.0.0-20170117053602-652027933df4 // indirect
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
@ -46,7 +47,6 @@ require (
github.com/russross/blackfriday v1.5.2
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
github.com/shazow/ssh-chat v0.0.0-20190125184227-81d7e1686296
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/sirupsen/logrus v1.4.2
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
@ -55,19 +55,12 @@ require (
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a // indirect
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 // indirect
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2 // indirect
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe // indirect
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 // indirect
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 // indirect
golang.org/x/image v0.0.0-20190902063713-cb417be4ba39
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect
golang.org/x/text v0.3.2 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/russross/blackfriday.v2 v2.0.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
)

19
go.sum
View File

@ -63,6 +63,8 @@ github.com/golang/protobuf v1.3.0 h1:kbxbvI4Un1LUWKxufD+BiE6AEExYYgkQLQmLFqA1LFk
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83 h1:w5VNUHB0SP2tr1+boQJWKvnyn3P61UFErZ2e2ih6x0A=
github.com/gomarkdown/markdown v0.0.0-20190912180731-281270bc6d83/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gops v0.3.6 h1:6akvbMlpZrEYOuoebn2kR+ZJekbZqJ28fJXTs84+8to=
@ -203,8 +205,6 @@ github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7 h1:80VN+vGkqM773Br
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
@ -247,18 +247,6 @@ github.com/xlab/treeprint v0.0.0-20180616005107-d6fb6747feb6/go.mod h1:ce1O1j6Ut
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2 h1:UQwvu7FjUEdVYofx0U6bsc5odNE7wa5TSA0fl559GcA=
github.com/zfjagann/golang-ring v0.0.0-20190304061218-d34796e0a6c2/go.mod h1:0MsIttMJIF/8Y7x0XjonJP7K99t3sR6bjj4m5S4JmqU=
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a h1:Ax7kdHNICZiIeFpmevmaEWb0Ae3BUj3zCTKhZHZ+zd0=
gitlab.com/golang-commonmark/html v0.0.0-20180917080848-cfaf75183c4a/go.mod h1:JT4uoTz0tfPoyVH88GZoWDNm5NHJI2VbUW+eyPClueI=
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 h1:oYrL81N608MLZhma3ruL8qTM4xcpYECGut8KSxRY59g=
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82/go.mod h1:Gn+LZmCrhPECMD3SOKlE+BOHwhOYD9j7WT9NUtkCrC8=
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f h1:jwXy/CsM4xS2aoiF2fHAlukmInWhd2TlWB+HDCyvzKc=
gitlab.com/golang-commonmark/markdown v0.0.0-20181102083822-772775880e1f/go.mod h1:SIHlEr9462fpIfTrVWf3GqQDxnA65Vm3BMMsUtuA6W0=
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2 h1:wD/sPUgx2QJFPTyXZpJnLaROolfeKuruh06U4pRV0WY=
gitlab.com/golang-commonmark/mdurl v0.0.0-20180912090424-e5bce34c34f2/go.mod h1:wQk4rLkWrdOPjUAtqJRJ10hIlseLSVYWP95PLrjDF9s=
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe h1:5kUPFAF52umOUPH12MuNUmyVTseJRNBftDl/KfsvX3I=
gitlab.com/golang-commonmark/puny v0.0.0-20180912090636-2cd490539afe/go.mod h1:P9LSM1KVzrIstFgUaveuwiAm8PK5VTB3yJEU8kqlbrU=
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 h1:uPZaMiz6Sz0PZs3IZJWpU5qHKGNy///1pacZC9txiUI=
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9XGmFtQCvV3Lm9NLico3UhFwUpCG/+mVU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -266,6 +254,7 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -326,8 +315,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/russross/blackfriday.v2 v2.0.0 h1:+FlnIV8DSQnT7NZ43hcVKcdJdzZoeCmJj4Ql8gq5keA=
gopkg.in/russross/blackfriday.v2 v2.0.0/go.mod h1:6sSBNz/GtOm/pJTuh5UmBK2ZHfmnxGbl2NZg1UliSOI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

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

7
vendor/github.com/gomarkdown/markdown/.gitpod generated vendored Normal file
View File

@ -0,0 +1,7 @@
checkoutLocation: "src/github.com/gomarkdown/markdown"
workspaceLocation: "."
tasks:
- command: >
cd /workspace/src/github.com/gomarkdown/markdown &&
go get -v ./... &&
go test -c

17
vendor/github.com/gomarkdown/markdown/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
dist: bionic
language: go
go:
- "1.12.x"
install:
- go build -v ./...
script:
- go test -v ./...
- go test -run=^$ -bench=BenchmarkReference -benchmem
- ./s/test_with_codecoverage.sh
- ./s/ci_fuzzit.sh
after_success:
- bash <(curl -s https://codecov.io/bash)

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.

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

@ -0,0 +1,325 @@
# Markdown Parser and HTML Renderer for Go
[![GoDoc](https://godoc.org/github.com/gomarkdown/markdown?status.svg)](https://godoc.org/github.com/gomarkdown/markdown) [![codecov](https://codecov.io/gh/gomarkdown/markdown/branch/master/graph/badge.svg)](https://codecov.io/gh/gomarkdown/markdown)
Package `github.com/gomarkdown/markdown` is a very fast Go library for parsing [Markdown](https://daringfireball.net/projects/markdown/) documents and rendering them to HTML.
It's fast and supports common extensions.
## Installation
go get -u github.com/gomarkdown/markdown
API Docs:
- https://godoc.org/github.com/gomarkdown/markdown : top level package
- https://godoc.org/github.com/gomarkdown/markdown/ast : defines abstract syntax tree of parsed markdown document
- https://godoc.org/github.com/gomarkdown/markdown/parser : parser
- https://godoc.org/github.com/gomarkdown/markdown/html : html renderer
## Usage
To convert markdown text to HTML using reasonable defaults:
```go
md := []byte("## markdown document")
output := markdown.ToHTML(md, nil, nil)
```
## Customizing markdown parser
Markdown format is loosely specified and there are multiple extensions invented after original specification was created.
The parser supports several [extensions](https://godoc.org/github.com/gomarkdown/markdown/parser#Extensions).
Default parser uses most common `parser.CommonExtensions` but you can easily use parser with custom extension:
```go
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/parser"
)
extensions := parser.CommonExtensions | parser.AutoHeadingIDs
parser := parser.NewWithExtensions(extensions)
md := []byte("markdown text")
html := markdown.ToHTML(md, parser, nil)
```
## Customizing HTML renderer
Similarly, HTML renderer can be configured with different [options](https://godoc.org/github.com/gomarkdown/markdown/html#RendererOptions)
Here's how to use a custom renderer:
```go
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
)
htmlFlags := html.CommonFlags | html.HrefTargetBlank
opts := html.RendererOptions{Flags: htmlFlags}
renderer := html.NewRenderer(opts)
md := []byte("markdown text")
html := markdown.ToHTML(md, nil, renderer)
```
HTML renderer also supports reusing most of the logic and overriding rendering of only specifc nodes.
You can provide [RenderNodeFunc](https://godoc.org/github.com/gomarkdown/markdown/html#RenderNodeFunc) in [RendererOptions](https://godoc.org/github.com/gomarkdown/markdown/html#RendererOptions).
The function is called for each node in AST, you can implement custom rendering logic and tell HTML renderer to skip rendering this node.
Here's the simplest example that drops all code blocks from the output:
````go
import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/html"
)
// return (ast.GoToNext, true) to tell html renderer to skip rendering this node
// (because you've rendered it)
func renderHookDropCodeBlock(w io.Writer, node ast.Node, entering bool) (ast.WalkStatus, bool) {
// skip all nodes that are not CodeBlock nodes
if _, ok := node.(*ast.CodeBlock); !ok {
return ast.GoToNext, false
}
// custom rendering logic for ast.CodeBlock. By doing nothing it won't be
// present in the output
return ast.GoToNext, true
}
opts := html.RendererOptions{
Flags: html.CommonFlags,
RenderNodeHook: renderHookDropCodeBlock,
}
renderer := html.NewRenderer(opts)
md := "test\n```\nthis code block will be dropped from output\n```\ntext"
html := markdown.ToHTML([]byte(s), nil, renderer)
````
## 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
```
- **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
translate into line breaks in the output. This extension is off by default.
- **Non blocking space**. With this extension enabled spaces preceeded by an backslash n the input
translate 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 `&ndash;`, and `---` is translated into
`&mdash;`. This differs from most smartypants processors, which
turn a single hyphen into an ndash and a double hyphen into an
mdash.
- **Smart fractions**, where anything that looks like a fraction
is translated into suitable HTML (instead of just a few special
cases like most smartypant processors). For example, `4/5`
becomes `<sup>4</sup>&frasl;<sub>5</sub>`, which renders as
<sup>4</sup>&frasl;<sub>5</sub>.
- **MathJaX Support** is an additional feature which is supported by
many markdown editor. It translate inline math equation quoted by `$`
and display math block quoted by `$$` into MathJax compatible format.
hyphen `_` 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
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.nl/syntax> for all new syntax elements this adds.
## Todo
- port https://github.com/russross/blackfriday/issues/348
- port [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
renders output as LaTeX.
- port https://github.com/shurcooL/github_flavored_markdown to markdown
- port [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
but for markdown.
- More unit testing
- Improve unicode support. It does not understand all unicode
rules (about what constitutes a letter, a punctuation symbol,
etc.), so it may fail to detect word boundaries correctly in
some instances. It is safe on all utf-8 input.
## History
markdown is a fork of v2 of https://github.com/russross/blackfriday that is:
- actively maintained (sadly in Feb 2018 blackfriday was inactive for 5 months with many bugs and pull requests accumulated)
- refactored 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)

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

@ -0,0 +1,10 @@
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
}

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

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

@ -0,0 +1,559 @@
package ast
// 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
}
// 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 becuase Leaf cannot have children
func (l *Leaf) SetChildren(newChildren []Node) {
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.
}
// CrossReference is a reference node.
type CrossReference struct {
Container
Destination []byte // Destination is where the reference points to
}
// 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
}
// 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)
}

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

@ -0,0 +1,165 @@
package ast
import (
"bytes"
"fmt"
"io"
"strings"
)
// 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 len(s) < maxLen {
return s
}
// add "..." to indicate truncation
return 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)
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.

8
vendor/github.com/gomarkdown/markdown/codecov.yml generated vendored Normal file
View File

@ -0,0 +1,8 @@
coverage:
status:
project:
default:
# basic
target: 60%
threshold: 2%
base: auto

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.NewWithExensions(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

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

@ -0,0 +1,9 @@
// +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
}

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

@ -0,0 +1,5 @@
module github.com/gomarkdown/markdown
go 1.12
require golang.org/dl v0.0.0-20190829154251-82a15e2f2ead // indirect

42
vendor/github.com/gomarkdown/markdown/html/callouts.go generated vendored Normal file
View File

@ -0,0 +1,42 @@
package html
import (
"bytes"
"io"
"github.com/gomarkdown/markdown/ast"
"github.com/gomarkdown/markdown/parser"
)
// EscapeHTMLCallouts writes html-escaped d to w. It escapes &, <, > and " characters, *but*
// expands callouts <<N>> with the callout HTML, i.e. by calling r.callout() with a newly created
// ast.Callout node.
func (r *Renderer) EscapeHTMLCallouts(w io.Writer, d []byte) {
ld := len(d)
Parse:
for i := 0; i < ld; i++ {
for _, comment := range r.opts.Comments {
if !bytes.HasPrefix(d[i:], comment) {
break
}
lc := len(comment)
if i+lc < ld {
if id, consumed := parser.IsCallout(d[i+lc:]); consumed > 0 {
// We have seen a callout
callout := &ast.Callout{ID: id}
r.callout(w, callout)
i += consumed + lc - 1
continue Parse
}
}
}
escSeq := Escaper[d[i]]
if escSeq != nil {
w.Write(escSeq)
} else {
w.Write([]byte{d[i]})
}
}
}

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.RenderOptions{
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.Data.(*ast.CodeBlockData)
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

50
vendor/github.com/gomarkdown/markdown/html/esc.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package html
import (
"html"
"io"
)
var Escaper = [256][]byte{
'&': []byte("&amp;"),
'<': []byte("&lt;"),
'>': []byte("&gt;"),
'"': []byte("&quot;"),
}
// EscapeHTML writes html-escaped d to w. It escapes &, <, > and " characters.
func EscapeHTML(w io.Writer, d []byte) {
var start, end int
n := len(d)
for end < n {
escSeq := Escaper[d[end]]
if escSeq != nil {
w.Write(d[start:end])
w.Write(escSeq)
start = end + 1
}
end++
}
if start < n && end <= n {
w.Write(d[start:end])
}
}
func escLink(w io.Writer, text []byte) {
unesc := html.UnescapeString(string(text))
EscapeHTML(w, []byte(unesc))
}
// Escape writes the text to w, but skips the escape character.
func Escape(w io.Writer, text []byte) {
esc := false
for i := 0; i < len(text); i++ {
if text[i] == '\\' {
esc = !esc
}
if esc && text[i] == '\\' {
continue
}
w.Write([]byte{text[i]})
}
}

1318
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,444 @@
package html
import (
"bytes"
"io"
)
// SmartyPants rendering
// 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:])
}
}

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

@ -0,0 +1,85 @@
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)
}

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 p.isEmpty(data[beg:]) <= 0 {
return false
}
if end >= len(data) {
return true
}
return p.asidePrefix(data[end:]) == 0 && p.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]
}

1978
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,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 := p.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 (p *Parser) 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 p.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,86 @@
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
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
}

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

@ -0,0 +1,119 @@
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
}
p.finalize(figure)
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]
}

1284
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

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

@ -0,0 +1,812 @@
/*
Package parser implements parser for markdown text that generates AST (abstract syntax tree).
*/
package parser
import (
"bytes"
"fmt"
"strings"
"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.nl/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
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
}
// 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: 16,
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) 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
}
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.
func (p *Parser) Parse(input []byte) ast.Node {
p.block(input)
// Walk the tree and finish up some of unfinished blocks
for p.tip != nil {
p.finalize(p.tip)
}
// Walk the tree again and process inline markdown in each block
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()
}
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 p.isEmpty(data[blockEnd:i]) > 0 {
containsBlankLine = true
blockEnd = i
continue
}
n := 0
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
// this is the end of the block.
// we don't want to include this last line in the index.
break gatherLines
}
// if there were blank lines before this one, insert a new one now
if containsBlankLine {
raw.WriteByte('\n')
containsBlankLine = false
}
// get rid of that first tab, write to buffer
raw.Write(data[blockEnd+n : i])
hasBlock = true
blockEnd = i
}
if data[blockEnd-1] != '\n' {
raw.WriteByte('\n')
}
contents = raw.Bytes()
return
}
// 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
}
// isSpace returns true if c is a white-space charactr
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)
}
// 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
}

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

@ -0,0 +1,89 @@
package parser
import (
"bytes"
"fmt"
"github.com/gomarkdown/markdown/ast"
)
// parse '(#r)', where r does not contain spaces. 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 == ':' {
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
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
}

7
vendor/github.com/gomarkdown/markdown/todo.md generated vendored Normal file
View File

@ -0,0 +1,7 @@
# Things to do
[ ] docs: add examples like https://godoc.org/github.com/dgrijalva/jwt-go (put in foo_example_test.go). Or see https://github.com/garyburd/redigo/blob/master/redis/zpop_example_test.go#L5 / https://godoc.org/github.com/garyburd/redigo/redis or https://godoc.org/github.com/go-redis/redis
[ ] figure out expandTabs and parser.TabSizeEight. Are those used?
[ ] SoftbreakData is not used

189
vendor/github.com/gomarkdown/markdown/tracking-perf.md generated vendored Normal file
View File

@ -0,0 +1,189 @@
## Tracking perf changes
Initial performance:
```
goos: darwin
goarch: amd64
pkg: github.com/gomarkdown/markdown
BenchmarkEscapeHTML-8 2000000 823 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 5033 ns/op 9872 B/op 56 allocs/op
BenchmarkReferenceAmps-8 100000 19538 ns/op 26776 B/op 150 allocs/op
BenchmarkReferenceAutoLinks-8 100000 17574 ns/op 24544 B/op 132 allocs/op
BenchmarkReferenceBackslashEscapes-8 30000 50977 ns/op 76752 B/op 243 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 8546 ns/op 12864 B/op 65 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 9000 ns/op 14912 B/op 70 allocs/op
BenchmarkReferenceCodeSpans-8 200000 8856 ns/op 14992 B/op 69 allocs/op
BenchmarkReferenceHardWrappedPara-8 200000 6599 ns/op 11312 B/op 57 allocs/op
BenchmarkReferenceHorizontalRules-8 100000 15483 ns/op 23536 B/op 98 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 200000 6839 ns/op 12150 B/op 62 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 100000 19940 ns/op 28488 B/op 117 allocs/op
BenchmarkReferenceInlineHTMLComments-8 200000 7455 ns/op 13440 B/op 64 allocs/op
BenchmarkReferenceLinksInline-8 100000 16425 ns/op 23664 B/op 147 allocs/op
BenchmarkReferenceLinksReference-8 30000 54895 ns/op 66464 B/op 416 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 17647 ns/op 23776 B/op 158 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 200000 9367 ns/op 14832 B/op 95 allocs/op
BenchmarkReferenceMarkdownBasics-8 10000 129772 ns/op 130848 B/op 378 allocs/op
BenchmarkReferenceMarkdownSyntax-8 3000 502365 ns/op 461411 B/op 1411 allocs/op
BenchmarkReferenceNestedBlockquotes-8 200000 7028 ns/op 12688 B/op 64 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 20000 79686 ns/op 107520 B/op 374 allocs/op
BenchmarkReferenceStrongAndEm-8 200000 10020 ns/op 17792 B/op 78 allocs/op
BenchmarkReferenceTabs-8 200000 12025 ns/op 18224 B/op 81 allocs/op
BenchmarkReferenceTidyness-8 200000 8985 ns/op 14432 B/op 71 allocs/op
PASS
ok github.com/gomarkdown/markdown 45.375s
```
After switching to using interface{} for Node.Data:
```
BenchmarkEscapeHTML-8 2000000 929 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 5126 ns/op 9248 B/op 56 allocs/op
BenchmarkReferenceAmps-8 100000 19927 ns/op 17880 B/op 154 allocs/op
BenchmarkReferenceAutoLinks-8 100000 20732 ns/op 17360 B/op 141 allocs/op
BenchmarkReferenceBackslashEscapes-8 30000 50267 ns/op 38128 B/op 244 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 8988 ns/op 10912 B/op 67 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 8611 ns/op 12256 B/op 74 allocs/op
BenchmarkReferenceCodeSpans-8 200000 8256 ns/op 11248 B/op 69 allocs/op
BenchmarkReferenceHardWrappedPara-8 200000 6739 ns/op 9856 B/op 57 allocs/op
BenchmarkReferenceHorizontalRules-8 100000 15503 ns/op 15600 B/op 104 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 200000 6874 ns/op 10278 B/op 62 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 100000 22271 ns/op 18552 B/op 121 allocs/op
BenchmarkReferenceInlineHTMLComments-8 200000 8315 ns/op 10736 B/op 64 allocs/op
BenchmarkReferenceLinksInline-8 100000 16155 ns/op 16912 B/op 152 allocs/op
BenchmarkReferenceLinksReference-8 30000 52387 ns/op 38192 B/op 445 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 17111 ns/op 16592 B/op 167 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 200000 9164 ns/op 12048 B/op 97 allocs/op
BenchmarkReferenceMarkdownBasics-8 10000 129262 ns/op 87264 B/op 416 allocs/op
BenchmarkReferenceMarkdownSyntax-8 3000 496873 ns/op 293906 B/op 1559 allocs/op
BenchmarkReferenceNestedBlockquotes-8 200000 6854 ns/op 10192 B/op 64 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 20000 79633 ns/op 55024 B/op 447 allocs/op
BenchmarkReferenceStrongAndEm-8 200000 9637 ns/op 12176 B/op 78 allocs/op
BenchmarkReferenceTabs-8 100000 12164 ns/op 13776 B/op 87 allocs/op
BenchmarkReferenceTidyness-8 200000 8677 ns/op 11296 B/op 75 allocs/op
```
Not necessarily faster, but uses less bytes per op (but sometimes more allocs).
After tweaking the API:
```
$ ./s/run-bench.sh
go test -bench=. -test.benchmem
goos: darwin
goarch: amd64
pkg: github.com/gomarkdown/markdown
BenchmarkEscapeHTML-8 2000000 834 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 3486 ns/op 6160 B/op 27 allocs/op
BenchmarkReferenceAmps-8 100000 18158 ns/op 14792 B/op 125 allocs/op
BenchmarkReferenceAutoLinks-8 100000 16824 ns/op 14272 B/op 112 allocs/op
BenchmarkReferenceBackslashEscapes-8 30000 44066 ns/op 35040 B/op 215 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 6868 ns/op 7824 B/op 38 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 7157 ns/op 9168 B/op 45 allocs/op
BenchmarkReferenceCodeSpans-8 200000 6663 ns/op 8160 B/op 40 allocs/op
BenchmarkReferenceHardWrappedPara-8 300000 4821 ns/op 6768 B/op 28 allocs/op
BenchmarkReferenceHorizontalRules-8 100000 13033 ns/op 12512 B/op 75 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 300000 4998 ns/op 7190 B/op 33 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 100000 17696 ns/op 15464 B/op 92 allocs/op
BenchmarkReferenceInlineHTMLComments-8 300000 5506 ns/op 7648 B/op 35 allocs/op
BenchmarkReferenceLinksInline-8 100000 14450 ns/op 13824 B/op 123 allocs/op
BenchmarkReferenceLinksReference-8 30000 52561 ns/op 35104 B/op 416 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 15616 ns/op 13504 B/op 138 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 200000 7772 ns/op 8960 B/op 68 allocs/op
BenchmarkReferenceMarkdownBasics-8 10000 121436 ns/op 84176 B/op 387 allocs/op
BenchmarkReferenceMarkdownSyntax-8 3000 487404 ns/op 290818 B/op 1530 allocs/op
BenchmarkReferenceNestedBlockquotes-8 300000 5098 ns/op 7104 B/op 35 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 20000 74422 ns/op 51936 B/op 418 allocs/op
BenchmarkReferenceStrongAndEm-8 200000 7888 ns/op 9088 B/op 49 allocs/op
BenchmarkReferenceTabs-8 200000 10061 ns/op 10688 B/op 58 allocs/op
BenchmarkReferenceTidyness-8 200000 7152 ns/op 8208 B/op 46 allocs/op
ok github.com/gomarkdown/markdown 40.809s
```
After refactoring Renderer:
```
BenchmarkEscapeHTML-8 2000000 883 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 3717 ns/op 6208 B/op 29 allocs/op
BenchmarkReferenceAmps-8 100000 19135 ns/op 14680 B/op 123 allocs/op
BenchmarkReferenceAutoLinks-8 100000 17142 ns/op 14176 B/op 110 allocs/op
BenchmarkReferenceBackslashEscapes-8 30000 54616 ns/op 35088 B/op 217 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 7993 ns/op 7872 B/op 40 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 8285 ns/op 9216 B/op 47 allocs/op
BenchmarkReferenceCodeSpans-8 200000 7684 ns/op 8208 B/op 42 allocs/op
BenchmarkReferenceHardWrappedPara-8 200000 5595 ns/op 6816 B/op 30 allocs/op
BenchmarkReferenceHorizontalRules-8 100000 16444 ns/op 12560 B/op 77 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 200000 5415 ns/op 7238 B/op 35 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 100000 19867 ns/op 15512 B/op 94 allocs/op
BenchmarkReferenceInlineHTMLComments-8 200000 6026 ns/op 7696 B/op 37 allocs/op
BenchmarkReferenceLinksInline-8 100000 14864 ns/op 13664 B/op 120 allocs/op
BenchmarkReferenceLinksReference-8 30000 52479 ns/op 34816 B/op 401 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 15812 ns/op 13472 B/op 135 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 200000 7767 ns/op 8880 B/op 68 allocs/op
BenchmarkReferenceMarkdownBasics-8 10000 131065 ns/op 84048 B/op 386 allocs/op
BenchmarkReferenceMarkdownSyntax-8 2000 515604 ns/op 289953 B/op 1501 allocs/op
BenchmarkReferenceNestedBlockquotes-8 200000 5655 ns/op 7152 B/op 37 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 20000 84188 ns/op 51984 B/op 420 allocs/op
BenchmarkReferenceStrongAndEm-8 200000 8664 ns/op 9136 B/op 51 allocs/op
BenchmarkReferenceTabs-8 100000 11110 ns/op 10736 B/op 60 allocs/op
BenchmarkReferenceTidyness-8 200000 7628 ns/op 8256 B/op 48 allocs/op
ok github.com/gomarkdown/markdown 40.841s
```
After Node refactor to have Children array:
```
BenchmarkEscapeHTML-8 2000000 901 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 3905 ns/op 6224 B/op 31 allocs/op
BenchmarkReferenceAmps-8 100000 22216 ns/op 15560 B/op 157 allocs/op
BenchmarkReferenceAutoLinks-8 100000 20335 ns/op 14824 B/op 146 allocs/op
BenchmarkReferenceBackslashEscapes-8 20000 69174 ns/op 37392 B/op 316 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 8443 ns/op 7968 B/op 48 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 9250 ns/op 9392 B/op 58 allocs/op
BenchmarkReferenceCodeSpans-8 200000 8515 ns/op 8432 B/op 54 allocs/op
BenchmarkReferenceHardWrappedPara-8 200000 5738 ns/op 6856 B/op 34 allocs/op
BenchmarkReferenceHorizontalRules-8 100000 20864 ns/op 13648 B/op 93 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 200000 6187 ns/op 7310 B/op 40 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 50000 23793 ns/op 16128 B/op 114 allocs/op
BenchmarkReferenceInlineHTMLComments-8 200000 7060 ns/op 7840 B/op 44 allocs/op
BenchmarkReferenceLinksInline-8 100000 18432 ns/op 14496 B/op 153 allocs/op
BenchmarkReferenceLinksReference-8 20000 67666 ns/op 37136 B/op 502 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 19324 ns/op 13984 B/op 162 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 200000 8998 ns/op 9320 B/op 83 allocs/op
BenchmarkReferenceMarkdownBasics-8 10000 160908 ns/op 88152 B/op 518 allocs/op
BenchmarkReferenceMarkdownSyntax-8 2000 707160 ns/op 303801 B/op 2044 allocs/op
BenchmarkReferenceNestedBlockquotes-8 200000 6740 ns/op 7248 B/op 45 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 10000 115808 ns/op 55052 B/op 626 allocs/op
BenchmarkReferenceStrongAndEm-8 100000 10540 ns/op 9416 B/op 72 allocs/op
BenchmarkReferenceTabs-8 100000 13171 ns/op 10968 B/op 77 allocs/op
BenchmarkReferenceTidyness-8 200000 8903 ns/op 8404 B/op 62 allocs/op
PASS
ok github.com/gomarkdown/markdown 43.477s
```
It's slower (but opens up possibilities for further improvements).
After refactoring to make ast.Node a top-level thing.
```
BenchmarkEscapeHTML-8 2000000 829 ns/op 0 B/op 0 allocs/op
BenchmarkSmartDoubleQuotes-8 300000 3998 ns/op 6192 B/op 31 allocs/op
BenchmarkReferenceAmps-8 50000 27389 ns/op 15480 B/op 153 allocs/op
BenchmarkReferenceAutoLinks-8 50000 23106 ns/op 14656 B/op 137 allocs/op
BenchmarkReferenceBackslashEscapes-8 10000 112435 ns/op 36696 B/op 315 allocs/op
BenchmarkReferenceBlockquotesWithCodeBlocks-8 200000 9227 ns/op 7856 B/op 46 allocs/op
BenchmarkReferenceCodeBlocks-8 200000 10469 ns/op 9248 B/op 54 allocs/op
BenchmarkReferenceCodeSpans-8 200000 10522 ns/op 8368 B/op 54 allocs/op
BenchmarkReferenceHardWrappedPara-8 200000 6354 ns/op 6784 B/op 34 allocs/op
BenchmarkReferenceHorizontalRules-8 50000 32393 ns/op 13952 B/op 87 allocs/op
BenchmarkReferenceInlineHTMLAdvances-8 200000 6894 ns/op 7238 B/op 40 allocs/op
BenchmarkReferenceInlineHTMLSimple-8 50000 32942 ns/op 15864 B/op 110 allocs/op
BenchmarkReferenceInlineHTMLComments-8 200000 8181 ns/op 7776 B/op 44 allocs/op
BenchmarkReferenceLinksInline-8 100000 21679 ns/op 14400 B/op 148 allocs/op
BenchmarkReferenceLinksReference-8 20000 83928 ns/op 36688 B/op 473 allocs/op
BenchmarkReferenceLinksShortcut-8 100000 22053 ns/op 13872 B/op 153 allocs/op
BenchmarkReferenceLiterQuotesInTitles-8 100000 10784 ns/op 9296 B/op 81 allocs/op
BenchmarkReferenceMarkdownBasics-8 5000 237097 ns/op 87760 B/op 480 allocs/op
BenchmarkReferenceMarkdownSyntax-8 1000 1465402 ns/op 300769 B/op 1896 allocs/op
BenchmarkReferenceNestedBlockquotes-8 200000 7461 ns/op 7152 B/op 45 allocs/op
BenchmarkReferenceOrderedAndUnorderedLists-8 5000 212256 ns/op 53724 B/op 553 allocs/op
BenchmarkReferenceStrongAndEm-8 100000 13018 ns/op 9264 B/op 72 allocs/op
BenchmarkReferenceTabs-8 100000 15005 ns/op 10752 B/op 71 allocs/op
BenchmarkReferenceTidyness-8 200000 10308 ns/op 8292 B/op 58 allocs/op
PASS
ok github.com/gomarkdown/markdown 42.176s
```

View File

@ -1,16 +0,0 @@
image: golang:1.11
stages:
- build
- test
build:
stage: build
script:
- go build ./...
test:
stage: test
script:
- test -z "$(gofmt -l . | tee /dev/stderr)"
- go test ./...

View File

@ -1,10 +0,0 @@
Copyright (c) 2015, The 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.

View File

@ -1,8 +0,0 @@
html [![License](https://img.shields.io/badge/licence-BSD--2--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) [![GoDoc](http://godoc.org/gitlab.com/golang-commonmark/html?status.svg)](http://godoc.org/gitlab.com/golang-commonmark/html) [![Pipeline status](https://gitlab.com/golang-commonmark/html/badges/master/pipeline.svg)](https://gitlab.com/golang-commonmark/html/commits/master)
====
Package html provides functions for escaping/unescaping HTML text and for parsing HTML entities.
## Install
go get -u gitlab.com/golang-commonmark/html

View File

@ -1,211 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package html provides functions for escaping/unescaping HTML text and for parsing HTML entities.
package html
import (
"io"
"strconv"
"strings"
"unicode/utf8"
)
const BadEntity = string(utf8.RuneError)
var htmlEscapeReplacer = strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
`"`, "&quot;",
)
func EscapeString(s string) string {
return htmlEscapeReplacer.Replace(s)
}
func WriteEscapedString(w io.Writer, s string) error {
_, err := htmlEscapeReplacer.WriteString(w, s)
return err
}
func isValidEntityCode(c int64) bool {
switch {
case !utf8.ValidRune(rune(c)):
return false
// never used
case c >= 0xfdd0 && c <= 0xfdef:
return false
case c&0xffff == 0xffff || c&0xffff == 0xfffe:
return false
// control codes
case c >= 0x00 && c <= 0x08:
return false
case c == 0x0b:
return false
case c >= 0x0e && c <= 0x1f:
return false
case c >= 0x7f && c <= 0x9f:
return false
}
return true
}
func letter(b byte) bool { return b >= 'a' && b <= 'z' || b >= 'A' && b <= 'Z' }
func digit(b byte) bool { return b >= '0' && b <= '9' }
func alphanum(b byte) bool { return letter(b) || digit(b) }
func hexDigit(b byte) bool {
return digit(b) || b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F'
}
func ParseEntity(s string) (string, int) {
st := 0
var n int
for i := 1; i < len(s); i++ {
b := s[i]
switch st {
case 0: // initial state
switch {
case b == '#':
st = 1
case letter(b):
n = 1
st = 2
default:
return "", 0
}
case 1: // &#
switch {
case b == 'x' || b == 'X':
st = 3
case digit(b):
n = 1
st = 4
default:
return "", 0
}
case 2: // &q
switch {
case alphanum(b):
n++
if n > 31 {
return "", 0
}
case b == ';':
if e, ok := entities[s[i-n:i]]; ok {
return e, i + 1
}
return "", 0
default:
return "", 0
}
case 3: // &#x
switch {
case hexDigit(b):
n = 1
st = 5
default:
return "", 0
}
case 4: // &#0
switch {
case digit(b):
n++
if n > 8 {
return "", 0
}
case b == ';':
c, _ := strconv.ParseInt(s[i-n:i], 10, 32)
if !isValidEntityCode(c) {
return BadEntity, i + 1
}
return string(rune(c)), i + 1
default:
return "", 0
}
case 5: // &#x0
switch {
case hexDigit(b):
n++
if n > 8 {
return "", 0
}
case b == ';':
c, err := strconv.ParseInt(s[i-n:i], 16, 32)
if err != nil {
return BadEntity, i + 1
}
if !isValidEntityCode(c) {
return BadEntity, i + 1
}
return string(rune(c)), i + 1
default:
return "", 0
}
}
}
return "", 0
}
func UnescapeString(s string) string {
i := strings.IndexByte(s, '&')
if i < 0 {
return s
}
anyChanges := false
var entityStr string
var entityLen int
for i < len(s) {
if s[i] == '&' {
entityStr, entityLen = ParseEntity(s[i:])
if entityLen > 0 {
anyChanges = true
break
}
}
i++
}
if !anyChanges {
return s
}
buf := make([]byte, len(s)-entityLen+len(entityStr))
copy(buf[:i], s)
n := copy(buf[i:], entityStr)
j := i + n
i += entityLen
for i < len(s) {
b := s[i]
if b == '&' {
entityStr, entityLen = ParseEntity(s[i:])
if entityLen > 0 {
n = copy(buf[j:], entityStr)
j += n
i += entityLen
continue
}
}
buf[j] = b
j++
i++
}
return string(buf[:j])
}

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
image: golang:1.13
stages:
- build
- test
before_script:
- go get golang.org/x/text/unicode/rangetable
build:
stage: build
script:
- go build ./...
test:
stage: test
script:
- test -z "$(gofmt -l . | tee /dev/stderr)"
- go test ./...

View File

@ -1,10 +0,0 @@
Copyright (c) 2015, The 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.

View File

@ -1,12 +0,0 @@
linkify [![License](https://img.shields.io/badge/licence-BSD--2--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) [![GoDoc](http://godoc.org/gitlab.com/golang-commonmark/linkify?status.svg)](http://godoc.org/gitlab.com/golang-commonmark/linkify) [![Pipeline status](https://gitlab.com/golang-commonmark/linkify/badges/master/pipeline.svg)](https://gitlab.com/golang-commonmark/linkify/commits/master)
=======
Package linkify provides a way to find what looks like links in plain text.
## Install
go get -u gitlab.com/golang-commonmark/linkify
## Use
See an [example](https://gitlab.com/golang-commonmark/linkify/blob/master/linkify_example_test.go).

View File

@ -1,51 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package linkify
import (
"unicode"
"golang.org/x/text/unicode/rangetable"
)
var (
unreserved = [256]bool{'-': true, '.': true, '_': true, '~': true}
basicPunct = [256]bool{'.': true, ',': true, '?': true, '!': true, ';': true, ':': true}
subdelims = [256]bool{'!': true, '$': true, '&': true, '\'': true, '(': true, ')': true,
'*': true, '+': true, ',': true, ';': true, '=': true}
emailcs = [256]bool{'a': true, 'b': true, 'c': true, 'd': true, 'e': true, 'f': true, 'g': true,
'h': true, 'i': true, 'j': true, 'k': true, 'l': true, 'm': true, 'n': true, 'o': true,
'p': true, 'q': true, 'r': true, 's': true, 't': true, 'u': true, 'v': true, 'w': true,
'x': true, 'y': true, 'z': true, 'A': true, 'B': true, 'C': true, 'D': true, 'E': true,
'F': true, 'G': true, 'H': true, 'I': true, 'J': true, 'K': true, 'L': true, 'M': true,
'N': true, 'O': true, 'P': true, 'Q': true, 'R': true, 'S': true, 'T': true, 'U': true,
'V': true, 'W': true, 'X': true, 'Y': true, 'Z': true, '0': true, '1': true, '2': true,
'3': true, '4': true, '5': true, '6': true, '7': true, '8': true, '9': true, '!': true,
'#': true, '$': true, '%': true, '&': true, '\'': true, '*': true, '+': true, '/': true,
'=': true, '?': true, '^': true, '_': true, '`': true, '{': true, '|': true, '}': true,
'~': true, '-': true}
letterOrDigit = rangetable.Merge(unicode.Letter, unicode.Digit)
punctSpaceCc = rangetable.Merge(unicode.Punct, unicode.Space, unicode.Cc)
)
func isAllowedInEmail(r rune) bool {
return r < 0x7f && emailcs[r]
}
func isLetterOrDigit(r rune) bool {
return unicode.Is(letterOrDigit, r)
}
func isPunctOrSpaceOrControl(r rune) bool {
return r == '<' || r == '>' || r == '\uff5c' || unicode.Is(punctSpaceCc, r)
}
func isUnreserved(r rune) bool {
return (r < 0x7f && unreserved[r]) || unicode.Is(letterOrDigit, r)
}
func isSubDelimiter(r rune) bool {
return r < 0x7f && subdelims[r]
}

View File

@ -1,93 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package linkify
import (
"unicode"
"unicode/utf8"
)
func findEmailStart(s string, start int) (_ int, _ bool) {
end := start
allowDot := false
for end >= 0 {
b := s[end]
switch {
case emailcs[b]:
allowDot = true
case b == '.':
if !allowDot {
return
}
allowDot = false
default:
if end == start {
return
}
if s[end+1] == '.' {
return
}
r, _ := utf8.DecodeLastRuneInString(s[:end+1])
if r == utf8.RuneError {
return
}
if !unicode.IsSpace(r) {
return
}
return end + 1, true
}
end--
}
if end < start && s[end+1] == '.' {
return
}
return end + 1, true
}
func findEmailEnd(s string, start int) (_ int, _ bool) {
end := start
allowDot := false
loop:
for end < len(s) {
b := s[end]
switch {
case emailcs[b]:
allowDot = true
case b == '.':
if !allowDot {
return
}
allowDot = false
case b == '@':
break loop
default:
return
}
end++
}
if end >= len(s)-5 {
return
}
if end > start && s[end-1] == '.' {
return
}
var dot int
var ok bool
end, dot, ok = findHostnameEnd(s, end+1)
if !ok || dot == -1 {
return
}
if dot+5 <= len(s) && s[dot+1:dot+5] == "xn--" {
return end, true
}
if length := match(s[dot+1:]); dot+length+1 != end {
return
}
return end, true
}

File diff suppressed because it is too large Load Diff

View File

@ -1,462 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package linkify provides a way to find links in plain text.
package linkify
import (
"strings"
"unicode/utf8"
)
// Link represents a link found in a string with a schema and a position in the string.
type Link struct {
Scheme string
Start, End int
}
func max(a, b int) int {
if a >= b {
return a
}
return b
}
// Links returns links found in s.
func Links(s string) (links []Link) {
for i := 0; i < len(s)-2; i++ {
switch s[i] {
case '.': // IP address or domain name
if i == 0 {
continue // . at the start of a line
}
if length := match(s[i+1:]); length > 0 {
pos := i + 1 + length
switch s[pos-1] {
case '.': // IP address
if pos >= len(s) {
continue // . at the end of line
}
if !digit(s[i-1]) {
i = pos
continue // . should be preceded by a digit
}
if !digit(s[pos]) {
i = pos
continue // . should be followed by a digit
}
// find the start of the IP address
j := i - 2
m := max(0, j-3)
for j >= m && digit(s[j]) {
j--
}
if i-2-j > 2 {
i = pos + 1
continue // at most 3 digits
}
start := 0
if j >= 0 {
r, rlen := utf8.DecodeLastRuneInString(s[:j+1])
if !isPunctOrSpaceOrControl(r) {
i = pos + 1
continue
}
switch r {
case '.', ':', '/', '\\', '-', '_':
i = pos + 1
continue
}
start = j + 2 - rlen
}
length, ok := skipIPv4(s[start:])
if !ok {
i = pos + 1
continue
}
end := start + length
if end == len(s) {
links = append(links, Link{
Scheme: "",
Start: start,
End: end,
})
return
}
r, _ := utf8.DecodeRuneInString(s[end:])
if !isPunctOrSpaceOrControl(r) {
continue
}
end = skipPort(s, end)
end = skipPath(s, end)
end = skipQuery(s, end)
end = skipFragment(s, end)
end = unskipPunct(s, end)
if end < len(s) {
r, _ = utf8.DecodeRuneInString(s[end:])
if !isPunctOrSpaceOrControl(r) || r == '%' {
continue
}
}
links = append(links, Link{
Scheme: "",
Start: start,
End: end,
})
i = end
default: // domain name
r, _ := utf8.DecodeLastRuneInString(s[:i])
if isPunctOrSpaceOrControl(r) {
continue
}
if pos == len(s) {
start, ok := findHostnameStart(s, i)
if !ok {
continue
}
links = append(links, Link{
Scheme: "",
Start: start,
End: pos,
})
return
}
if s[i+1:pos] != "xn--" {
r, _ = utf8.DecodeRuneInString(s[pos:])
if isLetterOrDigit(r) {
continue // should not be followed by a letter or a digit
}
}
end, dot, ok := findHostnameEnd(s, pos)
if !ok {
continue
}
dot = max(dot, i)
if !(dot+5 <= len(s) && s[dot+1:dot+5] == "xn--") {
if length := match(s[dot+1:]); dot+length+1 != end {
continue
}
}
start, ok := findHostnameStart(s, i)
if !ok {
continue
}
end = skipPort(s, end)
end = skipPath(s, end)
end = skipQuery(s, end)
end = skipFragment(s, end)
end = unskipPunct(s, end)
if end < len(s) {
r, _ = utf8.DecodeRuneInString(s[end:])
if !isPunctOrSpaceOrControl(r) || r == '%' {
continue // should be followed by punctuation or space
}
}
links = append(links, Link{
Scheme: "",
Start: start,
End: end,
})
i = end
}
}
case '/': // schema-less link
if s[i+1] != '/' {
continue
}
if i > 0 {
if s[i-1] == ':' {
i++
continue // should not be preceded by a colon
}
r, _ := utf8.DecodeLastRuneInString(s[:i])
if !isPunctOrSpaceOrControl(r) {
i++
continue // should be preceded by punctuation or space
}
}
r, _ := utf8.DecodeRuneInString(s[i+2:])
if !isLetterOrDigit(r) {
i++
continue // should be followed by a letter or a digit
}
start := i
end, dot, ok := findHostnameEnd(s, i+2)
if !ok {
continue
}
if s[i+2:end] != "localhost" {
if dot == -1 {
continue // no dot
}
if length, ok := skipIPv4(s[i+2:]); !ok || i+2+length != end {
if length := match(s[dot+1:]); dot+length+1 != end {
continue
}
}
}
end = skipPort(s, end)
end = skipPath(s, end)
end = skipQuery(s, end)
end = skipFragment(s, end)
end = unskipPunct(s, end)
if end < len(s) {
r, _ = utf8.DecodeRuneInString(s[end:])
if !isPunctOrSpaceOrControl(r) || r == '%' {
continue // should be followed by punctuation or space
}
}
links = append(links, Link{
Scheme: "//",
Start: start,
End: end,
})
i = end
case ':': // http, https, ftp, mailto or localhost
if i < 3 { // at least ftp:
continue
}
if i >= 9 && s[i-1] == 't' && s[i-9:i] == "localhost" {
j := i - 9
if !digit(s[j+10]) {
continue
}
if j > 0 {
r, _ := utf8.DecodeLastRuneInString(s[:j])
if !isPunctOrSpaceOrControl(r) {
i++
continue // should be preceded by punctuation or space
}
}
start := j
pos := j + 9
end := skipPort(s, pos)
if end == pos {
continue // invalid port
}
end = skipPath(s, end)
end = skipQuery(s, end)
end = skipFragment(s, end)
end = unskipPunct(s, end)
if end < len(s) {
r, _ := utf8.DecodeRuneInString(s[end:])
if !isPunctOrSpaceOrControl(r) || r == '%' {
i++
continue // should be followed by punctuation or space
}
}
links = append(links, Link{
Scheme: "",
Start: start,
End: end,
})
i = end
break
}
j := i - 1
var start int
var schema string
switch byteToLower(s[j]) {
case 'o': // mailto
if j < 5 {
continue // too short for mailto
}
if len(s)-j < 8 {
continue // insufficient length after
}
if strings.ToLower(s[j-5:j+2]) != "mailto:" {
continue
}
r, _ := utf8.DecodeLastRuneInString(s[:j-5])
if isLetterOrDigit(r) {
continue // should not be preceded by a letter or a digit
}
r, _ = utf8.DecodeRuneInString(s[j+2:])
if !isAllowedInEmail(r) {
continue // should be followed by a valid e-mail character
}
start = j - 5
end, ok := findEmailEnd(s, j+2)
if !ok {
continue
}
links = append(links, Link{
Scheme: "mailto:",
Start: start,
End: end,
})
i = end
continue // continue processing
case 'p': // http or ftp
if len(s)-j < 8 {
continue // insufficient length after
}
switch byteToLower(s[j-2]) {
case 'f':
if strings.ToLower(s[j-2:j+4]) != "ftp://" {
continue
}
start = j - 2
schema = "ftp:"
case 't':
if j < 3 {
continue
}
if strings.ToLower(s[j-3:j+4]) != "http://" {
continue
}
start = j - 3
schema = "http:"
default:
continue
}
case 's': // https
if j < 4 {
continue // too short for https
}
if len(s)-j < 8 {
continue // insufficient length after
}
start = j - 4
if strings.ToLower(s[start:j+4]) != "https://" {
continue
}
schema = "https:"
default:
continue
}
// http, https or ftp
if start > 0 {
r, _ := utf8.DecodeLastRuneInString(s[:start])
if !isPunctOrSpaceOrControl(r) {
continue // should be preceded by punctuation or space
}
}
r, _ := utf8.DecodeRuneInString(s[j+4:])
if isPunctOrSpaceOrControl(r) {
continue
}
end, dot, ok := findHostnameEnd(s, j+4)
if !ok {
continue
}
if s[j+4:end] != "localhost" {
if dot == -1 {
continue // no dot
}
if length, ok := skipIPv4(s[j+4:]); !ok || j+4+length != end {
if !(dot+5 <= len(s) && s[dot+1:dot+5] == "xn--") {
if length := match(s[dot+1:]); dot+length+1 != end {
continue
}
}
}
}
end = skipPort(s, end)
end = skipPath(s, end)
end = skipQuery(s, end)
end = skipFragment(s, end)
end = unskipPunct(s, end)
if end < len(s) {
r, _ = utf8.DecodeRuneInString(s[end:])
if !isPunctOrSpaceOrControl(r) || r == '%' {
continue // should be followed by punctuation or space
}
}
links = append(links, Link{
Scheme: schema,
Start: start,
End: end,
})
i = end
case '@': // schema-less e-mail
if i == 0 {
continue // @ at the start of a line
}
if len(s)-i < 5 {
continue // insufficient length after
}
r, _ := utf8.DecodeLastRuneInString(s[:i])
if !isAllowedInEmail(r) {
continue // should be preceded by a valid e-mail character
}
r, _ = utf8.DecodeRuneInString(s[i+1:])
if !isLetterOrDigit(r) {
continue // should be followed by a letter or a digit
}
start, ok := findEmailStart(s, i-1)
if !ok {
continue
}
end, dot, ok := findHostnameEnd(s, i+1)
if !ok {
continue
}
if dot == -1 {
continue // no dot
}
if !(dot+5 <= len(s) && s[dot+1:dot+5] == "xn--") {
if length := match(s[dot+1:]); dot+length+1 != end {
continue
}
}
links = append(links, Link{
Scheme: "mailto:",
Start: start,
End: end,
})
i = end
}
}
return
}

View File

@ -1,404 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package linkify
import "unicode/utf8"
func atoi3(s string, start int) (int, bool) {
n := 0
var i int
for i = start; i < len(s) && digit(s[i]); i++ {
n = n*10 + int(s[i]-'0')
if n > 255 {
return 0, false
}
}
if i == start {
return 0, false
}
return i, true
}
func skipIPv4(s string) (_ int, _ bool) {
j := 0
for i := 0; i < 4; i++ {
if j >= len(s) {
return
}
if i > 0 {
if s[j] != '.' {
return
}
j++
}
n, ok := atoi3(s, j)
if !ok {
return
}
j = n
}
return j, true
}
func atoi5(s string, start int) (int, bool) {
n := 0
var i int
for i = start; i < len(s) && digit(s[i]); i++ {
n = n*10 + int(s[i]-'0')
if n > 65535 {
return 0, false
}
}
if i == start || n == 0 {
return 0, false
}
return i, true
}
func skipPort(s string, start int) int {
if start >= len(s) || s[start] != ':' {
return start
}
end, ok := atoi5(s, start+1)
if !ok {
return start
}
return end
}
func skipPath(s string, start int) int {
if start >= len(s) || s[start] != '/' {
return start // skip empty path
}
var stack []rune
var notClosedIndex int
var nHyphen int
end := start + 1
loop:
for end < len(s) {
r, rlen := utf8.DecodeRuneInString(s[end:])
if r == utf8.RuneError {
nHyphen = 0
break
}
switch {
case isUnreserved(r):
if r == '-' {
nHyphen++
if nHyphen > 2 {
break loop
}
} else {
nHyphen = 0
}
case isSubDelimiter(r) || r == '[' || r == ']':
nHyphen = 0
switch r {
case '[', '(':
if len(stack) == 0 {
notClosedIndex = end
}
stack = append(stack, r)
case ']', ')':
opening := '['
if r == ')' {
opening = '('
}
if len(stack) == 0 || stack[len(stack)-1] != opening {
break loop
}
stack = stack[:len(stack)-1]
}
case r == '/' || r == ':' || r == '@':
nHyphen = 0
case r == '%':
nHyphen = 0
if end+2 >= len(s) {
break loop
}
if !(hexDigit(s[end+1]) &&
hexDigit(s[end+2])) {
break loop
}
end += 2
default:
nHyphen = 0
if r != ' ' || len(stack) == 0 {
break loop
}
}
end += rlen
}
if len(stack) > 0 {
return notClosedIndex
}
if nHyphen > 0 {
return end - nHyphen + 1
}
return end
}
func skipQuery(s string, start int) int {
if start >= len(s) || s[start] != '?' {
return start
}
var stack []rune
var notClosedIndex int
var nHyphen int
end := start + 1
loop:
for end < len(s) {
r, rlen := utf8.DecodeRuneInString(s[end:])
if r == utf8.RuneError {
nHyphen = 0
break
}
switch {
case isUnreserved(r):
if r == '-' {
nHyphen++
if nHyphen > 1 {
break loop
}
} else {
nHyphen = 0
}
case isSubDelimiter(r) || r == '[' || r == ']':
nHyphen = 0
switch r {
case '[', '(':
if len(stack) == 0 {
notClosedIndex = end
}
stack = append(stack, r)
case ']', ')':
opening := '['
if r == ')' {
opening = '('
}
if len(stack) == 0 || stack[len(stack)-1] != opening {
break loop
}
stack = stack[:len(stack)-1]
}
case r == '?' || r == '/' || r == ':' || r == '@':
nHyphen = 0
case r == '%':
nHyphen = 0
if end+2 >= len(s) {
break loop
}
if !(hexDigit(s[end+1]) &&
hexDigit(s[end+2])) {
break loop
}
end += 2
default:
nHyphen = 0
if r != ' ' || len(stack) == 0 {
break loop
}
}
end += rlen
}
if len(stack) > 0 {
return notClosedIndex
}
if nHyphen > 0 {
return end - nHyphen + 1
}
return end
}
func skipFragment(s string, start int) int {
if start >= len(s) || s[start] != '#' {
return start
}
var stack []rune
var notClosedIndex int
var nHyphen int
end := start + 1
loop:
for end < len(s) {
r, rlen := utf8.DecodeRuneInString(s[end:])
if r == utf8.RuneError {
nHyphen = 0
break
}
switch {
case isUnreserved(r):
if r == '-' {
nHyphen++
if nHyphen > 1 {
break loop
}
} else {
nHyphen = 0
}
case isSubDelimiter(r) || r == '[' || r == ']':
nHyphen = 0
switch r {
case '[', '(':
if len(stack) == 0 {
notClosedIndex = end
}
stack = append(stack, r)
case ']', ')':
opening := '['
if r == ')' {
opening = '('
}
if len(stack) == 0 || stack[len(stack)-1] != opening {
break loop
}
stack = stack[:len(stack)-1]
}
case r == '?' || r == '/' || r == ':' || r == '@':
nHyphen = 0
case r == '%':
nHyphen = 0
if end+2 >= len(s) {
break loop
}
if !(hexDigit(s[end+1]) &&
hexDigit(s[end+2])) {
break loop
}
end += 2
default:
nHyphen = 0
if r != ' ' || len(stack) == 0 {
break loop
}
}
end += rlen
}
if len(stack) > 0 {
return notClosedIndex
}
if nHyphen > 0 {
return end - nHyphen + 1
}
return end
}
func unskipPunct(s string, start int) int {
end := start - 1
if end < 0 || end >= len(s) || !basicPunct[s[end]] {
return start
}
return end
}
func findHostnameStart(s string, start int) (_ int, _ bool) {
end := start
lastDot := true
nHyphen := 0
loop:
for end > 0 {
r, rlen := utf8.DecodeLastRuneInString(s[:end])
if r == utf8.RuneError {
return
}
switch {
case r == '.':
if nHyphen > 0 {
return
}
lastDot = true
case r == '-':
if end == start {
return
}
if lastDot {
return
}
nHyphen++
if nHyphen == 3 {
return
}
case r == ':' || r == '/' || r == '\\' || r == '_':
return
case isPunctOrSpaceOrControl(r):
break loop
default:
lastDot = false
nHyphen = 0
}
end -= rlen
}
if lastDot || nHyphen > 0 {
return
}
return end, true
}
func findHostnameEnd(s string, start int) (_ int, _ int, _ bool) {
end := start
lastDot := false
lastDotPos := -1
nHyphen := 0
loop:
for end < len(s) {
r, rlen := utf8.DecodeRuneInString(s[end:])
if r == utf8.RuneError {
return
}
switch {
case r == '.':
if nHyphen > 0 {
return
}
if lastDot {
break loop
}
lastDot = true
lastDotPos = end
nHyphen = 0
case r == '-':
lastDot = false
if end == start {
return
}
if lastDot {
return
}
nHyphen++
if nHyphen == 3 {
break loop
}
case r == '\\' || r == '_':
return
case isPunctOrSpaceOrControl(r):
break loop
default:
lastDot = false
nHyphen = 0
}
end += rlen
}
if nHyphen > 0 {
end -= nHyphen
} else if lastDot {
if s[end-1] == '.' {
end--
}
lastDotPos = end - 1
for lastDotPos >= start && s[lastDotPos] != '.' {
lastDotPos--
}
if lastDotPos < start {
lastDotPos = -1
}
}
return end, lastDotPos, true
}

View File

@ -1,20 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package linkify
func digit(b byte) bool {
return b >= '0' && b <= '9'
}
func hexDigit(b byte) bool {
return digit(b) || b >= 'a' && b <= 'f' || b >= 'A' && b <= 'F'
}
func byteToLower(b byte) byte {
if b >= 'A' && b <= 'Z' {
return b - 'A' + 'a'
}
return b
}

View File

@ -1,25 +0,0 @@
image: golang:1.11
stages:
- build
- test
before_script:
- go get github.com/russross/blackfriday
- go get gitlab.com/golang-commonmark/html
- go get gitlab.com/golang-commonmark/linkify
- go get gitlab.com/golang-commonmark/mdurl
- go get gitlab.com/golang-commonmark/puny
- go get gitlab.com/opennota/wd
- go get gopkg.in/russross/blackfriday.v2
build:
stage: build
script:
- go build ./...
test:
stage: test
script:
- test -z "$(gofmt -l . | tee /dev/stderr)"
- go test -cover ./...

View File

@ -1 +0,0 @@
opennota@gmail.com

View File

@ -1,10 +0,0 @@
Copyright (c) 2015, The 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.

View File

@ -1,64 +0,0 @@
markdown [![GoDoc](http://godoc.org/gitlab.com/golang-commonmark/markdown?status.svg)](http://godoc.org/gitlab.com/golang-commonmark/markdown) [![License](https://img.shields.io/badge/licence-BSD--2--Clause-blue.svg)](https://opensource.org/licenses/BSD-2-Clause) [![Pipeline status](https://gitlab.com/golang-commonmark/markdown/badges/master/pipeline.svg)](https://gitlab.com/golang-commonmark/markdown/commits/master) [![Coverage report](https://gitlab.com/golang-commonmark/markdown/badges/master/coverage.svg)](https://gitlab.com/golang-commonmark/markdown/commits/master)
========
Package golang-commonmark/markdown provides a CommonMark-compliant markdown parser and renderer, written in Go.
## Installation
go get -u gitlab.com/golang-commonmark/markdown
You can also go get [mdtool](https://gitlab.com/golang-commonmark/mdtool), an example command-line tool:
go get -u gitlab.com/golang-commonmark/mdtool
## Standards support
Currently supported CommonMark spec: [v0.28](http://spec.commonmark.org/0.28/).
## Extensions
Besides the features required by CommonMark, golang-commonmark/markdown supports:
* Tables (GFM)
* Strikethrough (GFM)
* Autoconverting plain-text URLs to links
* Typographic replacements (smart quotes and other)
## Usage
``` go
md := markdown.New(markdown.XHTMLOutput(true))
fmt.Println(md.RenderToString([]byte("Header\n===\nText")))
```
Check out [the source of mdtool](https://gitlab.com/golang-commonmark/mdtool/blob/master/main.go) for a more complete example.
The following options are currently supported:
Name | Type | Description | Default
--------------- | --------- | ----------------------------------------------------------- | ---------
HTML | bool | whether to enable raw HTML | false
Tables | bool | whether to enable GFM tables | true
Linkify | bool | whether to autoconvert plain-text URLs to links | true
Typographer | bool | whether to enable typographic replacements | true
Quotes | string / []string | double + single quote replacement pairs for the typographer | “”‘’
MaxNesting | int | maximum nesting level | 20
LangPrefix | string | CSS language prefix for fenced blocks | language-
Breaks | bool | whether to convert newlines inside paragraphs into `<br>` | false
XHTMLOutput | bool | whether to output XHTML instead of HTML | false
## Benchmarks
Rendering spec/spec-0.28.txt on a Intel(R) Core(TM) i5-2400 CPU @ 3.10GHz
BenchmarkRenderSpecNoHTML 100 10254720 ns/op 2998037 B/op 18225 allocs/op
BenchmarkRenderSpec 100 10180241 ns/op 2997307 B/op 18214 allocs/op
BenchmarkRenderSpecBlackFriday 200 7241749 ns/op 2834340 B/op 17101 allocs/op
BenchmarkRenderSpecBlackFriday2 200 7448256 ns/op 2991202 B/op 16705 allocs/op
## See also
https://github.com/jgm/CommonMark — the reference CommonMark implementations in C and JavaScript,
also contains the latest spec and an online demo.
http://talk.commonmark.org — the CommonMark forum, a good place to join together the efforts of the developers.

View File

@ -1,26 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
type Align byte
const (
AlignNone = iota
AlignLeft
AlignCenter
AlignRight
)
func (a Align) String() string {
switch a {
case AlignLeft:
return "left"
case AlignCenter:
return "center"
case AlignRight:
return "right"
}
return ""
}

View File

@ -1,70 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"regexp"
"strings"
)
var (
rAutolink = regexp.MustCompile(`^<([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)>`)
rEmail = regexp.MustCompile(`^<([a-zA-Z0-9.!#$%&'*+/=?^_{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>`)
)
func ruleAutolink(s *StateInline, silent bool) bool {
pos := s.Pos
src := s.Src
if src[pos] != '<' {
return false
}
tail := src[pos:]
if strings.IndexByte(tail, '>') < 0 {
return false
}
link := rAutolink.FindString(tail)
if link != "" {
link = link[1 : len(link)-1]
href := normalizeLink(link)
if !validateLink(href) {
return false
}
if !silent {
s.PushOpeningToken(&LinkOpen{Href: href})
s.PushToken(&Text{Content: normalizeLinkText(link)})
s.PushClosingToken(&LinkClose{})
}
s.Pos += len(link) + 2
return true
}
email := rEmail.FindString(tail)
if email != "" {
email = email[1 : len(email)-1]
href := normalizeLink("mailto:" + email)
if !validateLink(href) {
return false
}
if !silent {
s.PushOpeningToken(&LinkOpen{Href: href})
s.PushToken(&Text{Content: normalizeLinkText(email)})
s.PushClosingToken(&LinkClose{})
}
s.Pos += len(email) + 2
return true
}
return false
}

View File

@ -1,60 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
func ruleBackticks(s *StateInline, silent bool) bool {
pos := s.Pos
src := s.Src
if src[pos] != '`' {
return false
}
start := pos
pos++
max := s.PosMax
for pos < max && src[pos] == '`' {
pos++
}
marker := src[start:pos]
matchStart := pos
matchEnd := pos
for {
matchStart = strings.IndexByte(src[matchEnd:], '`')
if matchStart == -1 {
break
}
matchStart += matchEnd
matchEnd = matchStart + 1
for matchEnd < max && src[matchEnd] == '`' {
matchEnd++
}
if matchEnd-matchStart == len(marker) {
if !silent {
s.PushToken(&CodeInline{
Content: normalizeInlineCode(src[pos:matchStart]),
})
}
s.Pos = matchEnd
return true
}
}
if !silent {
s.Pending.WriteString(marker)
}
s.Pos += len(marker)
return true
}

View File

@ -1,43 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleBalancePairs(s *StateInline) {
delimiters := s.Delimiters
max := len(delimiters)
for i := 0; i < max; i++ {
lastDelim := delimiters[i]
if !lastDelim.Close {
continue
}
j := i - lastDelim.Jump - 1
for j >= 0 {
currDelim := delimiters[j]
if currDelim.Open &&
currDelim.Marker == lastDelim.Marker &&
currDelim.End < 0 &&
currDelim.Level == lastDelim.Level {
oddMatch := (currDelim.Close || lastDelim.Open) &&
currDelim.Length != -1 &&
lastDelim.Length != -1 &&
(currDelim.Length+lastDelim.Length)%3 == 0
if !oddMatch {
delimiters[i].Jump = i - j
delimiters[i].Open = false
delimiters[j].End = i
delimiters[j].Jump = 0
break
}
}
j -= currDelim.Jump + 1
}
}
}

View File

@ -1,233 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "unicode/utf8"
var blockquoteTerminatedBy []BlockRule
func ruleBlockQuote(s *StateBlock, startLine, endLine int, silent bool) bool {
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
src := s.Src
if pos >= max {
return false
}
if src[pos] != '>' {
return false
}
pos++
if silent {
return true
}
initial := s.SCount[startLine] + pos - (s.BMarks[startLine] + s.TShift[startLine])
offset := initial
spaceAfterMarker := false
adjustTab := false
if pos < max {
if src[pos] == ' ' {
pos++
initial++
offset++
spaceAfterMarker = true
} else if src[pos] == '\t' {
spaceAfterMarker = true
if (s.BSCount[startLine]+offset)%4 == 3 {
pos++
initial++
offset++
} else {
adjustTab = true
}
}
}
oldBMarks := []int{s.BMarks[startLine]}
s.BMarks[startLine] = pos
for pos < max {
r, size := utf8.DecodeRuneInString(src[pos:])
if runeIsSpace(r) {
if r == '\t' {
d := 0
if adjustTab {
d = 1
}
offset += 4 - (offset+s.BSCount[startLine]+d)%4
} else {
offset++
}
} else {
break
}
pos += size
}
oldBSCount := []int{s.BSCount[startLine]}
d := 0
if spaceAfterMarker {
d = 1
}
s.BSCount[startLine] = s.SCount[startLine] + 1 + d
lastLineEmpty := pos >= max
oldSCount := []int{s.SCount[startLine]}
s.SCount[startLine] = offset - initial
oldTShift := []int{s.TShift[startLine]}
s.TShift[startLine] = pos - s.BMarks[startLine]
oldParentType := s.ParentType
s.ParentType = ptBlockQuote
wasOutdented := false
oldLineMax := s.LineMax
nextLine := startLine + 1
for ; nextLine < endLine; nextLine++ {
if s.SCount[nextLine] < s.BlkIndent {
wasOutdented = true
}
pos = s.BMarks[nextLine] + s.TShift[nextLine]
max = s.EMarks[nextLine]
if pos >= max {
break
}
pos++
if src[pos-1] == '>' && !wasOutdented {
initial = s.SCount[nextLine] + pos + (s.BMarks[nextLine] + s.TShift[nextLine])
offset = initial
if pos >= len(src) || src[pos] != ' ' && src[pos] != '\t' {
spaceAfterMarker = true
} else if src[pos] == ' ' {
pos++
initial++
offset++
adjustTab = false
spaceAfterMarker = true
} else if src[pos] == '\t' {
spaceAfterMarker = true
if (s.BSCount[nextLine]+offset)%4 == 3 {
pos++
initial++
offset++
adjustTab = false
} else {
adjustTab = true
}
}
oldBMarks = append(oldBMarks, s.BMarks[nextLine])
s.BMarks[nextLine] = pos
for pos < max {
r, size := utf8.DecodeRuneInString(src[pos:])
if runeIsSpace(r) {
if r == '\t' {
d := 0
if adjustTab {
d = 1
}
offset += 4 - (offset+s.BSCount[startLine]+d)%4
} else {
offset++
}
} else {
break
}
pos += size
}
lastLineEmpty = pos >= max
oldBSCount = append(oldBSCount, s.BSCount[nextLine])
d := 0
if spaceAfterMarker {
d = 1
}
s.BSCount[nextLine] = s.SCount[nextLine] + 1 + d
oldSCount = append(oldSCount, s.SCount[nextLine])
s.SCount[nextLine] = offset - initial
oldTShift = append(oldTShift, s.TShift[nextLine])
s.TShift[nextLine] = pos - s.BMarks[nextLine]
continue
}
if lastLineEmpty {
break
}
terminate := false
for _, r := range blockquoteTerminatedBy {
if r(s, nextLine, endLine, true) {
terminate = true
break
}
}
if terminate {
s.LineMax = nextLine
if s.BlkIndent != 0 {
oldBMarks = append(oldBMarks, s.BMarks[nextLine])
oldBSCount = append(oldBSCount, s.BSCount[nextLine])
oldTShift = append(oldTShift, s.TShift[nextLine])
oldSCount = append(oldSCount, s.SCount[nextLine])
s.SCount[nextLine] -= s.BlkIndent
}
break
}
oldBMarks = append(oldBMarks, s.BMarks[nextLine])
oldBSCount = append(oldBSCount, s.BSCount[nextLine])
oldTShift = append(oldTShift, s.TShift[nextLine])
oldSCount = append(oldSCount, s.SCount[nextLine])
s.SCount[nextLine] = -1
}
oldIndent := s.BlkIndent
s.BlkIndent = 0
tok := &BlockquoteOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
s.Md.Block.Tokenize(s, startLine, nextLine)
s.PushClosingToken(&BlockquoteClose{})
s.LineMax = oldLineMax
s.ParentType = oldParentType
tok.Map[1] = s.Line
for i := 0; i < len(oldTShift); i++ {
s.BMarks[startLine+i] = oldBMarks[i]
s.TShift[startLine+i] = oldTShift[i]
s.SCount[startLine+i] = oldSCount[i]
s.BSCount[startLine+i] = oldBSCount[i]
}
s.BlkIndent = oldIndent
return true
}

View File

@ -1,37 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleCode(s *StateBlock, startLine, endLine int, _ bool) bool {
if s.SCount[startLine]-s.BlkIndent < 4 {
return false
}
nextLine := startLine + 1
last := nextLine
for nextLine < endLine {
if s.IsLineEmpty(nextLine) {
nextLine++
continue
}
if s.SCount[nextLine]-s.BlkIndent >= 4 {
nextLine++
last = nextLine
continue
}
break
}
s.Line = last
s.PushToken(&CodeBlock{
Content: s.Lines(startLine, last, 4+s.BlkIndent, true),
Map: [2]int{startLine, s.Line},
})
return true
}

View File

@ -1,89 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
type Delimiter struct {
Length int
Jump int
Token int
Level int
End int
Open bool
Close bool
Marker byte
}
func ruleEmphasis(s *StateInline, silent bool) bool {
src := s.Src
start := s.Pos
marker := src[start]
if silent {
return false
}
if marker != '_' && marker != '*' {
return false
}
canOpen, canClose, length := s.scanDelims(s.Pos, marker == '*')
for i := 0; i < length; i++ {
s.PushToken(&Text{Content: string(marker)})
s.Delimiters = append(s.Delimiters, Delimiter{
Marker: marker,
Length: length,
Jump: i,
Token: len(s.Tokens) - 1,
Level: s.Level,
End: -1,
Open: canOpen,
Close: canClose,
})
}
s.Pos += length
return true
}
func ruleEmphasisPostprocess(s *StateInline) {
delimiters := s.Delimiters
max := len(delimiters)
for i := max - 1; i >= 0; i-- {
startDelim := delimiters[i]
if startDelim.Marker != '_' && startDelim.Marker != '*' {
continue
}
if startDelim.End == -1 {
continue
}
endDelim := delimiters[startDelim.End]
isStrong := i > 0 &&
delimiters[i-1].End == startDelim.End+1 &&
delimiters[i-1].Token == startDelim.Token-1 &&
delimiters[startDelim.End+1].Token == endDelim.Token+1 &&
delimiters[i-1].Marker == startDelim.Marker
if isStrong {
s.Tokens[startDelim.Token] = &StrongOpen{}
s.Tokens[endDelim.Token] = &StrongClose{}
if text, ok := s.Tokens[delimiters[i-1].Token].(*Text); ok {
text.Content = ""
}
if text, ok := s.Tokens[delimiters[startDelim.End+1].Token].(*Text); ok {
text.Content = ""
}
i--
} else {
s.Tokens[startDelim.Token] = &EmphasisOpen{}
s.Tokens[endDelim.Token] = &EmphasisClose{}
}
}
}

View File

@ -1,35 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "gitlab.com/golang-commonmark/html"
func ruleEntity(s *StateInline, silent bool) bool {
pos := s.Pos
src := s.Src
if src[pos] != '&' {
return false
}
max := s.PosMax
if pos+1 < max {
if e, n := html.ParseEntity(src[pos:]); n > 0 {
if !silent {
s.Pending.WriteString(e)
}
s.Pos += n
return true
}
}
if !silent {
s.Pending.WriteByte('&')
}
s.Pos++
return true
}

View File

@ -1,61 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
func escaped(b byte) bool {
return strings.IndexByte("\\!\"#$%&'()*+,./:;<=>?@[]^_`{|}~-", b) != -1
}
func ruleEscape(s *StateInline, silent bool) bool {
pos := s.Pos
src := s.Src
if src[pos] != '\\' {
return false
}
pos++
max := s.PosMax
if pos < max {
b := src[pos]
if b < 0x7f && escaped(b) {
if !silent {
s.Pending.WriteByte(b)
}
s.Pos += 2
return true
}
if b == '\n' {
if !silent {
s.PushToken(&Hardbreak{})
}
pos++
for pos < max {
b := src[pos]
if !byteIsSpace(b) {
break
}
pos++
}
s.Pos = pos
return true
}
}
if !silent {
s.Pending.WriteByte('\\')
}
s.Pos++
return true
}

View File

@ -1,102 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
func ruleFence(s *StateBlock, startLine, endLine int, silent bool) bool {
haveEndMarker := false
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
if pos+3 > max {
return false
}
src := s.Src
marker := src[pos]
if marker != '~' && marker != '`' {
return false
}
mem := pos
pos = s.SkipBytes(pos, marker)
len := pos - mem
if len < 3 {
return false
}
params := strings.TrimSpace(src[pos:max])
if strings.IndexByte(params, marker) >= 0 {
return false
}
if silent {
return true
}
nextLine := startLine
for {
nextLine++
if nextLine >= endLine {
break
}
mem = s.BMarks[nextLine] + s.TShift[nextLine]
pos = mem
max = s.EMarks[nextLine]
if pos < max && s.SCount[nextLine] < s.BlkIndent {
break
}
if pos >= max || src[pos] != marker {
continue
}
if s.SCount[nextLine]-s.BlkIndent >= 4 {
continue
}
pos = s.SkipBytes(pos, marker)
if pos-mem < len {
continue
}
pos = s.SkipSpaces(pos)
if pos < max {
continue
}
haveEndMarker = true
break
}
len = s.SCount[startLine]
s.Line = nextLine
if haveEndMarker {
s.Line++
}
s.PushToken(&Fence{
Params: params,
Content: s.Lines(startLine+1, nextLine, len, true),
Map: [2]int{startLine, s.Line},
})
return true
}

View File

@ -1,9 +0,0 @@
//+build gofuzz
package markdown
func Fuzz(data []byte) int {
md := New(HTML(true))
md.Parse(data)
return 1
}

View File

@ -1,59 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
func ruleHeading(s *StateBlock, startLine, _ int, silent bool) bool {
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
src := s.Src
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
if pos >= max || src[pos] != '#' {
return false
}
level := 1
pos++
for pos < max && src[pos] == '#' && level <= 6 {
level++
pos++
}
if level > 6 || (pos < max && !byteIsSpace(src[pos])) {
return false
}
if silent {
return true
}
max = s.SkipSpacesBack(max, pos)
tmp := s.SkipBytesBack(max, '#', pos)
if tmp > pos && byteIsSpace(src[tmp-1]) {
max = tmp
}
s.Line = startLine + 1
s.PushOpeningToken(&HeadingOpen{
HLevel: level,
Map: [2]int{startLine, s.Line},
})
if pos < max {
s.PushToken(&Inline{
Content: strings.TrimSpace(src[pos:max]),
Map: [2]int{startLine, s.Line},
})
}
s.PushClosingToken(&HeadingClose{HLevel: level})
return true
}

View File

@ -1,164 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func parseLinkLabel(s *StateInline, start int, disableNested bool) int {
src := s.Src
labelEnd := -1
max := s.PosMax
oldPos := s.Pos
s.Pos = start + 1
level := 1
found := false
for s.Pos < max {
marker := src[s.Pos]
if marker == ']' {
level--
if level == 0 {
found = true
break
}
}
prevPos := s.Pos
s.Md.Inline.SkipToken(s)
if marker == '[' {
if prevPos == s.Pos-1 {
level++
} else if disableNested {
s.Pos = oldPos
return -1
}
}
}
if found {
labelEnd = s.Pos
}
s.Pos = oldPos
return labelEnd
}
func parseLinkDestination(s string, pos, max int) (url string, lines, endpos int, ok bool) {
start := pos
if pos < max && s[pos] == '<' {
pos++
for pos < max {
b := s[pos]
if b == '\n' || byteIsSpace(b) {
return
}
if b == '>' {
endpos = pos + 1
url = unescapeAll(s[start+1 : pos])
ok = true
return
}
if b == '\\' && pos+1 < max {
pos += 2
continue
}
pos++
}
return
}
level := 0
for pos < max {
b := s[pos]
if b == ' ' {
break
}
if b < 0x20 || b == 0x7f {
break
}
if b == '\\' && pos+1 < max {
pos += 2
continue
}
if b == '(' {
level++
}
if b == ')' {
if level == 0 {
break
}
level--
}
pos++
}
if start == pos {
return
}
if level != 0 {
return
}
url = unescapeAll(s[start:pos])
endpos = pos
ok = true
return
}
func parseLinkTitle(s string, pos, max int) (title string, nlines, endpos int, ok bool) {
lines := 0
start := pos
if pos >= max {
return
}
marker := s[pos]
if marker != '"' && marker != '\'' && marker != '(' {
return
}
pos++
if marker == '(' {
marker = ')'
}
for pos < max {
switch s[pos] {
case marker:
endpos = pos + 1
nlines = lines
title = unescapeAll(s[start+1 : pos])
ok = true
return
case '\n':
lines++
case '\\':
if pos+1 < max {
pos++
if s[pos] == '\n' {
lines++
}
}
}
pos++
}
return
}

View File

@ -1,54 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleHR(s *StateBlock, startLine, endLine int, silent bool) bool {
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
if pos >= max {
return false
}
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
src := s.Src
marker := src[pos]
pos++
if marker != '*' && marker != '-' && marker != '_' {
return false
}
cnt := 1
for pos < max {
ch := src[pos]
pos++
if ch != marker && !byteIsSpace(ch) {
return false
}
if ch == marker {
cnt++
}
}
if cnt < 3 {
return false
}
if silent {
return true
}
s.Line = startLine + 1
s.PushToken(&Hr{
Map: [2]int{startLine, s.Line},
})
return true
}

View File

@ -1,222 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"regexp"
"strings"
)
var (
htmlBlocks = []string{
"address",
"article",
"aside",
"base",
"basefont",
"blockquote",
"body",
"caption",
"center",
"col",
"colgroup",
"dd",
"details",
"dialog",
"dir",
"div",
"dl",
"dt",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"frame",
"frameset",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"iframe",
"legend",
"li",
"link",
"main",
"menu",
"menuitem",
"meta",
"nav",
"noframes",
"ol",
"optgroup",
"option",
"p",
"param",
"section",
"source",
"summary",
"table",
"tbody",
"td",
"tfoot",
"th",
"thead",
"title",
"tr",
"track",
"ul",
}
htmlBlocksSet = make(map[string]bool)
rStartCond1 = regexp.MustCompile(`(?i)^(pre|script|style)([\n\t >]|$)`)
rEndCond1 = regexp.MustCompile(`(?i)</(pre|script|style)>`)
rStartCond6 = regexp.MustCompile(`(?i)^/?(` + strings.Join(htmlBlocks, "|") + `)(\s|$|>|/>)`)
rStartCond7 = regexp.MustCompile(`(?i)^(/[a-z][a-z0-9-]*|[a-z][a-z0-9-]*(\s+[a-z_:][a-z0-9_.:-]*\s*=\s*("[^"]*"|'[^']*'|[ "'=<>\x60]))*\s*/?)>\s*$`)
)
func init() {
for _, tag := range htmlBlocks {
htmlBlocksSet[tag] = true
}
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func matchTagName(s string) string {
if len(s) < 2 {
return ""
}
i := 0
if s[0] == '/' {
i++
}
start := i
max := min(15+i, len(s))
for i < max && isLetter(s[i]) {
i++
}
if i >= len(s) {
return ""
}
switch s[i] {
case ' ', '\n', '/', '>':
return strings.ToLower(s[start:i])
default:
return ""
}
}
func ruleHTMLBlock(s *StateBlock, startLine, endLine int, silent bool) bool {
if !s.Md.HTML {
return false
}
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
if pos+1 >= max {
return false
}
src := s.Src
if src[pos] != '<' {
return false
}
pos++
b := src[pos]
if !htmlSecond(b) {
return false
}
nextLine := startLine + 1
var endCond func(string) bool
if pos+2 < max && isLetter(b) && rStartCond1.MatchString(src[pos:]) {
endCond = func(s string) bool {
return rEndCond1.MatchString(s)
}
} else if strings.HasPrefix(src[pos:], "!--") {
endCond = func(s string) bool {
return strings.Contains(s, "-->")
}
} else if b == '?' {
endCond = func(s string) bool {
return strings.Contains(s, "?>")
}
} else if b == '!' && pos+1 < max && isUppercaseLetter(src[pos+1]) {
endCond = func(s string) bool {
return strings.Contains(s, ">")
}
} else if strings.HasPrefix(src[pos:], "![CDATA[") {
endCond = func(s string) bool {
return strings.Contains(s, "]]>")
}
} else if pos+2 < max && (isLetter(b) || b == '/' && isLetter(src[pos+1])) {
terminator := true
if rStartCond6.MatchString(src[pos:max]) {
} else if rStartCond7.MatchString(src[pos:max]) {
terminator = false
} else {
return false
}
if silent {
return terminator
}
endCond = func(s string) bool {
return s == ""
}
} else {
return false
}
if silent {
return true
}
if !endCond(src[pos:max]) {
for nextLine < endLine {
if s.SCount[nextLine] < s.BlkIndent {
break
}
pos := s.BMarks[nextLine] + s.TShift[nextLine]
max := s.EMarks[nextLine]
lineText := src[pos:max]
if endCond(lineText) {
if pos != max {
nextLine++
}
break
}
nextLine++
}
}
s.Line = nextLine
s.PushToken(&HTMLBlock{
Content: s.Lines(startLine, nextLine, s.BlkIndent, true),
Map: [2]int{startLine, nextLine},
})
return true
}

View File

@ -1,57 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "regexp"
var (
attrName = `[a-zA-Z_:][a-zA-Z0-9:._-]*`
unquoted = "[^\"'=<>`\\x00-\\x20]+"
singleQuoted = `'[^']*'`
doubleQuoted = `"[^"]*"`
attrValue = `(?:` + unquoted + `|` + singleQuoted + `|` + doubleQuoted + `)`
attribute = `(?:\s+` + attrName + `(?:\s*=\s*` + attrValue + `)?)`
openTag = `<[A-Za-z][A-Za-z0-9-]*` + attribute + `*\s*/?>`
closeTag = `</[A-Za-z][A-Za-z0-9-]*\s*>`
comment = `<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->`
processing = `<[?].*?[?]>`
declaration = `<![A-Z]+\s+[^>]*>`
cdata = `<!\[CDATA\[[\s\S]*?\]\]>`
rHTMLTag = regexp.MustCompile(`^(?:` + openTag + `|` + closeTag + `|` + comment +
`|` + processing + `|` + declaration + `|` + cdata + `)`)
)
func htmlSecond(b byte) bool {
return b == '!' || b == '/' || b == '?' || isLetter(b)
}
func ruleHTMLInline(s *StateInline, silent bool) bool {
if !s.Md.HTML {
return false
}
pos := s.Pos
src := s.Src
if pos+2 >= s.PosMax || src[pos] != '<' {
return false
}
if !htmlSecond(src[pos+1]) {
return false
}
match := rHTMLTag.FindString(src[pos:])
if match == "" {
return false
}
if !silent {
s.PushToken(&HTMLInline{Content: match})
}
s.Pos += len(match)
return true
}

View File

@ -1,131 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleImage(s *StateInline, silent bool) bool {
pos := s.Pos
max := s.PosMax
if pos+2 >= max {
return false
}
src := s.Src
if src[pos] != '!' || src[pos+1] != '[' {
return false
}
labelStart := pos + 2
labelEnd := parseLinkLabel(s, pos+1, false)
if labelEnd < 0 {
return false
}
var href, title, label string
oldPos := pos
pos = labelEnd + 1
if pos < max && src[pos] == '(' {
pos++
for pos < max {
b := src[pos]
if !byteIsSpace(b) && b != '\n' {
break
}
pos++
}
if pos >= max {
return false
}
start := pos
url, _, endpos, ok := parseLinkDestination(src, pos, s.PosMax)
if ok {
url = normalizeLink(url)
if validateLink(url) {
href = url
pos = endpos
}
}
start = pos
for pos < max {
b := src[pos]
if !byteIsSpace(b) && b != '\n' {
break
}
pos++
}
if pos >= max {
return false
}
title, _, endpos, ok = parseLinkTitle(src, pos, s.PosMax)
if pos < max && start != pos && ok {
pos = endpos
for pos < max {
b := src[pos]
if !byteIsSpace(b) && b != '\n' {
break
}
pos++
}
}
if pos >= max || src[pos] != ')' {
s.Pos = oldPos
return false
}
pos++
} else {
if s.Env.References == nil {
return false
}
if pos < max && src[pos] == '[' {
start := pos + 1
pos = parseLinkLabel(s, pos, false)
if pos >= 0 {
label = src[start:pos]
pos++
} else {
pos = labelEnd + 1
}
} else {
pos = labelEnd + 1
}
if label == "" {
label = src[labelStart:labelEnd]
}
ref, ok := s.Env.References[normalizeReference(label)]
if !ok {
s.Pos = oldPos
return false
}
href = ref["href"]
title = ref["title"]
}
if !silent {
content := src[labelStart:labelEnd]
tokens := s.Md.Inline.Parse(content, s.Md, s.Env)
s.PushToken(&Image{
Src: href,
Title: title,
Tokens: tokens,
})
}
s.Pos = pos
s.PosMax = max
return true
}

View File

@ -1,13 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleInline(s *StateCore) {
for _, tok := range s.Tokens {
if tok, ok := tok.(*Inline); ok {
tok.Children = s.Md.Inline.Parse(tok.Content, s.Md, s.Env)
}
}
}

View File

@ -1,80 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
func ruleLHeading(s *StateBlock, startLine, endLine int, silent bool) bool {
nextLine := startLine + 1
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
oldParentType := s.ParentType
s.ParentType = ptParagraph
src := s.Src
var pos int
var hLevel int
outer:
for ; nextLine < endLine && !s.IsLineEmpty(nextLine); nextLine++ {
if s.SCount[nextLine]-s.BlkIndent > 3 {
continue
}
if s.SCount[nextLine] >= s.BlkIndent {
pos = s.BMarks[nextLine] + s.TShift[nextLine]
max := s.EMarks[nextLine]
if pos < max {
marker := src[pos]
if marker == '-' || marker == '=' {
pos = s.SkipBytes(pos, marker)
pos = s.SkipSpaces(pos)
if pos >= max {
hLevel = 1
if marker == '-' {
hLevel++
}
break
}
}
}
}
if s.SCount[nextLine] < 0 {
continue
}
for _, r := range paragraphTerminatedBy {
if r(s, nextLine, endLine, true) {
break outer
}
}
}
if hLevel == 0 {
return false
}
s.Line = nextLine + 1
s.PushOpeningToken(&HeadingOpen{
HLevel: hLevel,
Map: [2]int{startLine, s.Line},
})
s.PushToken(&Inline{
Content: strings.TrimSpace(s.Lines(startLine, nextLine, s.BlkIndent, false)),
Map: [2]int{startLine, s.Line - 1},
})
s.PushClosingToken(&HeadingClose{HLevel: hLevel})
s.ParentType = oldParentType
return true
}

View File

@ -1,132 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleLink(s *StateInline, silent bool) bool {
pos := s.Pos
oldPos := s.Pos
max := s.PosMax
start := s.Pos
parseReference := true
src := s.Src
if src[pos] != '[' {
return false
}
labelStart := pos + 1
labelEnd := parseLinkLabel(s, pos, true)
if labelEnd < 0 {
return false
}
pos = labelEnd + 1
var title, href, label string
if pos < max && src[pos] == '(' {
parseReference = false
pos++
for pos < max {
code := src[pos]
if !byteIsSpace(code) && code != '\n' {
break
}
pos++
}
if pos >= max {
return false
}
start = pos
url, _, endpos, ok := parseLinkDestination(src, pos, s.PosMax)
if ok {
url = normalizeLink(url)
if validateLink(url) {
pos = endpos
href = url
}
}
start = pos
for pos < max {
code := src[pos]
if !byteIsSpace(code) && code != '\n' {
break
}
pos++
}
title, _, endpos, ok = parseLinkTitle(src, pos, s.PosMax)
if pos < max && start != pos && ok {
pos = endpos
for pos < max {
code := src[pos]
if !byteIsSpace(code) && code != '\n' {
break
}
pos++
}
}
if pos >= max || src[pos] != ')' {
parseReference = true
}
pos++
}
if parseReference {
if s.Env.References == nil {
return false
}
if pos < max && src[pos] == '[' {
start := pos + 1
pos = parseLinkLabel(s, pos, false)
if pos >= 0 {
label = src[start:pos]
pos++
} else {
pos = labelEnd + 1
}
} else {
pos = labelEnd + 1
}
if label == "" {
label = src[labelStart:labelEnd]
}
ref, ok := s.Env.References[normalizeReference(label)]
if !ok {
s.Pos = oldPos
return false
}
href = ref["href"]
title = ref["title"]
}
if !silent {
s.Pos = labelStart
s.PosMax = labelEnd
s.PushOpeningToken(&LinkOpen{
Href: href,
Title: title,
})
s.Md.Inline.Tokenize(s)
s.PushClosingToken(&LinkClose{})
}
s.Pos = pos
s.PosMax = max
return true
}

View File

@ -1,131 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"strings"
"gitlab.com/golang-commonmark/linkify"
)
func isLinkOpen(s string) bool { return isLetter(s[1]) }
func isLinkClose(s string) bool { return s[1] == '/' }
func ruleLinkify(s *StateCore) {
blockTokens := s.Tokens
if !s.Md.Linkify {
return
}
for _, tok := range blockTokens {
if tok, ok := tok.(*Inline); ok {
tokens := tok.Children
htmlLinkLevel := 0
for i := len(tokens) - 1; i >= 0; i-- {
currentTok := tokens[i]
if _, ok := currentTok.(*LinkClose); ok {
i--
for tokens[i].Level() != currentTok.Level() {
if _, ok := tokens[i].(*LinkOpen); ok {
break
}
i--
}
continue
}
if currentTok, ok := currentTok.(*HTMLInline); ok {
if isLinkOpen(currentTok.Content) && htmlLinkLevel > 0 {
htmlLinkLevel--
}
if isLinkClose(currentTok.Content) {
htmlLinkLevel++
}
}
if htmlLinkLevel > 0 {
continue
}
if currentTok, ok := currentTok.(*Text); ok {
text := currentTok.Content
links := linkify.Links(text)
if len(links) == 0 {
continue
}
var nodes []Token
level := currentTok.Lvl
lastPos := 0
for _, ln := range links {
urlText := text[ln.Start:ln.End]
url := urlText
if ln.Scheme == "" {
url = "http://" + url
} else if ln.Scheme == "mailto:" && !strings.HasPrefix(url, "mailto:") {
url = "mailto:" + url
}
url = normalizeLink(url)
if !validateLink(url) {
continue
}
if ln.Scheme == "" {
urlText = strings.TrimPrefix(normalizeLinkText("http://"+urlText), "http://")
} else if ln.Scheme == "mailto:" && !strings.HasPrefix(urlText, "mailto:") {
urlText = strings.TrimPrefix(normalizeLinkText("mailto:"+urlText), "mailto:")
} else {
urlText = normalizeLinkText(urlText)
}
pos := ln.Start
if pos > lastPos {
tok := Text{
Content: text[lastPos:pos],
Lvl: level,
}
nodes = append(nodes, &tok)
}
nodes = append(nodes, &LinkOpen{
Href: url,
Lvl: level,
})
nodes = append(nodes, &Text{
Content: urlText,
Lvl: level + 1,
})
nodes = append(nodes, &LinkClose{
Lvl: level,
})
lastPos = ln.End
}
if lastPos < len(text) {
tok := Text{
Content: text[lastPos:],
Lvl: level,
}
nodes = append(nodes, &tok)
}
children := make([]Token, len(tokens)+len(nodes)-1)
copy(children, tokens[:i])
copy(children[i:], nodes)
copy(children[i+len(nodes):], tokens[i+1:])
tok.Children = children
tokens = children
}
}
}
}
}

View File

@ -1,285 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strconv"
var listTerminatedBy []BlockRule
func skipBulletListMarker(s *StateBlock, startLine int) int {
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
src := s.Src
if pos >= max {
return -1
}
marker := src[pos]
if marker != '*' && marker != '-' && marker != '+' {
return -1
}
pos++
if pos < max && !byteIsSpace(src[pos]) {
return -1
}
return pos
}
func skipOrderedListMarker(s *StateBlock, startLine int) int {
start := s.BMarks[startLine] + s.TShift[startLine]
pos := start
max := s.EMarks[startLine]
if pos+1 >= max {
return -1
}
src := s.Src
ch := src[pos]
if ch < '0' || ch > '9' {
return -1
}
pos++
for {
if pos >= max {
return -1
}
ch = src[pos]
pos++
if ch >= '0' && ch <= '9' {
if pos-start >= 10 {
return -1
}
continue
}
if ch == ')' || ch == '.' {
break
}
return -1
}
if pos < max && !byteIsSpace(src[pos]) {
return -1
}
return pos
}
func markParagraphsTight(s *StateBlock, idx int) {
level := s.Level + 2
tokens := s.Tokens
for i := idx + 2; i < len(tokens)-2; i++ {
if tokens[i].Level() == level {
if tok, ok := tokens[i].(*ParagraphOpen); ok {
tok.Hidden = true
i += 2
tokens[i].(*ParagraphClose).Hidden = true
}
}
}
}
func ruleList(s *StateBlock, startLine, endLine int, silent bool) bool {
isTerminatingParagraph := false
tight := true
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
src := s.Src
if silent && s.ParentType == ptParagraph {
if s.TShift[startLine] >= s.BlkIndent {
isTerminatingParagraph = true
}
}
var start int
var markerValue int
isOrdered := false
posAfterMarker := skipOrderedListMarker(s, startLine)
if posAfterMarker > 0 {
isOrdered = true
start = s.BMarks[startLine] + s.TShift[startLine]
markerValue, _ = strconv.Atoi(src[start : posAfterMarker-1])
if isTerminatingParagraph && markerValue != 1 {
return false
}
} else {
posAfterMarker = skipBulletListMarker(s, startLine)
if posAfterMarker < 0 {
return false
}
}
if isTerminatingParagraph {
if s.SkipSpaces(posAfterMarker) >= s.EMarks[startLine] {
return false
}
}
markerChar := src[posAfterMarker-1]
if silent {
return true
}
tokenIdx := len(s.Tokens)
var listMap *[2]int
if isOrdered {
tok := &OrderedListOpen{
Order: markerValue,
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
listMap = &tok.Map
} else {
tok := &BulletListOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
listMap = &tok.Map
}
nextLine := startLine
prevEmptyEnd := false
oldParentType := s.ParentType
s.ParentType = ptList
var pos int
var contentStart int
outer:
for nextLine < endLine {
pos = posAfterMarker
max := s.EMarks[nextLine]
initial := s.SCount[nextLine] + posAfterMarker - (s.BMarks[startLine] + s.TShift[startLine])
offset := initial
loop:
for pos < max {
switch src[pos] {
case '\t':
offset += 4 - (offset+s.BSCount[nextLine])%4
case ' ':
offset++
default:
break loop
}
pos++
}
contentStart = pos
indentAfterMarker := 1
if contentStart < max {
if iam := offset - initial; iam <= 4 {
indentAfterMarker = iam
}
}
indent := initial + indentAfterMarker
tok := &ListItemOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tok)
itemMap := &tok.Map
oldIndent := s.BlkIndent
oldTight := s.Tight
oldTShift := s.TShift[startLine]
oldLIndent := s.SCount[startLine]
s.BlkIndent = indent
s.Tight = true
s.TShift[startLine] = contentStart - s.BMarks[startLine]
s.SCount[startLine] = offset
if contentStart >= max && s.IsLineEmpty(startLine+1) {
s.Line = min(s.Line+2, endLine)
} else {
s.Md.Block.Tokenize(s, startLine, endLine)
}
if !s.Tight || prevEmptyEnd {
tight = false
}
prevEmptyEnd = s.Line-startLine > 1 && s.IsLineEmpty(s.Line-1)
s.BlkIndent = oldIndent
s.TShift[startLine] = oldTShift
s.SCount[startLine] = oldLIndent
s.Tight = oldTight
s.PushClosingToken(&ListItemClose{})
startLine = s.Line
nextLine = startLine
(*itemMap)[1] = nextLine
if nextLine >= endLine {
break
}
contentStart = s.BMarks[startLine]
if s.SCount[nextLine] < s.BlkIndent {
break
}
for _, r := range listTerminatedBy {
if r(s, nextLine, endLine, true) {
break outer
}
}
if isOrdered {
posAfterMarker = skipOrderedListMarker(s, nextLine)
if posAfterMarker < 0 {
break
}
} else {
posAfterMarker = skipBulletListMarker(s, nextLine)
if posAfterMarker < 0 {
break
}
}
if markerChar != src[posAfterMarker-1] {
break
}
}
if isOrdered {
s.PushClosingToken(&OrderedListClose{})
} else {
s.PushClosingToken(&BulletListClose{})
}
(*listMap)[1] = nextLine
s.Line = nextLine
s.ParentType = oldParentType
if tight {
markParagraphsTight(s, tokenIdx)
}
return true
}

View File

@ -1,112 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package markdown provides CommonMark-compliant markdown parser and renderer.
package markdown
import (
"bytes"
"io"
)
type Markdown struct {
options
Block ParserBlock
Inline ParserInline
renderOptions RenderOptions
}
type RenderOptions struct {
LangPrefix string // CSS language class prefix for fenced blocks
XHTML bool // render as XHTML instead of HTML
Breaks bool // convert \n in paragraphs into <br>
Nofollow bool // add rel="nofollow" to the links
}
type options struct {
HTML bool // allow raw HTML in the markup
Tables bool // GFM tables
Linkify bool // autoconvert URL-like text to links
Typographer bool // enable some typographic replacements
Quotes [4]string // double/single quotes replacement pairs
MaxNesting int // maximum nesting level
}
type Environment struct {
References map[string]map[string]string
}
type CoreRule func(*StateCore)
var coreRules []CoreRule
func New(opts ...option) *Markdown {
m := &Markdown{
options: options{
Tables: true,
Linkify: true,
Typographer: true,
Quotes: [4]string{"“", "”", "", ""},
MaxNesting: 20,
},
renderOptions: RenderOptions{LangPrefix: "language-"},
}
for _, opt := range opts {
opt(m)
}
return m
}
func (m *Markdown) Parse(src []byte) []Token {
if len(src) == 0 {
return nil
}
s := &StateCore{
Md: m,
Env: &Environment{},
}
s.Tokens = m.Block.Parse(src, m, s.Env)
for _, r := range coreRules {
r(s)
}
return s.Tokens
}
func (m *Markdown) Render(w io.Writer, src []byte) error {
if len(src) == 0 {
return nil
}
return NewRenderer(w).Render(m.Parse(src), m.renderOptions)
}
func (m *Markdown) RenderTokens(w io.Writer, tokens []Token) error {
if len(tokens) == 0 {
return nil
}
return NewRenderer(w).Render(tokens, m.renderOptions)
}
func (m *Markdown) RenderToString(src []byte) string {
if len(src) == 0 {
return ""
}
var buf bytes.Buffer
NewRenderer(&buf).Render(m.Parse(src), m.renderOptions)
return buf.String()
}
func (m *Markdown) RenderTokensToString(tokens []Token) string {
if len(tokens) == 0 {
return ""
}
var buf bytes.Buffer
NewRenderer(&buf).Render(tokens, m.renderOptions)
return buf.String()
}

View File

@ -1,46 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleNewline(s *StateInline, silent bool) bool {
pos := s.Pos
src := s.Src
if src[pos] != '\n' {
return false
}
pending := s.Pending.Bytes()
pmax := len(pending) - 1
max := s.PosMax
if !silent {
if pmax >= 0 && pending[pmax] == ' ' {
if pmax >= 1 && pending[pmax-1] == ' ' {
pmax -= 2
for pmax >= 0 && pending[pmax] == ' ' {
pmax--
}
s.Pending.Truncate(pmax + 1)
s.PushToken(&Hardbreak{})
} else {
s.Pending.Truncate(pmax)
s.PushToken(&Softbreak{})
}
} else {
s.PushToken(&Softbreak{})
}
}
pos++
for pos < max && byteIsSpace(src[pos]) {
pos++
}
s.Pos = pos
return true
}

View File

@ -1,77 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
type option func(m *Markdown)
func HTML(b bool) option {
return func(m *Markdown) {
m.HTML = b
}
}
func Linkify(b bool) option {
return func(m *Markdown) {
m.Linkify = b
}
}
func Typographer(b bool) option {
return func(m *Markdown) {
m.Typographer = b
}
}
func Quotes(stringOrArray interface{}) option {
if s, ok := stringOrArray.(string); ok {
return func(m *Markdown) {
for i, r := range []rune(s) {
m.Quotes[i] = string(r)
}
}
}
a := stringOrArray.([]string)
return func(m *Markdown) {
for i, s := range a {
m.Quotes[i] = s
}
}
}
func MaxNesting(n int) option {
return func(m *Markdown) {
m.MaxNesting = n
}
}
func XHTMLOutput(b bool) option {
return func(m *Markdown) {
m.renderOptions.XHTML = b
}
}
func Breaks(b bool) option {
return func(m *Markdown) {
m.renderOptions.Breaks = b
}
}
func LangPrefix(p string) option {
return func(m *Markdown) {
m.renderOptions.LangPrefix = p
}
}
func Nofollow(b bool) option {
return func(m *Markdown) {
m.renderOptions.Nofollow = b
}
}
func Tables(b bool) option {
return func(m *Markdown) {
m.Tables = b
}
}

View File

@ -1,51 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
var paragraphTerminatedBy []BlockRule
func ruleParagraph(s *StateBlock, startLine, _ int, _ bool) bool {
nextLine := startLine + 1
endLine := s.LineMax
oldParentType := s.ParentType
s.ParentType = ptParagraph
outer:
for ; nextLine < endLine && !s.IsLineEmpty(nextLine); nextLine++ {
if s.SCount[nextLine]-s.BlkIndent > 3 {
continue
}
if s.SCount[nextLine] < 0 {
continue
}
for _, r := range paragraphTerminatedBy {
if r(s, nextLine, endLine, true) {
break outer
}
}
}
content := strings.TrimSpace(s.Lines(startLine, nextLine, s.BlkIndent, false))
s.Line = nextLine
s.PushOpeningToken(&ParagraphOpen{
Map: [2]int{startLine, s.Line},
})
s.PushToken(&Inline{
Content: content,
Map: [2]int{startLine, s.Line},
})
s.PushClosingToken(&ParagraphClose{})
s.ParentType = oldParentType
return true
}

View File

@ -1,159 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"bytes"
"unicode/utf8"
)
type ParserBlock struct{}
type BlockRule func(*StateBlock, int, int, bool) bool
var blockRules []BlockRule
var nl = []byte{'\n'}
func normalizeNewlines(src []byte) ([]byte, int) {
if bytes.IndexByte(src, '\r') == -1 {
return src, bytes.Count(src, nl)
}
n := 0
buf := make([]byte, 0, len(src))
for i := 0; i < len(src); i++ {
switch ch := src[i]; ch {
case '\n':
n++
buf = append(buf, '\n')
case '\r':
buf = append(buf, '\n')
n++
if i < len(src)-1 && src[i+1] == '\n' {
i++
}
default:
buf = append(buf, ch)
}
}
return buf, n
}
func (b ParserBlock) Parse(src []byte, md *Markdown, env *Environment) []Token {
src, n := normalizeNewlines(src)
if len(src) == 0 || src[len(src)-1] != '\n' {
n++
}
n++
indentFound := false
start := 0
indent := 0
offset := 0
mem := make([]int, 0, n*5)
bMarks := mem[0:0:n]
eMarks := mem[n : n : n*2]
tShift := mem[n*2 : n*2 : n*3]
sCount := mem[n*3 : n*3 : n*4]
bsCount := mem[n*4 : n*4 : n*5]
_, lastRuneLen := utf8.DecodeLastRune(src)
lastRunePos := len(src) - lastRuneLen
for pos, r := range string(src) {
if !indentFound {
if runeIsSpace(r) {
indent++
if r == '\t' {
offset += 4 - offset%4
} else {
offset++
}
continue
}
indentFound = true
}
if r == '\n' || pos == lastRunePos {
if r != '\n' {
pos = len(src)
}
bMarks = append(bMarks, start)
eMarks = append(eMarks, pos)
tShift = append(tShift, indent)
sCount = append(sCount, offset)
bsCount = append(bsCount, 0)
indentFound = false
indent = 0
offset = 0
start = pos + 1
}
}
bMarks = append(bMarks, len(src))
eMarks = append(eMarks, len(src))
tShift = append(tShift, 0)
sCount = append(sCount, 0)
bsCount = append(bsCount, 0)
var s StateBlock
s.BMarks = bMarks
s.EMarks = eMarks
s.TShift = tShift
s.SCount = sCount
s.BSCount = bsCount
s.LineMax = n - 1
s.Src = string(src)
s.Md = md
s.Env = env
b.Tokenize(&s, s.Line, s.LineMax)
return s.Tokens
}
func (ParserBlock) Tokenize(s *StateBlock, startLine, endLine int) {
line := startLine
hasEmptyLines := false
maxNesting := s.Md.MaxNesting
for line < endLine {
line = s.SkipEmptyLines(line)
s.Line = line
if line >= endLine {
break
}
if s.SCount[line] < s.BlkIndent {
break
}
if s.Level >= maxNesting {
s.Line = endLine
break
}
for _, r := range blockRules {
if r(s, line, endLine, false) {
break
}
}
s.Tight = !hasEmptyLines
if s.IsLineEmpty(s.Line - 1) {
hasEmptyLines = true
}
line = s.Line
if line < endLine && s.IsLineEmpty(line) {
hasEmptyLines = true
line++
s.Line = line
}
}
}

View File

@ -1,106 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "unicode/utf8"
type ParserInline struct {
}
type (
InlineRule func(*StateInline, bool) bool
PostprocessRule func(*StateInline)
)
var (
inlineRules []InlineRule
postprocessRules []PostprocessRule
)
func (i ParserInline) Parse(src string, md *Markdown, env *Environment) []Token {
if src == "" {
return nil
}
var s StateInline
s.Src = src
s.Md = md
s.Env = env
s.PosMax = len(src)
s.Tokens = s.bootstrap[:0]
i.Tokenize(&s)
for _, r := range postprocessRules {
r(&s)
}
return s.Tokens
}
func (ParserInline) Tokenize(s *StateInline) {
end := s.PosMax
src := s.Src
maxNesting := s.Md.MaxNesting
ok := false
for s.Pos < end {
if s.Level < maxNesting {
for _, rule := range inlineRules {
ok = rule(s, false)
if ok {
break
}
}
}
if ok {
if s.Pos >= end {
break
}
continue
}
r, size := utf8.DecodeRuneInString(src[s.Pos:])
s.Pending.WriteRune(r)
s.Pos += size
}
if s.Pending.Len() > 0 {
s.PushPending()
}
}
func (ParserInline) SkipToken(s *StateInline) {
pos := s.Pos
if s.Cache != nil {
if newPos, ok := s.Cache[pos]; ok {
s.Pos = newPos
return
}
} else {
s.Cache = make(map[int]int)
}
ok := false
if s.Level < s.Md.MaxNesting {
for _, r := range inlineRules {
s.Level++
ok = r(s, true)
s.Level--
if ok {
break
}
}
} else {
s.Pos = s.PosMax
}
if !ok {
_, size := utf8.DecodeRuneInString(s.Src[s.Pos:])
s.Pos += size
}
s.Cache[pos] = s.Pos
}

View File

@ -1,158 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "sort"
type registeredCoreRule struct {
id int
rule CoreRule
}
var registeredCoreRules []registeredCoreRule
type registeredBlockRule struct {
id int
rule BlockRule
terminates []int
}
var registeredBlockRules []registeredBlockRule
type registeredInlineRule struct {
id int
rule InlineRule
}
var registeredInlineRules []registeredInlineRule
type registeredPostprocessRule struct {
id int
rule PostprocessRule
}
var registeredPostprocessRules []registeredPostprocessRule
func indexInt(a []int, n int) int {
for i, m := range a {
if m == n {
return i
}
}
return -1
}
func RegisterCoreRule(id int, rule CoreRule) {
registeredCoreRules = append(registeredCoreRules, registeredCoreRule{
id: id,
rule: rule,
})
sort.Slice(registeredCoreRules, func(i, j int) bool {
return registeredCoreRules[i].id < registeredCoreRules[j].id
})
coreRules = coreRules[:0]
for _, r := range registeredCoreRules {
coreRules = append(coreRules, r.rule)
}
}
func RegisterBlockRule(id int, rule BlockRule, terminates []int) {
registeredBlockRules = append(registeredBlockRules, registeredBlockRule{
id: id,
rule: rule,
terminates: terminates,
})
sort.Slice(registeredBlockRules, func(i, j int) bool {
return registeredBlockRules[i].id < registeredBlockRules[j].id
})
blockRules = blockRules[:0]
blockquoteTerminatedBy = blockquoteTerminatedBy[:0]
listTerminatedBy = listTerminatedBy[:0]
referenceTerminatedBy = referenceTerminatedBy[:0]
paragraphTerminatedBy = paragraphTerminatedBy[:0]
for _, r := range registeredBlockRules {
blockRules = append(blockRules, r.rule)
if indexInt(r.terminates, 400) != -1 {
blockquoteTerminatedBy = append(blockquoteTerminatedBy, r.rule)
}
if indexInt(r.terminates, 600) != -1 {
listTerminatedBy = append(listTerminatedBy, r.rule)
}
if indexInt(r.terminates, 700) != -1 {
referenceTerminatedBy = append(referenceTerminatedBy, r.rule)
}
if indexInt(r.terminates, 1100) != -1 {
paragraphTerminatedBy = append(paragraphTerminatedBy, r.rule)
}
}
}
func RegisterInlineRule(id int, rule InlineRule) {
registeredInlineRules = append(registeredInlineRules, registeredInlineRule{
id: id,
rule: rule,
})
sort.Slice(registeredInlineRules, func(i, j int) bool {
return registeredInlineRules[i].id < registeredInlineRules[j].id
})
inlineRules = inlineRules[:0]
for _, r := range registeredInlineRules {
inlineRules = append(inlineRules, r.rule)
}
}
func RegisterPostprocessRule(id int, rule PostprocessRule) {
registeredPostprocessRules = append(registeredPostprocessRules, registeredPostprocessRule{
id: id,
rule: rule,
})
sort.Slice(registeredPostprocessRules, func(i, j int) bool {
return registeredPostprocessRules[i].id < registeredPostprocessRules[j].id
})
postprocessRules = postprocessRules[:0]
for _, r := range registeredPostprocessRules {
postprocessRules = append(postprocessRules, r.rule)
}
}
func init() {
RegisterCoreRule(100, ruleInline)
RegisterCoreRule(200, ruleLinkify)
RegisterCoreRule(300, ruleReplacements)
RegisterCoreRule(400, ruleSmartQuotes)
RegisterBlockRule(100, ruleTable, []int{1100, 700})
RegisterBlockRule(200, ruleCode, nil)
RegisterBlockRule(300, ruleFence, []int{1100, 700, 400, 600})
RegisterBlockRule(400, ruleBlockQuote, []int{1100, 700, 400, 600})
RegisterBlockRule(500, ruleHR, []int{1100, 700, 400, 600})
RegisterBlockRule(600, ruleList, []int{1100, 700, 400})
RegisterBlockRule(700, ruleReference, nil)
RegisterBlockRule(800, ruleHeading, []int{1100, 700, 400})
RegisterBlockRule(900, ruleLHeading, nil)
RegisterBlockRule(1000, ruleHTMLBlock, []int{1100, 700, 400})
RegisterBlockRule(1100, ruleParagraph, nil)
RegisterInlineRule(100, ruleText)
RegisterInlineRule(200, ruleNewline)
RegisterInlineRule(300, ruleEscape)
RegisterInlineRule(400, ruleBackticks)
RegisterInlineRule(500, ruleStrikeThrough)
RegisterInlineRule(600, ruleEmphasis)
RegisterInlineRule(700, ruleLink)
RegisterInlineRule(800, ruleImage)
RegisterInlineRule(900, ruleAutolink)
RegisterInlineRule(1000, ruleHTMLInline)
RegisterInlineRule(1100, ruleEntity)
RegisterPostprocessRule(100, ruleBalancePairs)
RegisterPostprocessRule(200, ruleStrikethroughPostprocess)
RegisterPostprocessRule(300, ruleEmphasisPostprocess)
RegisterPostprocessRule(400, ruleTextCollapse)
}

View File

@ -1,173 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
var referenceTerminatedBy []BlockRule
func ruleReference(s *StateBlock, startLine, _ int, silent bool) bool {
lines := 0
pos := s.BMarks[startLine] + s.TShift[startLine]
max := s.EMarks[startLine]
nextLine := startLine + 1
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
src := s.Src
if src[pos] != '[' {
return false
}
pos++
for pos < max {
if src[pos] == ']' && src[pos-1] != '\\' {
if pos+1 == max {
return false
}
if src[pos+1] != ':' {
return false
}
break
}
pos++
}
endLine := s.LineMax
oldParentType := s.ParentType
s.ParentType = ptReference
outer:
for ; nextLine < endLine && !s.IsLineEmpty(nextLine); nextLine++ {
if s.SCount[nextLine]-s.BlkIndent > 3 {
continue
}
if s.SCount[nextLine] < 0 {
continue
}
for _, r := range referenceTerminatedBy {
if r(s, nextLine, endLine, true) {
break outer
}
}
}
str := strings.TrimSpace(s.Lines(startLine, nextLine, s.BlkIndent, false))
max = len(str)
var labelEnd int
for pos = 1; pos < max; pos++ {
b := str[pos]
if b == '[' {
return false
} else if b == ']' {
labelEnd = pos
break
} else if b == '\n' {
lines++
} else if b == '\\' {
pos++
if pos < max && str[pos] == '\n' {
lines++
}
}
}
if labelEnd <= 0 || labelEnd+1 >= max || str[labelEnd+1] != ':' {
return false
}
for pos = labelEnd + 2; pos < max; pos++ {
b := str[pos]
if b == '\n' {
lines++
} else if !byteIsSpace(b) {
break
}
}
href, nlines, endpos, ok := parseLinkDestination(str, pos, max)
if !ok {
return false
}
href = normalizeLink(href)
if !validateLink(href) {
return false
}
pos = endpos
lines += nlines
destEndPos := pos
destEndLineNo := lines
start := pos
for ; pos < max; pos++ {
b := str[pos]
if b == '\n' {
lines++
} else if !byteIsSpace(b) {
break
}
}
title, nlines, endpos, ok := parseLinkTitle(str, pos, max)
if pos < max && start != pos && ok {
pos = endpos
lines += nlines
} else {
pos = destEndPos
lines = destEndLineNo
}
for pos < max && byteIsSpace(str[pos]) {
pos++
}
if pos < max && str[pos] != '\n' {
if title != "" {
title = ""
pos = destEndPos
lines = destEndLineNo
for pos < max && byteIsSpace(src[pos]) {
pos++
}
}
}
if pos < max && str[pos] != '\n' {
return false
}
label := normalizeReference(str[1:labelEnd])
if label == "" {
return false
}
if silent {
return true
}
if s.Env.References == nil {
s.Env.References = make(map[string]map[string]string)
}
if _, ok := s.Env.References[label]; !ok {
s.Env.References[label] = map[string]string{
"title": title,
"href": href,
}
}
s.ParentType = oldParentType
s.Line = startLine + lines + 1
return true
}

View File

@ -1,333 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"io"
"regexp"
"strconv"
"strings"
"gitlab.com/golang-commonmark/html"
)
type Renderer struct {
w *monadicWriter
}
func NewRenderer(w io.Writer) *Renderer {
return &Renderer{newMonadicWriter(w)}
}
func (r *Renderer) Render(tokens []Token, options RenderOptions) error {
for i, tok := range tokens {
if tok, ok := tok.(*Inline); ok {
r.renderInline(tok.Children, options)
} else {
r.renderToken(tokens, i, options)
}
}
r.w.Flush()
return r.w.err
}
func (r *Renderer) renderInline(tokens []Token, o RenderOptions) {
for i := range tokens {
r.renderToken(tokens, i, o)
}
}
func (r *Renderer) renderInlineAsText(tokens []Token) {
for _, tok := range tokens {
if text, ok := tok.(*Text); ok {
html.WriteEscapedString(r.w, text.Content)
} else if img, ok := tok.(*Image); ok {
r.renderInlineAsText(img.Tokens)
}
}
}
var rNotSpace = regexp.MustCompile(`^\S+`)
func (r *Renderer) renderToken(tokens []Token, idx int, options RenderOptions) {
tok := tokens[idx]
if idx > 0 && tok.Block() && !tok.Closing() {
switch t := tokens[idx-1].(type) {
case *ParagraphOpen:
if t.Hidden {
r.w.WriteByte('\n')
}
case *ParagraphClose:
if t.Hidden {
r.w.WriteByte('\n')
}
}
}
switch tok := tok.(type) {
case *BlockquoteClose:
r.w.WriteString("</blockquote>")
case *BlockquoteOpen:
r.w.WriteString("<blockquote>")
case *BulletListClose:
r.w.WriteString("</ul>")
case *BulletListOpen:
r.w.WriteString("<ul>")
case *CodeBlock:
r.w.WriteString("<pre><code>")
html.WriteEscapedString(r.w, tok.Content)
r.w.WriteString("</code></pre>")
case *CodeInline:
r.w.WriteString("<code>")
html.WriteEscapedString(r.w, tok.Content)
r.w.WriteString("</code>")
case *EmphasisClose:
r.w.WriteString("</em>")
case *EmphasisOpen:
r.w.WriteString("<em>")
case *Fence:
r.w.WriteString("<pre><code")
if tok.Params != "" {
langName := strings.SplitN(unescapeAll(tok.Params), " ", 2)[0]
langName = rNotSpace.FindString(langName)
if langName != "" {
r.w.WriteString(` class="`)
r.w.WriteString(options.LangPrefix)
html.WriteEscapedString(r.w, langName)
r.w.WriteByte('"')
}
}
r.w.WriteByte('>')
html.WriteEscapedString(r.w, tok.Content)
r.w.WriteString("</code></pre>")
case *Hardbreak:
if options.XHTML {
r.w.WriteString("<br />\n")
} else {
r.w.WriteString("<br>\n")
}
case *HeadingClose:
r.w.WriteString("</h")
r.w.WriteByte("0123456789"[tok.HLevel])
r.w.WriteString(">")
case *HeadingOpen:
r.w.WriteString("<h")
r.w.WriteByte("0123456789"[tok.HLevel])
r.w.WriteByte('>')
case *Hr:
if options.XHTML {
r.w.WriteString("<hr />")
} else {
r.w.WriteString("<hr>")
}
case *HTMLBlock:
r.w.WriteString(tok.Content)
return // no newline
case *HTMLInline:
r.w.WriteString(tok.Content)
case *Image:
r.w.WriteString(`<img src="`)
html.WriteEscapedString(r.w, tok.Src)
r.w.WriteString(`" alt="`)
r.renderInlineAsText(tok.Tokens)
r.w.WriteByte('"')
if tok.Title != "" {
r.w.WriteString(` title="`)
html.WriteEscapedString(r.w, tok.Title)
r.w.WriteByte('"')
}
if options.XHTML {
r.w.WriteString(" />")
} else {
r.w.WriteByte('>')
}
case *LinkClose:
r.w.WriteString("</a>")
case *LinkOpen:
r.w.WriteString(`<a href="`)
html.WriteEscapedString(r.w, tok.Href)
r.w.WriteByte('"')
if tok.Title != "" {
r.w.WriteString(` title="`)
html.WriteEscapedString(r.w, (tok.Title))
r.w.WriteByte('"')
}
if tok.Target != "" {
r.w.WriteString(` target="`)
html.WriteEscapedString(r.w, tok.Target)
r.w.WriteByte('"')
}
if options.Nofollow {
r.w.WriteString(` rel="nofollow"`)
}
r.w.WriteByte('>')
case *ListItemClose:
r.w.WriteString("</li>")
case *ListItemOpen:
r.w.WriteString("<li>")
case *OrderedListClose:
r.w.WriteString("</ol>")
case *OrderedListOpen:
if tok.Order != 1 {
r.w.WriteString(`<ol start="`)
r.w.WriteString(strconv.Itoa(tok.Order))
r.w.WriteString(`">`)
} else {
r.w.WriteString("<ol>")
}
case *ParagraphClose:
if tok.Hidden {
return
}
if !tok.Tight {
r.w.WriteString("</p>")
} else if tokens[idx+1].Closing() {
return // no newline
}
case *ParagraphOpen:
if tok.Hidden {
return
}
if !tok.Tight {
r.w.WriteString("<p>")
}
case *Softbreak:
if options.Breaks {
if options.XHTML {
r.w.WriteString("<br />\n")
} else {
r.w.WriteString("<br>\n")
}
} else {
r.w.WriteByte('\n')
}
return
case *StrongClose:
r.w.WriteString("</strong>")
case *StrongOpen:
r.w.WriteString("<strong>")
case *StrikethroughClose:
r.w.WriteString("</s>")
case *StrikethroughOpen:
r.w.WriteString("<s>")
case *TableClose:
r.w.WriteString("</table>")
case *TableOpen:
r.w.WriteString("<table>")
case *TbodyClose:
r.w.WriteString("</tbody>")
case *TbodyOpen:
r.w.WriteString("<tbody>")
case *TdClose:
r.w.WriteString("</td>")
case *TdOpen:
if tok.Align != AlignNone {
r.w.WriteString(`<td style="text-align:`)
r.w.WriteString(tok.Align.String())
r.w.WriteString(`">`)
} else {
r.w.WriteString("<td>")
}
case *Text:
html.WriteEscapedString(r.w, tok.Content)
case *TheadClose:
r.w.WriteString("</thead>")
case *TheadOpen:
r.w.WriteString("<thead>")
case *ThClose:
r.w.WriteString("</th>")
case *ThOpen:
if align := tok.Align; align != AlignNone {
r.w.WriteString(`<th style="text-align:`)
r.w.WriteString(align.String())
r.w.WriteString(`">`)
} else {
r.w.WriteString("<th>")
}
case *TrClose:
r.w.WriteString("</tr>")
case *TrOpen:
r.w.WriteString("<tr>")
default:
panic("unknown token type")
}
needLf := false
if tok.Block() {
needLf = true
if tok.Opening() {
nextTok := tokens[idx+1]
blockquote := false
switch nextTok := nextTok.(type) {
case *Inline:
needLf = false
case *ParagraphOpen:
if nextTok.Tight || nextTok.Hidden {
needLf = false
}
case *ParagraphClose:
if nextTok.Tight || nextTok.Hidden {
needLf = false
}
case *BlockquoteClose:
blockquote = true
}
if !blockquote && needLf && nextTok.Closing() && nextTok.Tag() == tok.Tag() {
needLf = false
}
}
}
if needLf {
r.w.WriteByte('\n')
}
}

View File

@ -1,229 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
func exclquest(b byte) bool {
return b == '!' || b == '?'
}
func byteToLower(b byte) byte {
if b >= 'A' && b <= 'Z' {
return b - 'A' + 'a'
}
return b
}
var replChar = [256]bool{
'(': true,
'!': true,
'+': true,
',': true,
'-': true,
'.': true,
'?': true,
}
func performReplacements(s string) string {
var ss []string
start := 0
for i := 0; i < len(s); i++ {
b := s[i]
if replChar[b] {
outer:
switch b {
case '(':
if i+2 >= len(s) {
break
}
b2 := s[i+1]
b2 = byteToLower(b2)
switch b2 {
case 'c', 'r', 'p':
if s[i+2] != ')' {
break outer
}
switch b2 {
case 'c':
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "©")
case 'r':
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "®")
case 'p':
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "§")
}
i += 2
start = i + 1
continue
case 't':
if i+3 >= len(s) {
break outer
}
if s[i+3] != ')' || byteToLower(s[i+2]) != 'm' {
break outer
}
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "™")
i += 3
start = i + 1
continue
default:
break outer
}
case '+':
if i+1 >= len(s) || s[i+1] != '-' {
break
}
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "±")
i++
start = i + 1
continue
case '.':
if i+1 >= len(s) || s[i+1] != '.' {
break
}
j := i + 2
for j < len(s) && s[j] == '.' {
j++
}
if start < i {
ss = append(ss, s[start:i])
}
if i == 0 || !(s[i-1] == '?' || s[i-1] == '!') {
ss = append(ss, "…")
} else {
ss = append(ss, "..")
}
i = j - 1
start = i + 1
continue
case '?', '!':
if i+3 >= len(s) {
break
}
if !(exclquest(s[i+1]) && exclquest(s[i+2]) && exclquest(s[i+3])) {
break
}
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, s[i:i+3])
j := i + 3
for j < len(s) && exclquest(s[j]) {
j++
}
i = j - 1
start = i + 1
continue
case ',':
if i+1 >= len(s) || s[i+1] != ',' {
break
}
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, ",")
j := i + 2
for j < len(s) && s[j] == ',' {
j++
}
i = j - 1
start = i + 1
continue
case '-':
if i+1 >= len(s) || s[i+1] != '-' {
break
}
if i+2 >= len(s) || s[i+2] != '-' {
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "")
i++
start = i + 1
continue
}
if i+3 >= len(s) || s[i+3] != '-' {
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, "—")
i += 2
start = i + 1
continue
}
j := i + 3
for j < len(s) && s[j] == '-' {
j++
}
if start < i {
ss = append(ss, s[start:i])
}
ss = append(ss, s[i:j])
i = j - 1
start = i + 1
continue
}
}
}
if ss == nil {
return s
}
if start < len(s) {
ss = append(ss, s[start:])
}
return strings.Join(ss, "")
}
func ruleReplacements(s *StateCore) {
if !s.Md.Typographer {
return
}
insideLink := false
for _, tok := range s.Tokens {
if tok, ok := tok.(*Inline); ok {
for _, itok := range tok.Children {
switch itok := itok.(type) {
case *LinkOpen:
insideLink = true
case *LinkClose:
insideLink = false
case *Text:
if !insideLink {
itok.Content = performReplacements(itok.Content)
}
}
}
}
}
}

View File

@ -1,256 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"strings"
"unicode"
"unicode/utf8"
)
func nextQuoteIndex(s []rune, from int) int {
for i := from; i < len(s); i++ {
r := s[i]
if r == '\'' || r == '"' {
return i
}
}
return -1
}
func firstRune(s string) rune {
for _, r := range s {
return r
}
return utf8.RuneError
}
func replaceQuotes(tokens []Token, s *StateCore) {
type stackItem struct {
token int
text []rune
pos int
single bool
level int
}
var stack []stackItem
var changed map[int][]rune
for i, tok := range tokens {
thisLevel := tok.Level()
j := len(stack) - 1
for j >= 0 {
if stack[j].level <= thisLevel {
break
}
j--
}
stack = stack[:j+1]
tok, ok := tok.(*Text)
if !ok || !strings.ContainsAny(tok.Content, `"'`) {
continue
}
text := []rune(tok.Content)
pos := 0
max := len(text)
loop:
for pos < max {
index := nextQuoteIndex(text, pos)
if index < 0 {
break
}
canOpen := true
canClose := true
pos = index + 1
isSingle := text[index] == '\''
lastChar := ' '
if index-1 > 0 {
lastChar = text[index-1]
} else {
loop1:
for j := i - 1; j >= 0; j-- {
switch tok := tokens[j].(type) {
case *Softbreak:
break loop1
case *Hardbreak:
break loop1
case *Text:
lastChar, _ = utf8.DecodeLastRuneInString(tok.Content)
break loop1
default:
continue
}
}
}
nextChar := ' '
if pos < max {
nextChar = text[pos]
} else {
loop2:
for j := i + 1; j < len(tokens); j++ {
switch tok := tokens[j].(type) {
case *Softbreak:
break loop2
case *Hardbreak:
break loop2
case *Text:
nextChar, _ = utf8.DecodeRuneInString(tok.Content)
break loop2
default:
continue
}
}
}
isLastPunct := isMdAsciiPunct(lastChar) || unicode.IsPunct(lastChar)
isNextPunct := isMdAsciiPunct(nextChar) || unicode.IsPunct(nextChar)
isLastWhiteSpace := unicode.IsSpace(lastChar)
isNextWhiteSpace := unicode.IsSpace(nextChar)
if isNextWhiteSpace {
canOpen = false
} else if isNextPunct {
if !(isLastWhiteSpace || isLastPunct) {
canOpen = false
}
}
if isLastWhiteSpace {
canClose = false
} else if isLastPunct {
if !(isNextWhiteSpace || isNextPunct) {
canClose = false
}
}
if nextChar == '"' && text[index] == '"' {
if lastChar >= '0' && lastChar <= '9' {
canClose = false
canOpen = false
}
}
if canOpen && canClose {
canOpen = false
canClose = isNextPunct
}
if !canOpen && !canClose {
if isSingle {
text[index] = ''
if changed == nil {
changed = make(map[int][]rune)
}
changed[i] = text
}
continue
}
if canClose {
for j := len(stack) - 1; j >= 0; j-- {
item := stack[j]
if item.level < thisLevel {
break
}
if item.single == isSingle && item.level == thisLevel {
if changed == nil {
changed = make(map[int][]rune)
}
var q1, q2 string
if isSingle {
q1 = s.Md.options.Quotes[2]
q2 = s.Md.options.Quotes[3]
} else {
q1 = s.Md.options.Quotes[0]
q2 = s.Md.options.Quotes[1]
}
if utf8.RuneCountInString(q1) == 1 && utf8.RuneCountInString(q2) == 1 {
item.text[item.pos] = firstRune(q1)
text[index] = firstRune(q2)
} else if tok == tokens[item.token] {
newText := make([]rune, 0, len(text)-2+len(q1)+len(q2))
newText = append(newText, text[:item.pos]...)
newText = append(newText, []rune(q1)...)
newText = append(newText, text[item.pos+1:index]...)
newText = append(newText, []rune(q2)...)
newText = append(newText, text[index+1:]...)
text = newText
item.text = newText
} else {
newText := make([]rune, 0, len(item.text)-1+len(q1))
newText = append(newText, item.text[:item.pos]...)
newText = append(newText, []rune(q1)...)
newText = append(newText, item.text[item.pos+1:]...)
item.text = newText
newText = make([]rune, 0, len(text)-1+len(q2))
newText = append(newText, text[:index]...)
newText = append(newText, []rune(q2)...)
newText = append(newText, text[index+1:]...)
text = newText
}
max = len(text)
if changed == nil {
changed = make(map[int][]rune)
}
changed[i] = text
changed[item.token] = item.text
stack = stack[:j]
continue loop
}
}
}
if canOpen {
stack = append(stack, stackItem{
token: i,
text: text,
pos: index,
single: isSingle,
level: thisLevel,
})
} else if canClose && isSingle {
text[index] = ''
if changed == nil {
changed = make(map[int][]rune)
}
changed[i] = text
}
}
}
if changed != nil {
for i, text := range changed {
tokens[i].(*Text).Content = string(text)
}
}
}
func ruleSmartQuotes(s *StateCore) {
if !s.Md.Typographer {
return
}
tokens := s.Tokens
for i := len(tokens) - 1; i >= 0; i-- {
tok := tokens[i]
if tok, ok := tok.(*Inline); ok {
replaceQuotes(tok.Children, s)
}
}
}

View File

@ -1,141 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import "strings"
const (
ptRoot = iota
ptList
ptBlockQuote
ptParagraph
ptReference
)
type StateBlock struct {
StateCore
BMarks []int // offsets of the line beginnings
EMarks []int // offsets of the line endings
TShift []int // indents for each line
SCount []int
BSCount []int
BlkIndent int // required block content indent (in a list etc.)
Line int // line index in the source string
LineMax int // number of lines
Tight bool // loose or tight mode for lists
ParentType byte // parent block type
Level int
}
func (s *StateBlock) IsLineEmpty(n int) bool {
return s.BMarks[n]+s.TShift[n] >= s.EMarks[n]
}
func (s *StateBlock) SkipEmptyLines(from int) int {
for from < s.LineMax && s.IsLineEmpty(from) {
from++
}
return from
}
func (s *StateBlock) SkipSpaces(pos int) int {
src := s.Src
for pos < len(src) && byteIsSpace(src[pos]) {
pos++
}
return pos
}
func (s *StateBlock) SkipBytes(pos int, b byte) int {
src := s.Src
for pos < len(src) && src[pos] == b {
pos++
}
return pos
}
func (s *StateBlock) SkipBytesBack(pos int, b byte, min int) int {
for pos > min {
pos--
if s.Src[pos] != b {
return pos + 1
}
}
return pos
}
func (s *StateBlock) SkipSpacesBack(pos int, min int) int {
for pos > min {
pos--
if !byteIsSpace(s.Src[pos]) {
return pos + 1
}
}
return pos
}
func (s *StateBlock) Lines(begin, end, indent int, keepLastLf bool) string {
if begin == end {
return ""
}
src := s.Src
queue := make([]string, end-begin)
for i, line := 0, begin; line < end; i, line = i+1, line+1 {
lineIndent := 0
lineStart := s.BMarks[line]
first := lineStart
last := s.EMarks[line]
if (line+1 < end || keepLastLf) && last < len(src) {
last++
}
for first < last && lineIndent < indent {
ch := src[first]
if byteIsSpace(ch) {
if ch == '\t' {
lineIndent += 4 - (lineIndent+s.BSCount[line])%4
} else {
lineIndent++
}
} else if first-lineStart < s.TShift[line] {
lineIndent++
} else {
break
}
first++
}
if lineIndent > indent {
queue[i] = strings.Repeat(" ", lineIndent-indent) + src[first:last]
} else {
queue[i] = src[first:last]
}
}
return strings.Join(queue, "")
}
func (s *StateBlock) PushToken(tok Token) {
tok.SetLevel(s.Level)
s.Tokens = append(s.Tokens, tok)
}
func (s *StateBlock) PushOpeningToken(tok Token) {
tok.SetLevel(s.Level)
s.Level++
s.Tokens = append(s.Tokens, tok)
}
func (s *StateBlock) PushClosingToken(tok Token) {
s.Level--
tok.SetLevel(s.Level)
s.Tokens = append(s.Tokens, tok)
}

View File

@ -1,13 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
type StateCore struct {
Src string
Tokens []Token
bootstrap [3]Token
Md *Markdown
Env *Environment
}

View File

@ -1,116 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"bytes"
"unicode"
"unicode/utf8"
)
type StateInline struct {
StateCore
Pos int
PosMax int
Level int
Pending bytes.Buffer
PendingLevel int
Delimiters []Delimiter
Cache map[int]int
}
func (s *StateInline) PushToken(tok Token) {
if s.Pending.Len() > 0 {
s.PushPending()
}
tok.SetLevel(s.Level)
s.PendingLevel = s.Level
s.Tokens = append(s.Tokens, tok)
}
func (s *StateInline) PushOpeningToken(tok Token) {
if s.Pending.Len() > 0 {
s.PushPending()
}
tok.SetLevel(s.Level)
s.Level++
s.PendingLevel = s.Level
s.Tokens = append(s.Tokens, tok)
}
func (s *StateInline) PushClosingToken(tok Token) {
if s.Pending.Len() > 0 {
s.PushPending()
}
s.Level--
tok.SetLevel(s.Level)
s.PendingLevel = s.Level
s.Tokens = append(s.Tokens, tok)
}
func (s *StateInline) PushPending() {
s.Tokens = append(s.Tokens, &Text{
Content: s.Pending.String(),
Lvl: s.PendingLevel,
})
s.Pending.Reset()
}
func (s *StateInline) scanDelims(start int, canSplitWord bool) (canOpen bool, canClose bool, length int) {
pos := start
max := s.PosMax
src := s.Src
marker := src[start]
leftFlanking, rightFlanking := true, true
lastChar := ' '
if start > 0 {
lastChar, _ = utf8.DecodeLastRuneInString(src[:start])
}
for pos < max && src[pos] == marker {
pos++
}
length = pos - start
nextChar := ' '
if pos < max {
nextChar, _ = utf8.DecodeRuneInString(src[pos:])
}
isLastPunct := isMdAsciiPunct(lastChar) || unicode.IsPunct(lastChar)
isNextPunct := isMdAsciiPunct(nextChar) || unicode.IsPunct(nextChar)
isLastWhiteSpace := unicode.IsSpace(lastChar)
isNextWhiteSpace := unicode.IsSpace(nextChar)
if isNextWhiteSpace {
leftFlanking = false
} else if isNextPunct {
if !(isLastWhiteSpace || isLastPunct) {
leftFlanking = false
}
}
if isLastWhiteSpace {
rightFlanking = false
} else if isLastPunct {
if !(isNextWhiteSpace || isNextPunct) {
rightFlanking = false
}
}
if !canSplitWord {
canOpen = leftFlanking && (!rightFlanking || isLastPunct)
canClose = rightFlanking && (!leftFlanking || isNextPunct)
} else {
canOpen = leftFlanking
canClose = rightFlanking
}
return
}

View File

@ -1,101 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleStrikeThrough(s *StateInline, silent bool) bool {
src := s.Src
start := s.Pos
marker := src[start]
if silent {
return false
}
if src[start] != '~' {
return false
}
canOpen, canClose, length := s.scanDelims(start, true)
origLength := length
ch := string(marker)
if length < 2 {
return false
}
if length%2 != 0 {
s.PushToken(&Text{
Content: ch,
})
length--
}
for i := 0; i < length; i += 2 {
s.PushToken(&Text{
Content: ch + ch,
})
s.Delimiters = append(s.Delimiters, Delimiter{
Marker: marker,
Length: -1,
Jump: i,
Token: len(s.Tokens) - 1,
Level: s.Level,
End: -1,
Open: canOpen,
Close: canClose,
})
}
s.Pos += origLength
return true
}
func ruleStrikethroughPostprocess(s *StateInline) {
var loneMarkers []int
delimiters := s.Delimiters
max := len(delimiters)
for i := 0; i < max; i++ {
startDelim := delimiters[i]
if startDelim.Marker != '~' {
continue
}
if startDelim.End == -1 {
continue
}
endDelim := delimiters[startDelim.End]
s.Tokens[startDelim.Token] = &StrikethroughOpen{}
s.Tokens[endDelim.Token] = &StrikethroughClose{}
if text, ok := s.Tokens[endDelim.Token-1].(*Text); ok && text.Content == "~" {
loneMarkers = append(loneMarkers, endDelim.Token-1)
}
}
for len(loneMarkers) > 0 {
i := loneMarkers[len(loneMarkers)-1]
loneMarkers = loneMarkers[:len(loneMarkers)-1]
j := i + 1
for j < len(s.Tokens) {
if _, ok := s.Tokens[j].(*StrikethroughClose); !ok {
break
}
j++
}
j--
if i != j {
s.Tokens[i], s.Tokens[j] = s.Tokens[j], s.Tokens[i]
}
}
}

View File

@ -1,229 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
import (
"regexp"
"strings"
)
func getLine(s *StateBlock, line int) string {
pos := s.BMarks[line] + s.BlkIndent
max := s.EMarks[line]
if pos >= max {
return ""
}
return s.Src[pos:max]
}
func escapedSplit(s string) (result []string) {
pos := 0
escapes := 0
lastPos := 0
backTicked := false
lastBackTick := 0
for pos < len(s) {
ch := s[pos]
if ch == '`' {
if backTicked {
backTicked = false
lastBackTick = pos
} else if escapes%2 == 0 {
backTicked = true
lastBackTick = pos
}
} else if ch == '|' && (escapes%2 == 0) && !backTicked {
result = append(result, s[lastPos:pos])
lastPos = pos + 1
}
if ch == '\\' {
escapes++
} else {
escapes = 0
}
pos++
if pos == len(s) && backTicked {
backTicked = false
pos = lastBackTick + 1
}
}
return append(result, s[lastPos:])
}
var rColumn = regexp.MustCompile("^:?-+:?$")
func ruleTable(s *StateBlock, startLine, endLine int, silent bool) bool {
if !s.Md.Tables {
return false
}
if startLine+2 > endLine {
return false
}
nextLine := startLine + 1
if s.SCount[nextLine] < s.BlkIndent {
return false
}
if s.SCount[nextLine]-s.BlkIndent >= 4 {
return false
}
pos := s.BMarks[nextLine] + s.TShift[nextLine]
if pos >= s.EMarks[nextLine] {
return false
}
src := s.Src
ch := src[pos]
pos++
if ch != '|' && ch != '-' && ch != ':' {
return false
}
for pos < s.EMarks[nextLine] {
ch = src[pos]
if ch != '|' && ch != '-' && ch != ':' && !byteIsSpace(ch) {
return false
}
pos++
}
//
lineText := getLine(s, startLine+1)
columns := strings.Split(lineText, "|")
var aligns []Align
for i := 0; i < len(columns); i++ {
t := strings.TrimSpace(columns[i])
if t == "" {
if i == 0 || i == len(columns)-1 {
continue
}
return false
}
if !rColumn.MatchString(t) {
return false
}
if t[len(t)-1] == ':' {
if t[0] == ':' {
aligns = append(aligns, AlignCenter)
} else {
aligns = append(aligns, AlignRight)
}
} else if t[0] == ':' {
aligns = append(aligns, AlignLeft)
} else {
aligns = append(aligns, AlignNone)
}
}
lineText = strings.TrimSpace(getLine(s, startLine))
if strings.IndexByte(lineText, '|') == -1 {
return false
}
if s.SCount[startLine]-s.BlkIndent >= 4 {
return false
}
columns = escapedSplit(strings.TrimSuffix(strings.TrimPrefix(lineText, "|"), "|"))
columnCount := len(columns)
if columnCount > len(aligns) {
return false
}
if silent {
return true
}
tableTok := &TableOpen{
Map: [2]int{startLine, 0},
}
s.PushOpeningToken(tableTok)
s.PushOpeningToken(&TheadOpen{
Map: [2]int{startLine, startLine + 1},
})
s.PushOpeningToken(&TrOpen{
Map: [2]int{startLine, startLine + 1},
})
for i := 0; i < len(columns); i++ {
s.PushOpeningToken(&ThOpen{
Align: aligns[i],
Map: [2]int{startLine, startLine + 1},
})
s.PushToken(&Inline{
Content: strings.TrimSpace(columns[i]),
Map: [2]int{startLine, startLine + 1},
})
s.PushClosingToken(&ThClose{})
}
s.PushClosingToken(&TrClose{})
s.PushClosingToken(&TheadClose{})
tbodyTok := &TbodyOpen{
Map: [2]int{startLine + 2, 0},
}
s.PushOpeningToken(tbodyTok)
for nextLine = startLine + 2; nextLine < endLine; nextLine++ {
if s.SCount[nextLine] < s.BlkIndent {
break
}
lineText = strings.TrimSpace(getLine(s, nextLine))
if strings.IndexByte(lineText, '|') == -1 {
break
}
if s.SCount[nextLine]-s.BlkIndent >= 4 {
break
}
columns = escapedSplit(strings.TrimPrefix(strings.TrimSuffix(lineText, "|"), "|"))
if len(columns) < len(aligns) {
columns = append(columns, make([]string, len(aligns)-len(columns))...)
} else if len(columns) > len(aligns) {
columns = columns[:len(aligns)]
}
s.PushOpeningToken(&TrOpen{})
for i := 0; i < columnCount; i++ {
tdOpen := TdOpen{}
if i < len(aligns) {
tdOpen.Align = aligns[i]
}
s.PushOpeningToken(&tdOpen)
inline := Inline{}
if i < len(columns) {
inline.Content = strings.TrimSpace(columns[i])
}
s.PushToken(&inline)
s.PushClosingToken(&TdClose{})
}
s.PushClosingToken(&TrClose{})
}
s.PushClosingToken(&TbodyClose{})
s.PushClosingToken(&TableClose{})
tableTok.Map[1] = nextLine
tbodyTok.Map[1] = nextLine
s.Line = nextLine
return true
}

View File

@ -1,108 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func isHeaderLine(s string) bool {
if s == "" {
return false
}
st := 0
n := 0
for i := 0; i < len(s); i++ {
b := s[i]
switch st {
case 0: // initial state
switch b {
case '|':
st = 1
case ':':
st = 2
case '-':
st = 3
n++
case ' ':
break
default:
return false
}
case 1: // |
switch b {
case ' ':
break
case ':':
st = 2
case '-':
st = 3
n++
default:
return false
}
case 2: // |:
switch b {
case ' ':
break
case '-':
st = 3
n++
default:
return false
}
case 3: // |:-
switch b {
case '-':
break
case ':':
st = 4
case '|':
st = 5
case ' ':
st = 6
default:
return false
}
case 4: // |:---:
switch b {
case ' ':
break
case '|':
st = 5
default:
return false
}
case 5: // |:---:|
switch b {
case ' ':
break
case ':':
st = 2
case '-':
st = 3
n++
default:
return false
}
case 6: // |:--- SPACE
switch b {
case ' ':
break
case ':':
st = 4
case '|':
st = 5
default:
return false
}
}
}
return n >= 1
}

View File

@ -1,52 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
var terminatorCharTable = [256]bool{
'\n': true,
'!': true,
'#': true,
'$': true,
'%': true,
'&': true,
'*': true,
'+': true,
'-': true,
':': true,
'<': true,
'=': true,
'>': true,
'@': true,
'[': true,
'\\': true,
']': true,
'^': true,
'_': true,
'`': true,
'{': true,
'}': true,
'~': true,
}
func ruleText(s *StateInline, silent bool) bool {
pos := s.Pos
max := s.PosMax
src := s.Src
for pos < max && !terminatorCharTable[src[pos]] {
pos++
}
if pos == s.Pos {
return false
}
if !silent {
s.Pending.WriteString(src[s.Pos:pos])
}
s.Pos = pos
return true
}

View File

@ -1,42 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
func ruleTextCollapse(s *StateInline) {
level := 0
tokens := s.Tokens
max := len(tokens)
curr := 0
last := 0
for ; curr < max; curr++ {
tok := tokens[curr]
if tok.Opening() {
level++
} else if tok.Closing() {
level--
}
tok.SetLevel(level)
if text, ok := tok.(*Text); ok && curr+1 < max {
if text2, ok := tokens[curr+1].(*Text); ok {
text2.Content = text.Content + text2.Content
continue
}
}
if curr != last {
tokens[last] = tokens[curr]
}
last++
}
if curr != last {
tokens = tokens[:last]
}
s.Tokens = tokens
}

View File

@ -1,753 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
type Token interface {
Tag() string
Opening() bool
Closing() bool
Block() bool
Level() int
SetLevel(lvl int)
}
type BlockquoteOpen struct {
Map [2]int
Lvl int
}
type BlockquoteClose struct {
Lvl int
}
type BulletListOpen struct {
Map [2]int
Lvl int
}
type BulletListClose struct {
Lvl int
}
type OrderedListOpen struct {
Order int
Map [2]int
Lvl int
}
type OrderedListClose struct {
Lvl int
}
type ListItemOpen struct {
Map [2]int
Lvl int
}
type ListItemClose struct {
Lvl int
}
type CodeBlock struct {
Content string
Map [2]int
Lvl int
}
type CodeInline struct {
Content string
Lvl int
}
type EmphasisOpen struct {
Lvl int
}
type EmphasisClose struct {
Lvl int
}
type StrongOpen struct {
Lvl int
}
type StrongClose struct {
Lvl int
}
type StrikethroughOpen struct {
Lvl int
}
type StrikethroughClose struct {
Lvl int
}
type Fence struct {
Params string
Content string
Map [2]int
Lvl int
}
type Softbreak struct {
Lvl int
}
type Hardbreak struct {
Lvl int
}
type HeadingOpen struct {
HLevel int
Map [2]int
Lvl int
}
type HeadingClose struct {
HLevel int
Lvl int
}
type HTMLBlock struct {
Content string
Map [2]int
Lvl int
}
type HTMLInline struct {
Content string
Lvl int
}
type Hr struct {
Map [2]int
Lvl int
}
type Image struct {
Src string
Title string
Tokens []Token
Lvl int
}
type Inline struct {
Content string
Map [2]int
Children []Token
Lvl int
}
type LinkOpen struct {
Href string
Title string
Target string
Lvl int
}
type LinkClose struct {
Lvl int
}
type ParagraphOpen struct {
Tight bool
Hidden bool
Map [2]int
Lvl int
}
type ParagraphClose struct {
Tight bool
Hidden bool
Lvl int
}
type TableOpen struct {
Map [2]int
Lvl int
}
type TableClose struct {
Lvl int
}
type TheadOpen struct {
Map [2]int
Lvl int
}
type TheadClose struct {
Lvl int
}
type TrOpen struct {
Map [2]int
Lvl int
}
type TrClose struct {
Lvl int
}
type ThOpen struct {
Align Align
Map [2]int
Lvl int
}
type ThClose struct {
Lvl int
}
type TbodyOpen struct {
Map [2]int
Lvl int
}
type TbodyClose struct {
Lvl int
}
type TdOpen struct {
Align Align
Map [2]int
Lvl int
}
type TdClose struct {
Lvl int
}
type Text struct {
Content string
Lvl int
}
var htags = []string{
"",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
}
func (t *BlockquoteOpen) Level() int { return t.Lvl }
func (t *BlockquoteClose) Level() int { return t.Lvl }
func (t *BulletListOpen) Level() int { return t.Lvl }
func (t *BulletListClose) Level() int { return t.Lvl }
func (t *OrderedListOpen) Level() int { return t.Lvl }
func (t *OrderedListClose) Level() int { return t.Lvl }
func (t *ListItemOpen) Level() int { return t.Lvl }
func (t *ListItemClose) Level() int { return t.Lvl }
func (t *CodeBlock) Level() int { return t.Lvl }
func (t *CodeInline) Level() int { return t.Lvl }
func (t *EmphasisOpen) Level() int { return t.Lvl }
func (t *EmphasisClose) Level() int { return t.Lvl }
func (t *StrongOpen) Level() int { return t.Lvl }
func (t *StrongClose) Level() int { return t.Lvl }
func (t *StrikethroughOpen) Level() int { return t.Lvl }
func (t *StrikethroughClose) Level() int { return t.Lvl }
func (t *Fence) Level() int { return t.Lvl }
func (t *Softbreak) Level() int { return t.Lvl }
func (t *Hardbreak) Level() int { return t.Lvl }
func (t *HeadingOpen) Level() int { return t.Lvl }
func (t *HeadingClose) Level() int { return t.Lvl }
func (t *HTMLBlock) Level() int { return t.Lvl }
func (t *HTMLInline) Level() int { return t.Lvl }
func (t *Hr) Level() int { return t.Lvl }
func (t *Image) Level() int { return t.Lvl }
func (t *Inline) Level() int { return t.Lvl }
func (t *LinkOpen) Level() int { return t.Lvl }
func (t *LinkClose) Level() int { return t.Lvl }
func (t *ParagraphOpen) Level() int { return t.Lvl }
func (t *ParagraphClose) Level() int { return t.Lvl }
func (t *TableOpen) Level() int { return t.Lvl }
func (t *TableClose) Level() int { return t.Lvl }
func (t *TheadOpen) Level() int { return t.Lvl }
func (t *TheadClose) Level() int { return t.Lvl }
func (t *TrOpen) Level() int { return t.Lvl }
func (t *TrClose) Level() int { return t.Lvl }
func (t *ThOpen) Level() int { return t.Lvl }
func (t *ThClose) Level() int { return t.Lvl }
func (t *TbodyOpen) Level() int { return t.Lvl }
func (t *TbodyClose) Level() int { return t.Lvl }
func (t *TdOpen) Level() int { return t.Lvl }
func (t *TdClose) Level() int { return t.Lvl }
func (t *Text) Level() int { return t.Lvl }
func (t *BlockquoteOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *BlockquoteClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *BulletListOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *BulletListClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *OrderedListOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *OrderedListClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *ListItemOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *ListItemClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *CodeBlock) SetLevel(lvl int) { t.Lvl = lvl }
func (t *CodeInline) SetLevel(lvl int) { t.Lvl = lvl }
func (t *EmphasisOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *EmphasisClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *StrongOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *StrongClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *StrikethroughOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *StrikethroughClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Fence) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Softbreak) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Hardbreak) SetLevel(lvl int) { t.Lvl = lvl }
func (t *HeadingOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *HeadingClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *HTMLBlock) SetLevel(lvl int) { t.Lvl = lvl }
func (t *HTMLInline) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Hr) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Image) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Inline) SetLevel(lvl int) { t.Lvl = lvl }
func (t *LinkOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *LinkClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *ParagraphOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *ParagraphClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TableOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TableClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TheadOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TheadClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TrOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TrClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *ThOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *ThClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TbodyOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TbodyClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TdOpen) SetLevel(lvl int) { t.Lvl = lvl }
func (t *TdClose) SetLevel(lvl int) { t.Lvl = lvl }
func (t *Text) SetLevel(lvl int) { t.Lvl = lvl }
func (t *BlockquoteOpen) Opening() bool { return true }
func (t *BlockquoteClose) Opening() bool { return false }
func (t *BulletListOpen) Opening() bool { return true }
func (t *BulletListClose) Opening() bool { return false }
func (t *OrderedListOpen) Opening() bool { return true }
func (t *OrderedListClose) Opening() bool { return false }
func (t *ListItemOpen) Opening() bool { return true }
func (t *ListItemClose) Opening() bool { return false }
func (t *CodeBlock) Opening() bool { return false }
func (t *CodeInline) Opening() bool { return false }
func (t *EmphasisOpen) Opening() bool { return true }
func (t *EmphasisClose) Opening() bool { return false }
func (t *StrongOpen) Opening() bool { return true }
func (t *StrongClose) Opening() bool { return false }
func (t *StrikethroughOpen) Opening() bool { return true }
func (t *StrikethroughClose) Opening() bool { return false }
func (t *Fence) Opening() bool { return false }
func (t *Softbreak) Opening() bool { return false }
func (t *Hardbreak) Opening() bool { return false }
func (t *HeadingOpen) Opening() bool { return true }
func (t *HeadingClose) Opening() bool { return false }
func (t *HTMLBlock) Opening() bool { return false }
func (t *HTMLInline) Opening() bool { return false }
func (t *Hr) Opening() bool { return false }
func (t *Image) Opening() bool { return false }
func (t *Inline) Opening() bool { return false }
func (t *LinkOpen) Opening() bool { return true }
func (t *LinkClose) Opening() bool { return false }
func (t *ParagraphOpen) Opening() bool { return true }
func (t *ParagraphClose) Opening() bool { return false }
func (t *TableOpen) Opening() bool { return true }
func (t *TableClose) Opening() bool { return false }
func (t *TheadOpen) Opening() bool { return true }
func (t *TheadClose) Opening() bool { return false }
func (t *TrOpen) Opening() bool { return true }
func (t *TrClose) Opening() bool { return false }
func (t *ThOpen) Opening() bool { return true }
func (t *ThClose) Opening() bool { return false }
func (t *TbodyOpen) Opening() bool { return true }
func (t *TbodyClose) Opening() bool { return false }
func (t *TdOpen) Opening() bool { return true }
func (t *TdClose) Opening() bool { return false }
func (t *Text) Opening() bool { return false }
func (t *BlockquoteOpen) Closing() bool { return false }
func (t *BlockquoteClose) Closing() bool { return true }
func (t *BulletListOpen) Closing() bool { return false }
func (t *BulletListClose) Closing() bool { return true }
func (t *OrderedListOpen) Closing() bool { return false }
func (t *OrderedListClose) Closing() bool { return true }
func (t *ListItemOpen) Closing() bool { return false }
func (t *ListItemClose) Closing() bool { return true }
func (t *CodeBlock) Closing() bool { return false }
func (t *CodeInline) Closing() bool { return false }
func (t *EmphasisOpen) Closing() bool { return false }
func (t *EmphasisClose) Closing() bool { return true }
func (t *StrongOpen) Closing() bool { return false }
func (t *StrongClose) Closing() bool { return true }
func (t *StrikethroughOpen) Closing() bool { return false }
func (t *StrikethroughClose) Closing() bool { return true }
func (t *Fence) Closing() bool { return false }
func (t *Softbreak) Closing() bool { return false }
func (t *Hardbreak) Closing() bool { return false }
func (t *HeadingOpen) Closing() bool { return false }
func (t *HeadingClose) Closing() bool { return true }
func (t *HTMLBlock) Closing() bool { return false }
func (t *HTMLInline) Closing() bool { return false }
func (t *Hr) Closing() bool { return false }
func (t *Image) Closing() bool { return false }
func (t *Inline) Closing() bool { return false }
func (t *LinkOpen) Closing() bool { return false }
func (t *LinkClose) Closing() bool { return true }
func (t *ParagraphOpen) Closing() bool { return false }
func (t *ParagraphClose) Closing() bool { return true }
func (t *TableOpen) Closing() bool { return false }
func (t *TableClose) Closing() bool { return true }
func (t *TheadOpen) Closing() bool { return false }
func (t *TheadClose) Closing() bool { return true }
func (t *TrOpen) Closing() bool { return false }
func (t *TrClose) Closing() bool { return true }
func (t *ThOpen) Closing() bool { return false }
func (t *ThClose) Closing() bool { return true }
func (t *TbodyOpen) Closing() bool { return false }
func (t *TbodyClose) Closing() bool { return true }
func (t *TdOpen) Closing() bool { return false }
func (t *TdClose) Closing() bool { return true }
func (t *Text) Closing() bool { return false }
func (t *BlockquoteOpen) Block() bool { return true }
func (t *BlockquoteClose) Block() bool { return true }
func (t *BulletListOpen) Block() bool { return true }
func (t *BulletListClose) Block() bool { return true }
func (t *OrderedListOpen) Block() bool { return true }
func (t *OrderedListClose) Block() bool { return true }
func (t *ListItemOpen) Block() bool { return true }
func (t *ListItemClose) Block() bool { return true }
func (t *CodeBlock) Block() bool { return true }
func (t *CodeInline) Block() bool { return false }
func (t *EmphasisOpen) Block() bool { return false }
func (t *EmphasisClose) Block() bool { return false }
func (t *StrongOpen) Block() bool { return false }
func (t *StrongClose) Block() bool { return false }
func (t *StrikethroughOpen) Block() bool { return false }
func (t *StrikethroughClose) Block() bool { return false }
func (t *Fence) Block() bool { return true }
func (t *Softbreak) Block() bool { return false }
func (t *Hardbreak) Block() bool { return false }
func (t *HeadingOpen) Block() bool { return true }
func (t *HeadingClose) Block() bool { return true }
func (t *HTMLBlock) Block() bool { return true }
func (t *HTMLInline) Block() bool { return false }
func (t *Hr) Block() bool { return true }
func (t *Image) Block() bool { return false }
func (t *Inline) Block() bool { return false }
func (t *LinkOpen) Block() bool { return false }
func (t *LinkClose) Block() bool { return false }
func (t *ParagraphOpen) Block() bool { return true }
func (t *ParagraphClose) Block() bool { return true }
func (t *TableOpen) Block() bool { return true }
func (t *TableClose) Block() bool { return true }
func (t *TheadOpen) Block() bool { return true }
func (t *TheadClose) Block() bool { return true }
func (t *TrOpen) Block() bool { return true }
func (t *TrClose) Block() bool { return true }
func (t *ThOpen) Block() bool { return true }
func (t *ThClose) Block() bool { return true }
func (t *TbodyOpen) Block() bool { return true }
func (t *TbodyClose) Block() bool { return true }
func (t *TdOpen) Block() bool { return true }
func (t *TdClose) Block() bool { return true }
func (t *Text) Block() bool { return false }
func (t *BlockquoteOpen) Tag() string { return "blockquote" }
func (t *BlockquoteClose) Tag() string { return "blockquote" }
func (t *BulletListOpen) Tag() string { return "ul" }
func (t *BulletListClose) Tag() string { return "ul" }
func (t *OrderedListOpen) Tag() string { return "ol" }
func (t *OrderedListClose) Tag() string { return "ol" }
func (t *ListItemOpen) Tag() string { return "li" }
func (t *ListItemClose) Tag() string { return "li" }
func (t *CodeBlock) Tag() string { return "code" }
func (t *CodeInline) Tag() string { return "code" }
func (t *EmphasisOpen) Tag() string { return "em" }
func (t *EmphasisClose) Tag() string { return "em" }
func (t *StrongOpen) Tag() string { return "strong" }
func (t *StrongClose) Tag() string { return "strong" }
func (t *StrikethroughOpen) Tag() string { return "s" }
func (t *StrikethroughClose) Tag() string { return "s" }
func (t *Fence) Tag() string { return "code" }
func (t *Softbreak) Tag() string { return "br" }
func (t *Hardbreak) Tag() string { return "br" }
func (t *HeadingOpen) Tag() string { return htags[t.HLevel] }
func (t *HeadingClose) Tag() string { return htags[t.HLevel] }
func (t *HTMLBlock) Tag() string { return "" }
func (t *HTMLInline) Tag() string { return "" }
func (t *Hr) Tag() string { return "hr" }
func (t *Image) Tag() string { return "img" }
func (t *Inline) Tag() string { return "" }
func (t *LinkOpen) Tag() string { return "a" }
func (t *LinkClose) Tag() string { return "a" }
func (t *ParagraphOpen) Tag() string { return "p" }
func (t *ParagraphClose) Tag() string { return "p" }
func (t *TableOpen) Tag() string { return "table" }
func (t *TableClose) Tag() string { return "table" }
func (t *TheadOpen) Tag() string { return "thead" }
func (t *TheadClose) Tag() string { return "thead" }
func (t *TrOpen) Tag() string { return "tr" }
func (t *TrClose) Tag() string { return "tr" }
func (t *ThOpen) Tag() string { return "th" }
func (t *ThClose) Tag() string { return "th" }
func (t *TbodyOpen) Tag() string { return "tbody" }
func (t *TbodyClose) Tag() string { return "tbody" }
func (t *TdOpen) Tag() string { return "td" }
func (t *TdClose) Tag() string { return "td" }
func (t *Text) Tag() string { return "" }

View File

@ -1,185 +0,0 @@
// Copyright 2015 The Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package markdown
var urlSchemas = []string{
"aaa",
"aaas",
"about",
"acap",
"adiumxtra",
"afp",
"afs",
"aim",
"apt",
"attachment",
"aw",
"beshare",
"bitcoin",
"bolo",
"callto",
"cap",
"chrome",
"chrome-extension",
"cid",
"coap",
"com-eventbrite-attendee",
"content",
"crid",
"cvs",
"data",
"dav",
"dict",
"dlna-playcontainer",
"dlna-playsingle",
"dns",
"doi",
"dtn",
"dvb",
"ed2k",
"facetime",
"feed",
"file",
"finger",
"fish",
"ftp",
"geo",
"gg",
"git",
"gizmoproject",
"go",
"gopher",
"gtalk",
"h323",
"hcp",
"http",
"https",
"iax",
"icap",
"icon",
"im",
"imap",
"info",
"ipn",
"ipp",
"irc",
"irc6",
"ircs",
"iris",
"iris.beep",
"iris.lwz",
"iris.xpc",
"iris.xpcs",
"itms",
"jar",
"javascript",
"jms",
"keyparc",
"lastfm",
"ldap",
"ldaps",
"magnet",
"mailto",
"maps",
"market",
"message",
"mid",
"mms",
"ms-help",
"msnim",
"msrp",
"msrps",
"mtqp",
"mumble",
"mupdate",
"mvn",
"news",
"nfs",
"ni",
"nih",
"nntp",
"notes",
"oid",
"opaquelocktoken",
"palm",
"paparazzi",
"platform",
"pop",
"pres",
"proxy",
"psyc",
"query",
"res",
"resource",
"rmi",
"rsync",
"rtmp",
"rtsp",
"secondlife",
"service",
"session",
"sftp",
"sgn",
"shttp",
"sieve",
"sip",
"sips",
"skype",
"smb",
"sms",
"snmp",
"soap.beep",
"soap.beeps",
"soldat",
"spotify",
"ssh",
"steam",
"svn",
"tag",
"teamspeak",
"tel",
"telnet",
"tftp",
"things",
"thismessage",
"tip",
"tn3270",
"tv",
"udp",
"unreal",
"urn",
"ut2004",
"vemmi",
"ventrilo",
"view-source",
"webcal",
"ws",
"wss",
"wtai",
"wyciwyg",
"xcon",
"xcon-userid",
"xfire",
"xmlrpc.beep",
"xmlrpc.beeps",
"xmpp",
"xri",
"ymsgr",
"z39.50r",
"z39.50s",
}
var urlSchemasSet = make(map[string]bool)
func init() {
for _, s := range urlSchemas {
urlSchemasSet[s] = true
}
}
func matchSchema(s string) bool {
_, ok := urlSchemasSet[s]
return ok
}

Some files were not shown because too many files have changed in this diff Show More