From 61e00ad5877b828b9e7deda6b5fd84a2868811a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Feb 2022 02:17:34 +0000 Subject: [PATCH] Bump github.com/russross/blackfriday from 1.6.0 to 2.0.0+incompatible Bumps [github.com/russross/blackfriday](https://github.com/russross/blackfriday) from 1.6.0 to 2.0.0+incompatible. - [Release notes](https://github.com/russross/blackfriday/releases) - [Commits](https://github.com/russross/blackfriday/compare/v1.6.0...v2.0.0) --- updated-dependencies: - dependency-name: github.com/russross/blackfriday dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- go.mod | 3 +- go.sum | 5 +- .../russross/blackfriday/.travis.yml | 28 +- .../russross/blackfriday/LICENSE.txt | 53 +- .../github.com/russross/blackfriday/README.md | 129 +- .../github.com/russross/blackfriday/block.go | 865 +++++----- vendor/github.com/russross/blackfriday/doc.go | 40 +- vendor/github.com/russross/blackfriday/esc.go | 34 + .../github.com/russross/blackfriday/html.go | 1537 ++++++++--------- .../github.com/russross/blackfriday/inline.go | 558 +++--- .../github.com/russross/blackfriday/latex.go | 334 ---- .../russross/blackfriday/markdown.go | 767 ++++---- .../github.com/russross/blackfriday/node.go | 354 ++++ .../russross/blackfriday/smartypants.go | 139 +- .../sanitized_anchor_name/.travis.yml | 16 + .../shurcooL/sanitized_anchor_name/LICENSE | 21 + .../shurcooL/sanitized_anchor_name/README.md | 36 + .../shurcooL/sanitized_anchor_name/main.go | 29 + vendor/modules.txt | 7 +- 19 files changed, 2585 insertions(+), 2370 deletions(-) create mode 100644 vendor/github.com/russross/blackfriday/esc.go delete mode 100644 vendor/github.com/russross/blackfriday/latex.go create mode 100644 vendor/github.com/russross/blackfriday/node.go create mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml create mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE create mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/README.md create mode 100644 vendor/github.com/shurcooL/sanitized_anchor_name/main.go diff --git a/go.mod b/go.mod index 65183907..2f7e0fd5 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/nelsonken/gomf v0.0.0-20180504123937-a9dd2f9deae9 github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c github.com/rs/xid v1.3.0 - github.com/russross/blackfriday v1.6.0 + github.com/russross/blackfriday v2.0.0+incompatible github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/shazow/ssh-chat v1.10.1 github.com/sirupsen/logrus v1.8.1 @@ -104,6 +104,7 @@ require ( github.com/rickb777/plural v1.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/spf13/afero v1.6.0 // indirect diff --git a/go.sum b/go.sum index 17e4f747..c7083a4f 100644 --- a/go.sum +++ b/go.sum @@ -1436,8 +1436,8 @@ github.com/rudderlabs/analytics-go v3.3.1+incompatible/go.mod h1:LF8/ty9kUX4PTY3 github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= +github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= @@ -1487,6 +1487,7 @@ github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +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/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= diff --git a/vendor/github.com/russross/blackfriday/.travis.yml b/vendor/github.com/russross/blackfriday/.travis.yml index a49fff15..a4eb2577 100644 --- a/vendor/github.com/russross/blackfriday/.travis.yml +++ b/vendor/github.com/russross/blackfriday/.travis.yml @@ -1,18 +1,18 @@ -sudo: false +# Travis CI (http://travis-ci.org/) is a continuous integration service for +# open source projects. This file configures it to run unit tests for +# blackfriday. + language: go + go: - - "1.9.x" - - "1.10.x" - - "1.11.x" - - tip -matrix: - fast_finish: true - allow_failures: - - go: tip + - 1.5 + - 1.6 + - 1.7 + install: - - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). + - go get -d -t -v ./... + - go build -v ./... + script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d -s .) - - go tool vet . - - go test -v -race ./... + - go test -v ./... + - go test -run=^$ -bench=BenchmarkReference -benchmem diff --git a/vendor/github.com/russross/blackfriday/LICENSE.txt b/vendor/github.com/russross/blackfriday/LICENSE.txt index 7fbb253a..2885af36 100644 --- a/vendor/github.com/russross/blackfriday/LICENSE.txt +++ b/vendor/github.com/russross/blackfriday/LICENSE.txt @@ -1,28 +1,29 @@ Blackfriday is distributed under the Simplified BSD License: -Copyright © 2011 Russ Ross -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided with - the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +> Copyright © 2011 Russ Ross +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions +> are met: +> +> 1. Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> +> 2. Redistributions in binary form must reproduce the above +> copyright notice, this list of conditions and the following +> disclaimer in the documentation and/or other materials provided with +> the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +> POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/russross/blackfriday/README.md b/vendor/github.com/russross/blackfriday/README.md index 997ef5d4..2e0db355 100644 --- a/vendor/github.com/russross/blackfriday/README.md +++ b/vendor/github.com/russross/blackfriday/README.md @@ -1,6 +1,4 @@ -Blackfriday -[![Build Status][BuildV2SVG]][BuildV2URL] -[![PkgGoDev][PkgGoDevV2SVG]][PkgGoDevV2URL] +Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) =========== Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It @@ -18,32 +16,27 @@ It started as a translation from C of [Sundown][3]. Installation ------------ -Blackfriday is compatible with modern Go releases in module mode. -With Go installed: +Blackfriday is compatible with any modern Go release. With Go 1.7 and git +installed: - go get github.com/russross/blackfriday + go get gopkg.in/russross/blackfriday.v2 -will resolve and add the package to the current development module, -then build and install it. Alternatively, you can achieve the same -if you import it in a package: +will download, compile, and install the package into your `$GOPATH` +directory hierarchy. Alternatively, you can achieve the same if you +import it into a project: - import "github.com/russross/blackfriday" + import "gopkg.in/russross/blackfriday.v2" and `go get` without parameters. -Old versions of Go and legacy GOPATH mode might work, -but no effort is made to keep them working. - Versions -------- Currently maintained and recommended version of Blackfriday is `v2`. It's being -developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the -documentation is available at -https://pkg.go.dev/github.com/russross/blackfriday/v2. - -It is `go get`-able in module mode at `github.com/russross/blackfriday/v2`. +developed on its own branch: https://github.com/russross/blackfriday/v2. You +should install and import it via [gopkg.in][6] at +`gopkg.in/russross/blackfriday.v2`. Version 2 offers a number of improvements over v1: @@ -63,32 +56,9 @@ Potential drawbacks: v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for tracking. -If you are still interested in the legacy `v1`, you can import it from -`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found -here: https://pkg.go.dev/github.com/russross/blackfriday. - - Usage ----- -### v1 - -For basic usage, it is as simple as getting your input into a byte -slice and calling: - -```go -output := blackfriday.MarkdownBasic(input) -``` - -This renders it with no extensions enabled. To get a more useful -feature set, use this instead: - -```go -output := blackfriday.MarkdownCommon(input) -``` - -### v2 - For the most sensible markdown processing, it is as simple as getting your input into a byte slice and calling: @@ -123,21 +93,11 @@ unsafe := blackfriday.Run(input) html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) ``` -### Custom options, v1 - -If you want to customize the set of options, first get a renderer -(currently only the HTML output engine), then use it to -call the more general `Markdown` function. For examples, see the -implementations of `MarkdownBasic` and `MarkdownCommon` in -`markdown.go`. - -### Custom options, v2 +### Custom options If you want to customize the set of options, use `blackfriday.WithExtensions`, `blackfriday.WithRenderer` and `blackfriday.WithRefOverride`. -### `blackfriday-tool` - You can also check out `blackfriday-tool` for a more complete example of how to use it. Download and install it using: @@ -148,7 +108,7 @@ markdown file using a standalone program. You can also browse the source directly on github if you are just looking for some example code: -* +* Note that if you have not already done so, installing `blackfriday-tool` will be sufficient to download and install @@ -157,22 +117,6 @@ installed in `$GOPATH/bin`. This is a statically-linked binary that can be copied to wherever you need it without worrying about dependencies and library versions. -### Sanitized anchor names - -Blackfriday includes an algorithm for creating sanitized anchor names -corresponding to a given input text. This algorithm is used to create -anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The -algorithm has a specification, so that other packages can create -compatible anchor names and links to those anchors. - -The specification is located at https://pkg.go.dev/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names. - -[`SanitizedAnchorName`](https://pkg.go.dev/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to -create compatible links to the anchor names generated by blackfriday. -This algorithm is also implemented in a small standalone package at -[`github.com/shurcooL/sanitized_anchor_name`](https://pkg.go.dev/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients -that want a small package and don't need full functionality of blackfriday. - Features -------- @@ -249,21 +193,12 @@ implements the following extensions: You can use 3 or more backticks to mark the beginning of the block, and the same number to mark the end of the block. - To preserve classes of fenced code blocks while using the bluemonday - HTML sanitizer, use the following policy: - - ```go - p := bluemonday.UGCPolicy() - p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code") - html := p.SanitizeBytes(unsafe) - ``` - * **Definition lists**. A simple definition list is made of a single-line term followed by a colon and the definition for that term. Cat : Fluffy animal everyone likes - + Internet : Vector of transmission for pictures of cats @@ -274,7 +209,7 @@ implements the following extensions: end of the document. A footnote looks like this: This is a footnote.[^1] - + [^1]: the footnote text. * **Autolinking**. Blackfriday can find URLs that have not been @@ -283,10 +218,8 @@ implements the following extensions: * **Strikethrough**. Use two tildes (`~~`) to mark text that should be crossed out. -* **Hard line breaks**. With this extension enabled (it is off by - default in the `MarkdownBasic` and `MarkdownCommon` convenience - functions), newlines in the input translate into line breaks in - the output. +* **Hard line breaks**. With this extension enabled newlines in the input + translate into line breaks in the output. This extension is off by default. * **Smart quotes**. Smartypants-style punctuation substitution is supported, turning normal double- and single-quote marks into @@ -311,7 +244,7 @@ Other renderers Blackfriday is structured to allow alternative rendering engines. Here are a few of note: -* [github_flavored_markdown](https://pkg.go.dev/github.com/shurcooL/github_flavored_markdown): +* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown): provides a GitHub Flavored Markdown renderer with fenced code block highlighting, clickable heading anchor links. @@ -322,28 +255,18 @@ are a few of note: * [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt, but for markdown. -* [LaTeX output](https://gitlab.com/ambrevar/blackfriday-latex): +* [LaTeX output](https://bitbucket.org/ambrevar/blackfriday-latex): renders output as LaTeX. -* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience - integration with the [Chroma](https://github.com/alecthomas/chroma) code - highlighting library. bfchroma is only compatible with v2 of Blackfriday and - provides a drop-in renderer ready to use with Blackfriday, as well as - options and means for further customization. -* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer. - -* [Blackfriday-Slack](https://github.com/karriereat/blackfriday-slack): converts markdown to slack message style - - -TODO +Todo ---- * More unit testing -* Improve Unicode support. It does not understand all Unicode +* Improve unicode support. It does not understand all unicode rules (about what constitutes a letter, a punctuation symbol, etc.), so it may fail to detect word boundaries correctly in - some instances. It is safe on all UTF-8 input. + some instances. It is safe on all utf-8 input. License @@ -355,10 +278,6 @@ License [1]: https://daringfireball.net/projects/markdown/ "Markdown" [2]: https://golang.org/ "Go Language" [3]: https://github.com/vmg/sundown "Sundown" - [4]: https://pkg.go.dev/github.com/russross/blackfriday/v2#Parse "Parse func" + [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" - - [BuildV2SVG]: https://travis-ci.org/russross/blackfriday.svg?branch=v2 - [BuildV2URL]: https://travis-ci.org/russross/blackfriday - [PkgGoDevV2SVG]: https://pkg.go.dev/badge/github.com/russross/blackfriday/v2 - [PkgGoDevV2URL]: https://pkg.go.dev/github.com/russross/blackfriday/v2 + [6]: https://labix.org/gopkg.in "gopkg.in" diff --git a/vendor/github.com/russross/blackfriday/block.go b/vendor/github.com/russross/blackfriday/block.go index 563cb290..d7da33f2 100644 --- a/vendor/github.com/russross/blackfriday/block.go +++ b/vendor/github.com/russross/blackfriday/block.go @@ -15,18 +15,26 @@ package blackfriday import ( "bytes" - "strings" - "unicode" + "html" + "regexp" + + "github.com/shurcooL/sanitized_anchor_name" +) + +const ( + charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});" + escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]" +) + +var ( + reBackslashOrAmp = regexp.MustCompile("[\\&]") + reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity) ) // Parse block-level data. // Note: this function and many that it calls assume that // the input buffer ends with a newline. -func (p *parser) block(out *bytes.Buffer, data []byte) { - if len(data) == 0 || data[len(data)-1] != '\n' { - panic("block input is missing terminating newline") - } - +func (p *Markdown) block(data []byte) { // this is called recursively: enforce a maximum depth if p.nesting >= p.maxNesting { return @@ -35,14 +43,14 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // parse out one block-level construct at a time for len(data) > 0 { - // prefixed header: + // prefixed heading: // - // # Header 1 - // ## Header 2 + // # Heading 1 + // ## Heading 2 // ... - // ###### Header 6 - if p.isPrefixHeader(data) { - data = data[p.prefixHeader(out, data):] + // ###### Heading 6 + if p.isPrefixHeading(data) { + data = data[p.prefixHeading(data):] continue } @@ -52,7 +60,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // ... // if data[0] == '<' { - if i := p.html(out, data, true); i > 0 { + if i := p.html(data, true); i > 0 { data = data[i:] continue } @@ -63,9 +71,9 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // % stuff // % more stuff // % even more stuff - if p.flags&EXTENSION_TITLEBLOCK != 0 { + if p.extensions&Titleblock != 0 { if data[0] == '%' { - if i := p.titleBlock(out, data, true); i > 0 { + if i := p.titleBlock(data, true); i > 0 { data = data[i:] continue } @@ -87,13 +95,13 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // return b // } if p.codePrefix(data) > 0 { - data = data[p.code(out, data):] + data = data[p.code(data):] continue } // fenced code block: // - // ``` go info string here + // ``` go // func fact(n int) int { // if n <= 1 { // return n @@ -101,8 +109,8 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // return n * fact(n-1) // } // ``` - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCodeBlock(out, data, true); i > 0 { + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data, true); i > 0 { data = data[i:] continue } @@ -116,9 +124,9 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // or // ______ if p.isHRule(data) { - p.r.HRule(out) + p.addBlock(HorizontalRule, nil) var i int - for i = 0; data[i] != '\n'; i++ { + for i = 0; i < len(data) && data[i] != '\n'; i++ { } data = data[i:] continue @@ -129,7 +137,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // > A big quote I found somewhere // > on the web if p.quotePrefix(data) > 0 { - data = data[p.quote(out, data):] + data = data[p.quote(data):] continue } @@ -139,8 +147,8 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // ------|-----|--------- // Bob | 31 | 555-1234 // Alice | 27 | 555-4321 - if p.flags&EXTENSION_TABLES != 0 { - if i := p.table(out, data); i > 0 { + if p.extensions&Tables != 0 { + if i := p.table(data); i > 0 { data = data[i:] continue } @@ -153,7 +161,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // // also works with + or - if p.uliPrefix(data) > 0 { - data = data[p.list(out, data, 0):] + data = data[p.list(data, 0):] continue } @@ -162,7 +170,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // 1. Item 1 // 2. Item 2 if p.oliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_ORDERED):] + data = data[p.list(data, ListTypeOrdered):] continue } @@ -174,55 +182,62 @@ func (p *parser) block(out *bytes.Buffer, data []byte) { // // Term 2 // : Definition c - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.extensions&DefinitionLists != 0 { if p.dliPrefix(data) > 0 { - data = data[p.list(out, data, LIST_TYPE_DEFINITION):] + data = data[p.list(data, ListTypeDefinition):] continue } } // anything else must look like a normal paragraph - // note: this finds underlined headers, too - data = data[p.paragraph(out, data):] + // note: this finds underlined headings, too + data = data[p.paragraph(data):] } p.nesting-- } -func (p *parser) isPrefixHeader(data []byte) bool { +func (p *Markdown) addBlock(typ NodeType, content []byte) *Node { + p.closeUnmatchedBlocks() + container := p.addChild(typ, 0) + container.content = content + return container +} + +func (p *Markdown) isPrefixHeading(data []byte) bool { if data[0] != '#' { return false } - if p.flags&EXTENSION_SPACE_HEADERS != 0 { + if p.extensions&SpaceHeadings != 0 { level := 0 - for level < 6 && data[level] == '#' { + for level < 6 && level < len(data) && data[level] == '#' { level++ } - if data[level] != ' ' { + if level == len(data) || data[level] != ' ' { return false } } return true } -func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { +func (p *Markdown) prefixHeading(data []byte) int { level := 0 - for level < 6 && data[level] == '#' { + for level < 6 && level < len(data) && data[level] == '#' { level++ } i := skipChar(data, level, ' ') end := skipUntilChar(data, i, '\n') skip := end id := "" - if p.flags&EXTENSION_HEADER_IDS != 0 { + if p.extensions&HeadingIDs != 0 { j, k := 0, 0 - // find start/end of header id + // find start/end of heading id for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { } for k = j + 1; k < end && data[k] != '}'; k++ { } - // extract header id iff found + // extract heading id iff found if j < end && k < end { id = string(data[j+2 : k]) end = j @@ -242,45 +257,41 @@ func (p *parser) prefixHeader(out *bytes.Buffer, data []byte) int { end-- } if end > i { - if id == "" && p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = SanitizedAnchorName(string(data[i:end])) + if id == "" && p.extensions&AutoHeadingIDs != 0 { + id = sanitized_anchor_name.Create(string(data[i:end])) } - work := func() bool { - p.inline(out, data[i:end]) - return true - } - p.r.Header(out, work, level, id) + block := p.addBlock(Heading, data[i:end]) + block.HeadingID = id + block.Level = level } return skip } -func (p *parser) isUnderlinedHeader(data []byte) int { - // test of level 1 header +func (p *Markdown) isUnderlinedHeading(data []byte) int { + // test of level 1 heading if data[0] == '=' { i := skipChar(data, 1, '=') i = skipChar(data, i, ' ') - if data[i] == '\n' { + if i < len(data) && data[i] == '\n' { return 1 - } else { - return 0 } + return 0 } - // test of level 2 header + // test of level 2 heading if data[0] == '-' { i := skipChar(data, 1, '-') i = skipChar(data, i, ' ') - if data[i] == '\n' { + if i < len(data) && data[i] == '\n' { return 2 - } else { - return 0 } + return 0 } return 0 } -func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { +func (p *Markdown) titleBlock(data []byte, doRender bool) int { if data[0] != '%' { return 0 } @@ -294,12 +305,17 @@ func (p *parser) titleBlock(out *bytes.Buffer, data []byte, doRender bool) int { } data = bytes.Join(splitData[0:i], []byte("\n")) - p.r.TitleBlock(out, data) + consumed := len(data) + data = bytes.TrimPrefix(data, []byte("% ")) + data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1) + block := p.addBlock(Heading, data) + block.Level = 1 + block.IsTitleblock = true - return len(data) + return consumed } -func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { +func (p *Markdown) html(data []byte, doRender bool) int { var i, j int // identify the opening tag @@ -311,17 +327,12 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { // handle special cases if !tagfound { // check for an HTML comment - if size := p.htmlComment(out, data, doRender); size > 0 { + if size := p.htmlComment(data, doRender); size > 0 { return size } // check for an
tag - if size := p.htmlHr(out, data, doRender); size > 0 { - return size - } - - // check for HTML CDATA - if size := p.htmlCDATA(out, data, doRender); size > 0 { + if size := p.htmlHr(data, doRender); size > 0 { return size } @@ -396,60 +407,42 @@ func (p *parser) html(out *bytes.Buffer, data []byte, doRender bool) int { for end > 0 && data[end-1] == '\n' { end-- } - p.r.BlockHtml(out, data[:end]) + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) } return i } -func (p *parser) renderHTMLBlock(out *bytes.Buffer, data []byte, start int, doRender bool) int { - // html block needs to end with a blank line - if i := p.isEmpty(data[start:]); i > 0 { - size := start + i +func finalizeHTMLBlock(block *Node) { + block.Literal = block.content + block.content = nil +} + +// HTML comment, lax form +func (p *Markdown) htmlComment(data []byte, doRender bool) int { + i := p.inlineHTMLComment(data) + // needs to end with a blank line + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j if doRender { // trim trailing newlines end := size for end > 0 && data[end-1] == '\n' { end-- } - p.r.BlockHtml(out, data[:end]) + block := p.addBlock(HTMLBlock, data[:end]) + finalizeHTMLBlock(block) } return size } return 0 } -// HTML comment, lax form -func (p *parser) htmlComment(out *bytes.Buffer, data []byte, doRender bool) int { - i := p.inlineHTMLComment(out, data) - return p.renderHTMLBlock(out, data, i, doRender) -} - -// HTML CDATA section -func (p *parser) htmlCDATA(out *bytes.Buffer, data []byte, doRender bool) int { - const cdataTag = "') { - i++ - } - i++ - // no end-of-comment marker - if i >= len(data) { - return 0 - } - return p.renderHTMLBlock(out, data, i, doRender) -} - // HR, which is the only self-closing block tag considered -func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { +func (p *Markdown) htmlHr(data []byte, doRender bool) int { + if len(data) < 4 { + return 0 + } if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { return 0 } @@ -457,22 +450,31 @@ func (p *parser) htmlHr(out *bytes.Buffer, data []byte, doRender bool) int { // not an
tag after all; at least not a valid one return 0 } - i := 3 - for data[i] != '>' && data[i] != '\n' { + for i < len(data) && data[i] != '>' && data[i] != '\n' { i++ } - - if data[i] == '>' { - return p.renderHTMLBlock(out, data, i+1, doRender) + if i < len(data) && data[i] == '>' { + i++ + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j + if doRender { + // trim newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) + } + return size + } } - return 0 } -func (p *parser) htmlFindTag(data []byte) (string, bool) { +func (p *Markdown) htmlFindTag(data []byte) (string, bool) { i := 0 - for isalnum(data[i]) { + for i < len(data) && isalnum(data[i]) { i++ } key := string(data[:i]) @@ -482,9 +484,11 @@ func (p *parser) htmlFindTag(data []byte) (string, bool) { return "", false } -func (p *parser) htmlFindEnd(tag string, data []byte) int { +func (p *Markdown) htmlFindEnd(tag string, data []byte) int { // assume data[0] == '<' && data[1] == '/' already tested - + if tag == "hr" { + return 2 + } // check if tag is a match closetag := []byte("") if !bytes.HasPrefix(data, closetag) { @@ -504,7 +508,7 @@ func (p *parser) htmlFindEnd(tag string, data []byte) int { return i } - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { + if p.extensions&LaxHTMLBlocks != 0 { return i } if skip = p.isEmpty(data[i:]); skip == 0 { @@ -515,7 +519,7 @@ func (p *parser) htmlFindEnd(tag string, data []byte) int { return i + skip } -func (*parser) isEmpty(data []byte) int { +func (*Markdown) isEmpty(data []byte) int { // it is okay to call isEmpty on an empty buffer if len(data) == 0 { return 0 @@ -527,10 +531,13 @@ func (*parser) isEmpty(data []byte) int { return 0 } } - return i + 1 + if i < len(data) && data[i] == '\n' { + i++ + } + return i } -func (*parser) isHRule(data []byte) bool { +func (*Markdown) isHRule(data []byte) bool { i := 0 // skip up to three spaces @@ -546,7 +553,7 @@ func (*parser) isHRule(data []byte) bool { // the whole line must be the char or whitespace n := 0 - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { switch { case data[i] == c: n++ @@ -562,8 +569,7 @@ func (*parser) isHRule(data []byte) bool { // isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, // and returns the end index if so, or 0 otherwise. It also returns the marker found. // If syntax is not nil, it gets set to the syntax specified in the fence line. -// A final newline is mandatory to recognize the fence line, unless newlineOptional is true. -func isFenceLine(data []byte, info *string, oldmarker string, newlineOptional bool) (end int, marker string) { +func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) { i, size := 0, 0 // skip up to three spaces @@ -599,26 +605,26 @@ func isFenceLine(data []byte, info *string, oldmarker string, newlineOptional bo } // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here - // into one, always get the info string, and discard it if the caller doesn't care. - if info != nil { - infoLength := 0 + // into one, always get the syntax, and discard it if the caller doesn't care. + if syntax != nil { + syn := 0 i = skipChar(data, i, ' ') if i >= len(data) { - if newlineOptional && i == len(data) { + if i == len(data) { return i, marker } return 0, "" } - infoStart := i + syntaxStart := i if data[i] == '{' { i++ - infoStart++ + syntaxStart++ for i < len(data) && data[i] != '}' && data[i] != '\n' { - infoLength++ + syn++ i++ } @@ -628,58 +634,55 @@ func isFenceLine(data []byte, info *string, oldmarker string, newlineOptional bo // strip all whitespace at the beginning and the end // of the {} block - for infoLength > 0 && isspace(data[infoStart]) { - infoStart++ - infoLength-- + for syn > 0 && isspace(data[syntaxStart]) { + syntaxStart++ + syn-- } - for infoLength > 0 && isspace(data[infoStart+infoLength-1]) { - infoLength-- + for syn > 0 && isspace(data[syntaxStart+syn-1]) { + syn-- } i++ } else { - for i < len(data) && !isverticalspace(data[i]) { - infoLength++ + for i < len(data) && !isspace(data[i]) { + syn++ i++ } } - *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength])) + *syntax = string(data[syntaxStart : syntaxStart+syn]) } i = skipChar(data, i, ' ') - if i >= len(data) { - if newlineOptional { + if i >= len(data) || data[i] != '\n' { + if i == len(data) { return i, marker } return 0, "" } - if data[i] == '\n' { - i++ // Take newline into account - } - - return i, marker + return i + 1, marker // Take newline into account. } // fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, // or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. // If doRender is true, a final newline is mandatory to recognize the fenced code block. -func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int { - var infoString string - beg, marker := isFenceLine(data, &infoString, "", false) +func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { + var syntax string + beg, marker := isFenceLine(data, &syntax, "") if beg == 0 || beg >= len(data) { return 0 } var work bytes.Buffer + work.Write([]byte(syntax)) + work.WriteByte('\n') for { // safe to assume beg < len(data) // check for the end of the code block - newlineOptional := !doRender - fenceEnd, _ := isFenceLine(data[beg:], nil, marker, newlineOptional) + fenceEnd, _ := isFenceLine(data[beg:], nil, marker) if fenceEnd != 0 { beg += fenceEnd break @@ -701,24 +704,55 @@ func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) } if doRender { - p.r.BlockCode(out, work.Bytes(), infoString) + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = true + finalizeCodeBlock(block) } return beg } -func (p *parser) table(out *bytes.Buffer, data []byte) int { - var header bytes.Buffer - i, columns := p.tableHeader(&header, data) +func unescapeChar(str []byte) []byte { + if str[0] == '\\' { + return []byte{str[1]} + } + return []byte(html.UnescapeString(string(str))) +} + +func unescapeString(str []byte) []byte { + if reBackslashOrAmp.Match(str) { + return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar) + } + return str +} + +func finalizeCodeBlock(block *Node) { + if block.IsFenced { + newlinePos := bytes.IndexByte(block.content, '\n') + firstLine := block.content[:newlinePos] + rest := block.content[newlinePos+1:] + block.Info = unescapeString(bytes.Trim(firstLine, "\n")) + block.Literal = rest + } else { + block.Literal = block.content + } + block.content = nil +} + +func (p *Markdown) table(data []byte) int { + table := p.addBlock(Table, nil) + i, columns := p.tableHeader(data) if i == 0 { + p.tip = table.Parent + table.Unlink() return 0 } - var body bytes.Buffer + p.addBlock(TableBody, nil) for i < len(data) { pipes, rowStart := 0, i - for ; data[i] != '\n'; i++ { + for ; i < len(data) && data[i] != '\n'; i++ { if data[i] == '|' { pipes++ } @@ -730,12 +764,12 @@ func (p *parser) table(out *bytes.Buffer, data []byte) int { } // include the newline in data sent to tableRow - i++ - p.tableRow(&body, data[rowStart:i], columns, false) + if i < len(data) && data[i] == '\n' { + i++ + } + p.tableRow(data[rowStart:i], columns, false) } - p.r.Table(out, header.Bytes(), body.Bytes(), columns) - return i } @@ -748,10 +782,10 @@ func isBackslashEscaped(data []byte, i int) bool { return backslashes&1 == 1 } -func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns []int) { +func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) { i := 0 colCount := 1 - for i = 0; data[i] != '\n'; i++ { + for i = 0; i < len(data) && data[i] != '\n'; i++ { if data[i] == '|' && !isBackslashEscaped(data, i) { colCount++ } @@ -763,7 +797,11 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns } // include the newline in the data sent to tableRow - header := data[:i+1] + j := i + if j < len(data) && data[j] == '\n' { + j++ + } + header := data[:j] // column count ignores pipes at beginning or end of line if data[0] == '|' { @@ -773,7 +811,7 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns colCount-- } - columns = make([]int, colCount) + columns = make([]CellAlignFlags, colCount) // move on to the header underline i++ @@ -789,27 +827,29 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 // and trailing | optional on last column col := 0 - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { dashes := 0 if data[i] == ':' { i++ - columns[col] |= TABLE_ALIGNMENT_LEFT + columns[col] |= TableAlignmentLeft dashes++ } - for data[i] == '-' { + for i < len(data) && data[i] == '-' { i++ dashes++ } - if data[i] == ':' { + if i < len(data) && data[i] == ':' { i++ - columns[col] |= TABLE_ALIGNMENT_RIGHT + columns[col] |= TableAlignmentRight dashes++ } - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } - + if i == len(data) { + return + } // end of column test is messy switch { case dashes < 3: @@ -820,12 +860,12 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns // marker found, now skip past trailing whitespace col++ i++ - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } // trailing junk found after last column - if col >= colCount && data[i] != '\n' { + if col >= colCount && i < len(data) && data[i] != '\n' { return } @@ -846,27 +886,31 @@ func (p *parser) tableHeader(out *bytes.Buffer, data []byte) (size int, columns return } - p.tableRow(out, header, columns, true) - size = i + 1 + p.addBlock(TableHead, nil) + p.tableRow(header, columns, true) + size = i + if size < len(data) && data[size] == '\n' { + size++ + } return } -func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header bool) { +func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) { + p.addBlock(TableRow, nil) i, col := 0, 0 - var rowWork bytes.Buffer if data[i] == '|' && !isBackslashEscaped(data, i) { i++ } for col = 0; col < len(columns) && i < len(data); col++ { - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } cellStart := i - for (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { + for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { i++ } @@ -875,42 +919,33 @@ func (p *parser) tableRow(out *bytes.Buffer, data []byte, columns []int, header // skip the end-of-cell marker, possibly taking us past end of buffer i++ - for cellEnd > cellStart && data[cellEnd-1] == ' ' { + for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' { cellEnd-- } - var cellWork bytes.Buffer - p.inline(&cellWork, data[cellStart:cellEnd]) - - if header { - p.r.TableHeaderCell(&rowWork, cellWork.Bytes(), columns[col]) - } else { - p.r.TableCell(&rowWork, cellWork.Bytes(), columns[col]) - } + cell := p.addBlock(TableCell, data[cellStart:cellEnd]) + cell.IsHeader = header + cell.Align = columns[col] } // pad it out with empty columns to get the right number for ; col < len(columns); col++ { - if header { - p.r.TableHeaderCell(&rowWork, nil, columns[col]) - } else { - p.r.TableCell(&rowWork, nil, columns[col]) - } + cell := p.addBlock(TableCell, nil) + cell.IsHeader = header + cell.Align = columns[col] } // silently ignore rows with too many cells - - p.r.TableRow(out, rowWork.Bytes()) } // returns blockquote prefix length -func (p *parser) quotePrefix(data []byte) int { +func (p *Markdown) quotePrefix(data []byte) int { i := 0 - for i < 3 && data[i] == ' ' { + for i < 3 && i < len(data) && data[i] == ' ' { i++ } - if data[i] == '>' { - if data[i+1] == ' ' { + if i < len(data) && data[i] == '>' { + if i+1 < len(data) && data[i+1] == ' ' { return i + 2 } return i + 1 @@ -920,7 +955,7 @@ func (p *parser) quotePrefix(data []byte) int { // blockquote ends with at least one blank line // followed by something without a blockquote prefix -func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { +func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool { if p.isEmpty(data[beg:]) <= 0 { return false } @@ -931,7 +966,8 @@ func (p *parser) terminateBlockquote(data []byte, beg, end int) bool { } // parse a blockquote fragment -func (p *parser) quote(out *bytes.Buffer, data []byte) int { +func (p *Markdown) quote(data []byte) int { + block := p.addBlock(BlockQuote, nil) var raw bytes.Buffer beg, end := 0, 0 for beg < len(data) { @@ -939,9 +975,9 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { // 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 data[end] != '\n' { - if p.flags&EXTENSION_FENCED_CODE != 0 { - if i := p.fencedCodeBlock(out, data[end:], false); i > 0 { + 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 @@ -949,44 +985,47 @@ func (p *parser) quote(out *bytes.Buffer, data []byte) int { } end++ } - end++ - + if end < len(data) && data[end] == '\n' { + end++ + } if pre := p.quotePrefix(data[beg:]); pre > 0 { // skip the prefix beg += pre } else if p.terminateBlockquote(data, beg, end) { break } - // this line is part of the blockquote raw.Write(data[beg:end]) beg = end } - - var cooked bytes.Buffer - p.block(&cooked, raw.Bytes()) - p.r.BlockQuote(out, cooked.Bytes()) + p.block(raw.Bytes()) + p.finalize(block) return end } // returns prefix length for block code -func (p *parser) codePrefix(data []byte) int { - if data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { +func (p *Markdown) codePrefix(data []byte) int { + if len(data) >= 1 && data[0] == '\t' { + return 1 + } + if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { return 4 } return 0 } -func (p *parser) code(out *bytes.Buffer, data []byte) int { +func (p *Markdown) code(data []byte) int { var work bytes.Buffer i := 0 for i < len(data) { beg := i - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { + i++ + } + if i < len(data) && data[i] == '\n' { i++ } - i++ blankline := p.isEmpty(data[beg:i]) > 0 if pre := p.codePrefix(data[beg:i]); pre > 0 { @@ -997,7 +1036,7 @@ func (p *parser) code(out *bytes.Buffer, data []byte) int { break } - // verbatim copy to the working buffeu + // verbatim copy to the working buffer if blankline { work.WriteByte('\n') } else { @@ -1017,134 +1056,186 @@ func (p *parser) code(out *bytes.Buffer, data []byte) int { work.WriteByte('\n') - p.r.BlockCode(out, work.Bytes(), "") + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = false + finalizeCodeBlock(block) return i } // returns unordered list item prefix -func (p *parser) uliPrefix(data []byte) int { +func (p *Markdown) uliPrefix(data []byte) int { i := 0 - // start with up to 3 spaces - for i < 3 && data[i] == ' ' { + for i < len(data) && i < 3 && data[i] == ' ' { i++ } - - // need a *, +, or - followed by a space + if i >= len(data)-1 { + return 0 + } + // need one of {'*', '+', '-'} followed by a space or a tab if (data[i] != '*' && data[i] != '+' && data[i] != '-') || - data[i+1] != ' ' { + (data[i+1] != ' ' && data[i+1] != '\t') { return 0 } return i + 2 } // returns ordered list item prefix -func (p *parser) oliPrefix(data []byte) int { +func (p *Markdown) oliPrefix(data []byte) int { i := 0 // start with up to 3 spaces - for i < 3 && data[i] == ' ' { + for i < 3 && i < len(data) && data[i] == ' ' { i++ } // count the digits start := i - for data[i] >= '0' && data[i] <= '9' { + for i < len(data) && data[i] >= '0' && data[i] <= '9' { i++ } + if start == i || i >= len(data)-1 { + return 0 + } - // we need >= 1 digits followed by a dot and a space - if start == i || data[i] != '.' || data[i+1] != ' ' { + // we need >= 1 digits followed by a dot and a space or a tab + if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') { return 0 } return i + 2 } // returns definition list item prefix -func (p *parser) dliPrefix(data []byte) int { - i := 0 - - // need a : followed by a spaces - if data[i] != ':' || data[i+1] != ' ' { +func (p *Markdown) dliPrefix(data []byte) int { + if len(data) < 2 { return 0 } - for data[i] == ' ' { + i := 0 + // need a ':' followed by a space or a tab + if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') { + return 0 + } + for i < len(data) && data[i] == ' ' { i++ } return i + 2 } // parse ordered or unordered list block -func (p *parser) list(out *bytes.Buffer, data []byte, flags int) int { +func (p *Markdown) list(data []byte, flags ListType) int { i := 0 - flags |= LIST_ITEM_BEGINNING_OF_LIST - work := func() bool { - for i < len(data) { - skip := p.listItem(out, data[i:], &flags) - i += skip + flags |= ListItemBeginningOfList + block := p.addBlock(List, nil) + block.ListFlags = flags + block.Tight = true - if skip == 0 || flags&LIST_ITEM_END_OF_LIST != 0 { - break - } - flags &= ^LIST_ITEM_BEGINNING_OF_LIST + for i < len(data) { + skip := p.listItem(data[i:], &flags) + if flags&ListItemContainsBlock != 0 { + block.ListData.Tight = false } - return true + i += skip + if skip == 0 || flags&ListItemEndOfList != 0 { + break + } + flags &= ^ListItemBeginningOfList } - p.r.List(out, work, flags) + above := block.Parent + finalizeList(block) + p.tip = above return i } +// Returns true if block ends with a blank line, descending if needed +// into lists and sublists. +func endsWithBlankLine(block *Node) bool { + // TODO: figure this out. Always false now. + for block != nil { + //if block.lastLineBlank { + //return true + //} + t := block.Type + if t == List || t == Item { + block = block.LastChild + } else { + break + } + } + return false +} + +func finalizeList(block *Node) { + block.open = false + item := block.FirstChild + for item != nil { + // check for non-final list item ending with blank line: + if endsWithBlankLine(item) && item.Next != nil { + block.ListData.Tight = false + break + } + // recurse into children of list item, to see if there are spaces + // between any of them: + subItem := item.FirstChild + for subItem != nil { + if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) { + block.ListData.Tight = false + break + } + subItem = subItem.Next + } + item = item.Next + } +} + // Parse a single list item. // Assumes initial prefix is already removed if this is a sublist. -func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int { +func (p *Markdown) listItem(data []byte, flags *ListType) int { // keep track of the indentation of the first line itemIndent := 0 - for itemIndent < 3 && data[itemIndent] == ' ' { - itemIndent++ + if data[0] == '\t' { + itemIndent += 4 + } else { + for itemIndent < 3 && data[itemIndent] == ' ' { + itemIndent++ + } } + var bulletChar byte = '*' i := p.uliPrefix(data) if i == 0 { i = p.oliPrefix(data) + } else { + bulletChar = data[i-2] } if i == 0 { i = p.dliPrefix(data) // reset definition term flag if i > 0 { - *flags &= ^LIST_TYPE_TERM + *flags &= ^ListTypeTerm } } if i == 0 { - // if in defnition list, set term flag and continue - if *flags&LIST_TYPE_DEFINITION != 0 { - *flags |= LIST_TYPE_TERM + // if in definition list, set term flag and continue + if *flags&ListTypeDefinition != 0 { + *flags |= ListTypeTerm } else { return 0 } } // skip leading whitespace on first line - for data[i] == ' ' { + for i < len(data) && data[i] == ' ' { i++ } // find the end of the line line := i - for i > 0 && data[i-1] != '\n' { + for i > 0 && i < len(data) && data[i-1] != '\n' { i++ } - // process the following lines - containsBlankLine := false - sublist := 0 - codeBlockMarker := "" - if p.flags&EXTENSION_FENCED_CODE != 0 && i > line { - // determine if codeblock starts on the first line - _, codeBlockMarker = isFenceLine(data[line:i], nil, "", false) - } - // get working buffer var raw bytes.Buffer @@ -1152,53 +1243,42 @@ func (p *parser) listItem(out *bytes.Buffer, data []byte, flags *int) int { raw.Write(data[line:i]) line = i + // process the following lines + containsBlankLine := false + sublist := 0 + gatherlines: for line < len(data) { i++ // find the end of this line - for data[i-1] != '\n' { + for i < len(data) && data[i-1] != '\n' { i++ } + // if it is an empty line, guess that it is part of this item // and move on to the next line if p.isEmpty(data[line:i]) > 0 { containsBlankLine = true - raw.Write(data[line:i]) line = i continue } // calculate the indentation indent := 0 - for indent < 4 && line+indent < i && data[line+indent] == ' ' { - indent++ - } - - chunk := data[line+indent : i] - - if p.flags&EXTENSION_FENCED_CODE != 0 { - // determine if in or out of codeblock - // if in codeblock, ignore normal list processing - _, marker := isFenceLine(chunk, nil, codeBlockMarker, false) - if marker != "" { - if codeBlockMarker == "" { - // start of codeblock - codeBlockMarker = marker - } else { - // end of codeblock. - *flags |= LIST_ITEM_CONTAINS_BLOCK - codeBlockMarker = "" - } - } - // we are in a codeblock, write line, and continue - if codeBlockMarker != "" || marker != "" { - raw.Write(data[line+indent : i]) - line = i - continue gatherlines + indentIndex := 0 + if data[line] == '\t' { + indentIndex++ + indent += 4 + } else { + for indent < 4 && line+indent < i && data[line+indent] == ' ' { + indent++ + indentIndex++ } } + chunk := data[line+indentIndex : i] + // evaluate how this line fits in switch { // is this a nested list item? @@ -1207,15 +1287,7 @@ gatherlines: p.dliPrefix(chunk) > 0: if containsBlankLine { - // end the list if the type changed after a blank line - if indent <= itemIndent && - ((*flags&LIST_TYPE_ORDERED != 0 && p.uliPrefix(chunk) > 0) || - (*flags&LIST_TYPE_ORDERED == 0 && p.oliPrefix(chunk) > 0)) { - - *flags |= LIST_ITEM_END_OF_LIST - break gatherlines - } - *flags |= LIST_ITEM_CONTAINS_BLOCK + *flags |= ListItemContainsBlock } // to be a nested list, it must be indented more @@ -1229,93 +1301,89 @@ gatherlines: sublist = raw.Len() } - // is this a nested prefix header? - case p.isPrefixHeader(chunk): - // if the header is not indented, it is not nested in the list + // is this a nested prefix heading? + case p.isPrefixHeading(chunk): + // if the heading is not indented, it is not nested in the list // and thus ends the list if containsBlankLine && indent < 4 { - *flags |= LIST_ITEM_END_OF_LIST + *flags |= ListItemEndOfList break gatherlines } - *flags |= LIST_ITEM_CONTAINS_BLOCK + *flags |= ListItemContainsBlock // anything following an empty line is only part // of this item if it is indented 4 spaces // (regardless of the indentation of the beginning of the item) case containsBlankLine && indent < 4: - if *flags&LIST_TYPE_DEFINITION != 0 && i < len(data)-1 { + if *flags&ListTypeDefinition != 0 && i < len(data)-1 { // is the next item still a part of this list? next := i - for data[next] != '\n' { + for next < len(data) && data[next] != '\n' { next++ } for next < len(data)-1 && data[next] == '\n' { next++ } if i < len(data)-1 && data[i] != ':' && data[next] != ':' { - *flags |= LIST_ITEM_END_OF_LIST + *flags |= ListItemEndOfList } } else { - *flags |= LIST_ITEM_END_OF_LIST + *flags |= ListItemEndOfList } break gatherlines // a blank line means this should be parsed as a block case containsBlankLine: - *flags |= LIST_ITEM_CONTAINS_BLOCK + raw.WriteByte('\n') + *flags |= ListItemContainsBlock } - containsBlankLine = false + // if this line was preceded by one or more blanks, + // re-introduce the blank into the buffer + if containsBlankLine { + containsBlankLine = false + raw.WriteByte('\n') + } // add the line into the working buffer without prefix - raw.Write(data[line+indent : i]) + raw.Write(data[line+indentIndex : i]) line = i } - // If reached end of data, the Renderer.ListItem call we're going to make below - // is definitely the last in the list. - if line >= len(data) { - *flags |= LIST_ITEM_END_OF_LIST - } - rawBytes := raw.Bytes() + block := p.addBlock(Item, nil) + block.ListFlags = *flags + block.Tight = false + block.BulletChar = bulletChar + block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark + // render the contents of the list item - var cooked bytes.Buffer - if *flags&LIST_ITEM_CONTAINS_BLOCK != 0 && *flags&LIST_TYPE_TERM == 0 { + if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 { // intermediate render of block item, except for definition term if sublist > 0 { - p.block(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) + p.block(rawBytes[:sublist]) + p.block(rawBytes[sublist:]) } else { - p.block(&cooked, rawBytes) + p.block(rawBytes) } } else { // intermediate render of inline item if sublist > 0 { - p.inline(&cooked, rawBytes[:sublist]) - p.block(&cooked, rawBytes[sublist:]) + child := p.addChild(Paragraph, 0) + child.content = rawBytes[:sublist] + p.block(rawBytes[sublist:]) } else { - p.inline(&cooked, rawBytes) + child := p.addChild(Paragraph, 0) + child.content = rawBytes } } - - // render the actual list item - cookedBytes := cooked.Bytes() - parsedEnd := len(cookedBytes) - - // strip trailing newlines - for parsedEnd > 0 && cookedBytes[parsedEnd-1] == '\n' { - parsedEnd-- - } - p.r.ListItem(out, cookedBytes[:parsedEnd], *flags) - return line } // render a single paragraph that has already been parsed out -func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { +func (p *Markdown) renderParagraph(data []byte) { if len(data) == 0 { return } @@ -1326,27 +1394,29 @@ func (p *parser) renderParagraph(out *bytes.Buffer, data []byte) { beg++ } + end := len(data) // trim trailing newline - end := len(data) - 1 + if data[len(data)-1] == '\n' { + end-- + } // trim trailing spaces for end > beg && data[end-1] == ' ' { end-- } - work := func() bool { - p.inline(out, data[beg:end]) - return true - } - p.r.Paragraph(out, work) + p.addBlock(Paragraph, data[beg:end]) } -func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { +func (p *Markdown) paragraph(data []byte) int { // prev: index of 1st char of previous line // line: index of 1st char of current line // i: index of cursor/end of current line var prev, line, i int - + tabSize := TabSizeDefault + if p.extensions&TabSizeEight != 0 { + tabSize = TabSizeDouble + } // keep going until we find something to mark the end of the paragraph for i < len(data) { // mark the beginning of the current line @@ -1354,24 +1424,32 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { current := data[i:] line = i + // did we find a reference or a footnote? If so, end a paragraph + // preceding it and report that we have consumed up to the end of that + // reference: + if refEnd := isReference(p, current, tabSize); refEnd > 0 { + p.renderParagraph(data[:i]) + return i + refEnd + } + // did we find a blank line marking the end of the paragraph? if n := p.isEmpty(current); n > 0 { // did this blank line followed by a definition list item? - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.extensions&DefinitionLists != 0 { if i < len(data)-1 && data[i+1] == ':' { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + return p.list(data[prev:], ListTypeDefinition) } } - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i + n } - // an underline under some text marks a header, so our paragraph ended on prev line + // an underline under some text marks a heading, so our paragraph ended on prev line if i > 0 { - if level := p.isUnderlinedHeader(current); level > 0 { + if level := p.isUnderlinedHeading(current); level > 0 { // render the paragraph - p.renderParagraph(out, data[:prev]) + p.renderParagraph(data[:prev]) // ignore leading and trailing whitespace eol := i - 1 @@ -1382,24 +1460,17 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { eol-- } - // render the header - // this ugly double closure avoids forcing variables onto the heap - work := func(o *bytes.Buffer, pp *parser, d []byte) func() bool { - return func() bool { - pp.inline(o, d) - return true - } - }(out, p, data[prev:eol]) - id := "" - if p.flags&EXTENSION_AUTO_HEADER_IDS != 0 { - id = SanitizedAnchorName(string(data[prev:eol])) + if p.extensions&AutoHeadingIDs != 0 { + id = sanitized_anchor_name.Create(string(data[prev:eol])) } - p.r.Header(out, work, level, id) + block := p.addBlock(Heading, data[prev:eol]) + block.Level = level + block.HeadingID = id // find the end of the underline - for data[i] != '\n' { + for i < len(data) && data[i] != '\n' { i++ } return i @@ -1407,74 +1478,72 @@ func (p *parser) paragraph(out *bytes.Buffer, data []byte) int { } // if the next line starts a block of HTML, then the paragraph ends here - if p.flags&EXTENSION_LAX_HTML_BLOCKS != 0 { - if data[i] == '<' && p.html(out, current, false) > 0 { + if p.extensions&LaxHTMLBlocks != 0 { + if data[i] == '<' && p.html(current, false) > 0 { // rewind to before the HTML block - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i } } - // if there's a prefixed header or a horizontal rule after this, paragraph is over - if p.isPrefixHeader(current) || p.isHRule(current) { - p.renderParagraph(out, data[:i]) + // if there's a prefixed heading or a horizontal rule after this, paragraph is over + if p.isPrefixHeading(current) || p.isHRule(current) { + p.renderParagraph(data[:i]) return i } // if there's a fenced code block, paragraph is over - if p.flags&EXTENSION_FENCED_CODE != 0 { - if p.fencedCodeBlock(out, current, false) > 0 { - p.renderParagraph(out, data[:i]) + if p.extensions&FencedCode != 0 { + if p.fencedCodeBlock(current, false) > 0 { + p.renderParagraph(data[:i]) return i } } // if there's a definition list item, prev line is a definition term - if p.flags&EXTENSION_DEFINITION_LISTS != 0 { + if p.extensions&DefinitionLists != 0 { if p.dliPrefix(current) != 0 { - return p.list(out, data[prev:], LIST_TYPE_DEFINITION) + ret := p.list(data[prev:], ListTypeDefinition) + return ret } } // if there's a list after this, paragraph is over - if p.flags&EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK != 0 { + if p.extensions&NoEmptyLineBeforeBlock != 0 { if p.uliPrefix(current) != 0 || p.oliPrefix(current) != 0 || p.quotePrefix(current) != 0 || p.codePrefix(current) != 0 { - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i } } // otherwise, scan to the beginning of the next line - for data[i] != '\n' { - i++ + nl := bytes.IndexByte(data[i:], '\n') + if nl >= 0 { + i += nl + 1 + } else { + i += len(data[i:]) } - i++ } - p.renderParagraph(out, data[:i]) + p.renderParagraph(data[:i]) return i } -// SanitizedAnchorName returns a sanitized anchor name for the given text. -// -// It implements the algorithm specified in the package comment. -func SanitizedAnchorName(text string) string { - var anchorName []rune - futureDash := false - for _, r := range text { - switch { - case unicode.IsLetter(r) || unicode.IsNumber(r): - if futureDash && len(anchorName) > 0 { - anchorName = append(anchorName, '-') - } - futureDash = false - anchorName = append(anchorName, unicode.ToLower(r)) - default: - futureDash = true - } +func skipChar(data []byte, start int, char byte) int { + i := start + for i < len(data) && data[i] == char { + i++ } - return string(anchorName) + return i +} + +func skipUntilChar(text []byte, start int, char byte) int { + i := start + for i < len(text) && text[i] != char { + i++ + } + return i } diff --git a/vendor/github.com/russross/blackfriday/doc.go b/vendor/github.com/russross/blackfriday/doc.go index 9656c42a..5b3fa987 100644 --- a/vendor/github.com/russross/blackfriday/doc.go +++ b/vendor/github.com/russross/blackfriday/doc.go @@ -1,32 +1,18 @@ -// Package blackfriday is a Markdown processor. +// Package blackfriday is a markdown processor. // -// It translates plain text with simple formatting rules into HTML or LaTeX. +// It translates plain text with simple formatting rules into an AST, which can +// then be further processed to HTML (provided by Blackfriday itself) or other +// formats (provided by the community). // -// Sanitized Anchor Names +// The simplest way to invoke Blackfriday is to call the Run function. It will +// take a text input and produce a text output in HTML (or other format). // -// Blackfriday includes an algorithm for creating sanitized anchor names -// corresponding to a given input text. This algorithm is used to create -// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The -// algorithm is specified below, so that other packages can create -// compatible anchor names and links to those anchors. +// A slightly more sophisticated way to use Blackfriday is to create a Markdown +// processor and to call Parse, which returns a syntax tree for the input +// document. You can leverage Blackfriday's parsing for content extraction from +// markdown documents. You can assign a custom renderer and set various options +// to the Markdown processor. // -// The algorithm iterates over the input text, interpreted as UTF-8, -// one Unicode code point (rune) at a time. All runes that are letters (category L) -// or numbers (category N) are considered valid characters. They are mapped to -// lower case, and included in the output. All other runes are considered -// invalid characters. Invalid characters that preceed the first valid character, -// as well as invalid character that follow the last valid character -// are dropped completely. All other sequences of invalid characters -// between two valid characters are replaced with a single dash character '-'. -// -// SanitizedAnchorName exposes this functionality, and can be used to -// create compatible links to the anchor names generated by blackfriday. -// This algorithm is also implemented in a small standalone package at -// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients -// that want a small package and don't need full functionality of blackfriday. +// If you're interested in calling Blackfriday from command line, see +// https://github.com/russross/blackfriday-tool. package blackfriday - -// NOTE: Keep Sanitized Anchor Name algorithm in sync with package -// github.com/shurcooL/sanitized_anchor_name. -// Otherwise, users of sanitized_anchor_name will get anchor names -// that are incompatible with those generated by blackfriday. diff --git a/vendor/github.com/russross/blackfriday/esc.go b/vendor/github.com/russross/blackfriday/esc.go new file mode 100644 index 00000000..6385f27c --- /dev/null +++ b/vendor/github.com/russross/blackfriday/esc.go @@ -0,0 +1,34 @@ +package blackfriday + +import ( + "html" + "io" +) + +var htmlEscaper = [256][]byte{ + '&': []byte("&"), + '<': []byte("<"), + '>': []byte(">"), + '"': []byte("""), +} + +func escapeHTML(w io.Writer, s []byte) { + var start, end int + for end < len(s) { + escSeq := htmlEscaper[s[end]] + if escSeq != nil { + w.Write(s[start:end]) + w.Write(escSeq) + start = end + 1 + } + end++ + } + if start < len(s) && end <= len(s) { + w.Write(s[start:end]) + } +} + +func escLink(w io.Writer, text []byte) { + unesc := html.UnescapeString(string(text)) + escapeHTML(w, []byte(unesc)) +} diff --git a/vendor/github.com/russross/blackfriday/html.go b/vendor/github.com/russross/blackfriday/html.go index fa044ca2..25fb185e 100644 --- a/vendor/github.com/russross/blackfriday/html.go +++ b/vendor/github.com/russross/blackfriday/html.go @@ -18,47 +18,62 @@ package blackfriday import ( "bytes" "fmt" + "io" "regexp" - "strconv" "strings" ) -// Html renderer configuration options. +// HTMLFlags control optional behavior of HTML renderer. +type HTMLFlags int + +// HTML renderer configuration options. const ( - HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks - HTML_SKIP_STYLE // skip embedded