mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-11-04 00:11:04 +00:00 
			
		
		
		
	Vendor Update Go Libs (#13166)
* update github.com/alecthomas/chroma v0.8.0 -> v0.8.1 * github.com/blevesearch/bleve v1.0.10 -> v1.0.12 * editorconfig-core-go v2.1.1 -> v2.3.7 * github.com/gliderlabs/ssh v0.2.2 -> v0.3.1 * migrate editorconfig.ParseBytes to Parse * github.com/shurcooL/vfsgen to 0d455de96546 * github.com/go-git/go-git/v5 v5.1.0 -> v5.2.0 * github.com/google/uuid v1.1.1 -> v1.1.2 * github.com/huandu/xstrings v1.3.0 -> v1.3.2 * github.com/klauspost/compress v1.10.11 -> v1.11.1 * github.com/markbates/goth v1.61.2 -> v1.65.0 * github.com/mattn/go-sqlite3 v1.14.0 -> v1.14.4 * github.com/mholt/archiver v3.3.0 -> v3.3.2 * github.com/microcosm-cc/bluemonday 4f7140c49acb -> v1.0.4 * github.com/minio/minio-go v7.0.4 -> v7.0.5 * github.com/olivere/elastic v7.0.9 -> v7.0.20 * github.com/urfave/cli v1.20.0 -> v1.22.4 * github.com/prometheus/client_golang v1.1.0 -> v1.8.0 * github.com/xanzy/go-gitlab v0.37.0 -> v0.38.1 * mvdan.cc/xurls v2.1.0 -> v2.2.0 Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
		
					parent
					
						
							
								91f2afdb54
							
						
					
				
			
			
				commit
				
					
						12a1f914f4
					
				
			
		
					 656 changed files with 52967 additions and 25229 deletions
				
			
		
							
								
								
									
										8
									
								
								vendor/github.com/russross/blackfriday/v2/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/russross/blackfriday/v2/.gitignore
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
*.out
 | 
			
		||||
*.swp
 | 
			
		||||
*.8
 | 
			
		||||
*.6
 | 
			
		||||
_obj
 | 
			
		||||
_test*
 | 
			
		||||
markdown
 | 
			
		||||
tags
 | 
			
		||||
							
								
								
									
										17
									
								
								vendor/github.com/russross/blackfriday/v2/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								vendor/github.com/russross/blackfriday/v2/.travis.yml
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
sudo: false
 | 
			
		||||
language: go
 | 
			
		||||
go:
 | 
			
		||||
  - "1.10.x"
 | 
			
		||||
  - "1.11.x"
 | 
			
		||||
  - tip
 | 
			
		||||
matrix:
 | 
			
		||||
  fast_finish: true
 | 
			
		||||
  allow_failures:
 | 
			
		||||
    - go: tip
 | 
			
		||||
install:
 | 
			
		||||
  - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
 | 
			
		||||
script:
 | 
			
		||||
  - go get -t -v ./...
 | 
			
		||||
  - diff -u <(echo -n) <(gofmt -d -s .)
 | 
			
		||||
  - go tool vet .
 | 
			
		||||
  - go test -v ./...
 | 
			
		||||
							
								
								
									
										29
									
								
								vendor/github.com/russross/blackfriday/v2/LICENSE.txt
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/russross/blackfriday/v2/LICENSE.txt
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
Blackfriday is distributed under the Simplified BSD License:
 | 
			
		||||
 | 
			
		||||
> Copyright © 2011 Russ Ross
 | 
			
		||||
> All rights reserved.
 | 
			
		||||
>
 | 
			
		||||
> Redistribution and use in source and binary forms, with or without
 | 
			
		||||
> modification, are permitted provided that the following conditions
 | 
			
		||||
> are met:
 | 
			
		||||
>
 | 
			
		||||
> 1.  Redistributions of source code must retain the above copyright
 | 
			
		||||
>     notice, this list of conditions and the following disclaimer.
 | 
			
		||||
>
 | 
			
		||||
> 2.  Redistributions in binary form must reproduce the above
 | 
			
		||||
>     copyright notice, this list of conditions and the following
 | 
			
		||||
>     disclaimer in the documentation and/or other materials provided with
 | 
			
		||||
>     the distribution.
 | 
			
		||||
>
 | 
			
		||||
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 | 
			
		||||
> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 | 
			
		||||
> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 | 
			
		||||
> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 | 
			
		||||
> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 | 
			
		||||
> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 | 
			
		||||
> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 | 
			
		||||
> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 | 
			
		||||
> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 | 
			
		||||
> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 | 
			
		||||
> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 | 
			
		||||
> POSSIBILITY OF SUCH DAMAGE.
 | 
			
		||||
							
								
								
									
										291
									
								
								vendor/github.com/russross/blackfriday/v2/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								vendor/github.com/russross/blackfriday/v2/README.md
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,291 @@
 | 
			
		|||
Blackfriday [](https://travis-ci.org/russross/blackfriday)
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It
 | 
			
		||||
is paranoid about its input (so you can safely feed it user-supplied
 | 
			
		||||
data), it is fast, it supports common extensions (tables, smart
 | 
			
		||||
punctuation substitutions, etc.), and it is safe for all utf-8
 | 
			
		||||
(unicode) input.
 | 
			
		||||
 | 
			
		||||
HTML output is currently supported, along with Smartypants
 | 
			
		||||
extensions.
 | 
			
		||||
 | 
			
		||||
It started as a translation from C of [Sundown][3].
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Installation
 | 
			
		||||
------------
 | 
			
		||||
 | 
			
		||||
Blackfriday is compatible with any modern Go release. With Go 1.7 and git
 | 
			
		||||
installed:
 | 
			
		||||
 | 
			
		||||
    go get gopkg.in/russross/blackfriday.v2
 | 
			
		||||
 | 
			
		||||
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 "gopkg.in/russross/blackfriday.v2"
 | 
			
		||||
 | 
			
		||||
and `go get` without parameters.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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://godoc.org/gopkg.in/russross/blackfriday.v2.
 | 
			
		||||
 | 
			
		||||
It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`,
 | 
			
		||||
but we highly recommend using package management tool like [dep][7] or
 | 
			
		||||
[Glide][8] and make use of semantic versioning. With package management you
 | 
			
		||||
should import `github.com/russross/blackfriday` and specify that you're using
 | 
			
		||||
version 2.0.0.
 | 
			
		||||
 | 
			
		||||
Version 2 offers a number of improvements over v1:
 | 
			
		||||
 | 
			
		||||
* Cleaned up API
 | 
			
		||||
* A separate call to [`Parse`][4], which produces an abstract syntax tree for
 | 
			
		||||
  the document
 | 
			
		||||
* Latest bug fixes
 | 
			
		||||
* Flexibility to easily add your own rendering extensions
 | 
			
		||||
 | 
			
		||||
Potential drawbacks:
 | 
			
		||||
 | 
			
		||||
* Our benchmarks show v2 to be slightly slower than v1. Currently in the
 | 
			
		||||
  ballpark of around 15%.
 | 
			
		||||
* API breakage. If you can't afford modifying your code to adhere to the new API
 | 
			
		||||
  and don't care too much about the new features, v2 is probably not for you.
 | 
			
		||||
* Several bug fixes are trailing behind and still need to be forward-ported to
 | 
			
		||||
  v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for
 | 
			
		||||
  tracking.
 | 
			
		||||
 | 
			
		||||
Usage
 | 
			
		||||
-----
 | 
			
		||||
 | 
			
		||||
For the most sensible markdown processing, it is as simple as getting your input
 | 
			
		||||
into a byte slice and calling:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
output := blackfriday.Run(input)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Your input will be parsed and the output rendered with a set of most popular
 | 
			
		||||
extensions enabled. If you want the most basic feature set, corresponding with
 | 
			
		||||
the bare Markdown specification, use:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
output := blackfriday.Run(input, blackfriday.WithNoExtensions())
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Sanitize untrusted content
 | 
			
		||||
 | 
			
		||||
Blackfriday itself does nothing to protect against malicious content. If you are
 | 
			
		||||
dealing with user-supplied markdown, we recommend running Blackfriday's output
 | 
			
		||||
through HTML sanitizer such as [Bluemonday][5].
 | 
			
		||||
 | 
			
		||||
Here's an example of simple usage of Blackfriday together with Bluemonday:
 | 
			
		||||
 | 
			
		||||
```go
 | 
			
		||||
import (
 | 
			
		||||
    "github.com/microcosm-cc/bluemonday"
 | 
			
		||||
    "github.com/russross/blackfriday"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ...
 | 
			
		||||
unsafe := blackfriday.Run(input)
 | 
			
		||||
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Custom options
 | 
			
		||||
 | 
			
		||||
If you want to customize the set of options, use `blackfriday.WithExtensions`,
 | 
			
		||||
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
 | 
			
		||||
 | 
			
		||||
You can also check out `blackfriday-tool` for a more complete example
 | 
			
		||||
of how to use it. Download and install it using:
 | 
			
		||||
 | 
			
		||||
    go get github.com/russross/blackfriday-tool
 | 
			
		||||
 | 
			
		||||
This is a simple command-line tool that allows you to process a
 | 
			
		||||
markdown file using a standalone program.  You can also browse the
 | 
			
		||||
source directly on github if you are just looking for some example
 | 
			
		||||
code:
 | 
			
		||||
 | 
			
		||||
* <http://github.com/russross/blackfriday-tool>
 | 
			
		||||
 | 
			
		||||
Note that if you have not already done so, installing
 | 
			
		||||
`blackfriday-tool` will be sufficient to download and install
 | 
			
		||||
blackfriday in addition to the tool itself. The tool binary will be
 | 
			
		||||
installed in `$GOPATH/bin`.  This is a statically-linked binary that
 | 
			
		||||
can be copied to wherever you need it without worrying about
 | 
			
		||||
dependencies and library versions.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Features
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
All features of Sundown are supported, including:
 | 
			
		||||
 | 
			
		||||
*   **Compatibility**. The Markdown v1.0.3 test suite passes with
 | 
			
		||||
    the `--tidy` option.  Without `--tidy`, the differences are
 | 
			
		||||
    mostly in whitespace and entity escaping, where blackfriday is
 | 
			
		||||
    more consistent and cleaner.
 | 
			
		||||
 | 
			
		||||
*   **Common extensions**, including table support, fenced code
 | 
			
		||||
    blocks, autolinks, strikethroughs, non-strict emphasis, etc.
 | 
			
		||||
 | 
			
		||||
*   **Safety**. Blackfriday is paranoid when parsing, making it safe
 | 
			
		||||
    to feed untrusted user input without fear of bad things
 | 
			
		||||
    happening. The test suite stress tests this and there are no
 | 
			
		||||
    known inputs that make it crash.  If you find one, please let me
 | 
			
		||||
    know and send me the input that does it.
 | 
			
		||||
 | 
			
		||||
    NOTE: "safety" in this context means *runtime safety only*. In order to
 | 
			
		||||
    protect yourself against JavaScript injection in untrusted content, see
 | 
			
		||||
    [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
 | 
			
		||||
 | 
			
		||||
*   **Fast processing**. It is fast enough to render on-demand in
 | 
			
		||||
    most web applications without having to cache the output.
 | 
			
		||||
 | 
			
		||||
*   **Thread safety**. You can run multiple parsers in different
 | 
			
		||||
    goroutines without ill effect. There is no dependence on global
 | 
			
		||||
    shared state.
 | 
			
		||||
 | 
			
		||||
*   **Minimal dependencies**. Blackfriday only depends on standard
 | 
			
		||||
    library packages in Go. The source code is pretty
 | 
			
		||||
    self-contained, so it is easy to add to any project, including
 | 
			
		||||
    Google App Engine projects.
 | 
			
		||||
 | 
			
		||||
*   **Standards compliant**. Output successfully validates using the
 | 
			
		||||
    W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Extensions
 | 
			
		||||
----------
 | 
			
		||||
 | 
			
		||||
In addition to the standard markdown syntax, this package
 | 
			
		||||
implements the following extensions:
 | 
			
		||||
 | 
			
		||||
*   **Intra-word emphasis supression**. The `_` character is
 | 
			
		||||
    commonly used inside words when discussing code, so having
 | 
			
		||||
    markdown interpret it as an emphasis command is usually the
 | 
			
		||||
    wrong thing. Blackfriday lets you treat all emphasis markers as
 | 
			
		||||
    normal characters when they occur inside a word.
 | 
			
		||||
 | 
			
		||||
*   **Tables**. Tables can be created by drawing them in the input
 | 
			
		||||
    using a simple syntax:
 | 
			
		||||
 | 
			
		||||
    ```
 | 
			
		||||
    Name    | Age
 | 
			
		||||
    --------|------
 | 
			
		||||
    Bob     | 27
 | 
			
		||||
    Alice   | 23
 | 
			
		||||
    ```
 | 
			
		||||
 | 
			
		||||
*   **Fenced code blocks**. In addition to the normal 4-space
 | 
			
		||||
    indentation to mark code blocks, you can explicitly mark them
 | 
			
		||||
    and supply a language (to make syntax highlighting simple). Just
 | 
			
		||||
    mark it like this:
 | 
			
		||||
 | 
			
		||||
        ```go
 | 
			
		||||
        func getTrue() bool {
 | 
			
		||||
            return true
 | 
			
		||||
        }
 | 
			
		||||
        ```
 | 
			
		||||
 | 
			
		||||
    You can use 3 or more backticks to mark the beginning of the
 | 
			
		||||
    block, and the same number to mark the end of the block.
 | 
			
		||||
 | 
			
		||||
*   **Definition lists**. A simple definition list is made of a single-line
 | 
			
		||||
    term followed by a colon and the definition for that term.
 | 
			
		||||
 | 
			
		||||
        Cat
 | 
			
		||||
        : Fluffy animal everyone likes
 | 
			
		||||
 | 
			
		||||
        Internet
 | 
			
		||||
        : Vector of transmission for pictures of cats
 | 
			
		||||
 | 
			
		||||
    Terms must be separated from the previous definition by a blank line.
 | 
			
		||||
 | 
			
		||||
*   **Footnotes**. A marker in the text that will become a superscript number;
 | 
			
		||||
    a footnote definition that will be placed in a list of footnotes at the
 | 
			
		||||
    end of the document. A footnote looks like this:
 | 
			
		||||
 | 
			
		||||
        This is a footnote.[^1]
 | 
			
		||||
 | 
			
		||||
        [^1]: the footnote text.
 | 
			
		||||
 | 
			
		||||
*   **Autolinking**. Blackfriday can find URLs that have not been
 | 
			
		||||
    explicitly marked as links and turn them into links.
 | 
			
		||||
 | 
			
		||||
*   **Strikethrough**. Use two tildes (`~~`) to mark text that
 | 
			
		||||
    should be crossed out.
 | 
			
		||||
 | 
			
		||||
*   **Hard line breaks**. With this extension enabled newlines in the input
 | 
			
		||||
    translate into line breaks in the output. This extension is off by default.
 | 
			
		||||
 | 
			
		||||
*   **Smart quotes**. Smartypants-style punctuation substitution is
 | 
			
		||||
    supported, turning normal double- and single-quote marks into
 | 
			
		||||
    curly quotes, etc.
 | 
			
		||||
 | 
			
		||||
*   **LaTeX-style dash parsing** is an additional option, where `--`
 | 
			
		||||
    is translated into `–`, and `---` is translated into
 | 
			
		||||
    `—`. This differs from most smartypants processors, which
 | 
			
		||||
    turn a single hyphen into an ndash and a double hyphen into an
 | 
			
		||||
    mdash.
 | 
			
		||||
 | 
			
		||||
*   **Smart fractions**, where anything that looks like a fraction
 | 
			
		||||
    is translated into suitable HTML (instead of just a few special
 | 
			
		||||
    cases like most smartypant processors). For example, `4/5`
 | 
			
		||||
    becomes `<sup>4</sup>⁄<sub>5</sub>`, which renders as
 | 
			
		||||
    <sup>4</sup>⁄<sub>5</sub>.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Other renderers
 | 
			
		||||
---------------
 | 
			
		||||
 | 
			
		||||
Blackfriday is structured to allow alternative rendering engines. Here
 | 
			
		||||
are a few of note:
 | 
			
		||||
 | 
			
		||||
*   [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.
 | 
			
		||||
 | 
			
		||||
    It's not customizable, and its goal is to produce HTML output
 | 
			
		||||
    equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode),
 | 
			
		||||
    except the rendering is performed locally.
 | 
			
		||||
 | 
			
		||||
*   [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt,
 | 
			
		||||
    but for markdown.
 | 
			
		||||
 | 
			
		||||
*   [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX):
 | 
			
		||||
    renders output as LaTeX.
 | 
			
		||||
 | 
			
		||||
*   [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.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Todo
 | 
			
		||||
----
 | 
			
		||||
 | 
			
		||||
*   More unit testing
 | 
			
		||||
*   Improve unicode support. It does not understand all unicode
 | 
			
		||||
    rules (about what constitutes a letter, a punctuation symbol,
 | 
			
		||||
    etc.), so it may fail to detect word boundaries correctly in
 | 
			
		||||
    some instances. It is safe on all utf-8 input.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
License
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
   [1]: https://daringfireball.net/projects/markdown/ "Markdown"
 | 
			
		||||
   [2]: https://golang.org/ "Go Language"
 | 
			
		||||
   [3]: https://github.com/vmg/sundown "Sundown"
 | 
			
		||||
   [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func"
 | 
			
		||||
   [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday"
 | 
			
		||||
   [6]: https://labix.org/gopkg.in "gopkg.in"
 | 
			
		||||
							
								
								
									
										1590
									
								
								vendor/github.com/russross/blackfriday/v2/block.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1590
									
								
								vendor/github.com/russross/blackfriday/v2/block.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										18
									
								
								vendor/github.com/russross/blackfriday/v2/doc.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								vendor/github.com/russross/blackfriday/v2/doc.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
// Package blackfriday is a markdown processor.
 | 
			
		||||
//
 | 
			
		||||
// It translates plain text with simple formatting rules into an AST, which can
 | 
			
		||||
// then be further processed to HTML (provided by Blackfriday itself) or other
 | 
			
		||||
// formats (provided by the community).
 | 
			
		||||
//
 | 
			
		||||
// The simplest way to invoke Blackfriday is to call the Run function. It will
 | 
			
		||||
// take a text input and produce a text output in HTML (or other format).
 | 
			
		||||
//
 | 
			
		||||
// A slightly more sophisticated way to use Blackfriday is to create a Markdown
 | 
			
		||||
// processor and to call Parse, which returns a syntax tree for the input
 | 
			
		||||
// document. You can leverage Blackfriday's parsing for content extraction from
 | 
			
		||||
// markdown documents. You can assign a custom renderer and set various options
 | 
			
		||||
// to the Markdown processor.
 | 
			
		||||
//
 | 
			
		||||
// If you're interested in calling Blackfriday from command line, see
 | 
			
		||||
// https://github.com/russross/blackfriday-tool.
 | 
			
		||||
package blackfriday
 | 
			
		||||
							
								
								
									
										34
									
								
								vendor/github.com/russross/blackfriday/v2/esc.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/russross/blackfriday/v2/esc.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								vendor/github.com/russross/blackfriday/v2/go.mod
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								vendor/github.com/russross/blackfriday/v2/go.mod
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
module github.com/russross/blackfriday/v2
 | 
			
		||||
							
								
								
									
										949
									
								
								vendor/github.com/russross/blackfriday/v2/html.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										949
									
								
								vendor/github.com/russross/blackfriday/v2/html.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,949 @@
 | 
			
		|||
//
 | 
			
		||||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// HTML rendering backend
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HTMLFlags control optional behavior of HTML renderer.
 | 
			
		||||
type HTMLFlags int
 | 
			
		||||
 | 
			
		||||
// HTML renderer configuration options.
 | 
			
		||||
const (
 | 
			
		||||
	HTMLFlagsNone           HTMLFlags = 0
 | 
			
		||||
	SkipHTML                HTMLFlags = 1 << iota // Skip preformatted HTML blocks
 | 
			
		||||
	SkipImages                                    // Skip embedded images
 | 
			
		||||
	SkipLinks                                     // Skip all links
 | 
			
		||||
	Safelink                                      // Only link to trusted protocols
 | 
			
		||||
	NofollowLinks                                 // Only link with rel="nofollow"
 | 
			
		||||
	NoreferrerLinks                               // Only link with rel="noreferrer"
 | 
			
		||||
	NoopenerLinks                                 // Only link with rel="noopener"
 | 
			
		||||
	HrefTargetBlank                               // Add a blank target
 | 
			
		||||
	CompletePage                                  // Generate a complete HTML page
 | 
			
		||||
	UseXHTML                                      // Generate XHTML output instead of HTML
 | 
			
		||||
	FootnoteReturnLinks                           // Generate a link at the end of a footnote to return to the source
 | 
			
		||||
	Smartypants                                   // Enable smart punctuation substitutions
 | 
			
		||||
	SmartypantsFractions                          // Enable smart fractions (with Smartypants)
 | 
			
		||||
	SmartypantsDashes                             // Enable smart dashes (with Smartypants)
 | 
			
		||||
	SmartypantsLatexDashes                        // Enable LaTeX-style dashes (with Smartypants)
 | 
			
		||||
	SmartypantsAngledQuotes                       // Enable angled double quotes (with Smartypants) for double quotes rendering
 | 
			
		||||
	SmartypantsQuotesNBSP                         // Enable « French guillemets » (with Smartypants)
 | 
			
		||||
	TOC                                           // Generate a table of contents
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
 | 
			
		||||
		processingInstruction + "|" + declaration + "|" + cdata + ")"
 | 
			
		||||
	closeTag              = "</" + tagName + "\\s*[>]"
 | 
			
		||||
	openTag               = "<" + tagName + attribute + "*" + "\\s*/?>"
 | 
			
		||||
	attribute             = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
 | 
			
		||||
	attributeValue        = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
 | 
			
		||||
	attributeValueSpec    = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
 | 
			
		||||
	attributeName         = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
 | 
			
		||||
	cdata                 = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
 | 
			
		||||
	declaration           = "<![A-Z]+" + "\\s+[^>]*>"
 | 
			
		||||
	doubleQuotedValue     = "\"[^\"]*\""
 | 
			
		||||
	htmlComment           = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
 | 
			
		||||
	processingInstruction = "[<][?].*?[?][>]"
 | 
			
		||||
	singleQuotedValue     = "'[^']*'"
 | 
			
		||||
	tagName               = "[A-Za-z][A-Za-z0-9-]*"
 | 
			
		||||
	unquotedValue         = "[^\"'=<>`\\x00-\\x20]+"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HTMLRendererParameters is a collection of supplementary parameters tweaking
 | 
			
		||||
// the behavior of various parts of HTML renderer.
 | 
			
		||||
type HTMLRendererParameters struct {
 | 
			
		||||
	// Prepend this text to each relative URL.
 | 
			
		||||
	AbsolutePrefix string
 | 
			
		||||
	// Add this text to each footnote anchor, to ensure uniqueness.
 | 
			
		||||
	FootnoteAnchorPrefix string
 | 
			
		||||
	// Show this text inside the <a> tag for a footnote return link, if the
 | 
			
		||||
	// HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
 | 
			
		||||
	// <sup>[return]</sup> is used.
 | 
			
		||||
	FootnoteReturnLinkContents string
 | 
			
		||||
	// If set, add this text to the front of each Heading ID, to ensure
 | 
			
		||||
	// uniqueness.
 | 
			
		||||
	HeadingIDPrefix string
 | 
			
		||||
	// If set, add this text to the back of each Heading ID, to ensure uniqueness.
 | 
			
		||||
	HeadingIDSuffix string
 | 
			
		||||
	// Increase heading levels: if the offset is 1, <h1> becomes <h2> etc.
 | 
			
		||||
	// Negative offset is also valid.
 | 
			
		||||
	// Resulting levels are clipped between 1 and 6.
 | 
			
		||||
	HeadingLevelOffset int
 | 
			
		||||
 | 
			
		||||
	Title string // Document title (used if CompletePage is set)
 | 
			
		||||
	CSS   string // Optional CSS file URL (used if CompletePage is set)
 | 
			
		||||
	Icon  string // Optional icon file URL (used if CompletePage is set)
 | 
			
		||||
 | 
			
		||||
	Flags HTMLFlags // Flags allow customizing this renderer's behavior
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HTMLRenderer is a type that implements the Renderer interface for HTML output.
 | 
			
		||||
//
 | 
			
		||||
// Do not create this directly, instead use the NewHTMLRenderer function.
 | 
			
		||||
type HTMLRenderer struct {
 | 
			
		||||
	HTMLRendererParameters
 | 
			
		||||
 | 
			
		||||
	closeTag string // how to end singleton tags: either " />" or ">"
 | 
			
		||||
 | 
			
		||||
	// Track heading IDs to prevent ID collision in a single generation.
 | 
			
		||||
	headingIDs map[string]int
 | 
			
		||||
 | 
			
		||||
	lastOutputLen int
 | 
			
		||||
	disableTags   int
 | 
			
		||||
 | 
			
		||||
	sr *SPRenderer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	xhtmlClose = " />"
 | 
			
		||||
	htmlClose  = ">"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NewHTMLRenderer creates and configures an HTMLRenderer object, which
 | 
			
		||||
// satisfies the Renderer interface.
 | 
			
		||||
func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
 | 
			
		||||
	// configure the rendering engine
 | 
			
		||||
	closeTag := htmlClose
 | 
			
		||||
	if params.Flags&UseXHTML != 0 {
 | 
			
		||||
		closeTag = xhtmlClose
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if params.FootnoteReturnLinkContents == "" {
 | 
			
		||||
		params.FootnoteReturnLinkContents = `<sup>[return]</sup>`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &HTMLRenderer{
 | 
			
		||||
		HTMLRendererParameters: params,
 | 
			
		||||
 | 
			
		||||
		closeTag:   closeTag,
 | 
			
		||||
		headingIDs: make(map[string]int),
 | 
			
		||||
 | 
			
		||||
		sr: NewSmartypantsRenderer(params.Flags),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isHTMLTag(tag []byte, tagname string) bool {
 | 
			
		||||
	found, _ := findHTMLTagPos(tag, tagname)
 | 
			
		||||
	return found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Look for a character, but ignore it when it's in any kind of quotes, it
 | 
			
		||||
// might be JavaScript
 | 
			
		||||
func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
 | 
			
		||||
	inSingleQuote := false
 | 
			
		||||
	inDoubleQuote := false
 | 
			
		||||
	inGraveQuote := false
 | 
			
		||||
	i := start
 | 
			
		||||
	for i < len(html) {
 | 
			
		||||
		switch {
 | 
			
		||||
		case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
 | 
			
		||||
			return i
 | 
			
		||||
		case html[i] == '\'':
 | 
			
		||||
			inSingleQuote = !inSingleQuote
 | 
			
		||||
		case html[i] == '"':
 | 
			
		||||
			inDoubleQuote = !inDoubleQuote
 | 
			
		||||
		case html[i] == '`':
 | 
			
		||||
			inGraveQuote = !inGraveQuote
 | 
			
		||||
		}
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	return start
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
 | 
			
		||||
	i := 0
 | 
			
		||||
	if i < len(tag) && tag[0] != '<' {
 | 
			
		||||
		return false, -1
 | 
			
		||||
	}
 | 
			
		||||
	i++
 | 
			
		||||
	i = skipSpace(tag, i)
 | 
			
		||||
 | 
			
		||||
	if i < len(tag) && tag[i] == '/' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	i = skipSpace(tag, i)
 | 
			
		||||
	j := 0
 | 
			
		||||
	for ; i < len(tag); i, j = i+1, j+1 {
 | 
			
		||||
		if j >= len(tagname) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if strings.ToLower(string(tag[i]))[0] != tagname[j] {
 | 
			
		||||
			return false, -1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if i == len(tag) {
 | 
			
		||||
		return false, -1
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
 | 
			
		||||
	if rightAngle >= i {
 | 
			
		||||
		return true, rightAngle
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false, -1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func skipSpace(tag []byte, i int) int {
 | 
			
		||||
	for i < len(tag) && isspace(tag[i]) {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isRelativeLink(link []byte) (yes bool) {
 | 
			
		||||
	// a tag begin with '#'
 | 
			
		||||
	if link[0] == '#' {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// link begin with '/' but not '//', the second maybe a protocol relative link
 | 
			
		||||
	if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// only the root '/'
 | 
			
		||||
	if len(link) == 1 && link[0] == '/' {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// current directory : begin with "./"
 | 
			
		||||
	if bytes.HasPrefix(link, []byte("./")) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// parent directory : begin with "../"
 | 
			
		||||
	if bytes.HasPrefix(link, []byte("../")) {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
 | 
			
		||||
	for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
 | 
			
		||||
		tmp := fmt.Sprintf("%s-%d", id, count+1)
 | 
			
		||||
 | 
			
		||||
		if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
 | 
			
		||||
			r.headingIDs[id] = count + 1
 | 
			
		||||
			id = tmp
 | 
			
		||||
		} else {
 | 
			
		||||
			id = id + "-1"
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, found := r.headingIDs[id]; !found {
 | 
			
		||||
		r.headingIDs[id] = 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
 | 
			
		||||
	if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
 | 
			
		||||
		newDest := r.AbsolutePrefix
 | 
			
		||||
		if link[0] != '/' {
 | 
			
		||||
			newDest += "/"
 | 
			
		||||
		}
 | 
			
		||||
		newDest += string(link)
 | 
			
		||||
		return []byte(newDest)
 | 
			
		||||
	}
 | 
			
		||||
	return link
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
 | 
			
		||||
	if isRelativeLink(link) {
 | 
			
		||||
		return attrs
 | 
			
		||||
	}
 | 
			
		||||
	val := []string{}
 | 
			
		||||
	if flags&NofollowLinks != 0 {
 | 
			
		||||
		val = append(val, "nofollow")
 | 
			
		||||
	}
 | 
			
		||||
	if flags&NoreferrerLinks != 0 {
 | 
			
		||||
		val = append(val, "noreferrer")
 | 
			
		||||
	}
 | 
			
		||||
	if flags&NoopenerLinks != 0 {
 | 
			
		||||
		val = append(val, "noopener")
 | 
			
		||||
	}
 | 
			
		||||
	if flags&HrefTargetBlank != 0 {
 | 
			
		||||
		attrs = append(attrs, "target=\"_blank\"")
 | 
			
		||||
	}
 | 
			
		||||
	if len(val) == 0 {
 | 
			
		||||
		return attrs
 | 
			
		||||
	}
 | 
			
		||||
	attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
 | 
			
		||||
	return append(attrs, attr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isMailto(link []byte) bool {
 | 
			
		||||
	return bytes.HasPrefix(link, []byte("mailto:"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func needSkipLink(flags HTMLFlags, dest []byte) bool {
 | 
			
		||||
	if flags&SkipLinks != 0 {
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isSmartypantable(node *Node) bool {
 | 
			
		||||
	pt := node.Parent.Type
 | 
			
		||||
	return pt != Link && pt != CodeBlock && pt != Code
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func appendLanguageAttr(attrs []string, info []byte) []string {
 | 
			
		||||
	if len(info) == 0 {
 | 
			
		||||
		return attrs
 | 
			
		||||
	}
 | 
			
		||||
	endOfLang := bytes.IndexAny(info, "\t ")
 | 
			
		||||
	if endOfLang < 0 {
 | 
			
		||||
		endOfLang = len(info)
 | 
			
		||||
	}
 | 
			
		||||
	return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
 | 
			
		||||
	w.Write(name)
 | 
			
		||||
	if len(attrs) > 0 {
 | 
			
		||||
		w.Write(spaceBytes)
 | 
			
		||||
		w.Write([]byte(strings.Join(attrs, " ")))
 | 
			
		||||
	}
 | 
			
		||||
	w.Write(gtBytes)
 | 
			
		||||
	r.lastOutputLen = 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func footnoteRef(prefix string, node *Node) []byte {
 | 
			
		||||
	urlFrag := prefix + string(slugify(node.Destination))
 | 
			
		||||
	anchor := fmt.Sprintf(`<a href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
 | 
			
		||||
	return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func footnoteItem(prefix string, slug []byte) []byte {
 | 
			
		||||
	return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
 | 
			
		||||
	const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
 | 
			
		||||
	return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func itemOpenCR(node *Node) bool {
 | 
			
		||||
	if node.Prev == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	ld := node.Parent.ListData
 | 
			
		||||
	return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func skipParagraphTags(node *Node) bool {
 | 
			
		||||
	grandparent := node.Parent.Parent
 | 
			
		||||
	if grandparent == nil || grandparent.Type != List {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
 | 
			
		||||
	return grandparent.Type == List && tightOrTerm
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cellAlignment(align CellAlignFlags) string {
 | 
			
		||||
	switch align {
 | 
			
		||||
	case TableAlignmentLeft:
 | 
			
		||||
		return "left"
 | 
			
		||||
	case TableAlignmentRight:
 | 
			
		||||
		return "right"
 | 
			
		||||
	case TableAlignmentCenter:
 | 
			
		||||
		return "center"
 | 
			
		||||
	default:
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) out(w io.Writer, text []byte) {
 | 
			
		||||
	if r.disableTags > 0 {
 | 
			
		||||
		w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
 | 
			
		||||
	} else {
 | 
			
		||||
		w.Write(text)
 | 
			
		||||
	}
 | 
			
		||||
	r.lastOutputLen = len(text)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) cr(w io.Writer) {
 | 
			
		||||
	if r.lastOutputLen > 0 {
 | 
			
		||||
		r.out(w, nlBytes)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	nlBytes    = []byte{'\n'}
 | 
			
		||||
	gtBytes    = []byte{'>'}
 | 
			
		||||
	spaceBytes = []byte{' '}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	brTag              = []byte("<br>")
 | 
			
		||||
	brXHTMLTag         = []byte("<br />")
 | 
			
		||||
	emTag              = []byte("<em>")
 | 
			
		||||
	emCloseTag         = []byte("</em>")
 | 
			
		||||
	strongTag          = []byte("<strong>")
 | 
			
		||||
	strongCloseTag     = []byte("</strong>")
 | 
			
		||||
	delTag             = []byte("<del>")
 | 
			
		||||
	delCloseTag        = []byte("</del>")
 | 
			
		||||
	ttTag              = []byte("<tt>")
 | 
			
		||||
	ttCloseTag         = []byte("</tt>")
 | 
			
		||||
	aTag               = []byte("<a")
 | 
			
		||||
	aCloseTag          = []byte("</a>")
 | 
			
		||||
	preTag             = []byte("<pre>")
 | 
			
		||||
	preCloseTag        = []byte("</pre>")
 | 
			
		||||
	codeTag            = []byte("<code>")
 | 
			
		||||
	codeCloseTag       = []byte("</code>")
 | 
			
		||||
	pTag               = []byte("<p>")
 | 
			
		||||
	pCloseTag          = []byte("</p>")
 | 
			
		||||
	blockquoteTag      = []byte("<blockquote>")
 | 
			
		||||
	blockquoteCloseTag = []byte("</blockquote>")
 | 
			
		||||
	hrTag              = []byte("<hr>")
 | 
			
		||||
	hrXHTMLTag         = []byte("<hr />")
 | 
			
		||||
	ulTag              = []byte("<ul>")
 | 
			
		||||
	ulCloseTag         = []byte("</ul>")
 | 
			
		||||
	olTag              = []byte("<ol>")
 | 
			
		||||
	olCloseTag         = []byte("</ol>")
 | 
			
		||||
	dlTag              = []byte("<dl>")
 | 
			
		||||
	dlCloseTag         = []byte("</dl>")
 | 
			
		||||
	liTag              = []byte("<li>")
 | 
			
		||||
	liCloseTag         = []byte("</li>")
 | 
			
		||||
	ddTag              = []byte("<dd>")
 | 
			
		||||
	ddCloseTag         = []byte("</dd>")
 | 
			
		||||
	dtTag              = []byte("<dt>")
 | 
			
		||||
	dtCloseTag         = []byte("</dt>")
 | 
			
		||||
	tableTag           = []byte("<table>")
 | 
			
		||||
	tableCloseTag      = []byte("</table>")
 | 
			
		||||
	tdTag              = []byte("<td")
 | 
			
		||||
	tdCloseTag         = []byte("</td>")
 | 
			
		||||
	thTag              = []byte("<th")
 | 
			
		||||
	thCloseTag         = []byte("</th>")
 | 
			
		||||
	theadTag           = []byte("<thead>")
 | 
			
		||||
	theadCloseTag      = []byte("</thead>")
 | 
			
		||||
	tbodyTag           = []byte("<tbody>")
 | 
			
		||||
	tbodyCloseTag      = []byte("</tbody>")
 | 
			
		||||
	trTag              = []byte("<tr>")
 | 
			
		||||
	trCloseTag         = []byte("</tr>")
 | 
			
		||||
	h1Tag              = []byte("<h1")
 | 
			
		||||
	h1CloseTag         = []byte("</h1>")
 | 
			
		||||
	h2Tag              = []byte("<h2")
 | 
			
		||||
	h2CloseTag         = []byte("</h2>")
 | 
			
		||||
	h3Tag              = []byte("<h3")
 | 
			
		||||
	h3CloseTag         = []byte("</h3>")
 | 
			
		||||
	h4Tag              = []byte("<h4")
 | 
			
		||||
	h4CloseTag         = []byte("</h4>")
 | 
			
		||||
	h5Tag              = []byte("<h5")
 | 
			
		||||
	h5CloseTag         = []byte("</h5>")
 | 
			
		||||
	h6Tag              = []byte("<h6")
 | 
			
		||||
	h6CloseTag         = []byte("</h6>")
 | 
			
		||||
 | 
			
		||||
	footnotesDivBytes      = []byte("\n<div class=\"footnotes\">\n\n")
 | 
			
		||||
	footnotesCloseDivBytes = []byte("\n</div>\n")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func headingTagsFromLevel(level int) ([]byte, []byte) {
 | 
			
		||||
	if level <= 1 {
 | 
			
		||||
		return h1Tag, h1CloseTag
 | 
			
		||||
	}
 | 
			
		||||
	switch level {
 | 
			
		||||
	case 2:
 | 
			
		||||
		return h2Tag, h2CloseTag
 | 
			
		||||
	case 3:
 | 
			
		||||
		return h3Tag, h3CloseTag
 | 
			
		||||
	case 4:
 | 
			
		||||
		return h4Tag, h4CloseTag
 | 
			
		||||
	case 5:
 | 
			
		||||
		return h5Tag, h5CloseTag
 | 
			
		||||
	}
 | 
			
		||||
	return h6Tag, h6CloseTag
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) outHRTag(w io.Writer) {
 | 
			
		||||
	if r.Flags&UseXHTML == 0 {
 | 
			
		||||
		r.out(w, hrTag)
 | 
			
		||||
	} else {
 | 
			
		||||
		r.out(w, hrXHTMLTag)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RenderNode is a default renderer of a single node of a syntax tree. For
 | 
			
		||||
// block nodes it will be called twice: first time with entering=true, second
 | 
			
		||||
// time with entering=false, so that it could know when it's working on an open
 | 
			
		||||
// tag and when on close. It writes the result to w.
 | 
			
		||||
//
 | 
			
		||||
// The return value is a way to tell the calling walker to adjust its walk
 | 
			
		||||
// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
 | 
			
		||||
// can ask the walker to skip a subtree of this node by returning SkipChildren.
 | 
			
		||||
// The typical behavior is to return GoToNext, which asks for the usual
 | 
			
		||||
// traversal to the next node.
 | 
			
		||||
func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
 | 
			
		||||
	attrs := []string{}
 | 
			
		||||
	switch node.Type {
 | 
			
		||||
	case Text:
 | 
			
		||||
		if r.Flags&Smartypants != 0 {
 | 
			
		||||
			var tmp bytes.Buffer
 | 
			
		||||
			escapeHTML(&tmp, node.Literal)
 | 
			
		||||
			r.sr.Process(w, tmp.Bytes())
 | 
			
		||||
		} else {
 | 
			
		||||
			if node.Parent.Type == Link {
 | 
			
		||||
				escLink(w, node.Literal)
 | 
			
		||||
			} else {
 | 
			
		||||
				escapeHTML(w, node.Literal)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case Softbreak:
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
		// TODO: make it configurable via out(renderer.softbreak)
 | 
			
		||||
	case Hardbreak:
 | 
			
		||||
		if r.Flags&UseXHTML == 0 {
 | 
			
		||||
			r.out(w, brTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, brXHTMLTag)
 | 
			
		||||
		}
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
	case Emph:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.out(w, emTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, emCloseTag)
 | 
			
		||||
		}
 | 
			
		||||
	case Strong:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.out(w, strongTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, strongCloseTag)
 | 
			
		||||
		}
 | 
			
		||||
	case Del:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.out(w, delTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, delCloseTag)
 | 
			
		||||
		}
 | 
			
		||||
	case HTMLSpan:
 | 
			
		||||
		if r.Flags&SkipHTML != 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		r.out(w, node.Literal)
 | 
			
		||||
	case Link:
 | 
			
		||||
		// mark it but don't link it if it is not a safe link: no smartypants
 | 
			
		||||
		dest := node.LinkData.Destination
 | 
			
		||||
		if needSkipLink(r.Flags, dest) {
 | 
			
		||||
			if entering {
 | 
			
		||||
				r.out(w, ttTag)
 | 
			
		||||
			} else {
 | 
			
		||||
				r.out(w, ttCloseTag)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			if entering {
 | 
			
		||||
				dest = r.addAbsPrefix(dest)
 | 
			
		||||
				var hrefBuf bytes.Buffer
 | 
			
		||||
				hrefBuf.WriteString("href=\"")
 | 
			
		||||
				escLink(&hrefBuf, dest)
 | 
			
		||||
				hrefBuf.WriteByte('"')
 | 
			
		||||
				attrs = append(attrs, hrefBuf.String())
 | 
			
		||||
				if node.NoteID != 0 {
 | 
			
		||||
					r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				attrs = appendLinkAttrs(attrs, r.Flags, dest)
 | 
			
		||||
				if len(node.LinkData.Title) > 0 {
 | 
			
		||||
					var titleBuff bytes.Buffer
 | 
			
		||||
					titleBuff.WriteString("title=\"")
 | 
			
		||||
					escapeHTML(&titleBuff, node.LinkData.Title)
 | 
			
		||||
					titleBuff.WriteByte('"')
 | 
			
		||||
					attrs = append(attrs, titleBuff.String())
 | 
			
		||||
				}
 | 
			
		||||
				r.tag(w, aTag, attrs)
 | 
			
		||||
			} else {
 | 
			
		||||
				if node.NoteID != 0 {
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				r.out(w, aCloseTag)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case Image:
 | 
			
		||||
		if r.Flags&SkipImages != 0 {
 | 
			
		||||
			return SkipChildren
 | 
			
		||||
		}
 | 
			
		||||
		if entering {
 | 
			
		||||
			dest := node.LinkData.Destination
 | 
			
		||||
			dest = r.addAbsPrefix(dest)
 | 
			
		||||
			if r.disableTags == 0 {
 | 
			
		||||
				//if options.safe && potentiallyUnsafe(dest) {
 | 
			
		||||
				//out(w, `<img src="" alt="`)
 | 
			
		||||
				//} else {
 | 
			
		||||
				r.out(w, []byte(`<img src="`))
 | 
			
		||||
				escLink(w, dest)
 | 
			
		||||
				r.out(w, []byte(`" alt="`))
 | 
			
		||||
				//}
 | 
			
		||||
			}
 | 
			
		||||
			r.disableTags++
 | 
			
		||||
		} else {
 | 
			
		||||
			r.disableTags--
 | 
			
		||||
			if r.disableTags == 0 {
 | 
			
		||||
				if node.LinkData.Title != nil {
 | 
			
		||||
					r.out(w, []byte(`" title="`))
 | 
			
		||||
					escapeHTML(w, node.LinkData.Title)
 | 
			
		||||
				}
 | 
			
		||||
				r.out(w, []byte(`" />`))
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case Code:
 | 
			
		||||
		r.out(w, codeTag)
 | 
			
		||||
		escapeHTML(w, node.Literal)
 | 
			
		||||
		r.out(w, codeCloseTag)
 | 
			
		||||
	case Document:
 | 
			
		||||
		break
 | 
			
		||||
	case Paragraph:
 | 
			
		||||
		if skipParagraphTags(node) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		if entering {
 | 
			
		||||
			// TODO: untangle this clusterfuck about when the newlines need
 | 
			
		||||
			// to be added and when not.
 | 
			
		||||
			if node.Prev != nil {
 | 
			
		||||
				switch node.Prev.Type {
 | 
			
		||||
				case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
 | 
			
		||||
					r.cr(w)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if node.Parent.Type == BlockQuote && node.Prev == nil {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			r.out(w, pTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, pCloseTag)
 | 
			
		||||
			if !(node.Parent.Type == Item && node.Next == nil) {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case BlockQuote:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			r.out(w, blockquoteTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, blockquoteCloseTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case HTMLBlock:
 | 
			
		||||
		if r.Flags&SkipHTML != 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
		r.out(w, node.Literal)
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
	case Heading:
 | 
			
		||||
		headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level
 | 
			
		||||
		openTag, closeTag := headingTagsFromLevel(headingLevel)
 | 
			
		||||
		if entering {
 | 
			
		||||
			if node.IsTitleblock {
 | 
			
		||||
				attrs = append(attrs, `class="title"`)
 | 
			
		||||
			}
 | 
			
		||||
			if node.HeadingID != "" {
 | 
			
		||||
				id := r.ensureUniqueHeadingID(node.HeadingID)
 | 
			
		||||
				if r.HeadingIDPrefix != "" {
 | 
			
		||||
					id = r.HeadingIDPrefix + id
 | 
			
		||||
				}
 | 
			
		||||
				if r.HeadingIDSuffix != "" {
 | 
			
		||||
					id = id + r.HeadingIDSuffix
 | 
			
		||||
				}
 | 
			
		||||
				attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
 | 
			
		||||
			}
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			r.tag(w, openTag, attrs)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, closeTag)
 | 
			
		||||
			if !(node.Parent.Type == Item && node.Next == nil) {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case HorizontalRule:
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
		r.outHRTag(w)
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
	case List:
 | 
			
		||||
		openTag := ulTag
 | 
			
		||||
		closeTag := ulCloseTag
 | 
			
		||||
		if node.ListFlags&ListTypeOrdered != 0 {
 | 
			
		||||
			openTag = olTag
 | 
			
		||||
			closeTag = olCloseTag
 | 
			
		||||
		}
 | 
			
		||||
		if node.ListFlags&ListTypeDefinition != 0 {
 | 
			
		||||
			openTag = dlTag
 | 
			
		||||
			closeTag = dlCloseTag
 | 
			
		||||
		}
 | 
			
		||||
		if entering {
 | 
			
		||||
			if node.IsFootnotesList {
 | 
			
		||||
				r.out(w, footnotesDivBytes)
 | 
			
		||||
				r.outHRTag(w)
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			if node.Parent.Type == Item && node.Parent.Parent.Tight {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			r.tag(w, openTag[:len(openTag)-1], attrs)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, closeTag)
 | 
			
		||||
			//cr(w)
 | 
			
		||||
			//if node.parent.Type != Item {
 | 
			
		||||
			//	cr(w)
 | 
			
		||||
			//}
 | 
			
		||||
			if node.Parent.Type == Item && node.Next != nil {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			if node.IsFootnotesList {
 | 
			
		||||
				r.out(w, footnotesCloseDivBytes)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case Item:
 | 
			
		||||
		openTag := liTag
 | 
			
		||||
		closeTag := liCloseTag
 | 
			
		||||
		if node.ListFlags&ListTypeDefinition != 0 {
 | 
			
		||||
			openTag = ddTag
 | 
			
		||||
			closeTag = ddCloseTag
 | 
			
		||||
		}
 | 
			
		||||
		if node.ListFlags&ListTypeTerm != 0 {
 | 
			
		||||
			openTag = dtTag
 | 
			
		||||
			closeTag = dtCloseTag
 | 
			
		||||
		}
 | 
			
		||||
		if entering {
 | 
			
		||||
			if itemOpenCR(node) {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			if node.ListData.RefLink != nil {
 | 
			
		||||
				slug := slugify(node.ListData.RefLink)
 | 
			
		||||
				r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			r.out(w, openTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			if node.ListData.RefLink != nil {
 | 
			
		||||
				slug := slugify(node.ListData.RefLink)
 | 
			
		||||
				if r.Flags&FootnoteReturnLinks != 0 {
 | 
			
		||||
					r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			r.out(w, closeTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case CodeBlock:
 | 
			
		||||
		attrs = appendLanguageAttr(attrs, node.Info)
 | 
			
		||||
		r.cr(w)
 | 
			
		||||
		r.out(w, preTag)
 | 
			
		||||
		r.tag(w, codeTag[:len(codeTag)-1], attrs)
 | 
			
		||||
		escapeHTML(w, node.Literal)
 | 
			
		||||
		r.out(w, codeCloseTag)
 | 
			
		||||
		r.out(w, preCloseTag)
 | 
			
		||||
		if node.Parent.Type != Item {
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case Table:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			r.out(w, tableTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, tableCloseTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case TableCell:
 | 
			
		||||
		openTag := tdTag
 | 
			
		||||
		closeTag := tdCloseTag
 | 
			
		||||
		if node.IsHeader {
 | 
			
		||||
			openTag = thTag
 | 
			
		||||
			closeTag = thCloseTag
 | 
			
		||||
		}
 | 
			
		||||
		if entering {
 | 
			
		||||
			align := cellAlignment(node.Align)
 | 
			
		||||
			if align != "" {
 | 
			
		||||
				attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
 | 
			
		||||
			}
 | 
			
		||||
			if node.Prev == nil {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
			r.tag(w, openTag, attrs)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, closeTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case TableHead:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			r.out(w, theadTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, theadCloseTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case TableBody:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			r.out(w, tbodyTag)
 | 
			
		||||
			// XXX: this is to adhere to a rather silly test. Should fix test.
 | 
			
		||||
			if node.FirstChild == nil {
 | 
			
		||||
				r.cr(w)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, tbodyCloseTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	case TableRow:
 | 
			
		||||
		if entering {
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
			r.out(w, trTag)
 | 
			
		||||
		} else {
 | 
			
		||||
			r.out(w, trCloseTag)
 | 
			
		||||
			r.cr(w)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		panic("Unknown node type " + node.Type.String())
 | 
			
		||||
	}
 | 
			
		||||
	return GoToNext
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RenderHeader writes HTML document preamble and TOC if requested.
 | 
			
		||||
func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
 | 
			
		||||
	r.writeDocumentHeader(w)
 | 
			
		||||
	if r.Flags&TOC != 0 {
 | 
			
		||||
		r.writeTOC(w, ast)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RenderFooter writes HTML document footer.
 | 
			
		||||
func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
 | 
			
		||||
	if r.Flags&CompletePage == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	io.WriteString(w, "\n</body>\n</html>\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
 | 
			
		||||
	if r.Flags&CompletePage == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	ending := ""
 | 
			
		||||
	if r.Flags&UseXHTML != 0 {
 | 
			
		||||
		io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
 | 
			
		||||
		io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
 | 
			
		||||
		io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
 | 
			
		||||
		ending = " /"
 | 
			
		||||
	} else {
 | 
			
		||||
		io.WriteString(w, "<!DOCTYPE html>\n")
 | 
			
		||||
		io.WriteString(w, "<html>\n")
 | 
			
		||||
	}
 | 
			
		||||
	io.WriteString(w, "<head>\n")
 | 
			
		||||
	io.WriteString(w, "  <title>")
 | 
			
		||||
	if r.Flags&Smartypants != 0 {
 | 
			
		||||
		r.sr.Process(w, []byte(r.Title))
 | 
			
		||||
	} else {
 | 
			
		||||
		escapeHTML(w, []byte(r.Title))
 | 
			
		||||
	}
 | 
			
		||||
	io.WriteString(w, "</title>\n")
 | 
			
		||||
	io.WriteString(w, "  <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
 | 
			
		||||
	io.WriteString(w, Version)
 | 
			
		||||
	io.WriteString(w, "\"")
 | 
			
		||||
	io.WriteString(w, ending)
 | 
			
		||||
	io.WriteString(w, ">\n")
 | 
			
		||||
	io.WriteString(w, "  <meta charset=\"utf-8\"")
 | 
			
		||||
	io.WriteString(w, ending)
 | 
			
		||||
	io.WriteString(w, ">\n")
 | 
			
		||||
	if r.CSS != "" {
 | 
			
		||||
		io.WriteString(w, "  <link rel=\"stylesheet\" type=\"text/css\" href=\"")
 | 
			
		||||
		escapeHTML(w, []byte(r.CSS))
 | 
			
		||||
		io.WriteString(w, "\"")
 | 
			
		||||
		io.WriteString(w, ending)
 | 
			
		||||
		io.WriteString(w, ">\n")
 | 
			
		||||
	}
 | 
			
		||||
	if r.Icon != "" {
 | 
			
		||||
		io.WriteString(w, "  <link rel=\"icon\" type=\"image/x-icon\" href=\"")
 | 
			
		||||
		escapeHTML(w, []byte(r.Icon))
 | 
			
		||||
		io.WriteString(w, "\"")
 | 
			
		||||
		io.WriteString(w, ending)
 | 
			
		||||
		io.WriteString(w, ">\n")
 | 
			
		||||
	}
 | 
			
		||||
	io.WriteString(w, "</head>\n")
 | 
			
		||||
	io.WriteString(w, "<body>\n\n")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
 | 
			
		||||
	buf := bytes.Buffer{}
 | 
			
		||||
 | 
			
		||||
	inHeading := false
 | 
			
		||||
	tocLevel := 0
 | 
			
		||||
	headingCount := 0
 | 
			
		||||
 | 
			
		||||
	ast.Walk(func(node *Node, entering bool) WalkStatus {
 | 
			
		||||
		if node.Type == Heading && !node.HeadingData.IsTitleblock {
 | 
			
		||||
			inHeading = entering
 | 
			
		||||
			if entering {
 | 
			
		||||
				node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
 | 
			
		||||
				if node.Level == tocLevel {
 | 
			
		||||
					buf.WriteString("</li>\n\n<li>")
 | 
			
		||||
				} else if node.Level < tocLevel {
 | 
			
		||||
					for node.Level < tocLevel {
 | 
			
		||||
						tocLevel--
 | 
			
		||||
						buf.WriteString("</li>\n</ul>")
 | 
			
		||||
					}
 | 
			
		||||
					buf.WriteString("</li>\n\n<li>")
 | 
			
		||||
				} else {
 | 
			
		||||
					for node.Level > tocLevel {
 | 
			
		||||
						tocLevel++
 | 
			
		||||
						buf.WriteString("\n<ul>\n<li>")
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
 | 
			
		||||
				headingCount++
 | 
			
		||||
			} else {
 | 
			
		||||
				buf.WriteString("</a>")
 | 
			
		||||
			}
 | 
			
		||||
			return GoToNext
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if inHeading {
 | 
			
		||||
			return r.RenderNode(&buf, node, entering)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return GoToNext
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	for ; tocLevel > 0; tocLevel-- {
 | 
			
		||||
		buf.WriteString("</li>\n</ul>")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if buf.Len() > 0 {
 | 
			
		||||
		io.WriteString(w, "<nav>\n")
 | 
			
		||||
		w.Write(buf.Bytes())
 | 
			
		||||
		io.WriteString(w, "\n\n</nav>\n")
 | 
			
		||||
	}
 | 
			
		||||
	r.lastOutputLen = buf.Len()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1228
									
								
								vendor/github.com/russross/blackfriday/v2/inline.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1228
									
								
								vendor/github.com/russross/blackfriday/v2/inline.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										950
									
								
								vendor/github.com/russross/blackfriday/v2/markdown.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										950
									
								
								vendor/github.com/russross/blackfriday/v2/markdown.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,950 @@
 | 
			
		|||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Markdown parsing and processing
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Version string of the package. Appears in the rendered document when
 | 
			
		||||
// CompletePage flag is on.
 | 
			
		||||
const Version = "2.0"
 | 
			
		||||
 | 
			
		||||
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
 | 
			
		||||
// extensions.
 | 
			
		||||
type Extensions int
 | 
			
		||||
 | 
			
		||||
// These are the supported markdown parsing extensions.
 | 
			
		||||
// OR these values together to select multiple extensions.
 | 
			
		||||
const (
 | 
			
		||||
	NoExtensions           Extensions = 0
 | 
			
		||||
	NoIntraEmphasis        Extensions = 1 << iota // Ignore emphasis markers inside words
 | 
			
		||||
	Tables                                        // Render tables
 | 
			
		||||
	FencedCode                                    // Render fenced code blocks
 | 
			
		||||
	Autolink                                      // Detect embedded URLs that are not explicitly marked
 | 
			
		||||
	Strikethrough                                 // Strikethrough text using ~~test~~
 | 
			
		||||
	LaxHTMLBlocks                                 // Loosen up HTML block parsing rules
 | 
			
		||||
	SpaceHeadings                                 // Be strict about prefix heading rules
 | 
			
		||||
	HardLineBreak                                 // Translate newlines into line breaks
 | 
			
		||||
	TabSizeEight                                  // Expand tabs to eight spaces instead of four
 | 
			
		||||
	Footnotes                                     // Pandoc-style footnotes
 | 
			
		||||
	NoEmptyLineBeforeBlock                        // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
 | 
			
		||||
	HeadingIDs                                    // specify heading IDs  with {#id}
 | 
			
		||||
	Titleblock                                    // Titleblock ala pandoc
 | 
			
		||||
	AutoHeadingIDs                                // Create the heading ID from the text
 | 
			
		||||
	BackslashLineBreak                            // Translate trailing backslashes into line breaks
 | 
			
		||||
	DefinitionLists                               // Render definition lists
 | 
			
		||||
 | 
			
		||||
	CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants |
 | 
			
		||||
		SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
 | 
			
		||||
 | 
			
		||||
	CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
 | 
			
		||||
		Autolink | Strikethrough | SpaceHeadings | HeadingIDs |
 | 
			
		||||
		BackslashLineBreak | DefinitionLists
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ListType contains bitwise or'ed flags for list and list item objects.
 | 
			
		||||
type ListType int
 | 
			
		||||
 | 
			
		||||
// These are the possible flag values for the ListItem renderer.
 | 
			
		||||
// Multiple flag values may be ORed together.
 | 
			
		||||
// These are mostly of interest if you are writing a new output format.
 | 
			
		||||
const (
 | 
			
		||||
	ListTypeOrdered ListType = 1 << iota
 | 
			
		||||
	ListTypeDefinition
 | 
			
		||||
	ListTypeTerm
 | 
			
		||||
 | 
			
		||||
	ListItemContainsBlock
 | 
			
		||||
	ListItemBeginningOfList // TODO: figure out if this is of any use now
 | 
			
		||||
	ListItemEndOfList
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// CellAlignFlags holds a type of alignment in a table cell.
 | 
			
		||||
type CellAlignFlags int
 | 
			
		||||
 | 
			
		||||
// These are the possible flag values for the table cell renderer.
 | 
			
		||||
// Only a single one of these values will be used; they are not ORed together.
 | 
			
		||||
// These are mostly of interest if you are writing a new output format.
 | 
			
		||||
const (
 | 
			
		||||
	TableAlignmentLeft CellAlignFlags = 1 << iota
 | 
			
		||||
	TableAlignmentRight
 | 
			
		||||
	TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// The size of a tab stop.
 | 
			
		||||
const (
 | 
			
		||||
	TabSizeDefault = 4
 | 
			
		||||
	TabSizeDouble  = 8
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// blockTags is a set of tags that are recognized as HTML block tags.
 | 
			
		||||
// Any of these can be included in markdown text without special escaping.
 | 
			
		||||
var blockTags = map[string]struct{}{
 | 
			
		||||
	"blockquote": {},
 | 
			
		||||
	"del":        {},
 | 
			
		||||
	"div":        {},
 | 
			
		||||
	"dl":         {},
 | 
			
		||||
	"fieldset":   {},
 | 
			
		||||
	"form":       {},
 | 
			
		||||
	"h1":         {},
 | 
			
		||||
	"h2":         {},
 | 
			
		||||
	"h3":         {},
 | 
			
		||||
	"h4":         {},
 | 
			
		||||
	"h5":         {},
 | 
			
		||||
	"h6":         {},
 | 
			
		||||
	"iframe":     {},
 | 
			
		||||
	"ins":        {},
 | 
			
		||||
	"math":       {},
 | 
			
		||||
	"noscript":   {},
 | 
			
		||||
	"ol":         {},
 | 
			
		||||
	"pre":        {},
 | 
			
		||||
	"p":          {},
 | 
			
		||||
	"script":     {},
 | 
			
		||||
	"style":      {},
 | 
			
		||||
	"table":      {},
 | 
			
		||||
	"ul":         {},
 | 
			
		||||
 | 
			
		||||
	// HTML5
 | 
			
		||||
	"address":    {},
 | 
			
		||||
	"article":    {},
 | 
			
		||||
	"aside":      {},
 | 
			
		||||
	"canvas":     {},
 | 
			
		||||
	"figcaption": {},
 | 
			
		||||
	"figure":     {},
 | 
			
		||||
	"footer":     {},
 | 
			
		||||
	"header":     {},
 | 
			
		||||
	"hgroup":     {},
 | 
			
		||||
	"main":       {},
 | 
			
		||||
	"nav":        {},
 | 
			
		||||
	"output":     {},
 | 
			
		||||
	"progress":   {},
 | 
			
		||||
	"section":    {},
 | 
			
		||||
	"video":      {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Renderer is the rendering interface. This is mostly of interest if you are
 | 
			
		||||
// implementing a new rendering format.
 | 
			
		||||
//
 | 
			
		||||
// Only an HTML implementation is provided in this repository, see the README
 | 
			
		||||
// for external implementations.
 | 
			
		||||
type Renderer interface {
 | 
			
		||||
	// RenderNode is the main rendering method. It will be called once for
 | 
			
		||||
	// every leaf node and twice for every non-leaf node (first with
 | 
			
		||||
	// entering=true, then with entering=false). The method should write its
 | 
			
		||||
	// rendition of the node to the supplied writer w.
 | 
			
		||||
	RenderNode(w io.Writer, node *Node, entering bool) WalkStatus
 | 
			
		||||
 | 
			
		||||
	// RenderHeader is a method that allows the renderer to produce some
 | 
			
		||||
	// content preceding the main body of the output document. The header is
 | 
			
		||||
	// understood in the broad sense here. For example, the default HTML
 | 
			
		||||
	// renderer will write not only the HTML document preamble, but also the
 | 
			
		||||
	// table of contents if it was requested.
 | 
			
		||||
	//
 | 
			
		||||
	// The method will be passed an entire document tree, in case a particular
 | 
			
		||||
	// implementation needs to inspect it to produce output.
 | 
			
		||||
	//
 | 
			
		||||
	// The output should be written to the supplied writer w. If your
 | 
			
		||||
	// implementation has no header to write, supply an empty implementation.
 | 
			
		||||
	RenderHeader(w io.Writer, ast *Node)
 | 
			
		||||
 | 
			
		||||
	// RenderFooter is a symmetric counterpart of RenderHeader.
 | 
			
		||||
	RenderFooter(w io.Writer, ast *Node)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Callback functions for inline parsing. One such function is defined
 | 
			
		||||
// for each character that triggers a response when parsing inline data.
 | 
			
		||||
type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node)
 | 
			
		||||
 | 
			
		||||
// Markdown is a type that holds extensions and the runtime state used by
 | 
			
		||||
// Parse, and the renderer. You can not use it directly, construct it with New.
 | 
			
		||||
type Markdown struct {
 | 
			
		||||
	renderer          Renderer
 | 
			
		||||
	referenceOverride ReferenceOverrideFunc
 | 
			
		||||
	refs              map[string]*reference
 | 
			
		||||
	inlineCallback    [256]inlineParser
 | 
			
		||||
	extensions        Extensions
 | 
			
		||||
	nesting           int
 | 
			
		||||
	maxNesting        int
 | 
			
		||||
	insideLink        bool
 | 
			
		||||
 | 
			
		||||
	// Footnotes need to be ordered as well as available to quickly check for
 | 
			
		||||
	// presence. If a ref is also a footnote, it's stored both in refs and here
 | 
			
		||||
	// in notes. Slice is nil if footnotes not enabled.
 | 
			
		||||
	notes []*reference
 | 
			
		||||
 | 
			
		||||
	doc                  *Node
 | 
			
		||||
	tip                  *Node // = doc
 | 
			
		||||
	oldTip               *Node
 | 
			
		||||
	lastMatchedContainer *Node // = doc
 | 
			
		||||
	allClosed            bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Markdown) getRef(refid string) (ref *reference, found bool) {
 | 
			
		||||
	if p.referenceOverride != nil {
 | 
			
		||||
		r, overridden := p.referenceOverride(refid)
 | 
			
		||||
		if overridden {
 | 
			
		||||
			if r == nil {
 | 
			
		||||
				return nil, false
 | 
			
		||||
			}
 | 
			
		||||
			return &reference{
 | 
			
		||||
				link:     []byte(r.Link),
 | 
			
		||||
				title:    []byte(r.Title),
 | 
			
		||||
				noteID:   0,
 | 
			
		||||
				hasBlock: false,
 | 
			
		||||
				text:     []byte(r.Text)}, true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// refs are case insensitive
 | 
			
		||||
	ref, found = p.refs[strings.ToLower(refid)]
 | 
			
		||||
	return ref, found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Markdown) finalize(block *Node) {
 | 
			
		||||
	above := block.Parent
 | 
			
		||||
	block.open = false
 | 
			
		||||
	p.tip = above
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Markdown) addChild(node NodeType, offset uint32) *Node {
 | 
			
		||||
	return p.addExistingChild(NewNode(node), offset)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node {
 | 
			
		||||
	for !p.tip.canContain(node.Type) {
 | 
			
		||||
		p.finalize(p.tip)
 | 
			
		||||
	}
 | 
			
		||||
	p.tip.AppendChild(node)
 | 
			
		||||
	p.tip = node
 | 
			
		||||
	return node
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Markdown) closeUnmatchedBlocks() {
 | 
			
		||||
	if !p.allClosed {
 | 
			
		||||
		for p.oldTip != p.lastMatchedContainer {
 | 
			
		||||
			parent := p.oldTip.Parent
 | 
			
		||||
			p.finalize(p.oldTip)
 | 
			
		||||
			p.oldTip = parent
 | 
			
		||||
		}
 | 
			
		||||
		p.allClosed = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// Public interface
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Reference represents the details of a link.
 | 
			
		||||
// See the documentation in Options for more details on use-case.
 | 
			
		||||
type Reference struct {
 | 
			
		||||
	// Link is usually the URL the reference points to.
 | 
			
		||||
	Link string
 | 
			
		||||
	// Title is the alternate text describing the link in more detail.
 | 
			
		||||
	Title string
 | 
			
		||||
	// Text is the optional text to override the ref with if the syntax used was
 | 
			
		||||
	// [refid][]
 | 
			
		||||
	Text string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReferenceOverrideFunc is expected to be called with a reference string and
 | 
			
		||||
// return either a valid Reference type that the reference string maps to or
 | 
			
		||||
// nil. If overridden is false, the default reference logic will be executed.
 | 
			
		||||
// See the documentation in Options for more details on use-case.
 | 
			
		||||
type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool)
 | 
			
		||||
 | 
			
		||||
// New constructs a Markdown processor. You can use the same With* functions as
 | 
			
		||||
// for Run() to customize parser's behavior and the renderer.
 | 
			
		||||
func New(opts ...Option) *Markdown {
 | 
			
		||||
	var p Markdown
 | 
			
		||||
	for _, opt := range opts {
 | 
			
		||||
		opt(&p)
 | 
			
		||||
	}
 | 
			
		||||
	p.refs = make(map[string]*reference)
 | 
			
		||||
	p.maxNesting = 16
 | 
			
		||||
	p.insideLink = false
 | 
			
		||||
	docNode := NewNode(Document)
 | 
			
		||||
	p.doc = docNode
 | 
			
		||||
	p.tip = docNode
 | 
			
		||||
	p.oldTip = docNode
 | 
			
		||||
	p.lastMatchedContainer = docNode
 | 
			
		||||
	p.allClosed = true
 | 
			
		||||
	// register inline parsers
 | 
			
		||||
	p.inlineCallback[' '] = maybeLineBreak
 | 
			
		||||
	p.inlineCallback['*'] = emphasis
 | 
			
		||||
	p.inlineCallback['_'] = emphasis
 | 
			
		||||
	if p.extensions&Strikethrough != 0 {
 | 
			
		||||
		p.inlineCallback['~'] = emphasis
 | 
			
		||||
	}
 | 
			
		||||
	p.inlineCallback['`'] = codeSpan
 | 
			
		||||
	p.inlineCallback['\n'] = lineBreak
 | 
			
		||||
	p.inlineCallback['['] = link
 | 
			
		||||
	p.inlineCallback['<'] = leftAngle
 | 
			
		||||
	p.inlineCallback['\\'] = escape
 | 
			
		||||
	p.inlineCallback['&'] = entity
 | 
			
		||||
	p.inlineCallback['!'] = maybeImage
 | 
			
		||||
	p.inlineCallback['^'] = maybeInlineFootnote
 | 
			
		||||
	if p.extensions&Autolink != 0 {
 | 
			
		||||
		p.inlineCallback['h'] = maybeAutoLink
 | 
			
		||||
		p.inlineCallback['m'] = maybeAutoLink
 | 
			
		||||
		p.inlineCallback['f'] = maybeAutoLink
 | 
			
		||||
		p.inlineCallback['H'] = maybeAutoLink
 | 
			
		||||
		p.inlineCallback['M'] = maybeAutoLink
 | 
			
		||||
		p.inlineCallback['F'] = maybeAutoLink
 | 
			
		||||
	}
 | 
			
		||||
	if p.extensions&Footnotes != 0 {
 | 
			
		||||
		p.notes = make([]*reference, 0)
 | 
			
		||||
	}
 | 
			
		||||
	return &p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Option customizes the Markdown processor's default behavior.
 | 
			
		||||
type Option func(*Markdown)
 | 
			
		||||
 | 
			
		||||
// WithRenderer allows you to override the default renderer.
 | 
			
		||||
func WithRenderer(r Renderer) Option {
 | 
			
		||||
	return func(p *Markdown) {
 | 
			
		||||
		p.renderer = r
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithExtensions allows you to pick some of the many extensions provided by
 | 
			
		||||
// Blackfriday. You can bitwise OR them.
 | 
			
		||||
func WithExtensions(e Extensions) Option {
 | 
			
		||||
	return func(p *Markdown) {
 | 
			
		||||
		p.extensions = e
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithNoExtensions turns off all extensions and custom behavior.
 | 
			
		||||
func WithNoExtensions() Option {
 | 
			
		||||
	return func(p *Markdown) {
 | 
			
		||||
		p.extensions = NoExtensions
 | 
			
		||||
		p.renderer = NewHTMLRenderer(HTMLRendererParameters{
 | 
			
		||||
			Flags: HTMLFlagsNone,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithRefOverride sets an optional function callback that is called every
 | 
			
		||||
// time a reference is resolved.
 | 
			
		||||
//
 | 
			
		||||
// In Markdown, the link reference syntax can be made to resolve a link to
 | 
			
		||||
// a reference instead of an inline URL, in one of the following ways:
 | 
			
		||||
//
 | 
			
		||||
//  * [link text][refid]
 | 
			
		||||
//  * [refid][]
 | 
			
		||||
//
 | 
			
		||||
// Usually, the refid is defined at the bottom of the Markdown document. If
 | 
			
		||||
// this override function is provided, the refid is passed to the override
 | 
			
		||||
// function first, before consulting the defined refids at the bottom. If
 | 
			
		||||
// the override function indicates an override did not occur, the refids at
 | 
			
		||||
// the bottom will be used to fill in the link details.
 | 
			
		||||
func WithRefOverride(o ReferenceOverrideFunc) Option {
 | 
			
		||||
	return func(p *Markdown) {
 | 
			
		||||
		p.referenceOverride = o
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run is the main entry point to Blackfriday. It parses and renders a
 | 
			
		||||
// block of markdown-encoded text.
 | 
			
		||||
//
 | 
			
		||||
// The simplest invocation of Run takes one argument, input:
 | 
			
		||||
//     output := Run(input)
 | 
			
		||||
// This will parse the input with CommonExtensions enabled and render it with
 | 
			
		||||
// the default HTMLRenderer (with CommonHTMLFlags).
 | 
			
		||||
//
 | 
			
		||||
// Variadic arguments opts can customize the default behavior. Since Markdown
 | 
			
		||||
// type does not contain exported fields, you can not use it directly. Instead,
 | 
			
		||||
// use the With* functions. For example, this will call the most basic
 | 
			
		||||
// functionality, with no extensions:
 | 
			
		||||
//     output := Run(input, WithNoExtensions())
 | 
			
		||||
//
 | 
			
		||||
// You can use any number of With* arguments, even contradicting ones. They
 | 
			
		||||
// will be applied in order of appearance and the latter will override the
 | 
			
		||||
// former:
 | 
			
		||||
//     output := Run(input, WithNoExtensions(), WithExtensions(exts),
 | 
			
		||||
//         WithRenderer(yourRenderer))
 | 
			
		||||
func Run(input []byte, opts ...Option) []byte {
 | 
			
		||||
	r := NewHTMLRenderer(HTMLRendererParameters{
 | 
			
		||||
		Flags: CommonHTMLFlags,
 | 
			
		||||
	})
 | 
			
		||||
	optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)}
 | 
			
		||||
	optList = append(optList, opts...)
 | 
			
		||||
	parser := New(optList...)
 | 
			
		||||
	ast := parser.Parse(input)
 | 
			
		||||
	var buf bytes.Buffer
 | 
			
		||||
	parser.renderer.RenderHeader(&buf, ast)
 | 
			
		||||
	ast.Walk(func(node *Node, entering bool) WalkStatus {
 | 
			
		||||
		return parser.renderer.RenderNode(&buf, node, entering)
 | 
			
		||||
	})
 | 
			
		||||
	parser.renderer.RenderFooter(&buf, ast)
 | 
			
		||||
	return buf.Bytes()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parse is an entry point to the parsing part of Blackfriday. It takes an
 | 
			
		||||
// input markdown document and produces a syntax tree for its contents. This
 | 
			
		||||
// tree can then be rendered with a default or custom renderer, or
 | 
			
		||||
// analyzed/transformed by the caller to whatever non-standard needs they have.
 | 
			
		||||
// The return value is the root node of the syntax tree.
 | 
			
		||||
func (p *Markdown) Parse(input []byte) *Node {
 | 
			
		||||
	p.block(input)
 | 
			
		||||
	// Walk the tree and finish up some of unfinished blocks
 | 
			
		||||
	for p.tip != nil {
 | 
			
		||||
		p.finalize(p.tip)
 | 
			
		||||
	}
 | 
			
		||||
	// Walk the tree again and process inline markdown in each block
 | 
			
		||||
	p.doc.Walk(func(node *Node, entering bool) WalkStatus {
 | 
			
		||||
		if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell {
 | 
			
		||||
			p.inline(node, node.content)
 | 
			
		||||
			node.content = nil
 | 
			
		||||
		}
 | 
			
		||||
		return GoToNext
 | 
			
		||||
	})
 | 
			
		||||
	p.parseRefsToAST()
 | 
			
		||||
	return p.doc
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *Markdown) parseRefsToAST() {
 | 
			
		||||
	if p.extensions&Footnotes == 0 || len(p.notes) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	p.tip = p.doc
 | 
			
		||||
	block := p.addBlock(List, nil)
 | 
			
		||||
	block.IsFootnotesList = true
 | 
			
		||||
	block.ListFlags = ListTypeOrdered
 | 
			
		||||
	flags := ListItemBeginningOfList
 | 
			
		||||
	// Note: this loop is intentionally explicit, not range-form. This is
 | 
			
		||||
	// because the body of the loop will append nested footnotes to p.notes and
 | 
			
		||||
	// we need to process those late additions. Range form would only walk over
 | 
			
		||||
	// the fixed initial set.
 | 
			
		||||
	for i := 0; i < len(p.notes); i++ {
 | 
			
		||||
		ref := p.notes[i]
 | 
			
		||||
		p.addExistingChild(ref.footnote, 0)
 | 
			
		||||
		block := ref.footnote
 | 
			
		||||
		block.ListFlags = flags | ListTypeOrdered
 | 
			
		||||
		block.RefLink = ref.link
 | 
			
		||||
		if ref.hasBlock {
 | 
			
		||||
			flags |= ListItemContainsBlock
 | 
			
		||||
			p.block(ref.title)
 | 
			
		||||
		} else {
 | 
			
		||||
			p.inline(block, ref.title)
 | 
			
		||||
		}
 | 
			
		||||
		flags &^= ListItemBeginningOfList | ListItemContainsBlock
 | 
			
		||||
	}
 | 
			
		||||
	above := block.Parent
 | 
			
		||||
	finalizeList(block)
 | 
			
		||||
	p.tip = above
 | 
			
		||||
	block.Walk(func(node *Node, entering bool) WalkStatus {
 | 
			
		||||
		if node.Type == Paragraph || node.Type == Heading {
 | 
			
		||||
			p.inline(node, node.content)
 | 
			
		||||
			node.content = nil
 | 
			
		||||
		}
 | 
			
		||||
		return GoToNext
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
// Link references
 | 
			
		||||
//
 | 
			
		||||
// This section implements support for references that (usually) appear
 | 
			
		||||
// as footnotes in a document, and can be referenced anywhere in the document.
 | 
			
		||||
// The basic format is:
 | 
			
		||||
//
 | 
			
		||||
//    [1]: http://www.google.com/ "Google"
 | 
			
		||||
//    [2]: http://www.github.com/ "Github"
 | 
			
		||||
//
 | 
			
		||||
// Anywhere in the document, the reference can be linked by referring to its
 | 
			
		||||
// label, i.e., 1 and 2 in this example, as in:
 | 
			
		||||
//
 | 
			
		||||
//    This library is hosted on [Github][2], a git hosting site.
 | 
			
		||||
//
 | 
			
		||||
// Actual footnotes as specified in Pandoc and supported by some other Markdown
 | 
			
		||||
// libraries such as php-markdown are also taken care of. They look like this:
 | 
			
		||||
//
 | 
			
		||||
//    This sentence needs a bit of further explanation.[^note]
 | 
			
		||||
//
 | 
			
		||||
//    [^note]: This is the explanation.
 | 
			
		||||
//
 | 
			
		||||
// Footnotes should be placed at the end of the document in an ordered list.
 | 
			
		||||
// Finally, there are inline footnotes such as:
 | 
			
		||||
//
 | 
			
		||||
//    Inline footnotes^[Also supported.] provide a quick inline explanation,
 | 
			
		||||
//    but are rendered at the bottom of the document.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// reference holds all information necessary for a reference-style links or
 | 
			
		||||
// footnotes.
 | 
			
		||||
//
 | 
			
		||||
// Consider this markdown with reference-style links:
 | 
			
		||||
//
 | 
			
		||||
//     [link][ref]
 | 
			
		||||
//
 | 
			
		||||
//     [ref]: /url/ "tooltip title"
 | 
			
		||||
//
 | 
			
		||||
// It will be ultimately converted to this HTML:
 | 
			
		||||
//
 | 
			
		||||
//     <p><a href=\"/url/\" title=\"title\">link</a></p>
 | 
			
		||||
//
 | 
			
		||||
// And a reference structure will be populated as follows:
 | 
			
		||||
//
 | 
			
		||||
//     p.refs["ref"] = &reference{
 | 
			
		||||
//         link: "/url/",
 | 
			
		||||
//         title: "tooltip title",
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
// Alternatively, reference can contain information about a footnote. Consider
 | 
			
		||||
// this markdown:
 | 
			
		||||
//
 | 
			
		||||
//     Text needing a footnote.[^a]
 | 
			
		||||
//
 | 
			
		||||
//     [^a]: This is the note
 | 
			
		||||
//
 | 
			
		||||
// A reference structure will be populated as follows:
 | 
			
		||||
//
 | 
			
		||||
//     p.refs["a"] = &reference{
 | 
			
		||||
//         link: "a",
 | 
			
		||||
//         title: "This is the note",
 | 
			
		||||
//         noteID: <some positive int>,
 | 
			
		||||
//     }
 | 
			
		||||
//
 | 
			
		||||
// TODO: As you can see, it begs for splitting into two dedicated structures
 | 
			
		||||
// for refs and for footnotes.
 | 
			
		||||
type reference struct {
 | 
			
		||||
	link     []byte
 | 
			
		||||
	title    []byte
 | 
			
		||||
	noteID   int // 0 if not a footnote ref
 | 
			
		||||
	hasBlock bool
 | 
			
		||||
	footnote *Node // a link to the Item node within a list of footnotes
 | 
			
		||||
 | 
			
		||||
	text []byte // only gets populated by refOverride feature with Reference.Text
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *reference) String() string {
 | 
			
		||||
	return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
 | 
			
		||||
		r.link, r.title, r.text, r.noteID, r.hasBlock)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check whether or not data starts with a reference link.
 | 
			
		||||
// If so, it is parsed and stored in the list of references
 | 
			
		||||
// (in the render struct).
 | 
			
		||||
// Returns the number of bytes to skip to move past it,
 | 
			
		||||
// or zero if the first line is not a reference.
 | 
			
		||||
func isReference(p *Markdown, data []byte, tabSize int) int {
 | 
			
		||||
	// up to 3 optional leading spaces
 | 
			
		||||
	if len(data) < 4 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	i := 0
 | 
			
		||||
	for i < 3 && data[i] == ' ' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	noteID := 0
 | 
			
		||||
 | 
			
		||||
	// id part: anything but a newline between brackets
 | 
			
		||||
	if data[i] != '[' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	i++
 | 
			
		||||
	if p.extensions&Footnotes != 0 {
 | 
			
		||||
		if i < len(data) && data[i] == '^' {
 | 
			
		||||
			// we can set it to anything here because the proper noteIds will
 | 
			
		||||
			// be assigned later during the second pass. It just has to be != 0
 | 
			
		||||
			noteID = 1
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	idOffset := i
 | 
			
		||||
	for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i >= len(data) || data[i] != ']' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	idEnd := i
 | 
			
		||||
	// footnotes can have empty ID, like this: [^], but a reference can not be
 | 
			
		||||
	// empty like this: []. Break early if it's not a footnote and there's no ID
 | 
			
		||||
	if noteID == 0 && idOffset == idEnd {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	// spacer: colon (space | tab)* newline? (space | tab)*
 | 
			
		||||
	i++
 | 
			
		||||
	if i >= len(data) || data[i] != ':' {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	i++
 | 
			
		||||
	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i < len(data) && (data[i] == '\n' || data[i] == '\r') {
 | 
			
		||||
		i++
 | 
			
		||||
		if i < len(data) && data[i] == '\n' && data[i-1] == '\r' {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i >= len(data) {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var (
 | 
			
		||||
		linkOffset, linkEnd   int
 | 
			
		||||
		titleOffset, titleEnd int
 | 
			
		||||
		lineEnd               int
 | 
			
		||||
		raw                   []byte
 | 
			
		||||
		hasBlock              bool
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if p.extensions&Footnotes != 0 && noteID != 0 {
 | 
			
		||||
		linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
 | 
			
		||||
		lineEnd = linkEnd
 | 
			
		||||
	} else {
 | 
			
		||||
		linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
 | 
			
		||||
	}
 | 
			
		||||
	if lineEnd == 0 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// a valid ref has been found
 | 
			
		||||
 | 
			
		||||
	ref := &reference{
 | 
			
		||||
		noteID:   noteID,
 | 
			
		||||
		hasBlock: hasBlock,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if noteID > 0 {
 | 
			
		||||
		// reusing the link field for the id since footnotes don't have links
 | 
			
		||||
		ref.link = data[idOffset:idEnd]
 | 
			
		||||
		// if footnote, it's not really a title, it's the contained text
 | 
			
		||||
		ref.title = raw
 | 
			
		||||
	} else {
 | 
			
		||||
		ref.link = data[linkOffset:linkEnd]
 | 
			
		||||
		ref.title = data[titleOffset:titleEnd]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// id matches are case-insensitive
 | 
			
		||||
	id := string(bytes.ToLower(data[idOffset:idEnd]))
 | 
			
		||||
 | 
			
		||||
	p.refs[id] = ref
 | 
			
		||||
 | 
			
		||||
	return lineEnd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
 | 
			
		||||
	// link: whitespace-free sequence, optionally between angle brackets
 | 
			
		||||
	if data[i] == '<' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	linkOffset = i
 | 
			
		||||
	for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	linkEnd = i
 | 
			
		||||
	if data[linkOffset] == '<' && data[linkEnd-1] == '>' {
 | 
			
		||||
		linkOffset++
 | 
			
		||||
		linkEnd--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// optional spacer: (space | tab)* (newline | '\'' | '"' | '(' )
 | 
			
		||||
	for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
	if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// compute end-of-line
 | 
			
		||||
	if i >= len(data) || data[i] == '\r' || data[i] == '\n' {
 | 
			
		||||
		lineEnd = i
 | 
			
		||||
	}
 | 
			
		||||
	if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' {
 | 
			
		||||
		lineEnd++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// optional (space|tab)* spacer after a newline
 | 
			
		||||
	if lineEnd > 0 {
 | 
			
		||||
		i = lineEnd + 1
 | 
			
		||||
		for i < len(data) && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// optional title: any non-newline sequence enclosed in '"() alone on its line
 | 
			
		||||
	if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') {
 | 
			
		||||
		i++
 | 
			
		||||
		titleOffset = i
 | 
			
		||||
 | 
			
		||||
		// look for EOL
 | 
			
		||||
		for i < len(data) && data[i] != '\n' && data[i] != '\r' {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
		if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' {
 | 
			
		||||
			titleEnd = i + 1
 | 
			
		||||
		} else {
 | 
			
		||||
			titleEnd = i
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// step back
 | 
			
		||||
		i--
 | 
			
		||||
		for i > titleOffset && (data[i] == ' ' || data[i] == '\t') {
 | 
			
		||||
			i--
 | 
			
		||||
		}
 | 
			
		||||
		if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') {
 | 
			
		||||
			lineEnd = titleEnd
 | 
			
		||||
			titleEnd = i
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// The first bit of this logic is the same as Parser.listItem, but the rest
 | 
			
		||||
// is much simpler. This function simply finds the entire block and shifts it
 | 
			
		||||
// over by one tab if it is indeed a block (just returns the line if it's not).
 | 
			
		||||
// blockEnd is the end of the section in the input buffer, and contents is the
 | 
			
		||||
// extracted text that was shifted over one tab. It will need to be rendered at
 | 
			
		||||
// the end of the document.
 | 
			
		||||
func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
 | 
			
		||||
	if i == 0 || len(data) == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// skip leading whitespace on first line
 | 
			
		||||
	for i < len(data) && data[i] == ' ' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	blockStart = i
 | 
			
		||||
 | 
			
		||||
	// find the end of the line
 | 
			
		||||
	blockEnd = i
 | 
			
		||||
	for i < len(data) && data[i-1] != '\n' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// get working buffer
 | 
			
		||||
	var raw bytes.Buffer
 | 
			
		||||
 | 
			
		||||
	// put the first line into the working buffer
 | 
			
		||||
	raw.Write(data[blockEnd:i])
 | 
			
		||||
	blockEnd = i
 | 
			
		||||
 | 
			
		||||
	// process the following lines
 | 
			
		||||
	containsBlankLine := false
 | 
			
		||||
 | 
			
		||||
gatherLines:
 | 
			
		||||
	for blockEnd < len(data) {
 | 
			
		||||
		i++
 | 
			
		||||
 | 
			
		||||
		// find the end of this line
 | 
			
		||||
		for i < len(data) && data[i-1] != '\n' {
 | 
			
		||||
			i++
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if it is an empty line, guess that it is part of this item
 | 
			
		||||
		// and move on to the next line
 | 
			
		||||
		if p.isEmpty(data[blockEnd:i]) > 0 {
 | 
			
		||||
			containsBlankLine = true
 | 
			
		||||
			blockEnd = i
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		n := 0
 | 
			
		||||
		if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
 | 
			
		||||
			// this is the end of the block.
 | 
			
		||||
			// we don't want to include this last line in the index.
 | 
			
		||||
			break gatherLines
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if there were blank lines before this one, insert a new one now
 | 
			
		||||
		if containsBlankLine {
 | 
			
		||||
			raw.WriteByte('\n')
 | 
			
		||||
			containsBlankLine = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// get rid of that first tab, write to buffer
 | 
			
		||||
		raw.Write(data[blockEnd+n : i])
 | 
			
		||||
		hasBlock = true
 | 
			
		||||
 | 
			
		||||
		blockEnd = i
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if data[blockEnd-1] != '\n' {
 | 
			
		||||
		raw.WriteByte('\n')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	contents = raw.Bytes()
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// Miscellaneous helper functions
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
// Test if a character is a punctuation symbol.
 | 
			
		||||
// Taken from a private function in regexp in the stdlib.
 | 
			
		||||
func ispunct(c byte) bool {
 | 
			
		||||
	for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") {
 | 
			
		||||
		if c == r {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is a whitespace character.
 | 
			
		||||
func isspace(c byte) bool {
 | 
			
		||||
	return ishorizontalspace(c) || isverticalspace(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is a horizontal whitespace character.
 | 
			
		||||
func ishorizontalspace(c byte) bool {
 | 
			
		||||
	return c == ' ' || c == '\t'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is a vertical character.
 | 
			
		||||
func isverticalspace(c byte) bool {
 | 
			
		||||
	return c == '\n' || c == '\r' || c == '\f' || c == '\v'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is letter.
 | 
			
		||||
func isletter(c byte) bool {
 | 
			
		||||
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Test if a character is a letter or a digit.
 | 
			
		||||
// TODO: check when this is looking for ASCII alnum and when it should use unicode
 | 
			
		||||
func isalnum(c byte) bool {
 | 
			
		||||
	return (c >= '0' && c <= '9') || isletter(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Replace tab characters with spaces, aligning to the next TAB_SIZE column.
 | 
			
		||||
// always ends output with a newline
 | 
			
		||||
func expandTabs(out *bytes.Buffer, line []byte, tabSize int) {
 | 
			
		||||
	// first, check for common cases: no tabs, or only tabs at beginning of line
 | 
			
		||||
	i, prefix := 0, 0
 | 
			
		||||
	slowcase := false
 | 
			
		||||
	for i = 0; i < len(line); i++ {
 | 
			
		||||
		if line[i] == '\t' {
 | 
			
		||||
			if prefix == i {
 | 
			
		||||
				prefix++
 | 
			
		||||
			} else {
 | 
			
		||||
				slowcase = true
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// no need to decode runes if all tabs are at the beginning of the line
 | 
			
		||||
	if !slowcase {
 | 
			
		||||
		for i = 0; i < prefix*tabSize; i++ {
 | 
			
		||||
			out.WriteByte(' ')
 | 
			
		||||
		}
 | 
			
		||||
		out.Write(line[prefix:])
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// the slow case: we need to count runes to figure out how
 | 
			
		||||
	// many spaces to insert for each tab
 | 
			
		||||
	column := 0
 | 
			
		||||
	i = 0
 | 
			
		||||
	for i < len(line) {
 | 
			
		||||
		start := i
 | 
			
		||||
		for i < len(line) && line[i] != '\t' {
 | 
			
		||||
			_, size := utf8.DecodeRune(line[i:])
 | 
			
		||||
			i += size
 | 
			
		||||
			column++
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i > start {
 | 
			
		||||
			out.Write(line[start:i])
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if i >= len(line) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			out.WriteByte(' ')
 | 
			
		||||
			column++
 | 
			
		||||
			if column%tabSize == 0 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find if a line counts as indented or not.
 | 
			
		||||
// Returns number of characters the indent is (0 = not indented).
 | 
			
		||||
func isIndented(data []byte, indentSize int) int {
 | 
			
		||||
	if len(data) == 0 {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	if data[0] == '\t' {
 | 
			
		||||
		return 1
 | 
			
		||||
	}
 | 
			
		||||
	if len(data) < indentSize {
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
	for i := 0; i < indentSize; i++ {
 | 
			
		||||
		if data[i] != ' ' {
 | 
			
		||||
			return 0
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return indentSize
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Create a url-safe slug for fragments
 | 
			
		||||
func slugify(in []byte) []byte {
 | 
			
		||||
	if len(in) == 0 {
 | 
			
		||||
		return in
 | 
			
		||||
	}
 | 
			
		||||
	out := make([]byte, 0, len(in))
 | 
			
		||||
	sym := false
 | 
			
		||||
 | 
			
		||||
	for _, ch := range in {
 | 
			
		||||
		if isalnum(ch) {
 | 
			
		||||
			sym = false
 | 
			
		||||
			out = append(out, ch)
 | 
			
		||||
		} else if sym {
 | 
			
		||||
			continue
 | 
			
		||||
		} else {
 | 
			
		||||
			out = append(out, '-')
 | 
			
		||||
			sym = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var a, b int
 | 
			
		||||
	var ch byte
 | 
			
		||||
	for a, ch = range out {
 | 
			
		||||
		if ch != '-' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	for b = len(out) - 1; b > 0; b-- {
 | 
			
		||||
		if out[b] != '-' {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return out[a : b+1]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										354
									
								
								vendor/github.com/russross/blackfriday/v2/node.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								vendor/github.com/russross/blackfriday/v2/node.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,354 @@
 | 
			
		|||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"fmt"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NodeType specifies a type of a single node of a syntax tree. Usually one
 | 
			
		||||
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
 | 
			
		||||
// or code block.
 | 
			
		||||
type NodeType int
 | 
			
		||||
 | 
			
		||||
// Constants for identifying different types of nodes. See NodeType.
 | 
			
		||||
const (
 | 
			
		||||
	Document NodeType = iota
 | 
			
		||||
	BlockQuote
 | 
			
		||||
	List
 | 
			
		||||
	Item
 | 
			
		||||
	Paragraph
 | 
			
		||||
	Heading
 | 
			
		||||
	HorizontalRule
 | 
			
		||||
	Emph
 | 
			
		||||
	Strong
 | 
			
		||||
	Del
 | 
			
		||||
	Link
 | 
			
		||||
	Image
 | 
			
		||||
	Text
 | 
			
		||||
	HTMLBlock
 | 
			
		||||
	CodeBlock
 | 
			
		||||
	Softbreak
 | 
			
		||||
	Hardbreak
 | 
			
		||||
	Code
 | 
			
		||||
	HTMLSpan
 | 
			
		||||
	Table
 | 
			
		||||
	TableCell
 | 
			
		||||
	TableHead
 | 
			
		||||
	TableBody
 | 
			
		||||
	TableRow
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var nodeTypeNames = []string{
 | 
			
		||||
	Document:       "Document",
 | 
			
		||||
	BlockQuote:     "BlockQuote",
 | 
			
		||||
	List:           "List",
 | 
			
		||||
	Item:           "Item",
 | 
			
		||||
	Paragraph:      "Paragraph",
 | 
			
		||||
	Heading:        "Heading",
 | 
			
		||||
	HorizontalRule: "HorizontalRule",
 | 
			
		||||
	Emph:           "Emph",
 | 
			
		||||
	Strong:         "Strong",
 | 
			
		||||
	Del:            "Del",
 | 
			
		||||
	Link:           "Link",
 | 
			
		||||
	Image:          "Image",
 | 
			
		||||
	Text:           "Text",
 | 
			
		||||
	HTMLBlock:      "HTMLBlock",
 | 
			
		||||
	CodeBlock:      "CodeBlock",
 | 
			
		||||
	Softbreak:      "Softbreak",
 | 
			
		||||
	Hardbreak:      "Hardbreak",
 | 
			
		||||
	Code:           "Code",
 | 
			
		||||
	HTMLSpan:       "HTMLSpan",
 | 
			
		||||
	Table:          "Table",
 | 
			
		||||
	TableCell:      "TableCell",
 | 
			
		||||
	TableHead:      "TableHead",
 | 
			
		||||
	TableBody:      "TableBody",
 | 
			
		||||
	TableRow:       "TableRow",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t NodeType) String() string {
 | 
			
		||||
	return nodeTypeNames[t]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListData contains fields relevant to a List and Item node type.
 | 
			
		||||
type ListData struct {
 | 
			
		||||
	ListFlags       ListType
 | 
			
		||||
	Tight           bool   // Skip <p>s around list item data if true
 | 
			
		||||
	BulletChar      byte   // '*', '+' or '-' in bullet lists
 | 
			
		||||
	Delimiter       byte   // '.' or ')' after the number in ordered lists
 | 
			
		||||
	RefLink         []byte // If not nil, turns this list item into a footnote item and triggers different rendering
 | 
			
		||||
	IsFootnotesList bool   // This is a list of footnotes
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LinkData contains fields relevant to a Link node type.
 | 
			
		||||
type LinkData struct {
 | 
			
		||||
	Destination []byte // Destination is what goes into a href
 | 
			
		||||
	Title       []byte // Title is the tooltip thing that goes in a title attribute
 | 
			
		||||
	NoteID      int    // NoteID contains a serial number of a footnote, zero if it's not a footnote
 | 
			
		||||
	Footnote    *Node  // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil.
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CodeBlockData contains fields relevant to a CodeBlock node type.
 | 
			
		||||
type CodeBlockData struct {
 | 
			
		||||
	IsFenced    bool   // Specifies whether it's a fenced code block or an indented one
 | 
			
		||||
	Info        []byte // This holds the info string
 | 
			
		||||
	FenceChar   byte
 | 
			
		||||
	FenceLength int
 | 
			
		||||
	FenceOffset int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TableCellData contains fields relevant to a TableCell node type.
 | 
			
		||||
type TableCellData struct {
 | 
			
		||||
	IsHeader bool           // This tells if it's under the header row
 | 
			
		||||
	Align    CellAlignFlags // This holds the value for align attribute
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HeadingData contains fields relevant to a Heading node type.
 | 
			
		||||
type HeadingData struct {
 | 
			
		||||
	Level        int    // This holds the heading level number
 | 
			
		||||
	HeadingID    string // This might hold heading ID, if present
 | 
			
		||||
	IsTitleblock bool   // Specifies whether it's a title block
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Node is a single element in the abstract syntax tree of the parsed document.
 | 
			
		||||
// It holds connections to the structurally neighboring nodes and, for certain
 | 
			
		||||
// types of nodes, additional information that might be needed when rendering.
 | 
			
		||||
type Node struct {
 | 
			
		||||
	Type       NodeType // Determines the type of the node
 | 
			
		||||
	Parent     *Node    // Points to the parent
 | 
			
		||||
	FirstChild *Node    // Points to the first child, if any
 | 
			
		||||
	LastChild  *Node    // Points to the last child, if any
 | 
			
		||||
	Prev       *Node    // Previous sibling; nil if it's the first child
 | 
			
		||||
	Next       *Node    // Next sibling; nil if it's the last child
 | 
			
		||||
 | 
			
		||||
	Literal []byte // Text contents of the leaf nodes
 | 
			
		||||
 | 
			
		||||
	HeadingData   // Populated if Type is Heading
 | 
			
		||||
	ListData      // Populated if Type is List
 | 
			
		||||
	CodeBlockData // Populated if Type is CodeBlock
 | 
			
		||||
	LinkData      // Populated if Type is Link
 | 
			
		||||
	TableCellData // Populated if Type is TableCell
 | 
			
		||||
 | 
			
		||||
	content []byte // Markdown content of the block nodes
 | 
			
		||||
	open    bool   // Specifies an open block node that has not been finished to process yet
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewNode allocates a node of a specified type.
 | 
			
		||||
func NewNode(typ NodeType) *Node {
 | 
			
		||||
	return &Node{
 | 
			
		||||
		Type: typ,
 | 
			
		||||
		open: true,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (n *Node) String() string {
 | 
			
		||||
	ellipsis := ""
 | 
			
		||||
	snippet := n.Literal
 | 
			
		||||
	if len(snippet) > 16 {
 | 
			
		||||
		snippet = snippet[:16]
 | 
			
		||||
		ellipsis = "..."
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Unlink removes node 'n' from the tree.
 | 
			
		||||
// It panics if the node is nil.
 | 
			
		||||
func (n *Node) Unlink() {
 | 
			
		||||
	if n.Prev != nil {
 | 
			
		||||
		n.Prev.Next = n.Next
 | 
			
		||||
	} else if n.Parent != nil {
 | 
			
		||||
		n.Parent.FirstChild = n.Next
 | 
			
		||||
	}
 | 
			
		||||
	if n.Next != nil {
 | 
			
		||||
		n.Next.Prev = n.Prev
 | 
			
		||||
	} else if n.Parent != nil {
 | 
			
		||||
		n.Parent.LastChild = n.Prev
 | 
			
		||||
	}
 | 
			
		||||
	n.Parent = nil
 | 
			
		||||
	n.Next = nil
 | 
			
		||||
	n.Prev = nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AppendChild adds a node 'child' as a child of 'n'.
 | 
			
		||||
// It panics if either node is nil.
 | 
			
		||||
func (n *Node) AppendChild(child *Node) {
 | 
			
		||||
	child.Unlink()
 | 
			
		||||
	child.Parent = n
 | 
			
		||||
	if n.LastChild != nil {
 | 
			
		||||
		n.LastChild.Next = child
 | 
			
		||||
		child.Prev = n.LastChild
 | 
			
		||||
		n.LastChild = child
 | 
			
		||||
	} else {
 | 
			
		||||
		n.FirstChild = child
 | 
			
		||||
		n.LastChild = child
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InsertBefore inserts 'sibling' immediately before 'n'.
 | 
			
		||||
// It panics if either node is nil.
 | 
			
		||||
func (n *Node) InsertBefore(sibling *Node) {
 | 
			
		||||
	sibling.Unlink()
 | 
			
		||||
	sibling.Prev = n.Prev
 | 
			
		||||
	if sibling.Prev != nil {
 | 
			
		||||
		sibling.Prev.Next = sibling
 | 
			
		||||
	}
 | 
			
		||||
	sibling.Next = n
 | 
			
		||||
	n.Prev = sibling
 | 
			
		||||
	sibling.Parent = n.Parent
 | 
			
		||||
	if sibling.Prev == nil {
 | 
			
		||||
		sibling.Parent.FirstChild = sibling
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (n *Node) isContainer() bool {
 | 
			
		||||
	switch n.Type {
 | 
			
		||||
	case Document:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case BlockQuote:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case List:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Item:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Paragraph:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Heading:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Emph:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Strong:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Del:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Link:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Image:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case Table:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case TableHead:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case TableBody:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case TableRow:
 | 
			
		||||
		fallthrough
 | 
			
		||||
	case TableCell:
 | 
			
		||||
		return true
 | 
			
		||||
	default:
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (n *Node) canContain(t NodeType) bool {
 | 
			
		||||
	if n.Type == List {
 | 
			
		||||
		return t == Item
 | 
			
		||||
	}
 | 
			
		||||
	if n.Type == Document || n.Type == BlockQuote || n.Type == Item {
 | 
			
		||||
		return t != Item
 | 
			
		||||
	}
 | 
			
		||||
	if n.Type == Table {
 | 
			
		||||
		return t == TableHead || t == TableBody
 | 
			
		||||
	}
 | 
			
		||||
	if n.Type == TableHead || n.Type == TableBody {
 | 
			
		||||
		return t == TableRow
 | 
			
		||||
	}
 | 
			
		||||
	if n.Type == TableRow {
 | 
			
		||||
		return t == TableCell
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WalkStatus allows NodeVisitor to have some control over the tree traversal.
 | 
			
		||||
// It is returned from NodeVisitor and different values allow Node.Walk to
 | 
			
		||||
// decide which node to go to next.
 | 
			
		||||
type WalkStatus int
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	// GoToNext is the default traversal of every node.
 | 
			
		||||
	GoToNext WalkStatus = iota
 | 
			
		||||
	// SkipChildren tells walker to skip all children of current node.
 | 
			
		||||
	SkipChildren
 | 
			
		||||
	// Terminate tells walker to terminate the traversal.
 | 
			
		||||
	Terminate
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// NodeVisitor is a callback to be called when traversing the syntax tree.
 | 
			
		||||
// Called twice for every node: once with entering=true when the branch is
 | 
			
		||||
// first visited, then with entering=false after all the children are done.
 | 
			
		||||
type NodeVisitor func(node *Node, entering bool) WalkStatus
 | 
			
		||||
 | 
			
		||||
// Walk is a convenience method that instantiates a walker and starts a
 | 
			
		||||
// traversal of subtree rooted at n.
 | 
			
		||||
func (n *Node) Walk(visitor NodeVisitor) {
 | 
			
		||||
	w := newNodeWalker(n)
 | 
			
		||||
	for w.current != nil {
 | 
			
		||||
		status := visitor(w.current, w.entering)
 | 
			
		||||
		switch status {
 | 
			
		||||
		case GoToNext:
 | 
			
		||||
			w.next()
 | 
			
		||||
		case SkipChildren:
 | 
			
		||||
			w.entering = false
 | 
			
		||||
			w.next()
 | 
			
		||||
		case Terminate:
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nodeWalker struct {
 | 
			
		||||
	current  *Node
 | 
			
		||||
	root     *Node
 | 
			
		||||
	entering bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newNodeWalker(root *Node) *nodeWalker {
 | 
			
		||||
	return &nodeWalker{
 | 
			
		||||
		current:  root,
 | 
			
		||||
		root:     root,
 | 
			
		||||
		entering: true,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (nw *nodeWalker) next() {
 | 
			
		||||
	if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root {
 | 
			
		||||
		nw.current = nil
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if nw.entering && nw.current.isContainer() {
 | 
			
		||||
		if nw.current.FirstChild != nil {
 | 
			
		||||
			nw.current = nw.current.FirstChild
 | 
			
		||||
			nw.entering = true
 | 
			
		||||
		} else {
 | 
			
		||||
			nw.entering = false
 | 
			
		||||
		}
 | 
			
		||||
	} else if nw.current.Next == nil {
 | 
			
		||||
		nw.current = nw.current.Parent
 | 
			
		||||
		nw.entering = false
 | 
			
		||||
	} else {
 | 
			
		||||
		nw.current = nw.current.Next
 | 
			
		||||
		nw.entering = true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dump(ast *Node) {
 | 
			
		||||
	fmt.Println(dumpString(ast))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dumpR(ast *Node, depth int) string {
 | 
			
		||||
	if ast == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	indent := bytes.Repeat([]byte("\t"), depth)
 | 
			
		||||
	content := ast.Literal
 | 
			
		||||
	if content == nil {
 | 
			
		||||
		content = ast.content
 | 
			
		||||
	}
 | 
			
		||||
	result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
 | 
			
		||||
	for n := ast.FirstChild; n != nil; n = n.Next {
 | 
			
		||||
		result += dumpR(n, depth+1)
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func dumpString(ast *Node) string {
 | 
			
		||||
	return dumpR(ast, 0)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										457
									
								
								vendor/github.com/russross/blackfriday/v2/smartypants.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										457
									
								
								vendor/github.com/russross/blackfriday/v2/smartypants.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,457 @@
 | 
			
		|||
//
 | 
			
		||||
// Blackfriday Markdown Processor
 | 
			
		||||
// Available at http://github.com/russross/blackfriday
 | 
			
		||||
//
 | 
			
		||||
// Copyright © 2011 Russ Ross <russ@russross.com>.
 | 
			
		||||
// Distributed under the Simplified BSD License.
 | 
			
		||||
// See README.md for details.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
// SmartyPants rendering
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
package blackfriday
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"io"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// SPRenderer is a struct containing state of a Smartypants renderer.
 | 
			
		||||
type SPRenderer struct {
 | 
			
		||||
	inSingleQuote bool
 | 
			
		||||
	inDoubleQuote bool
 | 
			
		||||
	callbacks     [256]smartCallback
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func wordBoundary(c byte) bool {
 | 
			
		||||
	return c == 0 || isspace(c) || ispunct(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func tolower(c byte) byte {
 | 
			
		||||
	if c >= 'A' && c <= 'Z' {
 | 
			
		||||
		return c - 'A' + 'a'
 | 
			
		||||
	}
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isdigit(c byte) bool {
 | 
			
		||||
	return c >= '0' && c <= '9'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
 | 
			
		||||
	// edge of the buffer is likely to be a tag that we don't get to see,
 | 
			
		||||
	// so we treat it like text sometimes
 | 
			
		||||
 | 
			
		||||
	// enumerate all sixteen possibilities for (previousChar, nextChar)
 | 
			
		||||
	// each can be one of {0, space, punct, other}
 | 
			
		||||
	switch {
 | 
			
		||||
	case previousChar == 0 && nextChar == 0:
 | 
			
		||||
		// context is not any help here, so toggle
 | 
			
		||||
		*isOpen = !*isOpen
 | 
			
		||||
	case isspace(previousChar) && nextChar == 0:
 | 
			
		||||
		// [ "] might be [ "<code>foo...]
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case ispunct(previousChar) && nextChar == 0:
 | 
			
		||||
		// [!"] hmm... could be [Run!"] or [("<code>...]
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case /* isnormal(previousChar) && */ nextChar == 0:
 | 
			
		||||
		// [a"] is probably a close
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case previousChar == 0 && isspace(nextChar):
 | 
			
		||||
		// [" ] might be [...foo</code>" ]
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case isspace(previousChar) && isspace(nextChar):
 | 
			
		||||
		// [ " ] context is not any help here, so toggle
 | 
			
		||||
		*isOpen = !*isOpen
 | 
			
		||||
	case ispunct(previousChar) && isspace(nextChar):
 | 
			
		||||
		// [!" ] is probably a close
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case /* isnormal(previousChar) && */ isspace(nextChar):
 | 
			
		||||
		// [a" ] this is one of the easy cases
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case previousChar == 0 && ispunct(nextChar):
 | 
			
		||||
		// ["!] hmm... could be ["$1.95] or [</code>"!...]
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case isspace(previousChar) && ispunct(nextChar):
 | 
			
		||||
		// [ "!] looks more like [ "$1.95]
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case ispunct(previousChar) && ispunct(nextChar):
 | 
			
		||||
		// [!"!] context is not any help here, so toggle
 | 
			
		||||
		*isOpen = !*isOpen
 | 
			
		||||
	case /* isnormal(previousChar) && */ ispunct(nextChar):
 | 
			
		||||
		// [a"!] is probably a close
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	case previousChar == 0 /* && isnormal(nextChar) */ :
 | 
			
		||||
		// ["a] is probably an open
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case isspace(previousChar) /* && isnormal(nextChar) */ :
 | 
			
		||||
		// [ "a] this is one of the easy cases
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	case ispunct(previousChar) /* && isnormal(nextChar) */ :
 | 
			
		||||
		// [!"a] is probably an open
 | 
			
		||||
		*isOpen = true
 | 
			
		||||
	default:
 | 
			
		||||
		// [a'b] maybe a contraction?
 | 
			
		||||
		*isOpen = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Note that with the limited lookahead, this non-breaking
 | 
			
		||||
	// space will also be appended to single double quotes.
 | 
			
		||||
	if addNBSP && !*isOpen {
 | 
			
		||||
		out.WriteString(" ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.WriteByte('&')
 | 
			
		||||
	if *isOpen {
 | 
			
		||||
		out.WriteByte('l')
 | 
			
		||||
	} else {
 | 
			
		||||
		out.WriteByte('r')
 | 
			
		||||
	}
 | 
			
		||||
	out.WriteByte(quote)
 | 
			
		||||
	out.WriteString("quo;")
 | 
			
		||||
 | 
			
		||||
	if addNBSP && *isOpen {
 | 
			
		||||
		out.WriteString(" ")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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("’")
 | 
			
		||||
			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("’")
 | 
			
		||||
				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("©")
 | 
			
		||||
			return 2
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if t1 == 'r' && t2 == ')' {
 | 
			
		||||
			out.WriteString("®")
 | 
			
		||||
			return 2
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
 | 
			
		||||
			out.WriteString("™")
 | 
			
		||||
			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("—")
 | 
			
		||||
			return 1
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if wordBoundary(previousChar) && wordBoundary(text[1]) {
 | 
			
		||||
			out.WriteString("–")
 | 
			
		||||
			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("—")
 | 
			
		||||
		return 2
 | 
			
		||||
	}
 | 
			
		||||
	if len(text) >= 2 && text[1] == '-' {
 | 
			
		||||
		out.WriteString("–")
 | 
			
		||||
		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(""")) {
 | 
			
		||||
		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("�")) {
 | 
			
		||||
		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("…")
 | 
			
		||||
		return 2
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
 | 
			
		||||
		out.WriteString("…")
 | 
			
		||||
		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>⁄<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("½")
 | 
			
		||||
				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("¼")
 | 
			
		||||
				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("¾")
 | 
			
		||||
				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(""")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
 | 
			
		||||
	return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
 | 
			
		||||
	return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
 | 
			
		||||
	i := 0
 | 
			
		||||
 | 
			
		||||
	for i < len(text) && text[i] != '>' {
 | 
			
		||||
		i++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	out.Write(text[:i+1])
 | 
			
		||||
	return i
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
 | 
			
		||||
 | 
			
		||||
// NewSmartypantsRenderer constructs a Smartypants renderer object.
 | 
			
		||||
func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer {
 | 
			
		||||
	var (
 | 
			
		||||
		r SPRenderer
 | 
			
		||||
 | 
			
		||||
		smartAmpAngled      = r.smartAmp(true, false)
 | 
			
		||||
		smartAmpAngledNBSP  = r.smartAmp(true, true)
 | 
			
		||||
		smartAmpRegular     = r.smartAmp(false, false)
 | 
			
		||||
		smartAmpRegularNBSP = r.smartAmp(false, true)
 | 
			
		||||
 | 
			
		||||
		addNBSP = flags&SmartypantsQuotesNBSP != 0
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if flags&SmartypantsAngledQuotes == 0 {
 | 
			
		||||
		r.callbacks['"'] = r.smartDoubleQuote
 | 
			
		||||
		if !addNBSP {
 | 
			
		||||
			r.callbacks['&'] = smartAmpRegular
 | 
			
		||||
		} else {
 | 
			
		||||
			r.callbacks['&'] = smartAmpRegularNBSP
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		r.callbacks['"'] = r.smartAngledDoubleQuote
 | 
			
		||||
		if !addNBSP {
 | 
			
		||||
			r.callbacks['&'] = smartAmpAngled
 | 
			
		||||
		} else {
 | 
			
		||||
			r.callbacks['&'] = smartAmpAngledNBSP
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.callbacks['\''] = r.smartSingleQuote
 | 
			
		||||
	r.callbacks['('] = r.smartParens
 | 
			
		||||
	if flags&SmartypantsDashes != 0 {
 | 
			
		||||
		if flags&SmartypantsLatexDashes == 0 {
 | 
			
		||||
			r.callbacks['-'] = r.smartDash
 | 
			
		||||
		} else {
 | 
			
		||||
			r.callbacks['-'] = r.smartDashLatex
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.callbacks['.'] = r.smartPeriod
 | 
			
		||||
	if flags&SmartypantsFractions == 0 {
 | 
			
		||||
		r.callbacks['1'] = r.smartNumber
 | 
			
		||||
		r.callbacks['3'] = r.smartNumber
 | 
			
		||||
	} else {
 | 
			
		||||
		for ch := '1'; ch <= '9'; ch++ {
 | 
			
		||||
			r.callbacks[ch] = r.smartNumberGeneric
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	r.callbacks['<'] = r.smartLeftAngle
 | 
			
		||||
	r.callbacks['`'] = r.smartBacktick
 | 
			
		||||
	return &r
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Process is the entry point of the Smartypants renderer.
 | 
			
		||||
func (r *SPRenderer) Process(w io.Writer, text []byte) {
 | 
			
		||||
	mark := 0
 | 
			
		||||
	for i := 0; i < len(text); i++ {
 | 
			
		||||
		if action := r.callbacks[text[i]]; action != nil {
 | 
			
		||||
			if i > mark {
 | 
			
		||||
				w.Write(text[mark:i])
 | 
			
		||||
			}
 | 
			
		||||
			previousChar := byte(0)
 | 
			
		||||
			if i > 0 {
 | 
			
		||||
				previousChar = text[i-1]
 | 
			
		||||
			}
 | 
			
		||||
			var tmp bytes.Buffer
 | 
			
		||||
			i += action(&tmp, previousChar, text[i:])
 | 
			
		||||
			w.Write(tmp.Bytes())
 | 
			
		||||
			mark = i + 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if mark < len(text) {
 | 
			
		||||
		w.Write(text[mark:])
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue