Add conmon man page

Also sort options in alphabetic order

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2020-09-30 12:52:12 -04:00 committed by Peter Hunt
parent 2fbeb9f01d
commit 5df41fb79e
35 changed files with 6502 additions and 41 deletions

View File

@ -26,7 +26,7 @@ env:
PRIOR_FEDORA_CONTAINER_FQIN: "registry.fedoraproject.org/fedora:31"
# VM Image built in containers/automation_images
_BUILT_IMAGE_SUFFIX: "c6110627968057344"
_BUILT_IMAGE_SUFFIX: "c5402398833246208"
FEDORA_CACHE_IMAGE_NAME: "fedora-${_BUILT_IMAGE_SUFFIX}"
####
@ -83,7 +83,7 @@ fedora_packaging_task:
memory: 12
script:
- dnf install -y make glib2-devel git gcc rpm-build
- dnf install -y make glib2-devel git gcc rpm-build golang
- cd $CIRRUS_WORKING_DIR
- make
- make -f .rpmbuild/Makefile

View File

@ -14,7 +14,7 @@ else
DEBUGFLAG=
endif
.PHONY: all git-vars
.PHONY: all git-vars docs
all: git-vars bin bin/conmon
git-vars:
@ -84,12 +84,18 @@ vendor:
GO111MODULE=on $(GO) mod vendor
GO111MODULE=on $(GO) mod verify
.PHONY: docs
docs: install.tools
$(MAKE) -C docs
.PHONY: clean
clean:
rm -rf bin/ src/*.o
$(MAKE) -C docs clean
.PHONY: install install.bin install.crio install.podman podman crio
install: install.bin
install: install.bin docs
$(MAKE) -C docs install
podman: install.podman
@ -104,6 +110,9 @@ install.crio: bin/conmon
install.podman: bin/conmon
install ${SELINUXOPT} -D -m 755 bin/conmon $(DESTDIR)$(LIBEXECDIR)/podman/conmon
install.tools:
make -C tools
.PHONY: fmt
fmt:
find . '(' -name '*.h' -o -name '*.c' ! -path './vendor/*' ')' -exec clang-format -i {} \+

View File

@ -23,6 +23,8 @@ BuildRequires: gcc
BuildRequires: glib2-devel
BuildRequires: glibc-devel
BuildRequires: git
# If go_compiler is not set to 1, there is no virtual provide. Use golang instead.
BuildRequires: golang
BuildRequires: pkgconfig
%description
@ -46,6 +48,7 @@ BuildRequires: pkgconfig
%license LICENSE
%doc README.md
%{_usr}/bin/%{name}
%{_mandir}/man8/*.8*
%changelog
* Mon Oct 01 2018 Lokesh Mandvekar <lsm5@fedoraproject.org> - 0-0.1.gite7805e2

18
docs/Makefile Normal file
View File

@ -0,0 +1,18 @@
PREFIX := /usr/local
DATADIR := ${PREFIX}/share
MANDIR := $(DATADIR)/man
GOMD2MAN = ../tools/build/go-md2man
docs: $(patsubst %.md,%,$(wildcard *.8.md))
%.8: %.8.md
$(GOMD2MAN) -in $^ -out $@
.PHONY: install
install:
install -d ${DESTDIR}/${MANDIR}/man8
install -m 0644 conmon*.8 ${DESTDIR}/${MANDIR}/man8
.PHONY: clean
clean:
$(RM) conmon*.8

140
docs/conmon.8.md Normal file
View File

@ -0,0 +1,140 @@
conmon 8 "User Commands"
==================================================
# NAME
conmon - container monitor utility
# SYNOPSIS
conmon [options]
# DESCRIPTION
conmon is a command-line program for monitoring and managing the lifecycle of
Linux containers that follow the Open Container Initiative (OCI) format.
# APPLICATION OPTIONS
**--api-version**
Conmon API version to use.
**-b**, **--bundle**
Location of the OCI Bundle path.
**-c**, **--cid**
Identification of Container.
**--exec-attach**
Attach to an exec session.
**-e**, **--exec**
Exec a command into a running container.
**--exec-process-spec**
Path to the process spec for execution.
**--exit-command**
Path to the program to execute when the container terminates its execution.
**--exit-command-arg**
Additional arguments to pass to the exit command. Can be specified multiple time.
**--exit-delay**
Delay before invoking the exit command (in seconds).
**--exit-dir**
Path to the directory where exit files are written.
**-h**, **--help**
Show help options.
**-i**, **--stdin**
Open up a pipe to pass stdin to the container.
This option tells conmon to setup the pipe regardless of whether there is a terminal connection.
**-l**, **--log-path**
Path to store all stdout and stderr messages from the container.
**--leave-stdin-open**
Leave stdin open when the attached client disconnects.
**--log-level**
Print debug logs based on the log level.
**--log-size-max**
Maximum size of the log file.
**--log-tag**
Additional tag to use for logging.
**-n**, **--name**
Container name.
**--no-new-keyring**
Do not create a new session keyring for the container.
**--no-pivot**
Do not use pivot_root.
**--no-sync-log**
Do not manually call sync on logs after container shutdown.
**-0**, **--persist-dir**
Persistent directory for a container that can be used for storing container data.
**-p**, **--container-pidfile**
PID file for the initial pid inside of the container.
**-P**, **--conmon-pidfile**
PID file for the conmon process.
**-r**, **--runtime**
Path to store runtime data for the container.
**--replace-listen-pid**
Replace listen pid if set for oci-runtime pid.
**--restore**
Restore a container from a checkpoint.
**--runtime-arg**
Additional arguments to pass to the runtime. Can be specified multiple times.
**--runtime-opt**
Additional options to pass to the restore or exec command. Can be specified multiple times.
**-s**, **--systemd-cgroup**
Enable systemd cgroup manager, rather then use the cgroupfs directly.
**--socket-dir-path**
Location of container attach sockets.
**--sync**
Keep the main conmon process as its child by only forking once.
**--syslog**
Log to syslog (use with cgroupfs cgroup manager).
**-t**, **--terminal**
Allocate a pseudo-TTY. The default is false.
When set to true, conmon will allocate a pseudo-tty and attach to the
standard input of the container. This can be used, for example, to run
a throwaway interactive shell. The default is false.
**-T**, **--timeout**
Kill container after specified timeout in seconds.
**-u**, **--cuuid**
Specify the Container UUID to use.
**--version**
Print the version and exit.
## SEE ALSO
podman(1), buildah(1), cri-o(1), crun(8), runc(8)
## HISTORY
October 2020, Originally compiled by Dan Walsh <dwalsh@redhat.com>

View File

@ -50,53 +50,53 @@ gboolean opt_sync = FALSE;
gboolean opt_no_sync_log = FALSE;
char *opt_sdnotify_socket = NULL;
GOptionEntry opt_entries[] = {
{"terminal", 't', 0, G_OPTION_ARG_NONE, &opt_terminal, "Terminal", NULL},
{"stdin", 'i', 0, G_OPTION_ARG_NONE, &opt_stdin, "Stdin", NULL},
{"leave-stdin-open", 0, 0, G_OPTION_ARG_NONE, &opt_leave_stdin_open, "Leave stdin open when attached client disconnects", NULL},
{"cid", 'c', 0, G_OPTION_ARG_STRING, &opt_cid, "Container ID", NULL},
{"cuuid", 'u', 0, G_OPTION_ARG_STRING, &opt_cuuid, "Container UUID", NULL},
{"name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Container name", NULL},
{"runtime", 'r', 0, G_OPTION_ARG_STRING, &opt_runtime_path, "Runtime path", NULL},
{"restore", 0, 0, G_OPTION_ARG_STRING, &opt_restore_path, "Restore a container from a checkpoint", NULL},
{"restore-arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_opts,
"Additional arg to pass to the restore command. Can be specified multiple times. (DEPRECATED)", NULL},
{"runtime-opt", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_opts,
"Additional opts to pass to the restore or exec command. Can be specified multiple times", NULL},
{"runtime-arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_args,
"Additional arg to pass to the runtime. Can be specified multiple times", NULL},
{"exec-attach", 0, 0, G_OPTION_ARG_NONE, &opt_attach, "Attach to an exec session", NULL},
{"no-new-keyring", 0, 0, G_OPTION_ARG_NONE, &opt_no_new_keyring, "Do not create a new session keyring for the container", NULL},
{"no-pivot", 0, 0, G_OPTION_ARG_NONE, &opt_no_pivot, "Do not use pivot_root", NULL},
{"replace-listen-pid", 0, 0, G_OPTION_ARG_NONE, &opt_replace_listen_pid, "Replace listen pid if set for oci-runtime pid", NULL},
{"bundle", 'b', 0, G_OPTION_ARG_STRING, &opt_bundle_path, "Bundle path", NULL},
{"persist-dir", '0', 0, G_OPTION_ARG_STRING, &opt_persist_path,
"Persistent directory for a container that can be used for storing container data", NULL},
{"pidfile", 0, 0, G_OPTION_ARG_STRING, &opt_container_pid_file, "PID file (DEPRECATED)", NULL},
{"container-pidfile", 'p', 0, G_OPTION_ARG_STRING, &opt_container_pid_file, "Container PID file", NULL},
{"conmon-pidfile", 'P', 0, G_OPTION_ARG_STRING, &opt_conmon_pid_file, "Conmon daemon PID file", NULL},
{"systemd-cgroup", 's', 0, G_OPTION_ARG_NONE, &opt_systemd_cgroup, "Enable systemd cgroup manager", NULL},
{"exec", 'e', 0, G_OPTION_ARG_NONE, &opt_exec, "Exec a command in a running container", NULL},
{"api-version", 0, 0, G_OPTION_ARG_NONE, &opt_api_version, "Conmon API version to use", NULL},
{"exec-process-spec", 0, 0, G_OPTION_ARG_STRING, &opt_exec_process_spec, "Path to the process spec for exec", NULL},
{"exit-dir", 0, 0, G_OPTION_ARG_STRING, &opt_exit_dir, "Path to the directory where exit files are written", NULL},
{"bundle", 'b', 0, G_OPTION_ARG_STRING, &opt_bundle_path, "Location of the OCI Bundle path", NULL},
{"cid", 'c', 0, G_OPTION_ARG_STRING, &opt_cid, "Identification of Container", NULL},
{"conmon-pidfile", 'P', 0, G_OPTION_ARG_STRING, &opt_conmon_pid_file, "PID file for the initial pid inside of container", NULL},
{"container-pidfile", 'p', 0, G_OPTION_ARG_STRING, &opt_container_pid_file, "PID file for the conmon process", NULL},
{"cuuid", 'u', 0, G_OPTION_ARG_STRING, &opt_cuuid, "Container UUID", NULL},
{"exec", 'e', 0, G_OPTION_ARG_NONE, &opt_exec, "Exec a command into a running container", NULL},
{"exec-attach", 0, 0, G_OPTION_ARG_NONE, &opt_attach, "Attach to an exec session", NULL},
{"exec-process-spec", 0, 0, G_OPTION_ARG_STRING, &opt_exec_process_spec, "Path to the process spec for execution", NULL},
{"exit-command", 0, 0, G_OPTION_ARG_STRING, &opt_exit_command,
"Path to the program to execute when the container terminates its execution", NULL},
{"exit-delay", 0, 0, G_OPTION_ARG_INT, &opt_exit_delay, "Delay before invoking the exit command (in seconds)", NULL},
{"exit-command-arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_exit_args,
"Additional arg to pass to the exit command. Can be specified multiple times", NULL},
{"log-path", 'l', 0, G_OPTION_ARG_STRING_ARRAY, &opt_log_path, "Log file path", NULL},
{"timeout", 'T', 0, G_OPTION_ARG_INT, &opt_timeout, "Timeout in seconds", NULL},
{"log-size-max", 0, 0, G_OPTION_ARG_INT64, &opt_log_size_max, "Maximum size of log file", NULL},
{"socket-dir-path", 0, 0, G_OPTION_ARG_STRING, &opt_socket_path, "Location of container attach sockets", NULL},
{"version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print the version and exit", NULL},
{"syslog", 0, 0, G_OPTION_ARG_NONE, &opt_syslog, "Log to syslog (use with cgroupfs cgroup manager)", NULL},
{"exit-delay", 0, 0, G_OPTION_ARG_INT, &opt_exit_delay, "Delay before invoking the exit command (in seconds)", NULL},
{"exit-dir", 0, 0, G_OPTION_ARG_STRING, &opt_exit_dir, "Path to the directory where exit files are written", NULL},
{"leave-stdin-open", 0, 0, G_OPTION_ARG_NONE, &opt_leave_stdin_open, "Leave stdin open when attached client disconnects", NULL},
{"log-level", 0, 0, G_OPTION_ARG_STRING, &opt_log_level, "Print debug logs based on log level", NULL},
{"log-path", 'l', 0, G_OPTION_ARG_STRING_ARRAY, &opt_log_path, "Log file path", NULL},
{"log-size-max", 0, 0, G_OPTION_ARG_INT64, &opt_log_size_max, "Maximum size of log file", NULL},
{"log-tag", 0, 0, G_OPTION_ARG_STRING, &opt_log_tag, "Additional tag to use for logging", NULL},
{"name", 'n', 0, G_OPTION_ARG_STRING, &opt_name, "Container name", NULL},
{"no-new-keyring", 0, 0, G_OPTION_ARG_NONE, &opt_no_new_keyring, "Do not create a new session keyring for the container", NULL},
{"no-pivot", 0, 0, G_OPTION_ARG_NONE, &opt_no_pivot, "Do not use pivot_root", NULL},
{"no-sync-log", 0, 0, G_OPTION_ARG_NONE, &opt_no_sync_log, "Do not manually call sync on logs after container shutdown", NULL},
{"sync", 0, 0, G_OPTION_ARG_NONE, &opt_sync, "Allowing caller to keep the main conmon process as its child by only forking once",
NULL},
{"persist-dir", '0', 0, G_OPTION_ARG_STRING, &opt_persist_path,
"Persistent directory for a container that can be used for storing container data", NULL},
{"pidfile", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_container_pid_file, "PID file (DEPRECATED)", NULL},
{"replace-listen-pid", 0, 0, G_OPTION_ARG_NONE, &opt_replace_listen_pid, "Replace listen pid if set for oci-runtime pid", NULL},
{"restore", 0, 0, G_OPTION_ARG_STRING, &opt_restore_path, "Restore a container from a checkpoint", NULL},
{"restore-arg", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_opts,
"Additional arg to pass to the restore command. Can be specified multiple times. (DEPRECATED)", NULL},
{"runtime", 'r', 0, G_OPTION_ARG_STRING, &opt_runtime_path, "Path to store runtime data for the container", NULL},
{"runtime-arg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_args,
"Additional arg to pass to the runtime. Can be specified multiple times", NULL},
{"runtime-opt", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_runtime_opts,
"Additional opts to pass to the restore or exec command. Can be specified multiple times", NULL},
{"sdnotify-socket", 0, 0, G_OPTION_ARG_STRING, &opt_sdnotify_socket, "Path to the host's sd-notify socket to relay messages to",
NULL},
{"socket-dir-path", 0, 0, G_OPTION_ARG_STRING, &opt_socket_path, "Location of container attach sockets", NULL},
{"stdin", 'i', 0, G_OPTION_ARG_NONE, &opt_stdin, "Open up a pipe to pass stdin to the container", NULL},
{"sync", 0, 0, G_OPTION_ARG_NONE, &opt_sync, "Keep the main conmon process as its child by only forking once", NULL},
{"syslog", 0, 0, G_OPTION_ARG_NONE, &opt_syslog, "Log to syslog (use with cgroupfs cgroup manager)", NULL},
{"systemd-cgroup", 's', 0, G_OPTION_ARG_NONE, &opt_systemd_cgroup,
"Enable systemd cgroup manager, rather then use the cgroupfs directly", NULL},
{"terminal", 't', 0, G_OPTION_ARG_NONE, &opt_terminal, "Allocate a pseudo-TTY. The default is false", NULL},
{"timeout", 'T', 0, G_OPTION_ARG_INT, &opt_timeout, "Kill container after specified timeout in seconds.", NULL},
{"version", 0, 0, G_OPTION_ARG_NONE, &opt_version, "Print the version and exit", NULL},
{NULL, 0, 0, 0, NULL, NULL, NULL}};

29
tools/Makefile Normal file
View File

@ -0,0 +1,29 @@
export GO111MODULE=off
GO := go
BUILDDIR := build
all: $(BUILDDIR)
.PHONY: vendor
vendor:
export GO111MODULE=on \
$(GO) mod tidy && \
$(GO) mod vendor && \
$(GO) mod verify
define go-build
$(shell cd `pwd` && $(GO) build -o $(BUILDDIR)/$(shell basename $(1)) $(1))
@echo > /dev/null
endef
.PHONY: clean
clean:
rm -rf $(BUILDDIR)
$(BUILDDIR): \
$(BUILDDIR)/go-md2man \
$(BUILDDIR)/go-md2man:
$(call go-build,./vendor/github.com/cpuguy83/go-md2man)

7
tools/go.mod Normal file
View File

@ -0,0 +1,7 @@
module github.com/containers/storage/tests/tools
go 1.14
require (
github.com/cpuguy83/go-md2man v1.0.10
)

4
tools/go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=

10
tools/tools.go Normal file
View File

@ -0,0 +1,10 @@
// +build tools
package tools
// Importing the packages here will allow to vendor those via
// `go mod vendor`.
import (
_ "github.com/cpuguy83/go-md2man"
)

View File

@ -0,0 +1,2 @@
go-md2man
bin

22
tools/vendor/github.com/cpuguy83/go-md2man/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,22 @@
language: go
go:
- "1.8.x"
- "1.9.x"
- "1.10.x"
- "1.11.x"
- "stable"
- tip
env:
- GO111MODULE: "on"
matrix:
allow_failures:
- go: tip
script:
- if [ "${TRAVIS_GO_VERSION}" = "stable" ]; then make check-mod; fi
- if [ "${TRAVIS_GO_VERSION}" = "stable" ]; then make golangci-lint; fi
- if [ "${TRAVIS_GO_VERSION}" = "stable" ]; then echo running check scripts; make check; fi
- make build
- make TEST_FLAGS="-v" test

View File

@ -0,0 +1,8 @@
FROM golang:1.8 AS build
COPY . /go/src/github.com/cpuguy83/go-md2man
WORKDIR /go/src/github.com/cpuguy83/go-md2man
RUN CGO_ENABLED=0 go build
FROM scratch
COPY --from=build /go/src/github.com/cpuguy83/go-md2man/go-md2man /go-md2man
ENTRYPOINT ["/go-md2man"]

21
tools/vendor/github.com/cpuguy83/go-md2man/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Brian Goff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

48
tools/vendor/github.com/cpuguy83/go-md2man/Makefile generated vendored Normal file
View File

@ -0,0 +1,48 @@
GO111MODULE ?= on
LINTER_BIN ?= golangci-lint
export GO111MODULE
.PHONY:
build: bin/go-md2man
.PHONY: clean
clean:
@rm -rf bin/*
.PHONY: check
check: lint
.PHONY: test
test:
@go test $(TEST_FLAGS) ./...
.PHONY: lint
lint:
@$(LINTER_BIN) run --new-from-rev "HEAD~$(git rev-list master.. --count)" ./...
bin/go-md2man: actual_build_flags := $(BUILD_FLAGS) -o bin/go-md2man
bin/go-md2man: bin
@CGO_ENABLED=0 go build $(actual_build_flags)
bin:
@mkdir ./bin
$(LINTER_BIN): linter_bin_path := $(shell which $(LINTER_BIN))
$(LINTER_BIN):
@if [ -z $(linter_bin_path) ] || [ ! -x $(linter_bin_path) ]; then \
curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.15.0; \
fi
.PHONY: mod
mod:
@go mod tidy
.PHONY: check-mod
check-mod: # verifies that module changes for go.mod and go.sum are checked in
@hack/ci/check_mods.sh
.PHONY: vendor
vendor: mod
@go mod vendor -v

21
tools/vendor/github.com/cpuguy83/go-md2man/README.md generated vendored Normal file
View File

@ -0,0 +1,21 @@
go-md2man
=========
** Work in Progress **
This still needs a lot of help to be complete, or even usable!
Uses blackfriday to process markdown into man pages.
### Usage
./md2man -in /path/to/markdownfile.md -out /manfile/output/path
### How to contribute
We use [dep](https://github.com/golang/dep/) for vendoring Go packages.
See dep documentation for how to update.
### TODO
- Needs oh so much testing love
- Look into blackfriday's 2.0 API

View File

@ -0,0 +1,23 @@
go-md2man 1 "January 2015" go-md2man "User Manual"
==================================================
# NAME
go-md2man - Convert mardown files into manpages
# SYNOPSIS
go-md2man -in=[/path/to/md/file] -out=[/path/to/output]
# Description
go-md2man converts standard markdown formatted documents into manpages. It is
written purely in Go so as to reduce dependencies on 3rd party libs.
By default, the input is stdin and the output is stdout.
# Example
Convert the markdown file "go-md2man.1.md" into a manpage.
go-md2man -in=README.md -out=go-md2man.1.out
# HISTORY
January 2015, Originally compiled by Brian Goff( cpuguy83@gmail.com )

5
tools/vendor/github.com/cpuguy83/go-md2man/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/cpuguy83/go-md2man
go 1.12
require github.com/russross/blackfriday v1.5.2

2
tools/vendor/github.com/cpuguy83/go-md2man/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=

51
tools/vendor/github.com/cpuguy83/go-md2man/md2man.go generated vendored Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"flag"
"fmt"
"io/ioutil"
"os"
"github.com/cpuguy83/go-md2man/md2man"
)
var inFilePath = flag.String("in", "", "Path to file to be processed (default: stdin)")
var outFilePath = flag.String("out", "", "Path to output processed file (default: stdout)")
func main() {
var err error
flag.Parse()
inFile := os.Stdin
if *inFilePath != "" {
inFile, err = os.Open(*inFilePath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
defer inFile.Close() // nolint: errcheck
doc, err := ioutil.ReadAll(inFile)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
out := md2man.Render(doc)
outFile := os.Stdout
if *outFilePath != "" {
outFile, err = os.Create(*outFilePath)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer outFile.Close() // nolint: errcheck
}
_, err = outFile.Write(out)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}

View File

@ -0,0 +1,20 @@
package md2man
import (
"github.com/russross/blackfriday"
)
// Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte {
renderer := RoffRenderer(0)
extensions := 0
extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
extensions |= blackfriday.EXTENSION_TABLES
extensions |= blackfriday.EXTENSION_FENCED_CODE
extensions |= blackfriday.EXTENSION_AUTOLINK
extensions |= blackfriday.EXTENSION_SPACE_HEADERS
extensions |= blackfriday.EXTENSION_FOOTNOTES
extensions |= blackfriday.EXTENSION_TITLEBLOCK
return blackfriday.Markdown(doc, renderer, extensions)
}

View File

@ -0,0 +1,285 @@
package md2man
import (
"bytes"
"fmt"
"html"
"strings"
"github.com/russross/blackfriday"
)
type roffRenderer struct {
ListCounters []int
}
// RoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown
func RoffRenderer(flags int) blackfriday.Renderer {
return &roffRenderer{}
}
func (r *roffRenderer) GetFlags() int {
return 0
}
func (r *roffRenderer) TitleBlock(out *bytes.Buffer, text []byte) {
out.WriteString(".TH ")
splitText := bytes.Split(text, []byte("\n"))
for i, line := range splitText {
line = bytes.TrimPrefix(line, []byte("% "))
if i == 0 {
line = bytes.Replace(line, []byte("("), []byte("\" \""), 1)
line = bytes.Replace(line, []byte(")"), []byte("\" \""), 1)
}
line = append([]byte("\""), line...)
line = append(line, []byte("\" ")...)
out.Write(line)
}
out.WriteString("\n")
// disable hyphenation
out.WriteString(".nh\n")
// disable justification (adjust text to left margin only)
out.WriteString(".ad l\n")
}
func (r *roffRenderer) BlockCode(out *bytes.Buffer, text []byte, lang string) {
out.WriteString("\n.PP\n.RS\n\n.nf\n")
escapeSpecialChars(out, text)
out.WriteString("\n.fi\n.RE\n")
}
func (r *roffRenderer) BlockQuote(out *bytes.Buffer, text []byte) {
out.WriteString("\n.PP\n.RS\n")
out.Write(text)
out.WriteString("\n.RE\n")
}
func (r *roffRenderer) BlockHtml(out *bytes.Buffer, text []byte) { // nolint: golint
out.Write(text)
}
func (r *roffRenderer) Header(out *bytes.Buffer, text func() bool, level int, id string) {
marker := out.Len()
switch {
case marker == 0:
// This is the doc header
out.WriteString(".TH ")
case level == 1:
out.WriteString("\n\n.SH ")
case level == 2:
out.WriteString("\n.SH ")
default:
out.WriteString("\n.SS ")
}
if !text() {
out.Truncate(marker)
return
}
}
func (r *roffRenderer) HRule(out *bytes.Buffer) {
out.WriteString("\n.ti 0\n\\l'\\n(.lu'\n")
}
func (r *roffRenderer) List(out *bytes.Buffer, text func() bool, flags int) {
marker := out.Len()
r.ListCounters = append(r.ListCounters, 1)
out.WriteString("\n.RS\n")
if !text() {
out.Truncate(marker)
return
}
r.ListCounters = r.ListCounters[:len(r.ListCounters)-1]
out.WriteString("\n.RE\n")
}
func (r *roffRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
if flags&blackfriday.LIST_TYPE_ORDERED != 0 {
out.WriteString(fmt.Sprintf(".IP \"%3d.\" 5\n", r.ListCounters[len(r.ListCounters)-1]))
r.ListCounters[len(r.ListCounters)-1]++
} else {
out.WriteString(".IP \\(bu 2\n")
}
out.Write(text)
out.WriteString("\n")
}
func (r *roffRenderer) Paragraph(out *bytes.Buffer, text func() bool) {
marker := out.Len()
out.WriteString("\n.PP\n")
if !text() {
out.Truncate(marker)
return
}
if marker != 0 {
out.WriteString("\n")
}
}
func (r *roffRenderer) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
out.WriteString("\n.TS\nallbox;\n")
maxDelims := 0
lines := strings.Split(strings.TrimRight(string(header), "\n")+"\n"+strings.TrimRight(string(body), "\n"), "\n")
for _, w := range lines {
curDelims := strings.Count(w, "\t")
if curDelims > maxDelims {
maxDelims = curDelims
}
}
out.Write([]byte(strings.Repeat("l ", maxDelims+1) + "\n"))
out.Write([]byte(strings.Repeat("l ", maxDelims+1) + ".\n"))
out.Write(header)
if len(header) > 0 {
out.Write([]byte("\n"))
}
out.Write(body)
out.WriteString("\n.TE\n")
}
func (r *roffRenderer) TableRow(out *bytes.Buffer, text []byte) {
if out.Len() > 0 {
out.WriteString("\n")
}
out.Write(text)
}
func (r *roffRenderer) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString("\t")
}
if len(text) == 0 {
text = []byte{' '}
}
out.Write([]byte("\\fB\\fC" + string(text) + "\\fR"))
}
func (r *roffRenderer) TableCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString("\t")
}
if len(text) > 30 {
text = append([]byte("T{\n"), text...)
text = append(text, []byte("\nT}")...)
}
if len(text) == 0 {
text = []byte{' '}
}
out.Write(text)
}
func (r *roffRenderer) Footnotes(out *bytes.Buffer, text func() bool) {
}
func (r *roffRenderer) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
}
func (r *roffRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
out.WriteString("\n\\[la]")
out.Write(link)
out.WriteString("\\[ra]")
}
func (r *roffRenderer) CodeSpan(out *bytes.Buffer, text []byte) {
out.WriteString("\\fB\\fC")
escapeSpecialChars(out, text)
out.WriteString("\\fR")
}
func (r *roffRenderer) DoubleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\fB")
out.Write(text)
out.WriteString("\\fP")
}
func (r *roffRenderer) Emphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\fI")
out.Write(text)
out.WriteString("\\fP")
}
func (r *roffRenderer) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
}
func (r *roffRenderer) LineBreak(out *bytes.Buffer) {
out.WriteString("\n.br\n")
}
func (r *roffRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
out.Write(content)
r.AutoLink(out, link, 0)
}
func (r *roffRenderer) RawHtmlTag(out *bytes.Buffer, tag []byte) { // nolint: golint
out.Write(tag)
}
func (r *roffRenderer) TripleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\s+2")
out.Write(text)
out.WriteString("\\s-2")
}
func (r *roffRenderer) StrikeThrough(out *bytes.Buffer, text []byte) {
}
func (r *roffRenderer) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
}
func (r *roffRenderer) Entity(out *bytes.Buffer, entity []byte) {
out.WriteString(html.UnescapeString(string(entity)))
}
func (r *roffRenderer) NormalText(out *bytes.Buffer, text []byte) {
escapeSpecialChars(out, text)
}
func (r *roffRenderer) DocumentHeader(out *bytes.Buffer) {
}
func (r *roffRenderer) DocumentFooter(out *bytes.Buffer) {
}
func needsBackslash(c byte) bool {
for _, r := range []byte("-_&\\~") {
if c == r {
return true
}
}
return false
}
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
for i := 0; i < len(text); i++ {
// escape initial apostrophe or period
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
out.WriteString("\\&")
}
// directly copy normal characters
org := i
for i < len(text) && !needsBackslash(text[i]) {
i++
}
if i > org {
out.Write(text[org:i])
}
// escape a character
if i >= len(text) {
break
}
out.WriteByte('\\')
out.WriteByte(text[i])
}
}

View File

@ -0,0 +1,8 @@
*.out
*.swp
*.8
*.6
_obj
_test*
markdown
tags

View File

@ -0,0 +1,17 @@
sudo: false
language: go
go:
- "1.9.x"
- "1.10.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 -race ./...

View 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.

369
tools/vendor/github.com/russross/blackfriday/README.md generated vendored Normal file
View File

@ -0,0 +1,369 @@
Blackfriday
[![Build Status][BuildSVG]][BuildURL]
[![Godoc][GodocV2SVG]][GodocV2URL]
===========
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 and git installed:
go get -u gopkg.in/russross/blackfriday.v2
will download, compile, and install the package into your `$GOPATH` directory
hierarchy.
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 [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.
If you are still interested in the legacy `v1`, you can import it from
`github.com/russross/blackfriday`. Documentation for the legacy v1 can be found
here: https://godoc.org/github.com/russross/blackfriday
### Known issue with `dep`
There is a known problem with using Blackfriday v1 _transitively_ and `dep`.
Currently `dep` prioritizes semver versions over anything else, and picks the
latest one, plus it does not apply a `[[constraint]]` specifier to transitively
pulled in packages. So if you're using something that uses Blackfriday v1, but
that something does not use `dep` yet, you will get Blackfriday v2 pulled in and
your first dependency will fail to build.
There are couple of fixes for it, documented here:
https://github.com/golang/dep/blob/master/docs/FAQ.md#how-do-i-constrain-a-transitive-dependencys-version
Meanwhile, `dep` team is working on a more general solution to the constraints
on transitive dependencies problem: https://github.com/golang/dep/issues/1124.
Usage
-----
### v1
For basic usage, it is as simple as getting your input into a byte
slice and calling:
output := blackfriday.MarkdownBasic(input)
This renders it with no extensions enabled. To get a more useful
feature set, use this instead:
output := blackfriday.MarkdownCommon(input)
### v2
For the most sensible markdown processing, it is as simple as getting your input
into a byte slice and calling:
```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"
"gopkg.in/russross/blackfriday.v2"
)
// ...
unsafe := blackfriday.Run(input)
html := bluemonday.UGCPolicy().SanitizeBytes(unsafe)
```
### Custom options, v1
If you want to customize the set of options, first get a renderer
(currently only the HTML output engine), then use it to
call the more general `Markdown` function. For examples, see the
implementations of `MarkdownBasic` and `MarkdownCommon` in
`markdown.go`.
### Custom options, v2
If you want to customize the set of options, use `blackfriday.WithExtensions`,
`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`.
### `blackfriday-tool`
You can also check out `blackfriday-tool` for a more complete example
of how to use it. Download and install it using:
go get github.com/russross/blackfriday-tool
This is a simple command-line tool that allows you to process a
markdown file using a standalone program. You can also browse the
source directly on github if you are just looking for some example
code:
* <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.
### Sanitized anchor names
Blackfriday includes an algorithm for creating sanitized anchor names
corresponding to a given input text. This algorithm is used to create
anchors for headings when `EXTENSION_AUTO_HEADER_IDS` is enabled. The
algorithm has a specification, so that other packages can create
compatible anchor names and links to those anchors.
The specification is located at https://godoc.org/github.com/russross/blackfriday#hdr-Sanitized_Anchor_Names.
[`SanitizedAnchorName`](https://godoc.org/github.com/russross/blackfriday#SanitizedAnchorName) exposes this functionality, and can be used to
create compatible links to the anchor names generated by blackfriday.
This algorithm is also implemented in a small standalone package at
[`github.com/shurcooL/sanitized_anchor_name`](https://godoc.org/github.com/shurcooL/sanitized_anchor_name). It can be useful for clients
that want a small package and don't need full functionality of blackfriday.
Features
--------
All features of Sundown are supported, including:
* **Compatibility**. The Markdown v1.0.3 test suite passes with
the `--tidy` option. Without `--tidy`, the differences are
mostly in whitespace and entity escaping, where blackfriday is
more consistent and cleaner.
* **Common extensions**, including table support, fenced code
blocks, autolinks, strikethroughs, non-strict emphasis, etc.
* **Safety**. Blackfriday is paranoid when parsing, making it safe
to feed untrusted user input without fear of bad things
happening. The test suite stress tests this and there are no
known inputs that make it crash. If you find one, please let me
know and send me the input that does it.
NOTE: "safety" in this context means *runtime safety only*. In order to
protect yourself against JavaScript injection in untrusted content, see
[this example](https://github.com/russross/blackfriday#sanitize-untrusted-content).
* **Fast processing**. It is fast enough to render on-demand in
most web applications without having to cache the output.
* **Thread safety**. You can run multiple parsers in different
goroutines without ill effect. There is no dependence on global
shared state.
* **Minimal dependencies**. Blackfriday only depends on standard
library packages in Go. The source code is pretty
self-contained, so it is easy to add to any project, including
Google App Engine projects.
* **Standards compliant**. Output successfully validates using the
W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional.
Extensions
----------
In addition to the standard markdown syntax, this package
implements the following extensions:
* **Intra-word emphasis supression**. The `_` character is
commonly used inside words when discussing code, so having
markdown interpret it as an emphasis command is usually the
wrong thing. Blackfriday lets you treat all emphasis markers as
normal characters when they occur inside a word.
* **Tables**. Tables can be created by drawing them in the input
using a simple syntax:
```
Name | Age
--------|------
Bob | 27
Alice | 23
```
* **Fenced code blocks**. In addition to the normal 4-space
indentation to mark code blocks, you can explicitly mark them
and supply a language (to make syntax highlighting simple). Just
mark it like this:
``` go
func getTrue() bool {
return true
}
```
You can use 3 or more backticks to mark the beginning of the
block, and the same number to mark the end of the block.
To preserve classes of fenced code blocks while using the bluemonday
HTML sanitizer, use the following policy:
``` go
p := bluemonday.UGCPolicy()
p.AllowAttrs("class").Matching(regexp.MustCompile("^language-[a-zA-Z0-9]+$")).OnElements("code")
html := p.SanitizeBytes(unsafe)
```
* **Definition lists**. A simple definition list is made of a single-line
term followed by a colon and the definition for that term.
Cat
: Fluffy animal everyone likes
Internet
: Vector of transmission for pictures of cats
Terms must be separated from the previous definition by a blank line.
* **Footnotes**. A marker in the text that will become a superscript number;
a footnote definition that will be placed in a list of footnotes at the
end of the document. A footnote looks like this:
This is a footnote.[^1]
[^1]: the footnote text.
* **Autolinking**. Blackfriday can find URLs that have not been
explicitly marked as links and turn them into links.
* **Strikethrough**. Use two tildes (`~~`) to mark text that
should be crossed out.
* **Hard line breaks**. With this extension enabled (it is off by
default in the `MarkdownBasic` and `MarkdownCommon` convenience
functions), newlines in the input translate into line breaks in
the output.
* **Smart quotes**. Smartypants-style punctuation substitution is
supported, turning normal double- and single-quote marks into
curly quotes, etc.
* **LaTeX-style dash parsing** is an additional option, where `--`
is translated into `&ndash;`, and `---` is translated into
`&mdash;`. This differs from most smartypants processors, which
turn a single hyphen into an ndash and a double hyphen into an
mdash.
* **Smart fractions**, where anything that looks like a fraction
is translated into suitable HTML (instead of just a few special
cases like most smartypant processors). For example, `4/5`
becomes `<sup>4</sup>&frasl;<sub>5</sub>`, which renders as
<sup>4</sup>&frasl;<sub>5</sub>.
Other renderers
---------------
Blackfriday is structured to allow alternative rendering engines. Here
are a few of note:
* [github_flavored_markdown](https://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://bitbucket.org/ambrevar/blackfriday-latex):
renders output as LaTeX.
* [bfchroma](https://github.com/Depado/bfchroma/): provides convenience
integration with the [Chroma](https://github.com/alecthomas/chroma) code
highlighting library. bfchroma is only compatible with v2 of Blackfriday and
provides a drop-in renderer ready to use with Blackfriday, as well as
options and means for further customization.
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"
[7]: https://github.com/golang/dep/ "dep"
[8]: https://github.com/Masterminds/glide "Glide"
[BuildSVG]: https://travis-ci.org/russross/blackfriday.svg?branch=master
[BuildURL]: https://travis-ci.org/russross/blackfriday
[GodocV2SVG]: https://godoc.org/gopkg.in/russross/blackfriday.v2?status.svg
[GodocV2URL]: https://godoc.org/gopkg.in/russross/blackfriday.v2

1474
tools/vendor/github.com/russross/blackfriday/block.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

32
tools/vendor/github.com/russross/blackfriday/doc.go generated vendored Normal file
View File

@ -0,0 +1,32 @@
// Package blackfriday is a Markdown processor.
//
// It translates plain text with simple formatting rules into HTML or LaTeX.
//
// Sanitized Anchor Names
//
// Blackfriday includes an algorithm for creating sanitized anchor names
// corresponding to a given input text. This algorithm is used to create
// anchors for headings when EXTENSION_AUTO_HEADER_IDS is enabled. The
// algorithm is specified below, so that other packages can create
// compatible anchor names and links to those anchors.
//
// The algorithm iterates over the input text, interpreted as UTF-8,
// one Unicode code point (rune) at a time. All runes that are letters (category L)
// or numbers (category N) are considered valid characters. They are mapped to
// lower case, and included in the output. All other runes are considered
// invalid characters. Invalid characters that preceed the first valid character,
// as well as invalid character that follow the last valid character
// are dropped completely. All other sequences of invalid characters
// between two valid characters are replaced with a single dash character '-'.
//
// SanitizedAnchorName exposes this functionality, and can be used to
// create compatible links to the anchor names generated by blackfriday.
// This algorithm is also implemented in a small standalone package at
// github.com/shurcooL/sanitized_anchor_name. It can be useful for clients
// that want a small package and don't need full functionality of blackfriday.
package blackfriday
// NOTE: Keep Sanitized Anchor Name algorithm in sync with package
// github.com/shurcooL/sanitized_anchor_name.
// Otherwise, users of sanitized_anchor_name will get anchor names
// that are incompatible with those generated by blackfriday.

1
tools/vendor/github.com/russross/blackfriday/go.mod generated vendored Normal file
View File

@ -0,0 +1 @@
module github.com/russross/blackfriday

938
tools/vendor/github.com/russross/blackfriday/html.go generated vendored Normal file
View File

@ -0,0 +1,938 @@
//
// 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"
"regexp"
"strconv"
"strings"
)
// Html renderer configuration options.
const (
HTML_SKIP_HTML = 1 << iota // skip preformatted HTML blocks
HTML_SKIP_STYLE // skip embedded <style> elements
HTML_SKIP_IMAGES // skip embedded images
HTML_SKIP_LINKS // skip all links
HTML_SAFELINK // only link to trusted protocols
HTML_NOFOLLOW_LINKS // only link with rel="nofollow"
HTML_NOREFERRER_LINKS // only link with rel="noreferrer"
HTML_HREF_TARGET_BLANK // add a blank target
HTML_TOC // generate a table of contents
HTML_OMIT_CONTENTS // skip the main contents (for a standalone table of contents)
HTML_COMPLETE_PAGE // generate a complete HTML page
HTML_USE_XHTML // generate XHTML output instead of HTML
HTML_USE_SMARTYPANTS // enable smart punctuation substitutions
HTML_SMARTYPANTS_FRACTIONS // enable smart fractions (with HTML_USE_SMARTYPANTS)
HTML_SMARTYPANTS_DASHES // enable smart dashes (with HTML_USE_SMARTYPANTS)
HTML_SMARTYPANTS_LATEX_DASHES // enable LaTeX-style dashes (with HTML_USE_SMARTYPANTS and HTML_SMARTYPANTS_DASHES)
HTML_SMARTYPANTS_ANGLED_QUOTES // enable angled double quotes (with HTML_USE_SMARTYPANTS) for double quotes rendering
HTML_SMARTYPANTS_QUOTES_NBSP // enable "French guillemets" (with HTML_USE_SMARTYPANTS)
HTML_FOOTNOTE_RETURN_LINKS // generate a link at the end of a footnote to return to the source
)
var (
alignments = []string{
"left",
"right",
"center",
}
// TODO: improve this regexp to catch all possible entities:
htmlEntity = regexp.MustCompile(`&[a-z]{2,5};`)
)
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 Header ID, to ensure
// uniqueness.
HeaderIDPrefix string
// If set, add this text to the back of each Header ID, to ensure uniqueness.
HeaderIDSuffix string
}
// Html is a type that implements the Renderer interface for HTML output.
//
// Do not create this directly, instead use the HtmlRenderer function.
type Html struct {
flags int // HTML_* options
closeTag string // how to end singleton tags: either " />" or ">"
title string // document title
css string // optional css file url (used with HTML_COMPLETE_PAGE)
parameters HtmlRendererParameters
// table of contents data
tocMarker int
headerCount int
currentLevel int
toc *bytes.Buffer
// Track header IDs to prevent ID collision in a single generation.
headerIDs map[string]int
smartypants *smartypantsRenderer
}
const (
xhtmlClose = " />"
htmlClose = ">"
)
// HtmlRenderer creates and configures an Html object, which
// satisfies the Renderer interface.
//
// flags is a set of HTML_* options ORed together.
// title is the title of the document, and css is a URL for the document's
// stylesheet.
// title and css are only used when HTML_COMPLETE_PAGE is selected.
func HtmlRenderer(flags int, title string, css string) Renderer {
return HtmlRendererWithParameters(flags, title, css, HtmlRendererParameters{})
}
func HtmlRendererWithParameters(flags int, title string,
css string, renderParameters HtmlRendererParameters) Renderer {
// configure the rendering engine
closeTag := htmlClose
if flags&HTML_USE_XHTML != 0 {
closeTag = xhtmlClose
}
if renderParameters.FootnoteReturnLinkContents == "" {
renderParameters.FootnoteReturnLinkContents = `<sup>[return]</sup>`
}
return &Html{
flags: flags,
closeTag: closeTag,
title: title,
css: css,
parameters: renderParameters,
headerCount: 0,
currentLevel: 0,
toc: new(bytes.Buffer),
headerIDs: make(map[string]int),
smartypants: smartypants(flags),
}
}
// Using if statements is a bit faster than a switch statement. As the compiler
// improves, this should be unnecessary this is only worthwhile because
// attrEscape is the single largest CPU user in normal use.
// Also tried using map, but that gave a ~3x slowdown.
func escapeSingleChar(char byte) (string, bool) {
if char == '"' {
return "&quot;", true
}
if char == '&' {
return "&amp;", true
}
if char == '<' {
return "&lt;", true
}
if char == '>' {
return "&gt;", true
}
return "", false
}
func attrEscape(out *bytes.Buffer, src []byte) {
org := 0
for i, ch := range src {
if entity, ok := escapeSingleChar(ch); ok {
if i > org {
// copy all the normal characters since the last escape
out.Write(src[org:i])
}
org = i + 1
out.WriteString(entity)
}
}
if org < len(src) {
out.Write(src[org:])
}
}
func entityEscapeWithSkip(out *bytes.Buffer, src []byte, skipRanges [][]int) {
end := 0
for _, rang := range skipRanges {
attrEscape(out, src[end:rang[0]])
out.Write(src[rang[0]:rang[1]])
end = rang[1]
}
attrEscape(out, src[end:])
}
func (options *Html) GetFlags() int {
return options.flags
}
func (options *Html) TitleBlock(out *bytes.Buffer, text []byte) {
text = bytes.TrimPrefix(text, []byte("% "))
text = bytes.Replace(text, []byte("\n% "), []byte("\n"), -1)
out.WriteString("<h1 class=\"title\">")
out.Write(text)
out.WriteString("\n</h1>")
}
func (options *Html) Header(out *bytes.Buffer, text func() bool, level int, id string) {
marker := out.Len()
doubleSpace(out)
if id == "" && options.flags&HTML_TOC != 0 {
id = fmt.Sprintf("toc_%d", options.headerCount)
}
if id != "" {
id = options.ensureUniqueHeaderID(id)
if options.parameters.HeaderIDPrefix != "" {
id = options.parameters.HeaderIDPrefix + id
}
if options.parameters.HeaderIDSuffix != "" {
id = id + options.parameters.HeaderIDSuffix
}
out.WriteString(fmt.Sprintf("<h%d id=\"%s\">", level, id))
} else {
out.WriteString(fmt.Sprintf("<h%d>", level))
}
tocMarker := out.Len()
if !text() {
out.Truncate(marker)
return
}
// are we building a table of contents?
if options.flags&HTML_TOC != 0 {
options.TocHeaderWithAnchor(out.Bytes()[tocMarker:], level, id)
}
out.WriteString(fmt.Sprintf("</h%d>\n", level))
}
func (options *Html) BlockHtml(out *bytes.Buffer, text []byte) {
if options.flags&HTML_SKIP_HTML != 0 {
return
}
doubleSpace(out)
out.Write(text)
out.WriteByte('\n')
}
func (options *Html) HRule(out *bytes.Buffer) {
doubleSpace(out)
out.WriteString("<hr")
out.WriteString(options.closeTag)
out.WriteByte('\n')
}
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, info string) {
doubleSpace(out)
endOfLang := strings.IndexAny(info, "\t ")
if endOfLang < 0 {
endOfLang = len(info)
}
lang := info[:endOfLang]
if len(lang) == 0 || lang == "." {
out.WriteString("<pre><code>")
} else {
out.WriteString("<pre><code class=\"language-")
attrEscape(out, []byte(lang))
out.WriteString("\">")
}
attrEscape(out, text)
out.WriteString("</code></pre>\n")
}
func (options *Html) BlockQuote(out *bytes.Buffer, text []byte) {
doubleSpace(out)
out.WriteString("<blockquote>\n")
out.Write(text)
out.WriteString("</blockquote>\n")
}
func (options *Html) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
doubleSpace(out)
out.WriteString("<table>\n<thead>\n")
out.Write(header)
out.WriteString("</thead>\n\n<tbody>\n")
out.Write(body)
out.WriteString("</tbody>\n</table>\n")
}
func (options *Html) TableRow(out *bytes.Buffer, text []byte) {
doubleSpace(out)
out.WriteString("<tr>\n")
out.Write(text)
out.WriteString("\n</tr>\n")
}
func (options *Html) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
doubleSpace(out)
switch align {
case TABLE_ALIGNMENT_LEFT:
out.WriteString("<th align=\"left\">")
case TABLE_ALIGNMENT_RIGHT:
out.WriteString("<th align=\"right\">")
case TABLE_ALIGNMENT_CENTER:
out.WriteString("<th align=\"center\">")
default:
out.WriteString("<th>")
}
out.Write(text)
out.WriteString("</th>")
}
func (options *Html) TableCell(out *bytes.Buffer, text []byte, align int) {
doubleSpace(out)
switch align {
case TABLE_ALIGNMENT_LEFT:
out.WriteString("<td align=\"left\">")
case TABLE_ALIGNMENT_RIGHT:
out.WriteString("<td align=\"right\">")
case TABLE_ALIGNMENT_CENTER:
out.WriteString("<td align=\"center\">")
default:
out.WriteString("<td>")
}
out.Write(text)
out.WriteString("</td>")
}
func (options *Html) Footnotes(out *bytes.Buffer, text func() bool) {
out.WriteString("<div class=\"footnotes\">\n")
options.HRule(out)
options.List(out, text, LIST_TYPE_ORDERED)
out.WriteString("</div>\n")
}
func (options *Html) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
if flags&LIST_ITEM_CONTAINS_BLOCK != 0 || flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
doubleSpace(out)
}
slug := slugify(name)
out.WriteString(`<li id="`)
out.WriteString(`fn:`)
out.WriteString(options.parameters.FootnoteAnchorPrefix)
out.Write(slug)
out.WriteString(`">`)
out.Write(text)
if options.flags&HTML_FOOTNOTE_RETURN_LINKS != 0 {
out.WriteString(` <a class="footnote-return" href="#`)
out.WriteString(`fnref:`)
out.WriteString(options.parameters.FootnoteAnchorPrefix)
out.Write(slug)
out.WriteString(`">`)
out.WriteString(options.parameters.FootnoteReturnLinkContents)
out.WriteString(`</a>`)
}
out.WriteString("</li>\n")
}
func (options *Html) List(out *bytes.Buffer, text func() bool, flags int) {
marker := out.Len()
doubleSpace(out)
if flags&LIST_TYPE_DEFINITION != 0 {
out.WriteString("<dl>")
} else if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("<ol>")
} else {
out.WriteString("<ul>")
}
if !text() {
out.Truncate(marker)
return
}
if flags&LIST_TYPE_DEFINITION != 0 {
out.WriteString("</dl>\n")
} else if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("</ol>\n")
} else {
out.WriteString("</ul>\n")
}
}
func (options *Html) ListItem(out *bytes.Buffer, text []byte, flags int) {
if (flags&LIST_ITEM_CONTAINS_BLOCK != 0 && flags&LIST_TYPE_DEFINITION == 0) ||
flags&LIST_ITEM_BEGINNING_OF_LIST != 0 {
doubleSpace(out)
}
if flags&LIST_TYPE_TERM != 0 {
out.WriteString("<dt>")
} else if flags&LIST_TYPE_DEFINITION != 0 {
out.WriteString("<dd>")
} else {
out.WriteString("<li>")
}
out.Write(text)
if flags&LIST_TYPE_TERM != 0 {
out.WriteString("</dt>\n")
} else if flags&LIST_TYPE_DEFINITION != 0 {
out.WriteString("</dd>\n")
} else {
out.WriteString("</li>\n")
}
}
func (options *Html) Paragraph(out *bytes.Buffer, text func() bool) {
marker := out.Len()
doubleSpace(out)
out.WriteString("<p>")
if !text() {
out.Truncate(marker)
return
}
out.WriteString("</p>\n")
}
func (options *Html) AutoLink(out *bytes.Buffer, link []byte, kind int) {
skipRanges := htmlEntity.FindAllIndex(link, -1)
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) && kind != LINK_TYPE_EMAIL {
// mark it but don't link it if it is not a safe link: no smartypants
out.WriteString("<tt>")
entityEscapeWithSkip(out, link, skipRanges)
out.WriteString("</tt>")
return
}
out.WriteString("<a href=\"")
if kind == LINK_TYPE_EMAIL {
out.WriteString("mailto:")
} else {
options.maybeWriteAbsolutePrefix(out, link)
}
entityEscapeWithSkip(out, link, skipRanges)
var relAttrs []string
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "nofollow")
}
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "noreferrer")
}
if len(relAttrs) > 0 {
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
}
// blank target only add to external link
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
out.WriteString("\" target=\"_blank")
}
out.WriteString("\">")
// Pretty print: if we get an email address as
// an actual URI, e.g. `mailto:foo@bar.com`, we don't
// want to print the `mailto:` prefix
switch {
case bytes.HasPrefix(link, []byte("mailto://")):
attrEscape(out, link[len("mailto://"):])
case bytes.HasPrefix(link, []byte("mailto:")):
attrEscape(out, link[len("mailto:"):])
default:
entityEscapeWithSkip(out, link, skipRanges)
}
out.WriteString("</a>")
}
func (options *Html) CodeSpan(out *bytes.Buffer, text []byte) {
out.WriteString("<code>")
attrEscape(out, text)
out.WriteString("</code>")
}
func (options *Html) DoubleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("<strong>")
out.Write(text)
out.WriteString("</strong>")
}
func (options *Html) Emphasis(out *bytes.Buffer, text []byte) {
if len(text) == 0 {
return
}
out.WriteString("<em>")
out.Write(text)
out.WriteString("</em>")
}
func (options *Html) maybeWriteAbsolutePrefix(out *bytes.Buffer, link []byte) {
if options.parameters.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
out.WriteString(options.parameters.AbsolutePrefix)
if link[0] != '/' {
out.WriteByte('/')
}
}
}
func (options *Html) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
if options.flags&HTML_SKIP_IMAGES != 0 {
return
}
out.WriteString("<img src=\"")
options.maybeWriteAbsolutePrefix(out, link)
attrEscape(out, link)
out.WriteString("\" alt=\"")
if len(alt) > 0 {
attrEscape(out, alt)
}
if len(title) > 0 {
out.WriteString("\" title=\"")
attrEscape(out, title)
}
out.WriteByte('"')
out.WriteString(options.closeTag)
}
func (options *Html) LineBreak(out *bytes.Buffer) {
out.WriteString("<br")
out.WriteString(options.closeTag)
out.WriteByte('\n')
}
func (options *Html) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
if options.flags&HTML_SKIP_LINKS != 0 {
// write the link text out but don't link it, just mark it with typewriter font
out.WriteString("<tt>")
attrEscape(out, content)
out.WriteString("</tt>")
return
}
if options.flags&HTML_SAFELINK != 0 && !isSafeLink(link) {
// write the link text out but don't link it, just mark it with typewriter font
out.WriteString("<tt>")
attrEscape(out, content)
out.WriteString("</tt>")
return
}
out.WriteString("<a href=\"")
options.maybeWriteAbsolutePrefix(out, link)
attrEscape(out, link)
if len(title) > 0 {
out.WriteString("\" title=\"")
attrEscape(out, title)
}
var relAttrs []string
if options.flags&HTML_NOFOLLOW_LINKS != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "nofollow")
}
if options.flags&HTML_NOREFERRER_LINKS != 0 && !isRelativeLink(link) {
relAttrs = append(relAttrs, "noreferrer")
}
if len(relAttrs) > 0 {
out.WriteString(fmt.Sprintf("\" rel=\"%s", strings.Join(relAttrs, " ")))
}
// blank target only add to external link
if options.flags&HTML_HREF_TARGET_BLANK != 0 && !isRelativeLink(link) {
out.WriteString("\" target=\"_blank")
}
out.WriteString("\">")
out.Write(content)
out.WriteString("</a>")
return
}
func (options *Html) RawHtmlTag(out *bytes.Buffer, text []byte) {
if options.flags&HTML_SKIP_HTML != 0 {
return
}
if options.flags&HTML_SKIP_STYLE != 0 && isHtmlTag(text, "style") {
return
}
if options.flags&HTML_SKIP_LINKS != 0 && isHtmlTag(text, "a") {
return
}
if options.flags&HTML_SKIP_IMAGES != 0 && isHtmlTag(text, "img") {
return
}
out.Write(text)
}
func (options *Html) TripleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("<strong><em>")
out.Write(text)
out.WriteString("</em></strong>")
}
func (options *Html) StrikeThrough(out *bytes.Buffer, text []byte) {
out.WriteString("<del>")
out.Write(text)
out.WriteString("</del>")
}
func (options *Html) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
slug := slugify(ref)
out.WriteString(`<sup class="footnote-ref" id="`)
out.WriteString(`fnref:`)
out.WriteString(options.parameters.FootnoteAnchorPrefix)
out.Write(slug)
out.WriteString(`"><a href="#`)
out.WriteString(`fn:`)
out.WriteString(options.parameters.FootnoteAnchorPrefix)
out.Write(slug)
out.WriteString(`">`)
out.WriteString(strconv.Itoa(id))
out.WriteString(`</a></sup>`)
}
func (options *Html) Entity(out *bytes.Buffer, entity []byte) {
out.Write(entity)
}
func (options *Html) NormalText(out *bytes.Buffer, text []byte) {
if options.flags&HTML_USE_SMARTYPANTS != 0 {
options.Smartypants(out, text)
} else {
attrEscape(out, text)
}
}
func (options *Html) Smartypants(out *bytes.Buffer, text []byte) {
smrt := smartypantsData{false, false}
// first do normal entity escaping
var escaped bytes.Buffer
attrEscape(&escaped, text)
text = escaped.Bytes()
mark := 0
for i := 0; i < len(text); i++ {
if action := options.smartypants[text[i]]; action != nil {
if i > mark {
out.Write(text[mark:i])
}
previousChar := byte(0)
if i > 0 {
previousChar = text[i-1]
}
i += action(out, &smrt, previousChar, text[i:])
mark = i + 1
}
}
if mark < len(text) {
out.Write(text[mark:])
}
}
func (options *Html) DocumentHeader(out *bytes.Buffer) {
if options.flags&HTML_COMPLETE_PAGE == 0 {
return
}
ending := ""
if options.flags&HTML_USE_XHTML != 0 {
out.WriteString("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
out.WriteString("\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
out.WriteString("<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
ending = " /"
} else {
out.WriteString("<!DOCTYPE html>\n")
out.WriteString("<html>\n")
}
out.WriteString("<head>\n")
out.WriteString(" <title>")
options.NormalText(out, []byte(options.title))
out.WriteString("</title>\n")
out.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
out.WriteString(VERSION)
out.WriteString("\"")
out.WriteString(ending)
out.WriteString(">\n")
out.WriteString(" <meta charset=\"utf-8\"")
out.WriteString(ending)
out.WriteString(">\n")
if options.css != "" {
out.WriteString(" <link rel=\"stylesheet\" type=\"text/css\" href=\"")
attrEscape(out, []byte(options.css))
out.WriteString("\"")
out.WriteString(ending)
out.WriteString(">\n")
}
out.WriteString("</head>\n")
out.WriteString("<body>\n")
options.tocMarker = out.Len()
}
func (options *Html) DocumentFooter(out *bytes.Buffer) {
// finalize and insert the table of contents
if options.flags&HTML_TOC != 0 {
options.TocFinalize()
// now we have to insert the table of contents into the document
var temp bytes.Buffer
// start by making a copy of everything after the document header
temp.Write(out.Bytes()[options.tocMarker:])
// now clear the copied material from the main output buffer
out.Truncate(options.tocMarker)
// corner case spacing issue
if options.flags&HTML_COMPLETE_PAGE != 0 {
out.WriteByte('\n')
}
// insert the table of contents
out.WriteString("<nav>\n")
out.Write(options.toc.Bytes())
out.WriteString("</nav>\n")
// corner case spacing issue
if options.flags&HTML_COMPLETE_PAGE == 0 && options.flags&HTML_OMIT_CONTENTS == 0 {
out.WriteByte('\n')
}
// write out everything that came after it
if options.flags&HTML_OMIT_CONTENTS == 0 {
out.Write(temp.Bytes())
}
}
if options.flags&HTML_COMPLETE_PAGE != 0 {
out.WriteString("\n</body>\n")
out.WriteString("</html>\n")
}
}
func (options *Html) TocHeaderWithAnchor(text []byte, level int, anchor string) {
for level > options.currentLevel {
switch {
case bytes.HasSuffix(options.toc.Bytes(), []byte("</li>\n")):
// this sublist can nest underneath a header
size := options.toc.Len()
options.toc.Truncate(size - len("</li>\n"))
case options.currentLevel > 0:
options.toc.WriteString("<li>")
}
if options.toc.Len() > 0 {
options.toc.WriteByte('\n')
}
options.toc.WriteString("<ul>\n")
options.currentLevel++
}
for level < options.currentLevel {
options.toc.WriteString("</ul>")
if options.currentLevel > 1 {
options.toc.WriteString("</li>\n")
}
options.currentLevel--
}
options.toc.WriteString("<li><a href=\"#")
if anchor != "" {
options.toc.WriteString(anchor)
} else {
options.toc.WriteString("toc_")
options.toc.WriteString(strconv.Itoa(options.headerCount))
}
options.toc.WriteString("\">")
options.headerCount++
options.toc.Write(text)
options.toc.WriteString("</a></li>\n")
}
func (options *Html) TocHeader(text []byte, level int) {
options.TocHeaderWithAnchor(text, level, "")
}
func (options *Html) TocFinalize() {
for options.currentLevel > 1 {
options.toc.WriteString("</ul></li>\n")
options.currentLevel--
}
if options.currentLevel > 0 {
options.toc.WriteString("</ul>\n")
}
}
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 skipUntilChar(text []byte, start int, char byte) int {
i := start
for i < len(text) && text[i] != char {
i++
}
return i
}
func skipSpace(tag []byte, i int) int {
for i < len(tag) && isspace(tag[i]) {
i++
}
return i
}
func skipChar(data []byte, start int, char byte) int {
i := start
for i < len(data) && data[i] == char {
i++
}
return i
}
func doubleSpace(out *bytes.Buffer) {
if out.Len() > 0 {
out.WriteByte('\n')
}
}
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 (options *Html) ensureUniqueHeaderID(id string) string {
for count, found := options.headerIDs[id]; found; count, found = options.headerIDs[id] {
tmp := fmt.Sprintf("%s-%d", id, count+1)
if _, tmpFound := options.headerIDs[tmp]; !tmpFound {
options.headerIDs[id] = count + 1
id = tmp
} else {
id = id + "-1"
}
}
if _, found := options.headerIDs[id]; !found {
options.headerIDs[id] = 0
}
return id
}

1154
tools/vendor/github.com/russross/blackfriday/inline.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

334
tools/vendor/github.com/russross/blackfriday/latex.go generated vendored Normal file
View File

@ -0,0 +1,334 @@
//
// 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.
//
//
//
// LaTeX rendering backend
//
//
package blackfriday
import (
"bytes"
"strings"
)
// Latex is a type that implements the Renderer interface for LaTeX output.
//
// Do not create this directly, instead use the LatexRenderer function.
type Latex struct {
}
// LatexRenderer creates and configures a Latex object, which
// satisfies the Renderer interface.
//
// flags is a set of LATEX_* options ORed together (currently no such options
// are defined).
func LatexRenderer(flags int) Renderer {
return &Latex{}
}
func (options *Latex) GetFlags() int {
return 0
}
// render code chunks using verbatim, or listings if we have a language
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, info string) {
if info == "" {
out.WriteString("\n\\begin{verbatim}\n")
} else {
lang := strings.Fields(info)[0]
out.WriteString("\n\\begin{lstlisting}[language=")
out.WriteString(lang)
out.WriteString("]\n")
}
out.Write(text)
if info == "" {
out.WriteString("\n\\end{verbatim}\n")
} else {
out.WriteString("\n\\end{lstlisting}\n")
}
}
func (options *Latex) TitleBlock(out *bytes.Buffer, text []byte) {
}
func (options *Latex) BlockQuote(out *bytes.Buffer, text []byte) {
out.WriteString("\n\\begin{quotation}\n")
out.Write(text)
out.WriteString("\n\\end{quotation}\n")
}
func (options *Latex) BlockHtml(out *bytes.Buffer, text []byte) {
// a pretty lame thing to do...
out.WriteString("\n\\begin{verbatim}\n")
out.Write(text)
out.WriteString("\n\\end{verbatim}\n")
}
func (options *Latex) Header(out *bytes.Buffer, text func() bool, level int, id string) {
marker := out.Len()
switch level {
case 1:
out.WriteString("\n\\section{")
case 2:
out.WriteString("\n\\subsection{")
case 3:
out.WriteString("\n\\subsubsection{")
case 4:
out.WriteString("\n\\paragraph{")
case 5:
out.WriteString("\n\\subparagraph{")
case 6:
out.WriteString("\n\\textbf{")
}
if !text() {
out.Truncate(marker)
return
}
out.WriteString("}\n")
}
func (options *Latex) HRule(out *bytes.Buffer) {
out.WriteString("\n\\HRule\n")
}
func (options *Latex) List(out *bytes.Buffer, text func() bool, flags int) {
marker := out.Len()
if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("\n\\begin{enumerate}\n")
} else {
out.WriteString("\n\\begin{itemize}\n")
}
if !text() {
out.Truncate(marker)
return
}
if flags&LIST_TYPE_ORDERED != 0 {
out.WriteString("\n\\end{enumerate}\n")
} else {
out.WriteString("\n\\end{itemize}\n")
}
}
func (options *Latex) ListItem(out *bytes.Buffer, text []byte, flags int) {
out.WriteString("\n\\item ")
out.Write(text)
}
func (options *Latex) Paragraph(out *bytes.Buffer, text func() bool) {
marker := out.Len()
out.WriteString("\n")
if !text() {
out.Truncate(marker)
return
}
out.WriteString("\n")
}
func (options *Latex) Table(out *bytes.Buffer, header []byte, body []byte, columnData []int) {
out.WriteString("\n\\begin{tabular}{")
for _, elt := range columnData {
switch elt {
case TABLE_ALIGNMENT_LEFT:
out.WriteByte('l')
case TABLE_ALIGNMENT_RIGHT:
out.WriteByte('r')
default:
out.WriteByte('c')
}
}
out.WriteString("}\n")
out.Write(header)
out.WriteString(" \\\\\n\\hline\n")
out.Write(body)
out.WriteString("\n\\end{tabular}\n")
}
func (options *Latex) TableRow(out *bytes.Buffer, text []byte) {
if out.Len() > 0 {
out.WriteString(" \\\\\n")
}
out.Write(text)
}
func (options *Latex) TableHeaderCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString(" & ")
}
out.Write(text)
}
func (options *Latex) TableCell(out *bytes.Buffer, text []byte, align int) {
if out.Len() > 0 {
out.WriteString(" & ")
}
out.Write(text)
}
// TODO: this
func (options *Latex) Footnotes(out *bytes.Buffer, text func() bool) {
}
func (options *Latex) FootnoteItem(out *bytes.Buffer, name, text []byte, flags int) {
}
func (options *Latex) AutoLink(out *bytes.Buffer, link []byte, kind int) {
out.WriteString("\\href{")
if kind == LINK_TYPE_EMAIL {
out.WriteString("mailto:")
}
out.Write(link)
out.WriteString("}{")
out.Write(link)
out.WriteString("}")
}
func (options *Latex) CodeSpan(out *bytes.Buffer, text []byte) {
out.WriteString("\\texttt{")
escapeSpecialChars(out, text)
out.WriteString("}")
}
func (options *Latex) DoubleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textbf{")
out.Write(text)
out.WriteString("}")
}
func (options *Latex) Emphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textit{")
out.Write(text)
out.WriteString("}")
}
func (options *Latex) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
if bytes.HasPrefix(link, []byte("http://")) || bytes.HasPrefix(link, []byte("https://")) {
// treat it like a link
out.WriteString("\\href{")
out.Write(link)
out.WriteString("}{")
out.Write(alt)
out.WriteString("}")
} else {
out.WriteString("\\includegraphics{")
out.Write(link)
out.WriteString("}")
}
}
func (options *Latex) LineBreak(out *bytes.Buffer) {
out.WriteString(" \\\\\n")
}
func (options *Latex) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
out.WriteString("\\href{")
out.Write(link)
out.WriteString("}{")
out.Write(content)
out.WriteString("}")
}
func (options *Latex) RawHtmlTag(out *bytes.Buffer, tag []byte) {
}
func (options *Latex) TripleEmphasis(out *bytes.Buffer, text []byte) {
out.WriteString("\\textbf{\\textit{")
out.Write(text)
out.WriteString("}}")
}
func (options *Latex) StrikeThrough(out *bytes.Buffer, text []byte) {
out.WriteString("\\sout{")
out.Write(text)
out.WriteString("}")
}
// TODO: this
func (options *Latex) FootnoteRef(out *bytes.Buffer, ref []byte, id int) {
}
func needsBackslash(c byte) bool {
for _, r := range []byte("_{}%$&\\~#") {
if c == r {
return true
}
}
return false
}
func escapeSpecialChars(out *bytes.Buffer, text []byte) {
for i := 0; i < len(text); i++ {
// directly copy normal characters
org := i
for i < len(text) && !needsBackslash(text[i]) {
i++
}
if i > org {
out.Write(text[org:i])
}
// escape a character
if i >= len(text) {
break
}
out.WriteByte('\\')
out.WriteByte(text[i])
}
}
func (options *Latex) Entity(out *bytes.Buffer, entity []byte) {
// TODO: convert this into a unicode character or something
out.Write(entity)
}
func (options *Latex) NormalText(out *bytes.Buffer, text []byte) {
escapeSpecialChars(out, text)
}
// header and footer
func (options *Latex) DocumentHeader(out *bytes.Buffer) {
out.WriteString("\\documentclass{article}\n")
out.WriteString("\n")
out.WriteString("\\usepackage{graphicx}\n")
out.WriteString("\\usepackage{listings}\n")
out.WriteString("\\usepackage[margin=1in]{geometry}\n")
out.WriteString("\\usepackage[utf8]{inputenc}\n")
out.WriteString("\\usepackage{verbatim}\n")
out.WriteString("\\usepackage[normalem]{ulem}\n")
out.WriteString("\\usepackage{hyperref}\n")
out.WriteString("\n")
out.WriteString("\\hypersetup{colorlinks,%\n")
out.WriteString(" citecolor=black,%\n")
out.WriteString(" filecolor=black,%\n")
out.WriteString(" linkcolor=black,%\n")
out.WriteString(" urlcolor=black,%\n")
out.WriteString(" pdfstartview=FitH,%\n")
out.WriteString(" breaklinks=true,%\n")
out.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
out.WriteString(VERSION)
out.WriteString("}}\n")
out.WriteString("\n")
out.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")
out.WriteString("\\addtolength{\\parskip}{0.5\\baselineskip}\n")
out.WriteString("\\parindent=0pt\n")
out.WriteString("\n")
out.WriteString("\\begin{document}\n")
}
func (options *Latex) DocumentFooter(out *bytes.Buffer) {
out.WriteString("\n\\end{document}\n")
}

View File

@ -0,0 +1,941 @@
//
// 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.
//
//
//
// Markdown parsing and processing
//
//
package blackfriday
import (
"bytes"
"fmt"
"strings"
"unicode/utf8"
)
const VERSION = "1.5"
// These are the supported markdown parsing extensions.
// OR these values together to select multiple extensions.
const (
EXTENSION_NO_INTRA_EMPHASIS = 1 << iota // ignore emphasis markers inside words
EXTENSION_TABLES // render tables
EXTENSION_FENCED_CODE // render fenced code blocks
EXTENSION_AUTOLINK // detect embedded URLs that are not explicitly marked
EXTENSION_STRIKETHROUGH // strikethrough text using ~~test~~
EXTENSION_LAX_HTML_BLOCKS // loosen up HTML block parsing rules
EXTENSION_SPACE_HEADERS // be strict about prefix header rules
EXTENSION_HARD_LINE_BREAK // translate newlines into line breaks
EXTENSION_TAB_SIZE_EIGHT // expand tabs to eight spaces instead of four
EXTENSION_FOOTNOTES // Pandoc-style footnotes
EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block
EXTENSION_HEADER_IDS // specify header IDs with {#id}
EXTENSION_TITLEBLOCK // Titleblock ala pandoc
EXTENSION_AUTO_HEADER_IDS // Create the header ID from the text
EXTENSION_BACKSLASH_LINE_BREAK // translate trailing backslashes into line breaks
EXTENSION_DEFINITION_LISTS // render definition lists
EXTENSION_JOIN_LINES // delete newline and join lines
commonHtmlFlags = 0 |
HTML_USE_XHTML |
HTML_USE_SMARTYPANTS |
HTML_SMARTYPANTS_FRACTIONS |
HTML_SMARTYPANTS_DASHES |
HTML_SMARTYPANTS_LATEX_DASHES
commonExtensions = 0 |
EXTENSION_NO_INTRA_EMPHASIS |
EXTENSION_TABLES |
EXTENSION_FENCED_CODE |
EXTENSION_AUTOLINK |
EXTENSION_STRIKETHROUGH |
EXTENSION_SPACE_HEADERS |
EXTENSION_HEADER_IDS |
EXTENSION_BACKSLASH_LINE_BREAK |
EXTENSION_DEFINITION_LISTS
)
// These are the possible flag values for the link 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 (
LINK_TYPE_NOT_AUTOLINK = iota
LINK_TYPE_NORMAL
LINK_TYPE_EMAIL
)
// 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 (
LIST_TYPE_ORDERED = 1 << iota
LIST_TYPE_DEFINITION
LIST_TYPE_TERM
LIST_ITEM_CONTAINS_BLOCK
LIST_ITEM_BEGINNING_OF_LIST
LIST_ITEM_END_OF_LIST
)
// 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 (
TABLE_ALIGNMENT_LEFT = 1 << iota
TABLE_ALIGNMENT_RIGHT
TABLE_ALIGNMENT_CENTER = (TABLE_ALIGNMENT_LEFT | TABLE_ALIGNMENT_RIGHT)
)
// The size of a tab stop.
const (
TAB_SIZE_DEFAULT = 4
TAB_SIZE_EIGHT = 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.
//
// When a byte slice is provided, it contains the (rendered) contents of the
// element.
//
// When a callback is provided instead, it will write the contents of the
// respective element directly to the output buffer and return true on success.
// If the callback returns false, the rendering function should reset the
// output buffer as though it had never been called.
//
// Currently Html and Latex implementations are provided
type Renderer interface {
// block-level callbacks
BlockCode(out *bytes.Buffer, text []byte, infoString string)
BlockQuote(out *bytes.Buffer, text []byte)
BlockHtml(out *bytes.Buffer, text []byte)
Header(out *bytes.Buffer, text func() bool, level int, id string)
HRule(out *bytes.Buffer)
List(out *bytes.Buffer, text func() bool, flags int)
ListItem(out *bytes.Buffer, text []byte, flags int)
Paragraph(out *bytes.Buffer, text func() bool)
Table(out *bytes.Buffer, header []byte, body []byte, columnData []int)
TableRow(out *bytes.Buffer, text []byte)
TableHeaderCell(out *bytes.Buffer, text []byte, flags int)
TableCell(out *bytes.Buffer, text []byte, flags int)
Footnotes(out *bytes.Buffer, text func() bool)
FootnoteItem(out *bytes.Buffer, name, text []byte, flags int)
TitleBlock(out *bytes.Buffer, text []byte)
// Span-level callbacks
AutoLink(out *bytes.Buffer, link []byte, kind int)
CodeSpan(out *bytes.Buffer, text []byte)
DoubleEmphasis(out *bytes.Buffer, text []byte)
Emphasis(out *bytes.Buffer, text []byte)
Image(out *bytes.Buffer, link []byte, title []byte, alt []byte)
LineBreak(out *bytes.Buffer)
Link(out *bytes.Buffer, link []byte, title []byte, content []byte)
RawHtmlTag(out *bytes.Buffer, tag []byte)
TripleEmphasis(out *bytes.Buffer, text []byte)
StrikeThrough(out *bytes.Buffer, text []byte)
FootnoteRef(out *bytes.Buffer, ref []byte, id int)
// Low-level callbacks
Entity(out *bytes.Buffer, entity []byte)
NormalText(out *bytes.Buffer, text []byte)
// Header and footer
DocumentHeader(out *bytes.Buffer)
DocumentFooter(out *bytes.Buffer)
GetFlags() int
}
// 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 *parser, out *bytes.Buffer, data []byte, offset int) int
// Parser holds runtime state used by the parser.
// This is constructed by the Markdown function.
type parser struct {
r Renderer
refOverride ReferenceOverrideFunc
refs map[string]*reference
inlineCallback [256]inlineParser
flags int
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
notesRecord map[string]struct{}
}
func (p *parser) getRef(refid string) (ref *reference, found bool) {
if p.refOverride != nil {
r, overridden := p.refOverride(refid)
if overridden {
if r == nil {
return nil, false
}
return &reference{
link: []byte(r.Link),
title: []byte(r.Title),
noteId: 0,
hasBlock: false,
text: []byte(r.Text)}, true
}
}
// refs are case insensitive
ref, found = p.refs[strings.ToLower(refid)]
return ref, found
}
func (p *parser) isFootnote(ref *reference) bool {
_, ok := p.notesRecord[string(ref.link)]
return ok
}
//
//
// 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)
// Options represents configurable overrides and callbacks (in addition to the
// extension flag set) for configuring a Markdown parse.
type Options struct {
// Extensions is a flag set of bit-wise ORed extension bits. See the
// EXTENSION_* flags defined in this package.
Extensions int
// ReferenceOverride is 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.
ReferenceOverride ReferenceOverrideFunc
}
// MarkdownBasic is a convenience function for simple rendering.
// It processes markdown input with no extensions enabled.
func MarkdownBasic(input []byte) []byte {
// set up the HTML renderer
htmlFlags := HTML_USE_XHTML
renderer := HtmlRenderer(htmlFlags, "", "")
// set up the parser
return MarkdownOptions(input, renderer, Options{Extensions: 0})
}
// Call Markdown with most useful extensions enabled
// MarkdownCommon is a convenience function for simple rendering.
// It processes markdown input with common extensions enabled, including:
//
// * Smartypants processing with smart fractions and LaTeX dashes
//
// * Intra-word emphasis suppression
//
// * Tables
//
// * Fenced code blocks
//
// * Autolinking
//
// * Strikethrough support
//
// * Strict header parsing
//
// * Custom Header IDs
func MarkdownCommon(input []byte) []byte {
// set up the HTML renderer
renderer := HtmlRenderer(commonHtmlFlags, "", "")
return MarkdownOptions(input, renderer, Options{
Extensions: commonExtensions})
}
// Markdown is the main rendering function.
// It parses and renders a block of markdown-encoded text.
// The supplied Renderer is used to format the output, and extensions dictates
// which non-standard extensions are enabled.
//
// To use the supplied Html or LaTeX renderers, see HtmlRenderer and
// LatexRenderer, respectively.
func Markdown(input []byte, renderer Renderer, extensions int) []byte {
return MarkdownOptions(input, renderer, Options{
Extensions: extensions})
}
// MarkdownOptions is just like Markdown but takes additional options through
// the Options struct.
func MarkdownOptions(input []byte, renderer Renderer, opts Options) []byte {
// no point in parsing if we can't render
if renderer == nil {
return nil
}
extensions := opts.Extensions
// fill in the render structure
p := new(parser)
p.r = renderer
p.flags = extensions
p.refOverride = opts.ReferenceOverride
p.refs = make(map[string]*reference)
p.maxNesting = 16
p.insideLink = false
// register inline parsers
p.inlineCallback['*'] = emphasis
p.inlineCallback['_'] = emphasis
if extensions&EXTENSION_STRIKETHROUGH != 0 {
p.inlineCallback['~'] = emphasis
}
p.inlineCallback['`'] = codeSpan
p.inlineCallback['\n'] = lineBreak
p.inlineCallback['['] = link
p.inlineCallback['<'] = leftAngle
p.inlineCallback['\\'] = escape
p.inlineCallback['&'] = entity
if extensions&EXTENSION_AUTOLINK != 0 {
p.inlineCallback[':'] = autoLink
}
if extensions&EXTENSION_FOOTNOTES != 0 {
p.notes = make([]*reference, 0)
p.notesRecord = make(map[string]struct{})
}
first := firstPass(p, input)
second := secondPass(p, first)
return second
}
// first pass:
// - normalize newlines
// - extract references (outside of fenced code blocks)
// - expand tabs (outside of fenced code blocks)
// - copy everything else
func firstPass(p *parser, input []byte) []byte {
var out bytes.Buffer
tabSize := TAB_SIZE_DEFAULT
if p.flags&EXTENSION_TAB_SIZE_EIGHT != 0 {
tabSize = TAB_SIZE_EIGHT
}
beg := 0
lastFencedCodeBlockEnd := 0
for beg < len(input) {
// Find end of this line, then process the line.
end := beg
for end < len(input) && input[end] != '\n' && input[end] != '\r' {
end++
}
if p.flags&EXTENSION_FENCED_CODE != 0 {
// track fenced code block boundaries to suppress tab expansion
// and reference extraction inside them:
if beg >= lastFencedCodeBlockEnd {
if i := p.fencedCodeBlock(&out, input[beg:], false); i > 0 {
lastFencedCodeBlockEnd = beg + i
}
}
}
// add the line body if present
if end > beg {
if end < lastFencedCodeBlockEnd { // Do not expand tabs while inside fenced code blocks.
out.Write(input[beg:end])
} else if refEnd := isReference(p, input[beg:], tabSize); refEnd > 0 {
beg += refEnd
continue
} else {
expandTabs(&out, input[beg:end], tabSize)
}
}
if end < len(input) && input[end] == '\r' {
end++
}
if end < len(input) && input[end] == '\n' {
end++
}
out.WriteByte('\n')
beg = end
}
// empty input?
if out.Len() == 0 {
out.WriteByte('\n')
}
return out.Bytes()
}
// second pass: actual rendering
func secondPass(p *parser, input []byte) []byte {
var output bytes.Buffer
p.r.DocumentHeader(&output)
p.block(&output, input)
if p.flags&EXTENSION_FOOTNOTES != 0 && len(p.notes) > 0 {
p.r.Footnotes(&output, func() bool {
flags := LIST_ITEM_BEGINNING_OF_LIST
for i := 0; i < len(p.notes); i += 1 {
ref := p.notes[i]
var buf bytes.Buffer
if ref.hasBlock {
flags |= LIST_ITEM_CONTAINS_BLOCK
p.block(&buf, ref.title)
} else {
p.inline(&buf, ref.title)
}
p.r.FootnoteItem(&output, ref.link, buf.Bytes(), flags)
flags &^= LIST_ITEM_BEGINNING_OF_LIST | LIST_ITEM_CONTAINS_BLOCK
}
return true
})
}
p.r.DocumentFooter(&output)
if p.nesting != 0 {
panic("Nesting level did not end at zero")
}
return output.Bytes()
}
//
// Link references
//
// This section implements support for references that (usually) appear
// as footnotes in a document, and can be referenced anywhere in the document.
// The basic format is:
//
// [1]: http://www.google.com/ "Google"
// [2]: http://www.github.com/ "Github"
//
// Anywhere in the document, the reference can be linked by referring to its
// label, i.e., 1 and 2 in this example, as in:
//
// This library is hosted on [Github][2], a git hosting site.
//
// Actual footnotes as specified in Pandoc and supported by some other Markdown
// libraries such as php-markdown are also taken care of. They look like this:
//
// This sentence needs a bit of further explanation.[^note]
//
// [^note]: This is the explanation.
//
// Footnotes should be placed at the end of the document in an ordered list.
// Inline footnotes such as:
//
// Inline footnotes^[Not supported.] also exist.
//
// are not yet supported.
// References are parsed and stored in this struct.
type reference struct {
link []byte
title []byte
noteId int // 0 if not a footnote ref
hasBlock bool
text []byte
}
func (r *reference) String() string {
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}",
r.link, r.title, r.text, r.noteId, r.hasBlock)
}
// Check whether or not data starts with a reference link.
// If so, it is parsed and stored in the list of references
// (in the render struct).
// Returns the number of bytes to skip to move past it,
// or zero if the first line is not a reference.
func isReference(p *parser, data []byte, tabSize int) int {
// up to 3 optional leading spaces
if len(data) < 4 {
return 0
}
i := 0
for i < 3 && data[i] == ' ' {
i++
}
noteId := 0
// id part: anything but a newline between brackets
if data[i] != '[' {
return 0
}
i++
if p.flags&EXTENSION_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
// 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.flags&EXTENSION_FOOTNOTES != 0 && noteId != 0 {
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
lineEnd = linkEnd
} else {
linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i)
}
if lineEnd == 0 {
return 0
}
// a valid ref has been found
ref := &reference{
noteId: noteId,
hasBlock: hasBlock,
}
if noteId > 0 {
// reusing the link field for the id since footnotes don't have links
ref.link = data[idOffset:idEnd]
// if footnote, it's not really a title, it's the contained text
ref.title = raw
} else {
ref.link = data[linkOffset:linkEnd]
ref.title = data[titleOffset:titleEnd]
}
// id matches are case-insensitive
id := string(bytes.ToLower(data[idOffset:idEnd]))
p.refs[id] = ref
return lineEnd
}
func scanLinkRef(p *parser, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) {
// link: whitespace-free sequence, optionally between angle brackets
if data[i] == '<' {
i++
}
linkOffset = i
if i == len(data) {
return
}
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 *parser, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) {
if i == 0 || len(data) == 0 {
return
}
// skip leading whitespace on first line
for i < len(data) && data[i] == ' ' {
i++
}
blockStart = i
// find the end of the line
blockEnd = i
for i < len(data) && data[i-1] != '\n' {
i++
}
// get working buffer
var raw bytes.Buffer
// put the first line into the working buffer
raw.Write(data[blockEnd:i])
blockEnd = i
// process the following lines
containsBlankLine := false
gatherLines:
for blockEnd < len(data) {
i++
// find the end of this line
for i < len(data) && data[i-1] != '\n' {
i++
}
// if it is an empty line, guess that it is part of this item
// and move on to the next line
if p.isEmpty(data[blockEnd:i]) > 0 {
containsBlankLine = true
blockEnd = i
continue
}
n := 0
if n = isIndented(data[blockEnd:i], indentSize); n == 0 {
// this is the end of the block.
// we don't want to include this last line in the index.
break gatherLines
}
// if there were blank lines before this one, insert a new one now
if containsBlankLine {
raw.WriteByte('\n')
containsBlankLine = false
}
// get rid of that first tab, write to buffer
raw.Write(data[blockEnd+n : i])
hasBlock = true
blockEnd = i
}
if data[blockEnd-1] != '\n' {
raw.WriteByte('\n')
}
contents = raw.Bytes()
return
}
//
//
// 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 whitespace 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]
}

View File

@ -0,0 +1,430 @@
//
// 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"
)
type smartypantsData struct {
inSingleQuote bool
inDoubleQuote bool
}
func wordBoundary(c byte) bool {
return c == 0 || isspace(c) || ispunct(c)
}
func tolower(c byte) byte {
if c >= 'A' && c <= 'Z' {
return c - 'A' + 'a'
}
return c
}
func isdigit(c byte) bool {
return c >= '0' && c <= '9'
}
func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool {
// edge of the buffer is likely to be a tag that we don't get to see,
// so we treat it like text sometimes
// enumerate all sixteen possibilities for (previousChar, nextChar)
// each can be one of {0, space, punct, other}
switch {
case previousChar == 0 && nextChar == 0:
// context is not any help here, so toggle
*isOpen = !*isOpen
case isspace(previousChar) && nextChar == 0:
// [ "] might be [ "<code>foo...]
*isOpen = true
case ispunct(previousChar) && nextChar == 0:
// [!"] hmm... could be [Run!"] or [("<code>...]
*isOpen = false
case /* isnormal(previousChar) && */ nextChar == 0:
// [a"] is probably a close
*isOpen = false
case previousChar == 0 && isspace(nextChar):
// [" ] might be [...foo</code>" ]
*isOpen = false
case isspace(previousChar) && isspace(nextChar):
// [ " ] context is not any help here, so toggle
*isOpen = !*isOpen
case ispunct(previousChar) && isspace(nextChar):
// [!" ] is probably a close
*isOpen = false
case /* isnormal(previousChar) && */ isspace(nextChar):
// [a" ] this is one of the easy cases
*isOpen = false
case previousChar == 0 && ispunct(nextChar):
// ["!] hmm... could be ["$1.95] or [</code>"!...]
*isOpen = false
case isspace(previousChar) && ispunct(nextChar):
// [ "!] looks more like [ "$1.95]
*isOpen = true
case ispunct(previousChar) && ispunct(nextChar):
// [!"!] context is not any help here, so toggle
*isOpen = !*isOpen
case /* isnormal(previousChar) && */ ispunct(nextChar):
// [a"!] is probably a close
*isOpen = false
case previousChar == 0 /* && isnormal(nextChar) */ :
// ["a] is probably an open
*isOpen = true
case isspace(previousChar) /* && isnormal(nextChar) */ :
// [ "a] this is one of the easy cases
*isOpen = true
case ispunct(previousChar) /* && isnormal(nextChar) */ :
// [!"a] is probably an open
*isOpen = true
default:
// [a'b] maybe a contraction?
*isOpen = false
}
// Note that with the limited lookahead, this non-breaking
// space will also be appended to single double quotes.
if addNBSP && !*isOpen {
out.WriteString("&nbsp;")
}
out.WriteByte('&')
if *isOpen {
out.WriteByte('l')
} else {
out.WriteByte('r')
}
out.WriteByte(quote)
out.WriteString("quo;")
if addNBSP && *isOpen {
out.WriteString("&nbsp;")
}
return true
}
func smartSingleQuote(out *bytes.Buffer, smrt *smartypantsData, 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', &smrt.inDoubleQuote, false) {
return 1
}
}
if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) {
out.WriteString("&rsquo;")
return 0
}
if len(text) >= 3 {
t2 := tolower(text[2])
if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) &&
(len(text) < 4 || wordBoundary(text[3])) {
out.WriteString("&rsquo;")
return 0
}
}
}
nextChar := byte(0)
if len(text) > 1 {
nextChar = text[1]
}
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote, false) {
return 0
}
out.WriteByte(text[0])
return 0
}
func smartParens(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 {
t1 := tolower(text[1])
t2 := tolower(text[2])
if t1 == 'c' && t2 == ')' {
out.WriteString("&copy;")
return 2
}
if t1 == 'r' && t2 == ')' {
out.WriteString("&reg;")
return 2
}
if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' {
out.WriteString("&trade;")
return 3
}
}
out.WriteByte(text[0])
return 0
}
func smartDash(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 2 {
if text[1] == '-' {
out.WriteString("&mdash;")
return 1
}
if wordBoundary(previousChar) && wordBoundary(text[1]) {
out.WriteString("&ndash;")
return 0
}
}
out.WriteByte(text[0])
return 0
}
func smartDashLatex(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
out.WriteString("&mdash;")
return 2
}
if len(text) >= 2 && text[1] == '-' {
out.WriteString("&ndash;")
return 1
}
out.WriteByte(text[0])
return 0
}
func smartAmpVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte, addNBSP bool) int {
if bytes.HasPrefix(text, []byte("&quot;")) {
nextChar := byte(0)
if len(text) >= 7 {
nextChar = text[6]
}
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, addNBSP) {
return 5
}
}
if bytes.HasPrefix(text, []byte("&#0;")) {
return 3
}
out.WriteByte('&')
return 0
}
func smartAmp(angledQuotes, addNBSP bool) func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
var quote byte = 'd'
if angledQuotes {
quote = 'a'
}
return func(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return smartAmpVariant(out, smrt, previousChar, text, quote, addNBSP)
}
}
func smartPeriod(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
out.WriteString("&hellip;")
return 2
}
if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' {
out.WriteString("&hellip;")
return 4
}
out.WriteByte(text[0])
return 0
}
func smartBacktick(out *bytes.Buffer, smrt *smartypantsData, 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', &smrt.inDoubleQuote, false) {
return 1
}
}
out.WriteByte(text[0])
return 0
}
func smartNumberGeneric(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
// note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8)
// and avoid changing dates like 1/23/2005 into fractions.
numEnd := 0
for len(text) > numEnd && isdigit(text[numEnd]) {
numEnd++
}
if numEnd == 0 {
out.WriteByte(text[0])
return 0
}
denStart := numEnd + 1
if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 {
denStart = numEnd + 3
} else if len(text) < numEnd+2 || text[numEnd] != '/' {
out.WriteByte(text[0])
return 0
}
denEnd := denStart
for len(text) > denEnd && isdigit(text[denEnd]) {
denEnd++
}
if denEnd == denStart {
out.WriteByte(text[0])
return 0
}
if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' {
out.WriteString("<sup>")
out.Write(text[:numEnd])
out.WriteString("</sup>&frasl;<sub>")
out.Write(text[denStart:denEnd])
out.WriteString("</sub>")
return denEnd - 1
}
}
out.WriteByte(text[0])
return 0
}
func smartNumber(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
if text[0] == '1' && text[1] == '/' && text[2] == '2' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
out.WriteString("&frac12;")
return 2
}
}
if text[0] == '1' && text[1] == '/' && text[2] == '4' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') {
out.WriteString("&frac14;")
return 2
}
}
if text[0] == '3' && text[1] == '/' && text[2] == '4' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') {
out.WriteString("&frac34;")
return 2
}
}
}
out.WriteByte(text[0])
return 0
}
func smartDoubleQuoteVariant(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte, quote byte) int {
nextChar := byte(0)
if len(text) > 1 {
nextChar = text[1]
}
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote, false) {
out.WriteString("&quot;")
}
return 0
}
func smartDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'd')
}
func smartAngledDoubleQuote(out *bytes.Buffer, smrt *smartypantsData, previousChar byte, text []byte) int {
return smartDoubleQuoteVariant(out, smrt, previousChar, text, 'a')
}
func smartLeftAngle(out *bytes.Buffer, smrt *smartypantsData, 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, smrt *smartypantsData, previousChar byte, text []byte) int
type smartypantsRenderer [256]smartCallback
var (
smartAmpAngled = smartAmp(true, false)
smartAmpAngledNBSP = smartAmp(true, true)
smartAmpRegular = smartAmp(false, false)
smartAmpRegularNBSP = smartAmp(false, true)
)
func smartypants(flags int) *smartypantsRenderer {
r := new(smartypantsRenderer)
addNBSP := flags&HTML_SMARTYPANTS_QUOTES_NBSP != 0
if flags&HTML_SMARTYPANTS_ANGLED_QUOTES == 0 {
r['"'] = smartDoubleQuote
if !addNBSP {
r['&'] = smartAmpRegular
} else {
r['&'] = smartAmpRegularNBSP
}
} else {
r['"'] = smartAngledDoubleQuote
if !addNBSP {
r['&'] = smartAmpAngled
} else {
r['&'] = smartAmpAngledNBSP
}
}
r['\''] = smartSingleQuote
r['('] = smartParens
if flags&HTML_SMARTYPANTS_DASHES != 0 {
if flags&HTML_SMARTYPANTS_LATEX_DASHES == 0 {
r['-'] = smartDash
} else {
r['-'] = smartDashLatex
}
}
r['.'] = smartPeriod
if flags&HTML_SMARTYPANTS_FRACTIONS == 0 {
r['1'] = smartNumber
r['3'] = smartNumber
} else {
for ch := '1'; ch <= '9'; ch++ {
r[ch] = smartNumberGeneric
}
}
r['<'] = smartLeftAngle
r['`'] = smartBacktick
return r
}

6
tools/vendor/modules.txt vendored Normal file
View File

@ -0,0 +1,6 @@
# github.com/cpuguy83/go-md2man v1.0.10
## explicit
github.com/cpuguy83/go-md2man
github.com/cpuguy83/go-md2man/md2man
# github.com/russross/blackfriday v1.5.2
github.com/russross/blackfriday