mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Compare commits
97 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04410ff159 | ||
|
|
f7b2d24c09 | ||
|
|
ba49c5c69d | ||
|
|
c05fb087a4 | ||
|
|
b39daae79e | ||
|
|
d9c03f07f0 | ||
|
|
65dcf6cd0a | ||
|
|
ad1565131a | ||
|
|
bc993b4f59 | ||
|
|
41273a4d07 | ||
|
|
d80ac9397c | ||
|
|
15000ac6a1 | ||
|
|
14d91f957f | ||
|
|
3847ca20c6 | ||
|
|
697e44ce88 | ||
|
|
fa88006eee | ||
|
|
fe34ea5d96 | ||
|
|
fd14edc9bc | ||
|
|
e367755421 | ||
|
|
e44645afbb | ||
|
|
15ade8aace | ||
|
|
dc32f35808 | ||
|
|
25bdeb0fee | ||
|
|
a590622b15 | ||
|
|
fde4948b4d | ||
|
|
9c09ae0019 | ||
|
|
c15e394c27 | ||
|
|
e405d57be0 | ||
|
|
ce6424aa0e | ||
|
|
09afa2feba | ||
|
|
4f3074451e | ||
|
|
4675c66d3d | ||
|
|
848dc66530 | ||
|
|
b8d6d3a9b7 | ||
|
|
90c46e0829 | ||
|
|
4bade05173 | ||
|
|
2b845f2615 | ||
|
|
e3d8ed9725 | ||
|
|
697cd509b1 | ||
|
|
ff3285aa2a | ||
|
|
c2167685c1 | ||
|
|
39a50c623e | ||
|
|
9c9003363f | ||
|
|
a89ad04c49 | ||
|
|
6b3067e7e7 | ||
|
|
6442ae1259 | ||
|
|
68e53654f2 | ||
|
|
04d4dd50ab | ||
|
|
8c6830d73b | ||
|
|
792af6819e | ||
|
|
9d0b1b6bb8 | ||
|
|
dc2230c235 | ||
|
|
2367b9ff46 | ||
|
|
6cbcfebb71 | ||
|
|
6ef9b10a3a | ||
|
|
d39ab8f93e | ||
|
|
9b02182dd0 | ||
|
|
ac56543632 | ||
|
|
2f1b40d881 | ||
|
|
254b9f8f77 | ||
|
|
31ccfc4039 | ||
|
|
b2df67847e | ||
|
|
4536092b45 | ||
|
|
2ce4086884 | ||
|
|
d9067d2324 | ||
|
|
69a0811de5 | ||
|
|
e46b1b5305 | ||
|
|
8e2127faa4 | ||
|
|
c6f0e7e0c5 | ||
|
|
d47eaf8c2d | ||
|
|
078e8aa5bb | ||
|
|
42f759fbe6 | ||
|
|
9d6f314b99 | ||
|
|
3505399723 | ||
|
|
82ff890073 | ||
|
|
550b0c7ca0 | ||
|
|
c446c414ef | ||
|
|
5e78751e90 | ||
|
|
023c1d9d21 | ||
|
|
60df4aadee | ||
|
|
a0962b569b | ||
|
|
a87c5778f9 | ||
|
|
b1ef69a870 | ||
|
|
aaeb9851a6 | ||
|
|
34756f289f | ||
|
|
c71a97b837 | ||
|
|
ae42b9179f | ||
|
|
1dd67d5750 | ||
|
|
95efaa1805 | ||
|
|
a3630e3073 | ||
|
|
3923ba0148 | ||
|
|
20df1691ad | ||
|
|
175c5ecd0c | ||
|
|
d77f38c53d | ||
|
|
42509ee585 | ||
|
|
db82c79f20 | ||
|
|
1efd8073c5 |
72 changed files with 7836 additions and 6096 deletions
17
.github/stale.yml
vendored
17
.github/stale.yml
vendored
|
|
@ -1,17 +0,0 @@
|
||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 30
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 7
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- pinned
|
|
||||||
- security
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: stale
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
for your contributions.
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
||||||
26
.github/workflows/stale.yaml
vendored
Normal file
26
.github/workflows/stale.yaml
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
name: Close inactive issues
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 9 * * *"
|
||||||
|
jobs:
|
||||||
|
close-issues:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@v5
|
||||||
|
with:
|
||||||
|
days-before-issue-stale: 30
|
||||||
|
days-before-issue-close: 14
|
||||||
|
stale-issue-label: "stale"
|
||||||
|
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||||
|
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
exempt-issue-labels: "pinned,security"
|
||||||
|
days-before-pr-stale: 180
|
||||||
|
days-before-pr-close: 14
|
||||||
|
stale-pr-label: "stale"
|
||||||
|
stale-pr-message: "This PR is stale because it has been open for 180 days with no activity."
|
||||||
|
close-pr-message: "This PR was closed because it has been inactive for 14 days since being marked as stale."
|
||||||
|
exempt-pr-labels: "pinned,security"
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
19
.github/workflows/test.yaml
vendored
19
.github/workflows/test.yaml
vendored
|
|
@ -5,22 +5,29 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.17.x, 1.18.x]
|
go-version: [1.21.x, 1.22.x]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v3
|
||||||
|
- name: Run lints
|
||||||
|
uses: golangci/golangci-lint-action@v6
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
if: "matrix.platform == 'ubuntu-latest'" # gofmt linter fails on Windows for CRLF problems
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
env:
|
||||||
|
GOLDMARK_TEST_TIMEOUT_MULTIPLIER: 5
|
||||||
run: go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=./...
|
run: go test -v ./... -covermode=count -coverprofile=coverage.out -coverpkg=./...
|
||||||
|
- name: Install goveralls
|
||||||
|
run: go install github.com/mattn/goveralls@latest
|
||||||
- name: Send coverage
|
- name: Send coverage
|
||||||
if: "matrix.platform == 'ubuntu-latest'"
|
if: "matrix.platform == 'ubuntu-latest'"
|
||||||
env:
|
env:
|
||||||
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: goveralls -coverprofile=coverage.out -service=github
|
||||||
GO111MODULE=off go get github.com/mattn/goveralls
|
|
||||||
$(go env GOPATH)/bin/goveralls -coverprofile=coverage.out -service=github
|
|
||||||
|
|
|
||||||
105
.golangci.yml
Normal file
105
.golangci.yml
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
run:
|
||||||
|
deadline: 10m
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-use-default: false
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test.go
|
||||||
|
linters:
|
||||||
|
- errcheck
|
||||||
|
- lll
|
||||||
|
exclude:
|
||||||
|
- "Package util"
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- errcheck
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- unused
|
||||||
|
- gofmt
|
||||||
|
- godot
|
||||||
|
- makezero
|
||||||
|
- misspell
|
||||||
|
- revive
|
||||||
|
- wastedassign
|
||||||
|
- lll
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
revive:
|
||||||
|
severity: "warning"
|
||||||
|
confidence: 0.8
|
||||||
|
rules:
|
||||||
|
- name: blank-imports
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: context-as-argument
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: context-keys-type
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: dot-imports
|
||||||
|
severity: warning
|
||||||
|
disabled: true
|
||||||
|
- name: error-return
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: error-strings
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: error-naming
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: exported
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: increment-decrement
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: var-naming
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: var-declaration
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: package-comments
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: range
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: receiver-naming
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: time-naming
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: unexported-return
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: indent-error-flow
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: errorf
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: empty-block
|
||||||
|
severity: warning
|
||||||
|
disabled: true
|
||||||
|
- name: superfluous-else
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: unused-parameter
|
||||||
|
severity: warning
|
||||||
|
disabled: true
|
||||||
|
- name: unreachable-code
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
|
- name: redefines-builtin-id
|
||||||
|
severity: warning
|
||||||
|
disabled: false
|
||||||
13
Makefile
13
Makefile
|
|
@ -1,4 +1,7 @@
|
||||||
.PHONY: test fuzz
|
.PHONY: test fuzz lint
|
||||||
|
|
||||||
|
lint:
|
||||||
|
golangci-lint run -c .golangci.yml ./...
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -coverprofile=profile.out -coverpkg=github.com/yuin/goldmark,github.com/yuin/goldmark/ast,github.com/yuin/goldmark/extension,github.com/yuin/goldmark/extension/ast,github.com/yuin/goldmark/parser,github.com/yuin/goldmark/renderer,github.com/yuin/goldmark/renderer/html,github.com/yuin/goldmark/text,github.com/yuin/goldmark/util ./...
|
go test -coverprofile=profile.out -coverpkg=github.com/yuin/goldmark,github.com/yuin/goldmark/ast,github.com/yuin/goldmark/extension,github.com/yuin/goldmark/extension/ast,github.com/yuin/goldmark/parser,github.com/yuin/goldmark/renderer,github.com/yuin/goldmark/renderer/html,github.com/yuin/goldmark/text,github.com/yuin/goldmark/util ./...
|
||||||
|
|
@ -7,10 +10,4 @@ cov: test
|
||||||
go tool cover -html=profile.out
|
go tool cover -html=profile.out
|
||||||
|
|
||||||
fuzz:
|
fuzz:
|
||||||
which go-fuzz > /dev/null 2>&1 || (GO111MODULE=off go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build; GO111MODULE=off go get -d github.com/dvyukov/go-fuzz-corpus; true)
|
cd ./fuzz && go test -fuzz=Fuzz
|
||||||
rm -rf ./fuzz/corpus
|
|
||||||
rm -rf ./fuzz/crashers
|
|
||||||
rm -rf ./fuzz/suppressions
|
|
||||||
rm -f ./fuzz/fuzz-fuzz.zip
|
|
||||||
cd ./fuzz && GO111MODULE=off go-fuzz-build
|
|
||||||
cd ./fuzz && go-fuzz
|
|
||||||
|
|
|
||||||
100
README.md
100
README.md
|
|
@ -2,13 +2,15 @@ goldmark
|
||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/yuin/goldmark)
|
[](https://pkg.go.dev/github.com/yuin/goldmark)
|
||||||
[](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
[](https://github.com/yuin/goldmark/actions?query=workflow:test)
|
||||||
[](https://coveralls.io/github/yuin/goldmark)
|
[](https://coveralls.io/github/yuin/goldmark)
|
||||||
[](https://goreportcard.com/report/github.com/yuin/goldmark)
|
[](https://goreportcard.com/report/github.com/yuin/goldmark)
|
||||||
|
|
||||||
> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
|
> A Markdown parser written in Go. Easy to extend, standards-compliant, well-structured.
|
||||||
|
|
||||||
goldmark is compliant with CommonMark 0.30.
|
goldmark is compliant with CommonMark 0.31.2.
|
||||||
|
|
||||||
|
- [goldmark playground](https://yuin.github.io/goldmark/playground/) : Try goldmark online. This playground is built with WASM(5-10MB).
|
||||||
|
|
||||||
Motivation
|
Motivation
|
||||||
----------------------
|
----------------------
|
||||||
|
|
@ -46,7 +48,7 @@ Features
|
||||||
renderers.
|
renderers.
|
||||||
- **Performance.** goldmark's performance is on par with that of cmark,
|
- **Performance.** goldmark's performance is on par with that of cmark,
|
||||||
the CommonMark reference implementation written in C.
|
the CommonMark reference implementation written in C.
|
||||||
- **Robust.** goldmark is tested with [go-fuzz](https://github.com/dvyukov/go-fuzz), a fuzz testing tool.
|
- **Robust.** goldmark is tested with `go test --fuzz`.
|
||||||
- **Built-in extensions.** goldmark ships with common extensions like tables, strikethrough,
|
- **Built-in extensions.** goldmark ships with common extensions like tables, strikethrough,
|
||||||
task lists, and definition lists.
|
task lists, and definition lists.
|
||||||
- **Depends only on standard libraries.**
|
- **Depends only on standard libraries.**
|
||||||
|
|
@ -180,6 +182,8 @@ Parser and Renderer options
|
||||||
- [PHP Markdown Extra: Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes)
|
- [PHP Markdown Extra: Footnotes](https://michelf.ca/projects/php-markdown/extra/#footnotes)
|
||||||
- `extension.Typographer`
|
- `extension.Typographer`
|
||||||
- This extension substitutes punctuations with typographic entities like [smartypants](https://daringfireball.net/projects/smartypants/).
|
- This extension substitutes punctuations with typographic entities like [smartypants](https://daringfireball.net/projects/smartypants/).
|
||||||
|
- `extension.CJK`
|
||||||
|
- This extension is a shortcut for CJK related functionalities.
|
||||||
|
|
||||||
### Attributes
|
### Attributes
|
||||||
The `parser.WithAttribute` option allows you to define attributes on some elements.
|
The `parser.WithAttribute` option allows you to define attributes on some elements.
|
||||||
|
|
@ -258,7 +262,7 @@ You can override autolinking patterns via options.
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithLinkifyAllowedProtocols` | `[][]byte` | List of allowed protocols such as `[][]byte{ []byte("http:") }` |
|
| `extension.WithLinkifyAllowedProtocols` | `[][]byte \| []string` | List of allowed protocols such as `[]string{ "http:" }` |
|
||||||
| `extension.WithLinkifyURLRegexp` | `*regexp.Regexp` | Regexp that defines URLs, including protocols |
|
| `extension.WithLinkifyURLRegexp` | `*regexp.Regexp` | Regexp that defines URLs, including protocols |
|
||||||
| `extension.WithLinkifyWWWRegexp` | `*regexp.Regexp` | Regexp that defines URL starting with `www.`. This pattern corresponds to [the extended www autolink](https://github.github.com/gfm/#extended-www-autolink) |
|
| `extension.WithLinkifyWWWRegexp` | `*regexp.Regexp` | Regexp that defines URL starting with `www.`. This pattern corresponds to [the extended www autolink](https://github.github.com/gfm/#extended-www-autolink) |
|
||||||
| `extension.WithLinkifyEmailRegexp` | `*regexp.Regexp` | Regexp that defines email addresses` |
|
| `extension.WithLinkifyEmailRegexp` | `*regexp.Regexp` | Regexp that defines email addresses` |
|
||||||
|
|
@ -275,12 +279,12 @@ markdown := goldmark.New(
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.NewLinkify(
|
extension.NewLinkify(
|
||||||
extension.WithLinkifyAllowedProtocols([][]byte{
|
extension.WithLinkifyAllowedProtocols([]string{
|
||||||
[]byte("http:"),
|
"http:",
|
||||||
[]byte("https:"),
|
"https:",
|
||||||
}),
|
}),
|
||||||
extension.WithLinkifyURLRegexp(
|
extension.WithLinkifyURLRegexp(
|
||||||
xurls.Strict,
|
xurls.Strict(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -295,13 +299,13 @@ This extension has some options:
|
||||||
|
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `extension.WithFootnoteIDPrefix` | `[]byte` | a prefix for the id attributes.|
|
| `extension.WithFootnoteIDPrefix` | `[]byte \| string` | a prefix for the id attributes.|
|
||||||
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
| `extension.WithFootnoteIDPrefixFunction` | `func(gast.Node) []byte` | a function that determines the id attribute for given Node.|
|
||||||
| `extension.WithFootnoteLinkTitle` | `[]byte` | an optional title attribute for footnote links.|
|
| `extension.WithFootnoteLinkTitle` | `[]byte \| string` | an optional title attribute for footnote links.|
|
||||||
| `extension.WithFootnoteBacklinkTitle` | `[]byte` | an optional title attribute for footnote backlinks. |
|
| `extension.WithFootnoteBacklinkTitle` | `[]byte \| string` | an optional title attribute for footnote backlinks. |
|
||||||
| `extension.WithFootnoteLinkClass` | `[]byte` | a class for footnote links. This defaults to `footnote-ref`. |
|
| `extension.WithFootnoteLinkClass` | `[]byte \| string` | a class for footnote links. This defaults to `footnote-ref`. |
|
||||||
| `extension.WithFootnoteBacklinkClass` | `[]byte` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
| `extension.WithFootnoteBacklinkClass` | `[]byte \| string` | a class for footnote backlinks. This defaults to `footnote-backref`. |
|
||||||
| `extension.WithFootnoteBacklinkHTML` | `[]byte` | a class for footnote backlinks. This defaults to `↩︎`. |
|
| `extension.WithFootnoteBacklinkHTML` | `[]byte \| string` | a class for footnote backlinks. This defaults to `↩︎`. |
|
||||||
|
|
||||||
Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
Some options can have special substitutions. Occurrences of “^^” in the string will be replaced by the corresponding footnote number in the HTML output. Occurrences of “%%” will be replaced by a number for the reference (footnotes can have multiple references).
|
||||||
|
|
||||||
|
|
@ -317,7 +321,7 @@ for _, path := range files {
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewFootnote(
|
NewFootnote(
|
||||||
WithFootnoteIDPrefix([]byte(path)),
|
WithFootnoteIDPrefix(path),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -370,6 +374,55 @@ footnote-prefix: article1
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### CJK extension
|
||||||
|
CommonMark gives compatibilities a high priority and original markdown was designed by westerners. So CommonMark lacks considerations for languages like CJK.
|
||||||
|
|
||||||
|
This extension provides additional options for CJK users.
|
||||||
|
|
||||||
|
| Functional option | Type | Description |
|
||||||
|
| ----------------- | ---- | ----------- |
|
||||||
|
| `extension.WithEastAsianLineBreaks` | `...extension.EastAsianLineBreaksStyle` | Soft line breaks are rendered as a newline. Some asian users will see it as an unnecessary space. With this option, soft line breaks between east asian wide characters will be ignored. This defaults to `EastAsianLineBreaksStyleSimple`. |
|
||||||
|
| `extension.WithEscapedSpace` | `-` | Without spaces around an emphasis started with east asian punctuations, it is not interpreted as an emphasis(as defined in CommonMark spec). With this option, you can avoid this inconvenient behavior by putting 'not rendered' spaces around an emphasis like `太郎は\ **「こんにちわ」**\ といった`. |
|
||||||
|
|
||||||
|
#### Styles of Line Breaking
|
||||||
|
|
||||||
|
| Style | Description |
|
||||||
|
| ----- | ----------- |
|
||||||
|
| `EastAsianLineBreaksStyleSimple` | Soft line breaks are ignored if both sides of the break are east asian wide character. This behavior is the same as [`east_asian_line_breaks`](https://pandoc.org/MANUAL.html#extension-east_asian_line_breaks) in Pandoc. |
|
||||||
|
| `EastAsianLineBreaksCSS3Draft` | This option implements CSS text level3 [Segment Break Transformation Rules](https://drafts.csswg.org/css-text-3/#line-break-transform) with [some enhancements](https://github.com/w3c/csswg-drafts/issues/5086). |
|
||||||
|
|
||||||
|
#### Example of `EastAsianLineBreaksStyleSimple`
|
||||||
|
|
||||||
|
Input Markdown:
|
||||||
|
|
||||||
|
```md
|
||||||
|
私はプログラマーです。
|
||||||
|
東京の会社に勤めています。
|
||||||
|
GoでWebアプリケーションを開発しています。
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p>私はプログラマーです。東京の会社に勤めています。\nGoでWebアプリケーションを開発しています。</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example of `EastAsianLineBreaksCSS3Draft`
|
||||||
|
|
||||||
|
Input Markdown:
|
||||||
|
|
||||||
|
```md
|
||||||
|
私はプログラマーです。
|
||||||
|
東京の会社に勤めています。
|
||||||
|
GoでWebアプリケーションを開発しています。
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p>私はプログラマーです。東京の会社に勤めています。GoでWebアプリケーションを開発しています。</p>
|
||||||
|
```
|
||||||
|
|
||||||
Security
|
Security
|
||||||
--------------------
|
--------------------
|
||||||
By default, goldmark does not render raw HTML or potentially-dangerous URLs.
|
By default, goldmark does not render raw HTML or potentially-dangerous URLs.
|
||||||
|
|
@ -416,6 +469,7 @@ As you can see, goldmark's performance is on par with cmark's.
|
||||||
|
|
||||||
Extensions
|
Extensions
|
||||||
--------------------
|
--------------------
|
||||||
|
### List of extensions
|
||||||
|
|
||||||
- [goldmark-meta](https://github.com/yuin/goldmark-meta): A YAML metadata
|
- [goldmark-meta](https://github.com/yuin/goldmark-meta): A YAML metadata
|
||||||
extension for the goldmark Markdown parser.
|
extension for the goldmark Markdown parser.
|
||||||
|
|
@ -427,10 +481,26 @@ Extensions
|
||||||
- [goldmark-pdf](https://github.com/stephenafamo/goldmark-pdf): A PDF renderer that can be passed to `goldmark.WithRenderer()`.
|
- [goldmark-pdf](https://github.com/stephenafamo/goldmark-pdf): A PDF renderer that can be passed to `goldmark.WithRenderer()`.
|
||||||
- [goldmark-hashtag](https://github.com/abhinav/goldmark-hashtag): Adds support for `#hashtag`-based tagging to goldmark.
|
- [goldmark-hashtag](https://github.com/abhinav/goldmark-hashtag): Adds support for `#hashtag`-based tagging to goldmark.
|
||||||
- [goldmark-wikilink](https://github.com/abhinav/goldmark-wikilink): Adds support for `[[wiki]]`-style links to goldmark.
|
- [goldmark-wikilink](https://github.com/abhinav/goldmark-wikilink): Adds support for `[[wiki]]`-style links to goldmark.
|
||||||
|
- [goldmark-anchor](https://github.com/abhinav/goldmark-anchor): Adds anchors (permalinks) next to all headers in a document.
|
||||||
|
- [goldmark-figure](https://github.com/mangoumbrella/goldmark-figure): Adds support for rendering paragraphs starting with an image to `<figure>` elements.
|
||||||
|
- [goldmark-frontmatter](https://github.com/abhinav/goldmark-frontmatter): Adds support for YAML, TOML, and custom front matter to documents.
|
||||||
- [goldmark-toc](https://github.com/abhinav/goldmark-toc): Adds support for generating tables-of-contents for goldmark documents.
|
- [goldmark-toc](https://github.com/abhinav/goldmark-toc): Adds support for generating tables-of-contents for goldmark documents.
|
||||||
- [goldmark-mermaid](https://github.com/abhinav/goldmark-mermaid): Adds support for rendering [Mermaid](https://mermaid-js.github.io/mermaid/) diagrams in goldmark documents.
|
- [goldmark-mermaid](https://github.com/abhinav/goldmark-mermaid): Adds support for rendering [Mermaid](https://mermaid-js.github.io/mermaid/) diagrams in goldmark documents.
|
||||||
- [goldmark-pikchr](https://github.com/jchenry/goldmark-pikchr): Adds support for rendering [Pikchr](https://pikchr.org/home/doc/trunk/homepage.md) diagrams in goldmark documents.
|
- [goldmark-pikchr](https://github.com/jchenry/goldmark-pikchr): Adds support for rendering [Pikchr](https://pikchr.org/home/doc/trunk/homepage.md) diagrams in goldmark documents.
|
||||||
- [goldmark-embed](https://github.com/13rac1/goldmark-embed): Adds support for rendering embeds from YouTube links.
|
- [goldmark-embed](https://github.com/13rac1/goldmark-embed): Adds support for rendering embeds from YouTube links.
|
||||||
|
- [goldmark-latex](https://github.com/soypat/goldmark-latex): A $\LaTeX$ renderer that can be passed to `goldmark.WithRenderer()`.
|
||||||
|
- [goldmark-fences](https://github.com/stefanfritsch/goldmark-fences): Support for pandoc-style [fenced divs](https://pandoc.org/MANUAL.html#divs-and-spans) in goldmark.
|
||||||
|
- [goldmark-d2](https://github.com/FurqanSoftware/goldmark-d2): Adds support for [D2](https://d2lang.com/) diagrams.
|
||||||
|
- [goldmark-katex](https://github.com/FurqanSoftware/goldmark-katex): Adds support for [KaTeX](https://katex.org/) math and equations.
|
||||||
|
- [goldmark-img64](https://github.com/tenkoh/goldmark-img64): Adds support for embedding images into the document as DataURL (base64 encoded).
|
||||||
|
- [goldmark-enclave](https://github.com/quailyquaily/goldmark-enclave): Adds support for embedding youtube/bilibili video, X's [oembed X](https://publish.x.com/), [tradingview chart](https://www.tradingview.com/widget/)'s chart, [quaily widget](https://quaily.com), [spotify embeds](https://developer.spotify.com/documentation/embeds), [dify embed](https://dify.ai/) and html audio into the document.
|
||||||
|
- [goldmark-wiki-table](https://github.com/movsb/goldmark-wiki-table): Adds support for embedding Wiki Tables.
|
||||||
|
- [goldmark-tgmd](https://github.com/Mad-Pixels/goldmark-tgmd): A Telegram markdown renderer that can be passed to `goldmark.WithRenderer()`.
|
||||||
|
|
||||||
|
### Loading extensions at runtime
|
||||||
|
[goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) allows you to write a goldmark extension in Lua and load it at runtime without re-compilation.
|
||||||
|
|
||||||
|
Please refer to [goldmark-dynamic](https://github.com/yuin/goldmark-dynamic) for details.
|
||||||
|
|
||||||
|
|
||||||
goldmark internal(for extension developers)
|
goldmark internal(for extension developers)
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,23 @@ module banchmark
|
||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/88250/lute v1.7.3
|
github.com/88250/lute v1.7.5
|
||||||
github.com/gomarkdown/markdown v0.0.0-20220114203417-14399d5448c4
|
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
||||||
github.com/russross/blackfriday/v2 v2.1.0
|
github.com/russross/blackfriday/v2 v2.1.0
|
||||||
github.com/yuin/goldmark v1.2.1
|
github.com/yuin/goldmark v0.0.0
|
||||||
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
|
gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma v0.9.2 // indirect
|
github.com/alecthomas/chroma v0.10.0 // indirect
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect
|
github.com/dlclark/regexp2 v1.10.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20210619142842-05447a1fa367 // indirect
|
|
||||||
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 // indirect
|
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 // indirect
|
||||||
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 // indirect
|
gitlab.com/golang-commonmark/linkify v0.0.0-20191026162114-a0c2df6c8f82 // indirect
|
||||||
gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84 // indirect
|
gitlab.com/golang-commonmark/mdurl v0.0.0-20191124015652-932350d1cb84 // indirect
|
||||||
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect
|
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f // indirect
|
||||||
golang.org/x/text v0.3.6 // indirect
|
golang.org/x/text v0.10.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace gopkg.in/russross/blackfriday.v2 v2.0.1 => github.com/russross/blackfriday/v2 v2.0.1
|
replace gopkg.in/russross/blackfriday.v2 v2.0.1 => github.com/russross/blackfriday/v2 v2.0.1
|
||||||
|
replace github.com/yuin/goldmark v0.0.0 => ../../
|
||||||
|
|
|
||||||
|
|
@ -1,205 +1,24 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/88250/lute v1.7.5 h1:mcPFURh5sK1WH1kFRjqK5DkMWOfVN2BhyrXitN8GmpQ=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
github.com/88250/lute v1.7.5/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
|
||||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
|
||||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
|
||||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
|
||||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
|
||||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
|
||||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
|
||||||
github.com/88250/lute v1.7.3 h1:Yuve6dTAZNjWktXpHK14PND4KaBwqVxVi3L+GZa3+HQ=
|
|
||||||
github.com/88250/lute v1.7.3/go.mod h1:3CPco034YZBxszJEqBPNgp3a1K+uddq4IegStqBiyTM=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
|
||||||
github.com/alecthomas/chroma v0.9.2 h1:yU1sE2+TZbLIQPMk30SolL2Hn53SR/Pv750f7qZ/XMs=
|
|
||||||
github.com/alecthomas/chroma v0.9.2/go.mod h1:eMuEnpA18XbG/WhOWtCzJHS7WqEtDAI+HxdwoW0nVSk=
|
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
|
||||||
github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk=
|
|
||||||
github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
|
||||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c h1:MVVbswUlqicyj8P/JljoocA7AyCo62gzD0O7jfvrhtE=
|
|
||||||
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
|
||||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
|
||||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
|
||||||
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a h1:AWZzzFrqyjYlRloN6edwTLTUbKxf5flLXNuTBDm3Ews=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/gomarkdown/markdown v0.0.0-20220114203417-14399d5448c4 h1:6GlsnS3GQYfrJZTJEUsheoyLE6kLXQJDvQKIKxgL/9Q=
|
|
||||||
github.com/gomarkdown/markdown v0.0.0-20220114203417-14399d5448c4/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
|
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
|
||||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
|
||||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20210619142842-05447a1fa367 h1:7x3XlN7ZRUUHFybdLftf4ZFpi1JYR1w6WL7RPFlWqMM=
|
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20210619142842-05447a1fa367/go.mod h1:MtKwTfDNYAP5EtbQSMYjTSqvj1aXJKQRASWq3bwaP+g=
|
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
|
||||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
|
||||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
|
||||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
|
||||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
|
||||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
|
||||||
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
|
||||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
|
||||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
|
||||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
|
||||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 h1:K+bMSIx9A7mLES1rtG+qKduLIXq40DAzYHtb0XuCukA=
|
gitlab.com/golang-commonmark/html v0.0.0-20191124015941-a22733972181 h1:K+bMSIx9A7mLES1rtG+qKduLIXq40DAzYHtb0XuCukA=
|
||||||
|
|
@ -214,157 +33,10 @@ gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f h1:Wku8eEde
|
||||||
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs=
|
gitlab.com/golang-commonmark/puny v0.0.0-20191124015043-9f83538fa04f/go.mod h1:Tiuhl+njh/JIg0uS/sOJVYi0x2HEa5rc1OAaVsb5tAs=
|
||||||
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 h1:uPZaMiz6Sz0PZs3IZJWpU5qHKGNy///1pacZC9txiUI=
|
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638 h1:uPZaMiz6Sz0PZs3IZJWpU5qHKGNy///1pacZC9txiUI=
|
||||||
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9XGmFtQCvV3Lm9NLico3UhFwUpCG/+mVU=
|
gitlab.com/opennota/wd v0.0.0-20180912061657-c5d65f63c638/go.mod h1:EGRJaqe2eO9XGmFtQCvV3Lm9NLico3UhFwUpCG/+mVU=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
|
||||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
|
||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
|
||||||
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988 h1:EjgCl+fVlIaPJSori0ikSz3uV0DOHKWOJFpv1sAAhBM=
|
|
||||||
golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
|
||||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
|
||||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
|
||||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
|
||||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
|
||||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
|
||||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
|
||||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
|
||||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
|
||||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
|
||||||
|
|
|
||||||
117
_test/extra.txt
117
_test/extra.txt
|
|
@ -385,7 +385,8 @@ a* b c d *e*
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
x
|
x
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<pre><code> x</code></pre>
|
<pre><code> x
|
||||||
|
</code></pre>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
26: NUL bytes must be replaced with U+FFFD
|
26: NUL bytes must be replaced with U+FFFD
|
||||||
|
|
@ -728,3 +729,117 @@ a <!-- b -->
|
||||||
<p>This is a quote.</p>
|
<p>This is a quote.</p>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
57: Tabbed fenced code block within a list
|
||||||
|
//- - - - - - - - -//
|
||||||
|
1.
|
||||||
|
```
|
||||||
|
```
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<pre><code></code></pre>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
58: HTML end tag without trailing new lines
|
||||||
|
OPTIONS: {"trim": true}
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<pre>
|
||||||
|
</pre>
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<pre>
|
||||||
|
</pre>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
59: Raw HTML tag with one new line
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<img src=./.assets/logo.svg
|
||||||
|
/>
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><img src=./.assets/logo.svg
|
||||||
|
/></p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
60: Raw HTML tag with multiple new lines
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<img src=./.assets/logo.svg
|
||||||
|
|
||||||
|
/>
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><img src=./.assets/logo.svg</p>
|
||||||
|
<p>/></p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
61: Image alt with a new line
|
||||||
|
//- - - - - - - - -//
|
||||||
|

|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><img src="logo.png" alt="alt
|
||||||
|
text" /></p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
62: Image alt with an escaped character
|
||||||
|
//- - - - - - - - -//
|
||||||
|

|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><img src="https://example.com/img.png" alt="`alt" /></p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
63: Emphasis in link label
|
||||||
|
//- - - - - - - - -//
|
||||||
|
[*[a]*](b)
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><a href="b"><em>[a]</em></a></p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
64: Nested list under an empty list item
|
||||||
|
//- - - - - - - - -//
|
||||||
|
-
|
||||||
|
- foo
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<ul>
|
||||||
|
<li>foo</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
65: Nested fenced code block with tab
|
||||||
|
//- - - - - - - - -//
|
||||||
|
> ```
|
||||||
|
> 0
|
||||||
|
> ```
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<blockquote>
|
||||||
|
<pre><code> 0
|
||||||
|
</code></pre>
|
||||||
|
</blockquote>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
66: EOF should be rendered as a newline with an unclosed block(w/ TAB)
|
||||||
|
//- - - - - - - - -//
|
||||||
|
> ```
|
||||||
|
> 0
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<blockquote>
|
||||||
|
<pre><code> 0
|
||||||
|
</code></pre>
|
||||||
|
</blockquote>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
67: EOF should be rendered as a newline with an unclosed block
|
||||||
|
//- - - - - - - - -//
|
||||||
|
> ```
|
||||||
|
> 0
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<blockquote>
|
||||||
|
<pre><code> 0
|
||||||
|
</code></pre>
|
||||||
|
</blockquote>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
3184
_test/spec.json
3184
_test/spec.json
File diff suppressed because it is too large
Load diff
61
_tools/build-oss-fuzz-corpus.go
Normal file
61
_tools/build-oss-fuzz-corpus.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestCase struct {
|
||||||
|
Example int `json:"example"`
|
||||||
|
Markdown string `json:"markdown"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
corpus_out := os.Args[1]
|
||||||
|
if !strings.HasSuffix(corpus_out, ".zip") {
|
||||||
|
log.Fatalln("Expected command line:", os.Args[0], "<corpus_output>.zip")
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_file, err := os.Create(corpus_out)
|
||||||
|
|
||||||
|
zip_writer := zip.NewWriter(zip_file)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed creating file:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
json_corpus := "_test/spec.json"
|
||||||
|
bs, err := ioutil.ReadFile(json_corpus)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Could not open file:", json_corpus)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var testCases []TestCase
|
||||||
|
if err := json.Unmarshal(bs, &testCases); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range testCases {
|
||||||
|
file_in_zip := "example-" + strconv.Itoa(c.Example)
|
||||||
|
f, err := zip_writer.Create(file_in_zip)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = f.Write([]byte(c.Markdown))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to write file: %s into zip file", file_in_zip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zip_writer.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to close zip writer", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zip_file.Close()
|
||||||
|
}
|
||||||
25
ast/ast.go
25
ast/ast.go
|
|
@ -39,7 +39,7 @@ func NewNodeKind(name string) NodeKind {
|
||||||
return kindMax
|
return kindMax
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Attribute is an attribute of the Node
|
// An Attribute is an attribute of the Node.
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
Name []byte
|
Name []byte
|
||||||
Value interface{}
|
Value interface{}
|
||||||
|
|
@ -123,6 +123,12 @@ type Node interface {
|
||||||
Dump(source []byte, level int)
|
Dump(source []byte, level int)
|
||||||
|
|
||||||
// Text returns text values of this node.
|
// Text returns text values of this node.
|
||||||
|
// This method is valid only for some inline nodes.
|
||||||
|
// If this node is a block node, Text returns a text value as reasonable as possible.
|
||||||
|
// Notice that there are no 'correct' text values for the block nodes.
|
||||||
|
// Result for the block nodes may be different from your expectation.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. Pragraph.Lines, Text.Value).
|
||||||
Text(source []byte) []byte
|
Text(source []byte) []byte
|
||||||
|
|
||||||
// HasBlankPreviousLines returns true if the row before this node is blank,
|
// HasBlankPreviousLines returns true if the row before this node is blank,
|
||||||
|
|
@ -248,7 +254,7 @@ func (n *BaseNode) RemoveChildren(self Node) {
|
||||||
n.childCount = 0
|
n.childCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SortChildren implements Node.SortChildren
|
// SortChildren implements Node.SortChildren.
|
||||||
func (n *BaseNode) SortChildren(comparator func(n1, n2 Node) int) {
|
func (n *BaseNode) SortChildren(comparator func(n1, n2 Node) int) {
|
||||||
var sorted Node
|
var sorted Node
|
||||||
current := n.firstChild
|
current := n.firstChild
|
||||||
|
|
@ -358,7 +364,7 @@ func (n *BaseNode) InsertBefore(self, v1, insertee Node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// OwnerDocument implements Node.OwnerDocument
|
// OwnerDocument implements Node.OwnerDocument.
|
||||||
func (n *BaseNode) OwnerDocument() *Document {
|
func (n *BaseNode) OwnerDocument() *Document {
|
||||||
d := n.Parent()
|
d := n.Parent()
|
||||||
for {
|
for {
|
||||||
|
|
@ -375,10 +381,17 @@ func (n *BaseNode) OwnerDocument() *Document {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text implements Node.Text .
|
// Text implements Node.Text .
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. Pragraph.Lines, Text.Value).
|
||||||
func (n *BaseNode) Text(source []byte) []byte {
|
func (n *BaseNode) Text(source []byte) []byte {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
for c := n.firstChild; c != nil; c = c.NextSibling() {
|
||||||
buf.Write(c.Text(source))
|
buf.Write(c.Text(source))
|
||||||
|
if sb, ok := c.(interface {
|
||||||
|
SoftLineBreak() bool
|
||||||
|
}); ok && sb.SoftLineBreak() {
|
||||||
|
buf.WriteByte('\n')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
@ -399,7 +412,7 @@ func (n *BaseNode) SetAttribute(name []byte, value interface{}) {
|
||||||
n.attributes = append(n.attributes, Attribute{name, value})
|
n.attributes = append(n.attributes, Attribute{name, value})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAttributeString implements Node.SetAttributeString
|
// SetAttributeString implements Node.SetAttributeString.
|
||||||
func (n *BaseNode) SetAttributeString(name string, value interface{}) {
|
func (n *BaseNode) SetAttributeString(name string, value interface{}) {
|
||||||
n.SetAttribute(util.StringToReadOnlyBytes(name), value)
|
n.SetAttribute(util.StringToReadOnlyBytes(name), value)
|
||||||
}
|
}
|
||||||
|
|
@ -422,12 +435,12 @@ func (n *BaseNode) AttributeString(s string) (interface{}, bool) {
|
||||||
return n.Attribute(util.StringToReadOnlyBytes(s))
|
return n.Attribute(util.StringToReadOnlyBytes(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attributes implements Node.Attributes
|
// Attributes implements Node.Attributes.
|
||||||
func (n *BaseNode) Attributes() []Attribute {
|
func (n *BaseNode) Attributes() []Attribute {
|
||||||
return n.attributes
|
return n.attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAttributes implements Node.RemoveAttributes
|
// RemoveAttributes implements Node.RemoveAttributes.
|
||||||
func (n *BaseNode) RemoveAttributes() {
|
func (n *BaseNode) RemoveAttributes() {
|
||||||
n.attributes = nil
|
n.attributes = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,21 +5,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRemoveChildren(t *testing.T) {
|
|
||||||
root := NewDocument()
|
|
||||||
|
|
||||||
node1 := NewDocument()
|
|
||||||
|
|
||||||
node2 := NewDocument()
|
|
||||||
|
|
||||||
root.AppendChild(root, node1)
|
|
||||||
root.AppendChild(root, node2)
|
|
||||||
|
|
||||||
root.RemoveChildren(root)
|
|
||||||
|
|
||||||
t.Logf("%+v", node2.PreviousSibling())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWalk(t *testing.T) {
|
func TestWalk(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|
|
||||||
63
ast/block.go
63
ast/block.go
|
|
@ -14,12 +14,12 @@ type BaseBlock struct {
|
||||||
lines *textm.Segments
|
lines *textm.Segments
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type implements Node.Type
|
// Type implements Node.Type.
|
||||||
func (b *BaseBlock) Type() NodeType {
|
func (b *BaseBlock) Type() NodeType {
|
||||||
return TypeBlock
|
return TypeBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRaw implements Node.IsRaw
|
// IsRaw implements Node.IsRaw.
|
||||||
func (b *BaseBlock) IsRaw() bool {
|
func (b *BaseBlock) IsRaw() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ func (b *BaseBlock) SetBlankPreviousLines(v bool) {
|
||||||
b.blankPreviousLines = v
|
b.blankPreviousLines = v
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lines implements Node.Lines
|
// Lines implements Node.Lines.
|
||||||
func (b *BaseBlock) Lines() *textm.Segments {
|
func (b *BaseBlock) Lines() *textm.Segments {
|
||||||
if b.lines == nil {
|
if b.lines == nil {
|
||||||
b.lines = textm.NewSegments()
|
b.lines = textm.NewSegments()
|
||||||
|
|
@ -42,7 +42,7 @@ func (b *BaseBlock) Lines() *textm.Segments {
|
||||||
return b.lines
|
return b.lines
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLines implements Node.SetLines
|
// SetLines implements Node.SetLines.
|
||||||
func (b *BaseBlock) SetLines(v *textm.Segments) {
|
func (b *BaseBlock) SetLines(v *textm.Segments) {
|
||||||
b.lines = v
|
b.lines = v
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +72,7 @@ func (n *Document) Kind() NodeKind {
|
||||||
return KindDocument
|
return KindDocument
|
||||||
}
|
}
|
||||||
|
|
||||||
// OwnerDocument implements Node.OwnerDocument
|
// OwnerDocument implements Node.OwnerDocument.
|
||||||
func (n *Document) OwnerDocument() *Document {
|
func (n *Document) OwnerDocument() *Document {
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
@ -130,6 +130,13 @@ func (n *TextBlock) Kind() NodeKind {
|
||||||
return KindTextBlock
|
return KindTextBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. TextBlock.Lines).
|
||||||
|
func (n *TextBlock) Text(source []byte) []byte {
|
||||||
|
return n.Lines().Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
// NewTextBlock returns a new TextBlock node.
|
// NewTextBlock returns a new TextBlock node.
|
||||||
func NewTextBlock() *TextBlock {
|
func NewTextBlock() *TextBlock {
|
||||||
return &TextBlock{
|
return &TextBlock{
|
||||||
|
|
@ -155,6 +162,13 @@ func (n *Paragraph) Kind() NodeKind {
|
||||||
return KindParagraph
|
return KindParagraph
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. Paragraph.Lines).
|
||||||
|
func (n *Paragraph) Text(source []byte) []byte {
|
||||||
|
return n.Lines().Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
// NewParagraph returns a new Paragraph node.
|
// NewParagraph returns a new Paragraph node.
|
||||||
func NewParagraph() *Paragraph {
|
func NewParagraph() *Paragraph {
|
||||||
return &Paragraph{
|
return &Paragraph{
|
||||||
|
|
@ -249,6 +263,13 @@ func (n *CodeBlock) Kind() NodeKind {
|
||||||
return KindCodeBlock
|
return KindCodeBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. CodeBlock.Lines).
|
||||||
|
func (n *CodeBlock) Text(source []byte) []byte {
|
||||||
|
return n.Lines().Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
// NewCodeBlock returns a new CodeBlock node.
|
// NewCodeBlock returns a new CodeBlock node.
|
||||||
func NewCodeBlock() *CodeBlock {
|
func NewCodeBlock() *CodeBlock {
|
||||||
return &CodeBlock{
|
return &CodeBlock{
|
||||||
|
|
@ -304,6 +325,13 @@ func (n *FencedCodeBlock) Kind() NodeKind {
|
||||||
return KindFencedCodeBlock
|
return KindFencedCodeBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. FencedCodeBlock.Lines).
|
||||||
|
func (n *FencedCodeBlock) Text(source []byte) []byte {
|
||||||
|
return n.Lines().Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
// NewFencedCodeBlock return a new FencedCodeBlock node.
|
// NewFencedCodeBlock return a new FencedCodeBlock node.
|
||||||
func NewFencedCodeBlock(info *Text) *FencedCodeBlock {
|
func NewFencedCodeBlock(info *Text) *FencedCodeBlock {
|
||||||
return &FencedCodeBlock{
|
return &FencedCodeBlock{
|
||||||
|
|
@ -431,19 +459,19 @@ func NewListItem(offset int) *ListItem {
|
||||||
type HTMLBlockType int
|
type HTMLBlockType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// HTMLBlockType1 represents type 1 html blocks
|
// HTMLBlockType1 represents type 1 html blocks.
|
||||||
HTMLBlockType1 HTMLBlockType = iota + 1
|
HTMLBlockType1 HTMLBlockType = iota + 1
|
||||||
// HTMLBlockType2 represents type 2 html blocks
|
// HTMLBlockType2 represents type 2 html blocks.
|
||||||
HTMLBlockType2
|
HTMLBlockType2
|
||||||
// HTMLBlockType3 represents type 3 html blocks
|
// HTMLBlockType3 represents type 3 html blocks.
|
||||||
HTMLBlockType3
|
HTMLBlockType3
|
||||||
// HTMLBlockType4 represents type 4 html blocks
|
// HTMLBlockType4 represents type 4 html blocks.
|
||||||
HTMLBlockType4
|
HTMLBlockType4
|
||||||
// HTMLBlockType5 represents type 5 html blocks
|
// HTMLBlockType5 represents type 5 html blocks.
|
||||||
HTMLBlockType5
|
HTMLBlockType5
|
||||||
// HTMLBlockType6 represents type 6 html blocks
|
// HTMLBlockType6 represents type 6 html blocks.
|
||||||
HTMLBlockType6
|
HTMLBlockType6
|
||||||
// HTMLBlockType7 represents type 7 html blocks
|
// HTMLBlockType7 represents type 7 html blocks.
|
||||||
HTMLBlockType7
|
HTMLBlockType7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -498,6 +526,17 @@ func (n *HTMLBlock) Kind() NodeKind {
|
||||||
return KindHTMLBlock
|
return KindHTMLBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. HTMLBlock.Lines).
|
||||||
|
func (n *HTMLBlock) Text(source []byte) []byte {
|
||||||
|
ret := n.Lines().Value(source)
|
||||||
|
if n.HasClosure() {
|
||||||
|
ret = append(ret, n.ClosureLine.Value(source)...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// NewHTMLBlock returns a new HTMLBlock node.
|
// NewHTMLBlock returns a new HTMLBlock node.
|
||||||
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
func NewHTMLBlock(typ HTMLBlockType) *HTMLBlock {
|
||||||
return &HTMLBlock{
|
return &HTMLBlock{
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,12 @@ type BaseInline struct {
|
||||||
BaseNode
|
BaseNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type implements Node.Type
|
// Type implements Node.Type.
|
||||||
func (b *BaseInline) Type() NodeType {
|
func (b *BaseInline) Type() NodeType {
|
||||||
return TypeInline
|
return TypeInline
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRaw implements Node.IsRaw
|
// IsRaw implements Node.IsRaw.
|
||||||
func (b *BaseInline) IsRaw() bool {
|
func (b *BaseInline) IsRaw() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -33,12 +33,12 @@ func (b *BaseInline) SetBlankPreviousLines(v bool) {
|
||||||
panic("can not call with inline nodes.")
|
panic("can not call with inline nodes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lines implements Node.Lines
|
// Lines implements Node.Lines.
|
||||||
func (b *BaseInline) Lines() *textm.Segments {
|
func (b *BaseInline) Lines() *textm.Segments {
|
||||||
panic("can not call with inline nodes.")
|
panic("can not call with inline nodes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetLines implements Node.SetLines
|
// SetLines implements Node.SetLines.
|
||||||
func (b *BaseInline) SetLines(v *textm.Segments) {
|
func (b *BaseInline) SetLines(v *textm.Segments) {
|
||||||
panic("can not call with inline nodes.")
|
panic("can not call with inline nodes.")
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ func (n *Text) SetSoftLineBreak(v bool) {
|
||||||
if v {
|
if v {
|
||||||
n.flags |= textSoftLineBreak
|
n.flags |= textSoftLineBreak
|
||||||
} else {
|
} else {
|
||||||
n.flags = n.flags &^ textHardLineBreak
|
n.flags = n.flags &^ textSoftLineBreak
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,7 +132,8 @@ func (n *Text) Merge(node Node, source []byte) bool {
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if n.Segment.Stop != t.Segment.Start || t.Segment.Padding != 0 || source[n.Segment.Stop-1] == '\n' || t.IsRaw() != n.IsRaw() {
|
if n.Segment.Stop != t.Segment.Start || t.Segment.Padding != 0 ||
|
||||||
|
source[n.Segment.Stop-1] == '\n' || t.IsRaw() != n.IsRaw() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
n.Segment.Stop = t.Segment.Stop
|
n.Segment.Stop = t.Segment.Stop
|
||||||
|
|
@ -142,17 +143,25 @@ func (n *Text) Merge(node Node, source []byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text implements Node.Text.
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. Text.Value).
|
||||||
func (n *Text) Text(source []byte) []byte {
|
func (n *Text) Text(source []byte) []byte {
|
||||||
return n.Segment.Value(source)
|
return n.Segment.Value(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns a value of this node.
|
||||||
|
// SoftLineBreaks are not included in the returned value.
|
||||||
|
func (n *Text) Value(source []byte) []byte {
|
||||||
|
return n.Segment.Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump.
|
// Dump implements Node.Dump.
|
||||||
func (n *Text) Dump(source []byte, level int) {
|
func (n *Text) Dump(source []byte, level int) {
|
||||||
fs := textFlagsString(n.flags)
|
fs := textFlagsString(n.flags)
|
||||||
if len(fs) != 0 {
|
if len(fs) != 0 {
|
||||||
fs = "(" + fs + ")"
|
fs = "(" + fs + ")"
|
||||||
}
|
}
|
||||||
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Text(source)), "\n"))
|
fmt.Printf("%sText%s: \"%s\"\n", strings.Repeat(" ", level), fs, strings.TrimRight(string(n.Value(source)), "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// KindText is a NodeKind of the Text node.
|
// KindText is a NodeKind of the Text node.
|
||||||
|
|
@ -214,7 +223,7 @@ func MergeOrReplaceTextSegment(parent Node, n Node, s textm.Segment) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A String struct is a textual content that has a concrete value
|
// A String struct is a textual content that has a concrete value.
|
||||||
type String struct {
|
type String struct {
|
||||||
BaseInline
|
BaseInline
|
||||||
|
|
||||||
|
|
@ -257,6 +266,8 @@ func (n *String) SetCode(v bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text implements Node.Text.
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. String.Value).
|
||||||
func (n *String) Text(source []byte) []byte {
|
func (n *String) Text(source []byte) []byte {
|
||||||
return n.Value
|
return n.Value
|
||||||
}
|
}
|
||||||
|
|
@ -305,7 +316,7 @@ func (n *CodeSpan) IsBlank(source []byte) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump
|
// Dump implements Node.Dump.
|
||||||
func (n *CodeSpan) Dump(source []byte, level int) {
|
func (n *CodeSpan) Dump(source []byte, level int) {
|
||||||
DumpHelper(n, source, level, nil, nil)
|
DumpHelper(n, source, level, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
@ -467,7 +478,7 @@ type AutoLink struct {
|
||||||
// Inline implements Inline.Inline.
|
// Inline implements Inline.Inline.
|
||||||
func (n *AutoLink) Inline() {}
|
func (n *AutoLink) Inline() {}
|
||||||
|
|
||||||
// Dump implements Node.Dump
|
// Dump implements Node.Dump.
|
||||||
func (n *AutoLink) Dump(source []byte, level int) {
|
func (n *AutoLink) Dump(source []byte, level int) {
|
||||||
segment := n.value.Segment
|
segment := n.value.Segment
|
||||||
m := map[string]string{
|
m := map[string]string{
|
||||||
|
|
@ -491,15 +502,22 @@ func (n *AutoLink) URL(source []byte) []byte {
|
||||||
ret := make([]byte, 0, len(n.Protocol)+s.Len()+3)
|
ret := make([]byte, 0, len(n.Protocol)+s.Len()+3)
|
||||||
ret = append(ret, n.Protocol...)
|
ret = append(ret, n.Protocol...)
|
||||||
ret = append(ret, ':', '/', '/')
|
ret = append(ret, ':', '/', '/')
|
||||||
ret = append(ret, n.value.Text(source)...)
|
ret = append(ret, n.value.Value(source)...)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
return n.value.Text(source)
|
return n.value.Value(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label returns a label of this node.
|
// Label returns a label of this node.
|
||||||
func (n *AutoLink) Label(source []byte) []byte {
|
func (n *AutoLink) Label(source []byte) []byte {
|
||||||
return n.value.Text(source)
|
return n.value.Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. AutoLink.Label).
|
||||||
|
func (n *AutoLink) Text(source []byte) []byte {
|
||||||
|
return n.value.Value(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAutoLink returns a new AutoLink node.
|
// NewAutoLink returns a new AutoLink node.
|
||||||
|
|
@ -540,6 +558,13 @@ func (n *RawHTML) Kind() NodeKind {
|
||||||
return KindRawHTML
|
return KindRawHTML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Text implements Node.Text.
|
||||||
|
//
|
||||||
|
// Deprecated: Use other properties of the node to get the text value(i.e. RawHTML.Segments).
|
||||||
|
func (n *RawHTML) Text(source []byte) []byte {
|
||||||
|
return n.Segments.Value(source)
|
||||||
|
}
|
||||||
|
|
||||||
// NewRawHTML returns a new RawHTML node.
|
// NewRawHTML returns a new RawHTML node.
|
||||||
func NewRawHTML() *RawHTML {
|
func NewRawHTML() *RawHTML {
|
||||||
return &RawHTML{
|
return &RawHTML{
|
||||||
|
|
|
||||||
204
ast_test.go
Normal file
204
ast_test.go
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
package goldmark_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/testutil"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestASTBlockNodeText(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
Name string
|
||||||
|
Source string
|
||||||
|
T1 string
|
||||||
|
T2 string
|
||||||
|
C bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "AtxHeading",
|
||||||
|
Source: `# l1
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
# l2`,
|
||||||
|
T1: `l1`,
|
||||||
|
T2: `l2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "SetextHeading",
|
||||||
|
Source: `l1
|
||||||
|
l2
|
||||||
|
===============
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
l3
|
||||||
|
l4
|
||||||
|
==============`,
|
||||||
|
T1: `l1
|
||||||
|
l2`,
|
||||||
|
T2: `l3
|
||||||
|
l4`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "CodeBlock",
|
||||||
|
Source: ` l1
|
||||||
|
l2
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
l3
|
||||||
|
l4`,
|
||||||
|
T1: `l1
|
||||||
|
l2
|
||||||
|
`,
|
||||||
|
T2: `l3
|
||||||
|
l4
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "FencedCodeBlock",
|
||||||
|
Source: "```" + `
|
||||||
|
l1
|
||||||
|
l2
|
||||||
|
` + "```" + `
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
` + "```" + `
|
||||||
|
l3
|
||||||
|
l4`,
|
||||||
|
T1: `l1
|
||||||
|
l2
|
||||||
|
`,
|
||||||
|
T2: `l3
|
||||||
|
l4
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Blockquote",
|
||||||
|
Source: `> l1
|
||||||
|
> l2
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
> l3
|
||||||
|
> l4`,
|
||||||
|
T1: `l1
|
||||||
|
l2`,
|
||||||
|
T2: `l3
|
||||||
|
l4`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "List",
|
||||||
|
Source: `- l1
|
||||||
|
l2
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
- l3
|
||||||
|
l4`,
|
||||||
|
T1: `l1
|
||||||
|
l2`,
|
||||||
|
T2: `l3
|
||||||
|
l4`,
|
||||||
|
C: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "HTMLBlock",
|
||||||
|
Source: `<div>
|
||||||
|
l1
|
||||||
|
l2
|
||||||
|
</div>
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
<div>
|
||||||
|
l3
|
||||||
|
l4`,
|
||||||
|
T1: `<div>
|
||||||
|
l1
|
||||||
|
l2
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
T2: `<div>
|
||||||
|
l3
|
||||||
|
l4`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range cases {
|
||||||
|
t.Run(cs.Name, func(t *testing.T) {
|
||||||
|
s := []byte(cs.Source)
|
||||||
|
md := New()
|
||||||
|
n := md.Parser().Parse(text.NewReader(s))
|
||||||
|
c1 := n.FirstChild()
|
||||||
|
c2 := c1.NextSibling().NextSibling()
|
||||||
|
if cs.C {
|
||||||
|
c1 = c1.FirstChild()
|
||||||
|
c2 = c2.FirstChild()
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||||
|
|
||||||
|
t.Errorf("%s unmatch: %s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||||
|
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c2.Text(s), []byte(cs.T2)) { // nolint: staticcheck
|
||||||
|
|
||||||
|
t.Errorf("%s(EOF) unmatch: %s", cs.Name, testutil.DiffPretty(c2.Text(s), []byte(cs.T2))) // nolint: staticcheck
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestASTInlineNodeText(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
Name string
|
||||||
|
Source string
|
||||||
|
T1 string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "CodeSpan",
|
||||||
|
Source: "`c1`",
|
||||||
|
T1: `c1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Emphasis",
|
||||||
|
Source: `*c1 **c2***`,
|
||||||
|
T1: `c1 c2`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Link",
|
||||||
|
Source: `[label](url)`,
|
||||||
|
T1: `label`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "AutoLink",
|
||||||
|
Source: `<http://url>`,
|
||||||
|
T1: `http://url`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "RawHTML",
|
||||||
|
Source: `<span>c1</span>`,
|
||||||
|
T1: `<span>`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range cases {
|
||||||
|
t.Run(cs.Name, func(t *testing.T) {
|
||||||
|
s := []byte(cs.Source)
|
||||||
|
md := New()
|
||||||
|
n := md.Parser().Parse(text.NewReader(s))
|
||||||
|
c1 := n.FirstChild().FirstChild()
|
||||||
|
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||||
|
t.Errorf("%s unmatch:\n%s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package goldmark_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yuin/goldmark"
|
. "github.com/yuin/goldmark"
|
||||||
|
|
@ -20,7 +20,7 @@ type commonmarkSpecTestCase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSpec(t *testing.T) {
|
func TestSpec(t *testing.T) {
|
||||||
bs, err := ioutil.ReadFile("_test/spec.json")
|
bs, err := os.ReadFile("_test/spec.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,8 @@ on two lines.</p>
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<dl>
|
<dl>
|
||||||
<dt>0</dt>
|
<dt>0</dt>
|
||||||
<dd><pre><code> 0</code></pre>
|
<dd><pre><code> 0
|
||||||
|
</code></pre>
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@
|
||||||
<p><del>Hi</del> Hello, world!</p>
|
<p><del>Hi</del> Hello, world!</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2
|
2
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
This ~~has a
|
This ~~has a
|
||||||
|
|
@ -16,3 +14,26 @@ new paragraph~~.
|
||||||
<p>This ~~has a</p>
|
<p>This ~~has a</p>
|
||||||
<p>new paragraph~~.</p>
|
<p>new paragraph~~.</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
3
|
||||||
|
//- - - - - - - - -//
|
||||||
|
~Hi~ Hello, world!
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><del>Hi</del> Hello, world!</p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
4: Three or more tildes do not create a strikethrough
|
||||||
|
//- - - - - - - - -//
|
||||||
|
This will ~~~not~~~ strike.
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p>This will ~~~not~~~ strike.</p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
5: Leading three or more tildes do not create a strikethrough, create a code block
|
||||||
|
//- - - - - - - - -//
|
||||||
|
~~~Hi~~~ Hello, world!
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<pre><code class="language-Hi~~~"></code></pre>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -253,3 +253,30 @@ foo|bar
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
12: A delimiter can not start with more than 3 spaces
|
||||||
|
//- - - - - - - - -//
|
||||||
|
Foo
|
||||||
|
---
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p>Foo
|
||||||
|
---</p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
13: A delimiter can not start with more than 3 spaces(w/ tabs)
|
||||||
|
OPTIONS: {"enableEscape": true}
|
||||||
|
//- - - - - - - - -//
|
||||||
|
- aaa
|
||||||
|
|
||||||
|
Foo
|
||||||
|
\t\t---
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<p>aaa</p>
|
||||||
|
<p>Foo
|
||||||
|
---</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,3 +28,24 @@
|
||||||
<li><input disabled="" type="checkbox"> bim</li>
|
<li><input disabled="" type="checkbox"> bim</li>
|
||||||
</ul>
|
</ul>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3
|
||||||
|
//- - - - - - - - -//
|
||||||
|
- test[x]=[x]
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<ul>
|
||||||
|
<li>test[x]=[x]</li>
|
||||||
|
</ul>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
|
||||||
|
4
|
||||||
|
//- - - - - - - - -//
|
||||||
|
+ [x] [x]
|
||||||
|
//- - - - - - - - -//
|
||||||
|
<ul>
|
||||||
|
<li><input checked="" disabled="" type="checkbox"> [x]</li>
|
||||||
|
</ul>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -134,3 +134,10 @@ We're talking about the internet --- 'net for short. Let's rock 'n roll!
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<p>We’re talking about the internet — ’net for short. Let’s rock ’n roll!</p>
|
<p>We’re talking about the internet — ’net for short. Let’s rock ’n roll!</p>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
||||||
|
19: Quotes in alt text
|
||||||
|
//- - - - - - - - -//
|
||||||
|

|
||||||
|
//- - - - - - - - -//
|
||||||
|
<p><img src="https://example.com/image.jpg" alt="Nice & day, isn’t it?"></p>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ type Footnote struct {
|
||||||
func (n *Footnote) Dump(source []byte, level int) {
|
func (n *Footnote) Dump(source []byte, level int) {
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
m["Index"] = fmt.Sprintf("%v", n.Index)
|
m["Index"] = fmt.Sprintf("%v", n.Index)
|
||||||
m["Ref"] = fmt.Sprintf("%s", n.Ref)
|
m["Ref"] = string(n.Ref)
|
||||||
gast.DumpHelper(n, source, level, m, nil)
|
gast.DumpHelper(n, source, level, m, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ package ast
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
gast "github.com/yuin/goldmark/ast"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
gast "github.com/yuin/goldmark/ast"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Alignment is a text alignment of table cells.
|
// Alignment is a text alignment of table cells.
|
||||||
|
|
@ -45,7 +46,7 @@ type Table struct {
|
||||||
Alignments []Alignment
|
Alignments []Alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dump implements Node.Dump
|
// Dump implements Node.Dump.
|
||||||
func (n *Table) Dump(source []byte, level int) {
|
func (n *Table) Dump(source []byte, level int) {
|
||||||
gast.DumpHelper(n, source, level, nil, func(level int) {
|
gast.DumpHelper(n, source, level, nil, func(level int) {
|
||||||
indent := strings.Repeat(" ", level)
|
indent := strings.Repeat(" ", level)
|
||||||
|
|
@ -97,7 +98,7 @@ func (n *TableRow) Kind() gast.NodeKind {
|
||||||
|
|
||||||
// NewTableRow returns a new TableRow node.
|
// NewTableRow returns a new TableRow node.
|
||||||
func NewTableRow(alignments []Alignment) *TableRow {
|
func NewTableRow(alignments []Alignment) *TableRow {
|
||||||
return &TableRow{}
|
return &TableRow{Alignments: alignments}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A TableHeader struct represents a table header of Markdown(GFM) text.
|
// A TableHeader struct represents a table header of Markdown(GFM) text.
|
||||||
|
|
|
||||||
123
extension/ast_test.go
Normal file
123
extension/ast_test.go
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package extension
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/testutil"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestASTBlockNodeText(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
Name string
|
||||||
|
Source string
|
||||||
|
T1 string
|
||||||
|
T2 string
|
||||||
|
C bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "DefinitionList",
|
||||||
|
Source: `c1
|
||||||
|
: c2
|
||||||
|
c3
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
c4
|
||||||
|
: c5
|
||||||
|
c6`,
|
||||||
|
T1: `c1c2
|
||||||
|
c3`,
|
||||||
|
T2: `c4c5
|
||||||
|
c6`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "Table",
|
||||||
|
Source: `| h1 | h2 |
|
||||||
|
| -- | -- |
|
||||||
|
| c1 | c2 |
|
||||||
|
|
||||||
|
a
|
||||||
|
|
||||||
|
|
||||||
|
| h3 | h4 |
|
||||||
|
| -- | -- |
|
||||||
|
| c3 | c4 |`,
|
||||||
|
|
||||||
|
T1: `h1h2c1c2`,
|
||||||
|
T2: `h3h4c3c4`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range cases {
|
||||||
|
t.Run(cs.Name, func(t *testing.T) {
|
||||||
|
s := []byte(cs.Source)
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
DefinitionList,
|
||||||
|
Table,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
n := md.Parser().Parse(text.NewReader(s))
|
||||||
|
c1 := n.FirstChild()
|
||||||
|
c2 := c1.NextSibling().NextSibling()
|
||||||
|
if cs.C {
|
||||||
|
c1 = c1.FirstChild()
|
||||||
|
c2 = c2.FirstChild()
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||||
|
|
||||||
|
t.Errorf("%s unmatch:\n%s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||||
|
|
||||||
|
}
|
||||||
|
if !bytes.Equal(c2.Text(s), []byte(cs.T2)) { // nolint: staticcheck
|
||||||
|
|
||||||
|
t.Errorf("%s(EOF) unmatch: %s", cs.Name, testutil.DiffPretty(c2.Text(s), []byte(cs.T2))) // nolint: staticcheck
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestASTInlineNodeText(t *testing.T) {
|
||||||
|
var cases = []struct {
|
||||||
|
Name string
|
||||||
|
Source string
|
||||||
|
T1 string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Name: "Strikethrough",
|
||||||
|
Source: `~c1 *c2*~`,
|
||||||
|
T1: `c1 c2`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cs := range cases {
|
||||||
|
t.Run(cs.Name, func(t *testing.T) {
|
||||||
|
s := []byte(cs.Source)
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
Strikethrough,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
n := md.Parser().Parse(text.NewReader(s))
|
||||||
|
c1 := n.FirstChild().FirstChild()
|
||||||
|
if !bytes.Equal(c1.Text(s), []byte(cs.T1)) { // nolint: staticcheck
|
||||||
|
|
||||||
|
t.Errorf("%s unmatch:\n%s", cs.Name, testutil.DiffPretty(c1.Text(s), []byte(cs.T1))) // nolint: staticcheck
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
72
extension/cjk.go
Normal file
72
extension/cjk.go
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
package extension
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A CJKOption sets options for CJK support mostly for HTML based renderers.
|
||||||
|
type CJKOption func(*cjk)
|
||||||
|
|
||||||
|
// A EastAsianLineBreaks is a style of east asian line breaks.
|
||||||
|
type EastAsianLineBreaks int
|
||||||
|
|
||||||
|
const (
|
||||||
|
//EastAsianLineBreaksNone renders line breaks as it is.
|
||||||
|
EastAsianLineBreaksNone EastAsianLineBreaks = iota
|
||||||
|
// EastAsianLineBreaksSimple is a style where soft line breaks are ignored
|
||||||
|
// if both sides of the break are east asian wide characters.
|
||||||
|
EastAsianLineBreaksSimple
|
||||||
|
// EastAsianLineBreaksCSS3Draft is a style where soft line breaks are ignored
|
||||||
|
// even if only one side of the break is an east asian wide character.
|
||||||
|
EastAsianLineBreaksCSS3Draft
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
|
||||||
|
// between east asian wide characters should be ignored.
|
||||||
|
// style defauts to [EastAsianLineBreaksSimple] .
|
||||||
|
func WithEastAsianLineBreaks(style ...EastAsianLineBreaks) CJKOption {
|
||||||
|
return func(c *cjk) {
|
||||||
|
if len(style) == 0 {
|
||||||
|
c.EastAsianLineBreaks = EastAsianLineBreaksSimple
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.EastAsianLineBreaks = style[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEscapedSpace is a functional option that indicates that a '\' escaped half-space(0x20) should not be rendered.
|
||||||
|
func WithEscapedSpace() CJKOption {
|
||||||
|
return func(c *cjk) {
|
||||||
|
c.EscapedSpace = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type cjk struct {
|
||||||
|
EastAsianLineBreaks EastAsianLineBreaks
|
||||||
|
EscapedSpace bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// CJK is a goldmark extension that provides functionalities for CJK languages.
|
||||||
|
var CJK = NewCJK(WithEastAsianLineBreaks(), WithEscapedSpace())
|
||||||
|
|
||||||
|
// NewCJK returns a new extension with given options.
|
||||||
|
func NewCJK(opts ...CJKOption) goldmark.Extender {
|
||||||
|
e := &cjk{
|
||||||
|
EastAsianLineBreaks: EastAsianLineBreaksNone,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(e)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *cjk) Extend(m goldmark.Markdown) {
|
||||||
|
m.Renderer().AddOptions(html.WithEastAsianLineBreaks(
|
||||||
|
html.EastAsianLineBreaks(e.EastAsianLineBreaks)))
|
||||||
|
if e.EscapedSpace {
|
||||||
|
m.Renderer().AddOptions(html.WithWriter(html.NewWriter(html.WithEscapedSpace())))
|
||||||
|
m.Parser().AddOptions(parser.WithEscapedSpace())
|
||||||
|
}
|
||||||
|
}
|
||||||
269
extension/cjk_test.go
Normal file
269
extension/cjk_test.go
Normal file
|
|
@ -0,0 +1,269 @@
|
||||||
|
package extension
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEscapedSpace(t *testing.T) {
|
||||||
|
markdown := goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
))
|
||||||
|
no := 1
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Without spaces around an emphasis started with east asian punctuations, it is not interpreted as an emphasis(as defined in CommonMark spec)",
|
||||||
|
Markdown: "太郎は**「こんにちわ」**と言った\nんです",
|
||||||
|
Expected: "<p>太郎は**「こんにちわ」**と言った\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 2
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "With spaces around an emphasis started with east asian punctuations, it is interpreted as an emphasis(but remains unnecessary spaces)",
|
||||||
|
Markdown: "太郎は **「こんにちわ」** と言った\nんです",
|
||||||
|
Expected: "<p>太郎は <strong>「こんにちわ」</strong> と言った\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enables EscapedSpace
|
||||||
|
markdown = goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(NewCJK(WithEscapedSpace())),
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 3
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "With spaces around an emphasis started with east asian punctuations,it is interpreted as an emphasis",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nんです",
|
||||||
|
Expected: "<p>太郎は<strong>「こんにちわ」</strong>と言った\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ' ' triggers Linkify extension inline parser.
|
||||||
|
// Escaped spaces should not trigger the inline parser.
|
||||||
|
|
||||||
|
markdown = goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
NewCJK(WithEscapedSpace()),
|
||||||
|
Linkify,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 4
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Escaped space and linkfy extension",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nんです",
|
||||||
|
Expected: "<p>太郎は<strong>「こんにちわ」</strong>と言った\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEastAsianLineBreaks(t *testing.T) {
|
||||||
|
markdown := goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
))
|
||||||
|
no := 1
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks are rendered as a newline, so some asian users will see it as an unnecessary space",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言った\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Enables EastAsianLineBreaks
|
||||||
|
|
||||||
|
markdown = goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(NewCJK(WithEastAsianLineBreaks())),
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 2
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between east asian wide characters are ignored",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言ったんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 3
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between western characters are rendered as a newline",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言ったa\nbんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言ったa\nbんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 4
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between a western character and an east asian wide character are rendered as a newline",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言ったa\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言ったa\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 5
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between an east asian wide character and a western character are rendered as a newline",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nbんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言った\nbんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithHardWraps take precedence over WithEastAsianLineBreaks
|
||||||
|
markdown = goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithHardWraps(),
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(NewCJK(WithEastAsianLineBreaks())),
|
||||||
|
)
|
||||||
|
no = 6
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "WithHardWraps take precedence over WithEastAsianLineBreaks",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言った<br />\nんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests with EastAsianLineBreaksStyleSimple
|
||||||
|
markdown = goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
NewCJK(WithEastAsianLineBreaks()),
|
||||||
|
Linkify,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
no = 7
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "WithEastAsianLineBreaks and linkfy extension",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\r\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言ったんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
no = 8
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between east asian wide characters or punctuations are ignored",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と、\r\n言った\r\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と、言ったんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
no = 9
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between an east asian wide character and a western character are ignored",
|
||||||
|
Markdown: "私はプログラマーです。\n東京の会社に勤めています。\nGoでWebアプリケーションを開発しています。",
|
||||||
|
Expected: "<p>私はプログラマーです。東京の会社に勤めています。\nGoでWebアプリケーションを開発しています。</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests with EastAsianLineBreaksCSS3Draft
|
||||||
|
markdown = goldmark.New(goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
NewCJK(WithEastAsianLineBreaks(EastAsianLineBreaksCSS3Draft)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
no = 10
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between a western character and an east asian wide character are ignored",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言ったa\nんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言ったaんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 11
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between an east asian wide character and a western character are ignored",
|
||||||
|
Markdown: "太郎は\\ **「こんにちわ」**\\ と言った\nbんです",
|
||||||
|
Expected: "<p>太郎は\\ <strong>「こんにちわ」</strong>\\ と言ったbんです</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
no = 12
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: no,
|
||||||
|
Description: "Soft line breaks between an east asian wide character and a western character are ignored",
|
||||||
|
Markdown: "私はプログラマーです。\n東京の会社に勤めています。\nGoでWebアプリケーションを開発しています。",
|
||||||
|
Expected: "<p>私はプログラマーです。東京の会社に勤めています。GoでWebアプリケーションを開発しています。</p>",
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -113,7 +113,8 @@ func (b *definitionDescriptionParser) Trigger() []byte {
|
||||||
return []byte{':'}
|
return []byte{':'}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
func (b *definitionDescriptionParser) Open(
|
||||||
|
parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
||||||
line, _ := reader.PeekLine()
|
line, _ := reader.PeekLine()
|
||||||
pos := pc.BlockOffset()
|
pos := pc.BlockOffset()
|
||||||
indent := pc.BlockIndent()
|
indent := pc.BlockIndent()
|
||||||
|
|
@ -199,7 +200,8 @@ func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFunc
|
||||||
// DefinitionListAttributeFilter defines attribute names which dl elements can have.
|
// DefinitionListAttributeFilter defines attribute names which dl elements can have.
|
||||||
var DefinitionListAttributeFilter = html.GlobalAttributeFilter
|
var DefinitionListAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionList(
|
||||||
|
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
_, _ = w.WriteString("<dl")
|
_, _ = w.WriteString("<dl")
|
||||||
|
|
@ -217,7 +219,8 @@ func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, sour
|
||||||
// DefinitionTermAttributeFilter defines attribute names which dd elements can have.
|
// DefinitionTermAttributeFilter defines attribute names which dd elements can have.
|
||||||
var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
|
var DefinitionTermAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(
|
||||||
|
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
_, _ = w.WriteString("<dt")
|
_, _ = w.WriteString("<dt")
|
||||||
|
|
@ -235,7 +238,8 @@ func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, sour
|
||||||
// DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
|
// DefinitionDescriptionAttributeFilter defines attribute names which dd elements can have.
|
||||||
var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
|
var DefinitionDescriptionAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.DefinitionDescription)
|
n := node.(*ast.DefinitionDescription)
|
||||||
_, _ = w.WriteString("<dd")
|
_, _ = w.WriteString("<dd")
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc pars
|
||||||
return nil, parser.NoChildren
|
return nil, parser.NoChildren
|
||||||
}
|
}
|
||||||
open := pos + 1
|
open := pos + 1
|
||||||
closes := 0
|
var closes int
|
||||||
closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
|
closure := util.FindClosure(line[pos+1:], '[', ']', false, false) //nolint:staticcheck
|
||||||
closes = pos + 1 + closure
|
closes = pos + 1 + closure
|
||||||
next := closes + 1
|
next := closes + 1
|
||||||
if closure > -1 {
|
if closure > -1 {
|
||||||
|
|
@ -136,7 +136,7 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
open := pos
|
open := pos
|
||||||
closure := util.FindClosure(line[pos:], '[', ']', false, false)
|
closure := util.FindClosure(line[pos:], '[', ']', false, false) //nolint:staticcheck
|
||||||
if closure < 0 {
|
if closure < 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +156,7 @@ func (s *footnoteParser) Parse(parent gast.Node, block text.Reader, pc parser.Co
|
||||||
d := def.(*ast.Footnote)
|
d := def.(*ast.Footnote)
|
||||||
if bytes.Equal(d.Ref, value) {
|
if bytes.Equal(d.Ref, value) {
|
||||||
if d.Index < 0 {
|
if d.Index < 0 {
|
||||||
list.Count += 1
|
list.Count++
|
||||||
d.Index = list.Count
|
d.Index = list.Count
|
||||||
}
|
}
|
||||||
index = d.Index
|
index = d.Index
|
||||||
|
|
@ -272,9 +272,9 @@ func (a *footnoteASTTransformer) Transform(node *gast.Document, reader text.Read
|
||||||
// FootnoteConfig holds configuration values for the footnote extension.
|
// FootnoteConfig holds configuration values for the footnote extension.
|
||||||
//
|
//
|
||||||
// Link* and Backlink* configurations have some variables:
|
// Link* and Backlink* configurations have some variables:
|
||||||
// Occurrances of “^^” in the string will be replaced by the
|
// Occurrences of “^^” in the string will be replaced by the
|
||||||
// corresponding footnote number in the HTML output.
|
// corresponding footnote number in the HTML output.
|
||||||
// Occurrances of “%%” will be replaced by a number for the
|
// Occurrences of “%%” will be replaced by a number for the
|
||||||
// reference (footnotes can have multiple references).
|
// reference (footnotes can have multiple references).
|
||||||
type FootnoteConfig struct {
|
type FootnoteConfig struct {
|
||||||
html.Config
|
html.Config
|
||||||
|
|
@ -382,8 +382,8 @@ func (o *withFootnoteIDPrefix) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
// WithFootnoteIDPrefix is a functional option that is a prefix for the id attributes generated by footnotes.
|
||||||
func WithFootnoteIDPrefix(a []byte) FootnoteOption {
|
func WithFootnoteIDPrefix[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteIDPrefix{a}
|
return &withFootnoteIDPrefix{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
const optFootnoteIDPrefixFunction renderer.OptionName = "FootnoteIDPrefixFunction"
|
||||||
|
|
@ -420,8 +420,8 @@ func (o *withFootnoteLinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
// WithFootnoteLinkTitle is a functional option that is an optional title attribute for footnote links.
|
||||||
func WithFootnoteLinkTitle(a []byte) FootnoteOption {
|
func WithFootnoteLinkTitle[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteLinkTitle{a}
|
return &withFootnoteLinkTitle{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
const optFootnoteBacklinkTitle renderer.OptionName = "FootnoteBacklinkTitle"
|
||||||
|
|
@ -439,8 +439,8 @@ func (o *withFootnoteBacklinkTitle) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
// WithFootnoteBacklinkTitle is a functional option that is an optional title attribute for footnote backlinks.
|
||||||
func WithFootnoteBacklinkTitle(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkTitle[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkTitle{a}
|
return &withFootnoteBacklinkTitle{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
const optFootnoteLinkClass renderer.OptionName = "FootnoteLinkClass"
|
||||||
|
|
@ -458,8 +458,8 @@ func (o *withFootnoteLinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
// WithFootnoteLinkClass is a functional option that is a class for footnote links.
|
||||||
func WithFootnoteLinkClass(a []byte) FootnoteOption {
|
func WithFootnoteLinkClass[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteLinkClass{a}
|
return &withFootnoteLinkClass{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
const optFootnoteBacklinkClass renderer.OptionName = "FootnoteBacklinkClass"
|
||||||
|
|
@ -477,8 +477,8 @@ func (o *withFootnoteBacklinkClass) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
// WithFootnoteBacklinkClass is a functional option that is a class for footnote backlinks.
|
||||||
func WithFootnoteBacklinkClass(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkClass[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkClass{a}
|
return &withFootnoteBacklinkClass{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
const optFootnoteBacklinkHTML renderer.OptionName = "FootnoteBacklinkHTML"
|
||||||
|
|
@ -496,8 +496,8 @@ func (o *withFootnoteBacklinkHTML) SetFootnoteOption(c *FootnoteConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
// WithFootnoteBacklinkHTML is an HTML content for footnote backlinks.
|
||||||
func WithFootnoteBacklinkHTML(a []byte) FootnoteOption {
|
func WithFootnoteBacklinkHTML[T []byte | string](a T) FootnoteOption {
|
||||||
return &withFootnoteBacklinkHTML{a}
|
return &withFootnoteBacklinkHTML{[]byte(a)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
// FootnoteHTMLRenderer is a renderer.NodeRenderer implementation that
|
||||||
|
|
@ -525,7 +525,8 @@ func (r *FootnoteHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegist
|
||||||
reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
|
reg.Register(ast.KindFootnoteList, r.renderFootnoteList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnoteLink(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.FootnoteLink)
|
n := node.(*ast.FootnoteLink)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
|
|
@ -556,7 +557,8 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.FootnoteBacklink)
|
n := node.(*ast.FootnoteBacklink)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
|
|
@ -581,7 +583,8 @@ func (r *FootnoteHTMLRenderer) renderFootnoteBacklink(w util.BufWriter, source [
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnote(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
n := node.(*ast.Footnote)
|
n := node.(*ast.Footnote)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
if entering {
|
if entering {
|
||||||
|
|
@ -600,7 +603,8 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *FootnoteHTMLRenderer) renderFootnoteList(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
|
_, _ = w.WriteString(`<div class="footnotes" role="doc-endnotes"`)
|
||||||
if node.Attributes() != nil {
|
if node.Attributes() != nil {
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,12 @@ func TestFootnoteOptions(t *testing.T) {
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewFootnote(
|
NewFootnote(
|
||||||
WithFootnoteIDPrefix([]byte("article12-")),
|
WithFootnoteIDPrefix("article12-"),
|
||||||
WithFootnoteLinkClass([]byte("link-class")),
|
WithFootnoteLinkClass("link-class"),
|
||||||
WithFootnoteBacklinkClass([]byte("backlink-class")),
|
WithFootnoteBacklinkClass("backlink-class"),
|
||||||
WithFootnoteLinkTitle([]byte("link-title-%%-^^")),
|
WithFootnoteLinkTitle("link-title-%%-^^"),
|
||||||
WithFootnoteBacklinkTitle([]byte("backlink-title")),
|
WithFootnoteBacklinkTitle("backlink-title"),
|
||||||
WithFootnoteBacklinkHTML([]byte("^")),
|
WithFootnoteBacklinkHTML("^"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ import (
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
var wwwURLRegxp = regexp.MustCompile(`^www\.[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?:[/#?][-a-zA-Z0-9@:%_\+.~#!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`) //nolint:golint,lll
|
||||||
|
|
||||||
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`)
|
var urlRegexp = regexp.MustCompile(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]+(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=\(\);,'">\^{}\[\]` + "`" + `]*)?`) //nolint:golint,lll
|
||||||
|
|
||||||
// An LinkifyConfig struct is a data structure that holds configuration of the
|
// An LinkifyConfig struct is a data structure that holds configuration of the
|
||||||
// Linkify extension.
|
// Linkify extension.
|
||||||
|
|
@ -66,10 +66,12 @@ func (o *withLinkifyAllowedProtocols) SetLinkifyOption(p *LinkifyConfig) {
|
||||||
// WithLinkifyAllowedProtocols is a functional option that specify allowed
|
// WithLinkifyAllowedProtocols is a functional option that specify allowed
|
||||||
// protocols in autolinks. Each protocol must end with ':' like
|
// protocols in autolinks. Each protocol must end with ':' like
|
||||||
// 'http:' .
|
// 'http:' .
|
||||||
func WithLinkifyAllowedProtocols(value [][]byte) LinkifyOption {
|
func WithLinkifyAllowedProtocols[T []byte | string](value []T) LinkifyOption {
|
||||||
return &withLinkifyAllowedProtocols{
|
opt := &withLinkifyAllowedProtocols{}
|
||||||
value: value,
|
for _, v := range value {
|
||||||
|
opt.value = append(opt.value, []byte(v))
|
||||||
}
|
}
|
||||||
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
type withLinkifyURLRegexp struct {
|
type withLinkifyURLRegexp struct {
|
||||||
|
|
@ -92,9 +94,6 @@ func WithLinkifyURLRegexp(value *regexp.Regexp) LinkifyOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLinkifyWWWRegexp is a functional option that specify
|
|
||||||
// a pattern of the URL without a protocol.
|
|
||||||
// This pattern must start with 'www.' .
|
|
||||||
type withLinkifyWWWRegexp struct {
|
type withLinkifyWWWRegexp struct {
|
||||||
value *regexp.Regexp
|
value *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
@ -107,14 +106,15 @@ func (o *withLinkifyWWWRegexp) SetLinkifyOption(p *LinkifyConfig) {
|
||||||
p.WWWRegexp = o.value
|
p.WWWRegexp = o.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithLinkifyWWWRegexp is a functional option that specify
|
||||||
|
// a pattern of the URL without a protocol.
|
||||||
|
// This pattern must start with 'www.' .
|
||||||
func WithLinkifyWWWRegexp(value *regexp.Regexp) LinkifyOption {
|
func WithLinkifyWWWRegexp(value *regexp.Regexp) LinkifyOption {
|
||||||
return &withLinkifyWWWRegexp{
|
return &withLinkifyWWWRegexp{
|
||||||
value: value,
|
value: value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithLinkifyWWWRegexp is a functional otpion that specify
|
|
||||||
// a pattern of the email address.
|
|
||||||
type withLinkifyEmailRegexp struct {
|
type withLinkifyEmailRegexp struct {
|
||||||
value *regexp.Regexp
|
value *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
@ -127,6 +127,8 @@ func (o *withLinkifyEmailRegexp) SetLinkifyOption(p *LinkifyConfig) {
|
||||||
p.EmailRegexp = o.value
|
p.EmailRegexp = o.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithLinkifyEmailRegexp is a functional otpion that specify
|
||||||
|
// a pattern of the email address.
|
||||||
func WithLinkifyEmailRegexp(value *regexp.Regexp) LinkifyOption {
|
func WithLinkifyEmailRegexp(value *regexp.Regexp) LinkifyOption {
|
||||||
return &withLinkifyEmailRegexp{
|
return &withLinkifyEmailRegexp{
|
||||||
value: value,
|
value: value,
|
||||||
|
|
@ -303,6 +305,8 @@ type linkify struct {
|
||||||
// Linkify is an extension that allow you to parse text that seems like a URL.
|
// Linkify is an extension that allow you to parse text that seems like a URL.
|
||||||
var Linkify = &linkify{}
|
var Linkify = &linkify{}
|
||||||
|
|
||||||
|
// NewLinkify creates a new [goldmark.Extender] that
|
||||||
|
// allow you to parse text that seems like a URL.
|
||||||
func NewLinkify(opts ...LinkifyOption) goldmark.Extender {
|
func NewLinkify(opts ...LinkifyOption) goldmark.Extender {
|
||||||
return &linkify{
|
return &linkify{
|
||||||
options: opts,
|
options: opts,
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,8 @@ func TestLinkifyWithAllowedProtocols(t *testing.T) {
|
||||||
),
|
),
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
NewLinkify(
|
NewLinkify(
|
||||||
WithLinkifyAllowedProtocols([][]byte{
|
WithLinkifyAllowedProtocols([]string{
|
||||||
[]byte("ssh:"),
|
"ssh:",
|
||||||
}),
|
}),
|
||||||
WithLinkifyURLRegexp(
|
WithLinkifyURLRegexp(
|
||||||
regexp.MustCompile(`\w+://[^\s]+`),
|
regexp.MustCompile(`\w+://[^\s]+`),
|
||||||
|
|
|
||||||
2
extension/package.go
Normal file
2
extension/package.go
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package extension is a collection of builtin extensions.
|
||||||
|
package extension
|
||||||
|
|
@ -46,10 +46,11 @@ func (s *strikethroughParser) Trigger() []byte {
|
||||||
func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
func (s *strikethroughParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||||
before := block.PrecendingCharacter()
|
before := block.PrecendingCharacter()
|
||||||
line, segment := block.PeekLine()
|
line, segment := block.PeekLine()
|
||||||
node := parser.ScanDelimiter(line, before, 2, defaultStrikethroughDelimiterProcessor)
|
node := parser.ScanDelimiter(line, before, 1, defaultStrikethroughDelimiterProcessor)
|
||||||
if node == nil {
|
if node == nil || node.OriginalLength > 2 || before == '~' {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
|
node.Segment = segment.WithStop(segment.Start + node.OriginalLength)
|
||||||
block.Advance(node.OriginalLength)
|
block.Advance(node.OriginalLength)
|
||||||
pc.PushDelimiter(node)
|
pc.PushDelimiter(node)
|
||||||
|
|
@ -85,7 +86,8 @@ func (r *StrikethroughHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncR
|
||||||
// StrikethroughAttributeFilter defines attribute names which dd elements can have.
|
// StrikethroughAttributeFilter defines attribute names which dd elements can have.
|
||||||
var StrikethroughAttributeFilter = html.GlobalAttributeFilter
|
var StrikethroughAttributeFilter = html.GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *StrikethroughHTMLRenderer) renderStrikethrough(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *StrikethroughHTMLRenderer) renderStrikethrough(
|
||||||
|
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
_, _ = w.WriteString("<del")
|
_, _ = w.WriteString("<del")
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ type escapedPipeCell struct {
|
||||||
Transformed bool
|
Transformed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableCellAlignMethod indicates how are table cells aligned in HTML format.indicates how are table cells aligned in HTML format.
|
// TableCellAlignMethod indicates how are table cells aligned in HTML format.
|
||||||
type TableCellAlignMethod int
|
type TableCellAlignMethod int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
@ -122,6 +122,9 @@ func WithTableCellAlignMethod(a TableCellAlignMethod) TableOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
func isTableDelim(bs []byte) bool {
|
func isTableDelim(bs []byte) bool {
|
||||||
|
if w, _ := util.IndentWidth(bs, 0); w > 3 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
for _, b := range bs {
|
for _, b := range bs {
|
||||||
if !(util.IsSpace(b) || b == '-' || b == '|' || b == ':') {
|
if !(util.IsSpace(b) || b == '-' || b == '|' || b == ':') {
|
||||||
return false
|
return false
|
||||||
|
|
@ -178,13 +181,14 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
func (b *tableParagraphTransformer) parseRow(segment text.Segment,
|
||||||
|
alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
||||||
source := reader.Source()
|
source := reader.Source()
|
||||||
|
segment = segment.TrimLeftSpace(source)
|
||||||
|
segment = segment.TrimRightSpace(source)
|
||||||
line := segment.Value(source)
|
line := segment.Value(source)
|
||||||
pos := 0
|
pos := 0
|
||||||
pos += util.TrimLeftSpaceLength(line)
|
|
||||||
limit := len(line)
|
limit := len(line)
|
||||||
limit -= util.TrimRightSpaceLength(line)
|
|
||||||
row := ast.NewTableRow(alignments)
|
row := ast.NewTableRow(alignments)
|
||||||
if len(line) > 0 && line[pos] == '|' {
|
if len(line) > 0 && line[pos] == '|' {
|
||||||
pos++
|
pos++
|
||||||
|
|
@ -243,6 +247,7 @@ func (b *tableParagraphTransformer) parseRow(segment text.Segment, alignments []
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment {
|
func (b *tableParagraphTransformer) parseDelimiter(segment text.Segment, reader text.Reader) []ast.Alignment {
|
||||||
|
|
||||||
line := segment.Value(reader.Source())
|
line := segment.Value(reader.Source())
|
||||||
if !isTableDelim(line) {
|
if !isTableDelim(line) {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -365,7 +370,8 @@ var TableAttributeFilter = html.GlobalAttributeFilter.Extend(
|
||||||
[]byte("width"), // [Deprecated]
|
[]byte("width"), // [Deprecated]
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTable(
|
||||||
|
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<table")
|
_, _ = w.WriteString("<table")
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
|
|
@ -387,7 +393,8 @@ var TableHeaderAttributeFilter = html.GlobalAttributeFilter.Extend(
|
||||||
[]byte("valign"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
[]byte("valign"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTableHeader(
|
||||||
|
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<thead")
|
_, _ = w.WriteString("<thead")
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
|
|
@ -414,7 +421,8 @@ var TableRowAttributeFilter = html.GlobalAttributeFilter.Extend(
|
||||||
[]byte("valign"), // [Obsolete since HTML5]
|
[]byte("valign"), // [Obsolete since HTML5]
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTableRow(
|
||||||
|
w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<tr")
|
_, _ = w.WriteString("<tr")
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
|
|
@ -441,12 +449,14 @@ var TableThCellAttributeFilter = html.GlobalAttributeFilter.Extend(
|
||||||
[]byte("charoff"), // [Obsolete since HTML5]
|
[]byte("charoff"), // [Obsolete since HTML5]
|
||||||
|
|
||||||
[]byte("colspan"), // [OK] Number of columns that the cell is to span
|
[]byte("colspan"), // [OK] Number of columns that the cell is to span
|
||||||
[]byte("headers"), // [OK] This attribute contains a list of space-separated strings, each corresponding to the id attribute of the <th> elements that apply to this element
|
[]byte("headers"), // [OK] This attribute contains a list of space-separated
|
||||||
|
// strings, each corresponding to the id attribute of the <th> elements that apply to this element
|
||||||
|
|
||||||
[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
||||||
|
|
||||||
[]byte("rowspan"), // [OK] Number of rows that the cell is to span
|
[]byte("rowspan"), // [OK] Number of rows that the cell is to span
|
||||||
[]byte("scope"), // [OK] This enumerated attribute defines the cells that the header (defined in the <th>) element relates to [NOT OK in <td>]
|
[]byte("scope"), // [OK] This enumerated attribute defines the cells that
|
||||||
|
// the header (defined in the <th>) element relates to [NOT OK in <td>]
|
||||||
|
|
||||||
[]byte("valign"), // [Obsolete since HTML5]
|
[]byte("valign"), // [Obsolete since HTML5]
|
||||||
[]byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
[]byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
||||||
|
|
@ -462,7 +472,8 @@ var TableTdCellAttributeFilter = html.GlobalAttributeFilter.Extend(
|
||||||
[]byte("charoff"), // [Obsolete since HTML5]
|
[]byte("charoff"), // [Obsolete since HTML5]
|
||||||
|
|
||||||
[]byte("colspan"), // [OK] Number of columns that the cell is to span
|
[]byte("colspan"), // [OK] Number of columns that the cell is to span
|
||||||
[]byte("headers"), // [OK] This attribute contains a list of space-separated strings, each corresponding to the id attribute of the <th> elements that apply to this element
|
[]byte("headers"), // [OK] This attribute contains a list of space-separated
|
||||||
|
// strings, each corresponding to the id attribute of the <th> elements that apply to this element
|
||||||
|
|
||||||
[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
[]byte("height"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
||||||
|
|
||||||
|
|
@ -473,14 +484,15 @@ var TableTdCellAttributeFilter = html.GlobalAttributeFilter.Extend(
|
||||||
[]byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
[]byte("width"), // [Deprecated since HTML4] [Obsolete since HTML5]
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTableCell(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
n := node.(*ast.TableCell)
|
n := node.(*ast.TableCell)
|
||||||
tag := "td"
|
tag := "td"
|
||||||
if n.Parent().Kind() == ast.KindTableHeader {
|
if n.Parent().Kind() == ast.KindTableHeader {
|
||||||
tag = "th"
|
tag = "th"
|
||||||
}
|
}
|
||||||
if entering {
|
if entering {
|
||||||
fmt.Fprintf(w, "<%s", tag)
|
_, _ = fmt.Fprintf(w, "<%s", tag)
|
||||||
if n.Alignment != ast.AlignNone {
|
if n.Alignment != ast.AlignNone {
|
||||||
amethod := r.TableConfig.TableCellAlignMethod
|
amethod := r.TableConfig.TableCellAlignMethod
|
||||||
if amethod == TableCellAlignDefault {
|
if amethod == TableCellAlignDefault {
|
||||||
|
|
@ -493,7 +505,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
|
||||||
switch amethod {
|
switch amethod {
|
||||||
case TableCellAlignAttribute:
|
case TableCellAlignAttribute:
|
||||||
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
|
if _, ok := n.AttributeString("align"); !ok { // Skip align render if overridden
|
||||||
fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
|
_, _ = fmt.Fprintf(w, ` align="%s"`, n.Alignment.String())
|
||||||
}
|
}
|
||||||
case TableCellAlignStyle:
|
case TableCellAlignStyle:
|
||||||
v, ok := n.AttributeString("style")
|
v, ok := n.AttributeString("style")
|
||||||
|
|
@ -516,7 +528,7 @@ func (r *TableHTMLRenderer) renderTableCell(w util.BufWriter, source []byte, nod
|
||||||
}
|
}
|
||||||
_ = w.WriteByte('>')
|
_ = w.WriteByte('>')
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "</%s>\n", tag)
|
_, _ = fmt.Fprintf(w, "</%s>\n", tag)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -355,3 +355,40 @@ bar | baz
|
||||||
t,
|
t,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTableFuzzedPanics(t *testing.T) {
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
NewTable(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
testutil.DoTestCase(
|
||||||
|
markdown,
|
||||||
|
testutil.MarkdownTestCase{
|
||||||
|
No: 1,
|
||||||
|
Description: "This should not panic",
|
||||||
|
Markdown: "* 0\n-|\n\t0",
|
||||||
|
Expected: `<ul>
|
||||||
|
<li>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>0</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>0</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</li>
|
||||||
|
</ul>`,
|
||||||
|
},
|
||||||
|
t,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package extension
|
package extension
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
gast "github.com/yuin/goldmark/ast"
|
gast "github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/extension/ast"
|
"github.com/yuin/goldmark/extension/ast"
|
||||||
|
|
@ -9,7 +11,6 @@ import (
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
"regexp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var taskListRegexp = regexp.MustCompile(`^\[([\sxX])\]\s*`)
|
var taskListRegexp = regexp.MustCompile(`^\[([\sxX])\]\s*`)
|
||||||
|
|
@ -40,6 +41,9 @@ func (s *taskCheckBoxParser) Parse(parent gast.Node, block text.Reader, pc parse
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if parent.HasChildren() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
if _, ok := parent.Parent().(*gast.ListItem); !ok {
|
if _, ok := parent.Parent().(*gast.ListItem); !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -80,21 +84,22 @@ func (r *TaskCheckBoxHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRe
|
||||||
reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
|
reg.Register(ast.KindTaskCheckBox, r.renderTaskCheckBox)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TaskCheckBoxHTMLRenderer) renderTaskCheckBox(
|
||||||
|
w util.BufWriter, source []byte, node gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
n := node.(*ast.TaskCheckBox)
|
n := node.(*ast.TaskCheckBox)
|
||||||
|
|
||||||
if n.IsChecked {
|
if n.IsChecked {
|
||||||
w.WriteString(`<input checked="" disabled="" type="checkbox"`)
|
_, _ = w.WriteString(`<input checked="" disabled="" type="checkbox"`)
|
||||||
} else {
|
} else {
|
||||||
w.WriteString(`<input disabled="" type="checkbox"`)
|
_, _ = w.WriteString(`<input disabled="" type="checkbox"`)
|
||||||
}
|
}
|
||||||
if r.XHTML {
|
if r.XHTML {
|
||||||
w.WriteString(" /> ")
|
_, _ = w.WriteString(" /> ")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("> ")
|
_, _ = w.WriteString("> ")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,25 +36,25 @@ func getUnclosedCounter(pc parser.Context) *unclosedCounter {
|
||||||
type TypographicPunctuation int
|
type TypographicPunctuation int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// LeftSingleQuote is '
|
// LeftSingleQuote is ' .
|
||||||
LeftSingleQuote TypographicPunctuation = iota + 1
|
LeftSingleQuote TypographicPunctuation = iota + 1
|
||||||
// RightSingleQuote is '
|
// RightSingleQuote is ' .
|
||||||
RightSingleQuote
|
RightSingleQuote
|
||||||
// LeftDoubleQuote is "
|
// LeftDoubleQuote is " .
|
||||||
LeftDoubleQuote
|
LeftDoubleQuote
|
||||||
// RightDoubleQuote is "
|
// RightDoubleQuote is " .
|
||||||
RightDoubleQuote
|
RightDoubleQuote
|
||||||
// EnDash is --
|
// EnDash is -- .
|
||||||
EnDash
|
EnDash
|
||||||
// EmDash is ---
|
// EmDash is --- .
|
||||||
EmDash
|
EmDash
|
||||||
// Ellipsis is ...
|
// Ellipsis is ... .
|
||||||
Ellipsis
|
Ellipsis
|
||||||
// LeftAngleQuote is <<
|
// LeftAngleQuote is << .
|
||||||
LeftAngleQuote
|
LeftAngleQuote
|
||||||
// RightAngleQuote is >>
|
// RightAngleQuote is >> .
|
||||||
RightAngleQuote
|
RightAngleQuote
|
||||||
// Apostrophe is '
|
// Apostrophe is ' .
|
||||||
Apostrophe
|
Apostrophe
|
||||||
|
|
||||||
typographicPunctuationMax
|
typographicPunctuationMax
|
||||||
|
|
@ -115,10 +115,10 @@ func (o *withTypographicSubstitutions) SetTypographerOption(p *TypographerConfig
|
||||||
|
|
||||||
// WithTypographicSubstitutions is a functional otpion that specify replacement text
|
// WithTypographicSubstitutions is a functional otpion that specify replacement text
|
||||||
// for punctuations.
|
// for punctuations.
|
||||||
func WithTypographicSubstitutions(values map[TypographicPunctuation][]byte) TypographerOption {
|
func WithTypographicSubstitutions[T []byte | string](values map[TypographicPunctuation]T) TypographerOption {
|
||||||
replacements := newDefaultSubstitutions()
|
replacements := newDefaultSubstitutions()
|
||||||
for k, v := range values {
|
for k, v := range values {
|
||||||
replacements[k] = v
|
replacements[k] = []byte(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &withTypographicSubstitutions{replacements}
|
return &withTypographicSubstitutions{replacements}
|
||||||
|
|
@ -218,7 +218,8 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
if c == '\'' {
|
if c == '\'' {
|
||||||
if s.Substitutions[Apostrophe] != nil {
|
if s.Substitutions[Apostrophe] != nil {
|
||||||
// Handle decade abbrevations such as '90s
|
// Handle decade abbrevations such as '90s
|
||||||
if d.CanOpen && !d.CanClose && len(line) > 3 && util.IsNumeric(line[1]) && util.IsNumeric(line[2]) && line[3] == 's' {
|
if d.CanOpen && !d.CanClose && len(line) > 3 &&
|
||||||
|
util.IsNumeric(line[1]) && util.IsNumeric(line[2]) && line[3] == 's' {
|
||||||
after := rune(' ')
|
after := rune(' ')
|
||||||
if len(line) > 4 {
|
if len(line) > 4 {
|
||||||
after = util.ToRune(line, 4)
|
after = util.ToRune(line, 4)
|
||||||
|
|
@ -231,7 +232,8 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// special cases: 'twas, 'em, 'net
|
// special cases: 'twas, 'em, 'net
|
||||||
if len(line) > 1 && (unicode.IsPunct(before) || unicode.IsSpace(before)) && (line[1] == 't' || line[1] == 'e' || line[1] == 'n' || line[1] == 'l') {
|
if len(line) > 1 && (unicode.IsPunct(before) || unicode.IsSpace(before)) &&
|
||||||
|
(line[1] == 't' || line[1] == 'e' || line[1] == 'n' || line[1] == 'l') {
|
||||||
node := gast.NewString(s.Substitutions[Apostrophe])
|
node := gast.NewString(s.Substitutions[Apostrophe])
|
||||||
node.SetCode(true)
|
node.SetCode(true)
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
|
|
@ -239,7 +241,8 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
}
|
}
|
||||||
// Convert normal apostrophes. This is probably more flexible than necessary but
|
// Convert normal apostrophes. This is probably more flexible than necessary but
|
||||||
// converts any apostrophe in between two alphanumerics.
|
// converts any apostrophe in between two alphanumerics.
|
||||||
if len(line) > 1 && (unicode.IsDigit(before) || unicode.IsLetter(before)) && (unicode.IsLetter(util.ToRune(line, 1))) {
|
if len(line) > 1 && (unicode.IsDigit(before) || unicode.IsLetter(before)) &&
|
||||||
|
(unicode.IsLetter(util.ToRune(line, 1))) {
|
||||||
node := gast.NewString(s.Substitutions[Apostrophe])
|
node := gast.NewString(s.Substitutions[Apostrophe])
|
||||||
node.SetCode(true)
|
node.SetCode(true)
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
|
|
@ -249,11 +252,14 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose {
|
if s.Substitutions[LeftSingleQuote] != nil && d.CanOpen && !d.CanClose {
|
||||||
nt := LeftSingleQuote
|
nt := LeftSingleQuote
|
||||||
// special cases: Alice's, I'm, Don't, You'd
|
// special cases: Alice's, I'm, Don't, You'd
|
||||||
if len(line) > 1 && (line[1] == 's' || line[1] == 'm' || line[1] == 't' || line[1] == 'd') && (len(line) < 3 || util.IsPunct(line[2]) || util.IsSpace(line[2])) {
|
if len(line) > 1 && (line[1] == 's' || line[1] == 'm' || line[1] == 't' || line[1] == 'd') &&
|
||||||
|
(len(line) < 3 || util.IsPunct(line[2]) || util.IsSpace(line[2])) {
|
||||||
nt = RightSingleQuote
|
nt = RightSingleQuote
|
||||||
}
|
}
|
||||||
// special cases: I've, I'll, You're
|
// special cases: I've, I'll, You're
|
||||||
if len(line) > 2 && ((line[1] == 'v' && line[2] == 'e') || (line[1] == 'l' && line[2] == 'l') || (line[1] == 'r' && line[2] == 'e')) && (len(line) < 4 || util.IsPunct(line[3]) || util.IsSpace(line[3])) {
|
if len(line) > 2 && ((line[1] == 'v' && line[2] == 'e') ||
|
||||||
|
(line[1] == 'l' && line[2] == 'l') || (line[1] == 'r' && line[2] == 'e')) &&
|
||||||
|
(len(line) < 4 || util.IsPunct(line[3]) || util.IsSpace(line[3])) {
|
||||||
nt = RightSingleQuote
|
nt = RightSingleQuote
|
||||||
}
|
}
|
||||||
if nt == LeftSingleQuote {
|
if nt == LeftSingleQuote {
|
||||||
|
|
@ -266,8 +272,9 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
if s.Substitutions[RightSingleQuote] != nil {
|
if s.Substitutions[RightSingleQuote] != nil {
|
||||||
// plural possesives and abbreviations: Smiths', doin'
|
// plural possesive and abbreviations: Smiths', doin'
|
||||||
if len(line) > 1 && unicode.IsSpace(util.ToRune(line, 0)) || unicode.IsPunct(util.ToRune(line, 0)) && (len(line) > 2 && !unicode.IsDigit(util.ToRune(line, 1))) {
|
if len(line) > 1 && unicode.IsSpace(util.ToRune(line, 0)) || unicode.IsPunct(util.ToRune(line, 0)) &&
|
||||||
|
(len(line) > 2 && !unicode.IsDigit(util.ToRune(line, 1))) {
|
||||||
node := gast.NewString(s.Substitutions[RightSingleQuote])
|
node := gast.NewString(s.Substitutions[RightSingleQuote])
|
||||||
node.SetCode(true)
|
node.SetCode(true)
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
|
|
@ -276,7 +283,8 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
}
|
}
|
||||||
if s.Substitutions[RightSingleQuote] != nil && counter.Single > 0 {
|
if s.Substitutions[RightSingleQuote] != nil && counter.Single > 0 {
|
||||||
isClose := d.CanClose && !d.CanOpen
|
isClose := d.CanClose && !d.CanOpen
|
||||||
maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && unicode.IsPunct(util.ToRune(line, 1)) && (len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
|
maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && unicode.IsPunct(util.ToRune(line, 1)) &&
|
||||||
|
(len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
|
||||||
if isClose || maybeClose {
|
if isClose || maybeClose {
|
||||||
node := gast.NewString(s.Substitutions[RightSingleQuote])
|
node := gast.NewString(s.Substitutions[RightSingleQuote])
|
||||||
node.SetCode(true)
|
node.SetCode(true)
|
||||||
|
|
@ -296,7 +304,8 @@ func (s *typographerParser) Parse(parent gast.Node, block text.Reader, pc parser
|
||||||
}
|
}
|
||||||
if s.Substitutions[RightDoubleQuote] != nil && counter.Double > 0 {
|
if s.Substitutions[RightDoubleQuote] != nil && counter.Double > 0 {
|
||||||
isClose := d.CanClose && !d.CanOpen
|
isClose := d.CanClose && !d.CanOpen
|
||||||
maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && (unicode.IsPunct(util.ToRune(line, 1))) && (len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
|
maybeClose := d.CanClose && d.CanOpen && len(line) > 1 && (unicode.IsPunct(util.ToRune(line, 1))) &&
|
||||||
|
(len(line) == 2 || (len(line) > 2 && util.IsPunct(line[2]) || util.IsSpace(line[2])))
|
||||||
if isClose || maybeClose {
|
if isClose || maybeClose {
|
||||||
// special case: "Monitor 21""
|
// special case: "Monitor 21""
|
||||||
if len(line) > 1 && line[1] == '"' && unicode.IsDigit(before) {
|
if len(line) > 1 && line[1] == '"' && unicode.IsDigit(before) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package goldmark_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -13,6 +15,15 @@ import (
|
||||||
"github.com/yuin/goldmark/testutil"
|
"github.com/yuin/goldmark/testutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var testTimeoutMultiplier = 1.0
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
m, err := strconv.ParseFloat(os.Getenv("GOLDMARK_TEST_TIMEOUT_MULTIPLIER"), 64)
|
||||||
|
if err == nil {
|
||||||
|
testTimeoutMultiplier = m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExtras(t *testing.T) {
|
func TestExtras(t *testing.T) {
|
||||||
markdown := New(WithRendererOptions(
|
markdown := New(WithRendererOptions(
|
||||||
html.WithXHTML(),
|
html.WithXHTML(),
|
||||||
|
|
@ -108,8 +119,8 @@ func TestDeepNestedLabelPerformance(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
_ = markdown.Convert(source, &b)
|
_ = markdown.Convert(source, &b)
|
||||||
finished := nowMillis()
|
finished := nowMillis()
|
||||||
if (finished - started) > 5000 {
|
if (finished - started) > int64(5000*testTimeoutMultiplier) {
|
||||||
t.Error("Parsing deep nested labels took more 5 secs")
|
t.Error("Parsing deep nested labels took too long")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -128,8 +139,8 @@ func TestManyProcessingInstructionPerformance(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
_ = markdown.Convert(source, &b)
|
_ = markdown.Convert(source, &b)
|
||||||
finished := nowMillis()
|
finished := nowMillis()
|
||||||
if (finished - started) > 5000 {
|
if (finished - started) > int64(5000*testTimeoutMultiplier) {
|
||||||
t.Error("Parsing processing instructions took more 5 secs")
|
t.Error("Parsing processing instructions took too long")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,8 +159,8 @@ func TestManyCDATAPerformance(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
_ = markdown.Convert(source, &b)
|
_ = markdown.Convert(source, &b)
|
||||||
finished := nowMillis()
|
finished := nowMillis()
|
||||||
if (finished - started) > 5000 {
|
if (finished - started) > int64(5000*testTimeoutMultiplier) {
|
||||||
t.Error("Parsing processing instructions took more 5 secs")
|
t.Error("Parsing processing instructions took too long")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,8 +179,8 @@ func TestManyDeclPerformance(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
_ = markdown.Convert(source, &b)
|
_ = markdown.Convert(source, &b)
|
||||||
finished := nowMillis()
|
finished := nowMillis()
|
||||||
if (finished - started) > 5000 {
|
if (finished - started) > int64(5000*testTimeoutMultiplier) {
|
||||||
t.Error("Parsing processing instructions took more 5 secs")
|
t.Error("Parsing processing instructions took too long")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -188,7 +199,23 @@ func TestManyCommentPerformance(t *testing.T) {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
_ = markdown.Convert(source, &b)
|
_ = markdown.Convert(source, &b)
|
||||||
finished := nowMillis()
|
finished := nowMillis()
|
||||||
if (finished - started) > 5000 {
|
if (finished - started) > int64(5000*testTimeoutMultiplier) {
|
||||||
t.Error("Parsing processing instructions took more 5 secs")
|
t.Error("Parsing processing instructions took too long")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDangerousURLStringCase(t *testing.T) {
|
||||||
|
markdown := New()
|
||||||
|
|
||||||
|
source := []byte(`[Basic](javascript:alert('Basic'))
|
||||||
|
[CaseInsensitive](JaVaScRiPt:alert('CaseInsensitive'))
|
||||||
|
`)
|
||||||
|
expected := []byte(`<p><a href="">Basic</a>
|
||||||
|
<a href="">CaseInsensitive</a></p>
|
||||||
|
`)
|
||||||
|
var b bytes.Buffer
|
||||||
|
_ = markdown.Convert(source, &b)
|
||||||
|
if !bytes.Equal(expected, b.Bytes()) {
|
||||||
|
t.Error("Dangerous URL should ignore cases:\n" + string(testutil.DiffPretty(expected, b.Bytes())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
fuzz/fuzz.go
39
fuzz/fuzz.go
|
|
@ -1,39 +0,0 @@
|
||||||
package fuzz
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
|
||||||
"github.com/yuin/goldmark/extension"
|
|
||||||
"github.com/yuin/goldmark/parser"
|
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fuzz runs automated fuzzing against goldmark.
|
|
||||||
func Fuzz(data []byte) int {
|
|
||||||
markdown := goldmark.New(
|
|
||||||
goldmark.WithParserOptions(
|
|
||||||
parser.WithAutoHeadingID(),
|
|
||||||
parser.WithAttribute(),
|
|
||||||
),
|
|
||||||
goldmark.WithRendererOptions(
|
|
||||||
html.WithUnsafe(),
|
|
||||||
html.WithXHTML(),
|
|
||||||
),
|
|
||||||
goldmark.WithExtensions(
|
|
||||||
extension.DefinitionList,
|
|
||||||
extension.Footnote,
|
|
||||||
extension.GFM,
|
|
||||||
extension.Linkify,
|
|
||||||
extension.Table,
|
|
||||||
extension.TaskList,
|
|
||||||
extension.Typographer,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
var b bytes.Buffer
|
|
||||||
if err := markdown.Convert(data, &b); err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,8 @@ package fuzz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
|
|
@ -13,16 +13,8 @@ import (
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = fmt.Printf
|
func fuzz(f *testing.F) {
|
||||||
|
f.Fuzz(func(t *testing.T, orig string) {
|
||||||
func TestFuzz(t *testing.T) {
|
|
||||||
crasher := "6dff3d03167cb144d4e2891edac76ee740a77bc7"
|
|
||||||
data, err := ioutil.ReadFile("crashers/" + crasher)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fmt.Printf("%s\n", util.VisualizeSpaces(data))
|
|
||||||
fmt.Println("||||||||||||||||||||||")
|
|
||||||
markdown := goldmark.New(
|
markdown := goldmark.New(
|
||||||
goldmark.WithParserOptions(
|
goldmark.WithParserOptions(
|
||||||
parser.WithAutoHeadingID(),
|
parser.WithAutoHeadingID(),
|
||||||
|
|
@ -43,8 +35,23 @@ func TestFuzz(t *testing.T) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := markdown.Convert(data, &b); err != nil {
|
if err := markdown.Convert(util.StringToReadOnlyBytes(orig), &b); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
fmt.Println(b.String())
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FuzzDefault(f *testing.F) {
|
||||||
|
bs, err := os.ReadFile("../_test/spec.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var testCases []map[string]interface{}
|
||||||
|
if err := json.Unmarshal(bs, &testCases); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, c := range testCases {
|
||||||
|
f.Add(c["markdown"])
|
||||||
|
}
|
||||||
|
fuzz(f)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
fuzz/oss_fuzz_test.go
Normal file
9
fuzz/oss_fuzz_test.go
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package fuzz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func FuzzOss(f *testing.F) {
|
||||||
|
fuzz(f)
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -1,3 +1,3 @@
|
||||||
module github.com/yuin/goldmark
|
module github.com/yuin/goldmark
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,13 @@
|
||||||
package goldmark
|
package goldmark
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer"
|
"github.com/yuin/goldmark/renderer"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultParser returns a new Parser that is configured by default values.
|
// DefaultParser returns a new Parser that is configured by default values.
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
var attrNameID = []byte("id")
|
var attrNameID = []byte("id")
|
||||||
var attrNameClass = []byte("class")
|
var attrNameClass = []byte("class")
|
||||||
|
|
||||||
// An Attribute is an attribute of the markdown elements
|
// An Attribute is an attribute of the markdown elements.
|
||||||
type Attribute struct {
|
type Attribute struct {
|
||||||
Name []byte
|
Name []byte
|
||||||
Value interface{}
|
Value interface{}
|
||||||
|
|
@ -93,7 +93,8 @@ func parseAttribute(reader text.Reader) (Attribute, bool) {
|
||||||
// CommonMark is basically defined for XHTML(even though it is legacy).
|
// CommonMark is basically defined for XHTML(even though it is legacy).
|
||||||
// So we restrict id characters.
|
// So we restrict id characters.
|
||||||
for ; i < len(line) && !util.IsSpace(line[i]) &&
|
for ; i < len(line) && !util.IsSpace(line[i]) &&
|
||||||
(!util.IsPunct(line[i]) || line[i] == '_' || line[i] == '-' || line[i] == ':' || line[i] == '.'); i++ {
|
(!util.IsPunct(line[i]) || line[i] == '_' ||
|
||||||
|
line[i] == '-' || line[i] == ':' || line[i] == '.'); i++ {
|
||||||
}
|
}
|
||||||
name := attrNameClass
|
name := attrNameClass
|
||||||
if c == '#' {
|
if c == '#' {
|
||||||
|
|
@ -145,7 +146,7 @@ func parseAttributeValue(reader text.Reader) (interface{}, bool) {
|
||||||
reader.SkipSpaces()
|
reader.SkipSpaces()
|
||||||
c := reader.Peek()
|
c := reader.Peek()
|
||||||
var value interface{}
|
var value interface{}
|
||||||
ok := false
|
var ok bool
|
||||||
switch c {
|
switch c {
|
||||||
case text.EOF:
|
case text.EOF:
|
||||||
return Attribute{}, false
|
return Attribute{}, false
|
||||||
|
|
@ -244,7 +245,7 @@ func scanAttributeDecimal(reader text.Reader, w io.ByteWriter) {
|
||||||
for {
|
for {
|
||||||
c := reader.Peek()
|
c := reader.Peek()
|
||||||
if util.IsNumeric(c) {
|
if util.IsNumeric(c) {
|
||||||
w.WriteByte(c)
|
_ = w.WriteByte(c)
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +287,7 @@ func parseAttributeNumber(reader text.Reader) (float64, bool) {
|
||||||
}
|
}
|
||||||
scanAttributeDecimal(reader, &buf)
|
scanAttributeDecimal(reader, &buf)
|
||||||
}
|
}
|
||||||
f, err := strconv.ParseFloat(buf.String(), 10)
|
f, err := strconv.ParseFloat(buf.String(), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ type HeadingConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOption implements SetOptioner.
|
// SetOption implements SetOptioner.
|
||||||
func (b *HeadingConfig) SetOption(name OptionName, value interface{}) {
|
func (b *HeadingConfig) SetOption(name OptionName, _ interface{}) {
|
||||||
switch name {
|
switch name {
|
||||||
case optAutoHeadingID:
|
case optAutoHeadingID:
|
||||||
b.AutoHeadingID = true
|
b.AutoHeadingID = true
|
||||||
|
|
@ -135,7 +135,9 @@ func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
for _, attr := range attrs {
|
for _, attr := range attrs {
|
||||||
node.SetAttribute(attr.Name, attr.Value)
|
node.SetAttribute(attr.Name, attr.Value)
|
||||||
}
|
}
|
||||||
node.Lines().Append(text.NewSegment(segment.Start+start+1-segment.Padding, segment.Start+closureOpen-segment.Padding))
|
node.Lines().Append(text.NewSegment(
|
||||||
|
segment.Start+start+1-segment.Padding,
|
||||||
|
segment.Start+closureOpen-segment.Padding))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,13 @@ func (b *blockquoteParser) process(reader text.Reader) bool {
|
||||||
reader.Advance(pos)
|
reader.Advance(pos)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if line[pos] == ' ' || line[pos] == '\t' {
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
reader.Advance(pos)
|
reader.Advance(pos)
|
||||||
if line[pos-1] == '\t' {
|
if line[pos] == ' ' || line[pos] == '\t' {
|
||||||
reader.SetPadding(2)
|
padding := 0
|
||||||
|
if line[pos] == '\t' {
|
||||||
|
padding = util.TabWidth(reader.LineOffset()) - 1
|
||||||
|
}
|
||||||
|
reader.AdvanceAndSetPadding(1, padding)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ func (b *codeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
if segment.Padding != 0 {
|
if segment.Padding != 0 {
|
||||||
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
||||||
}
|
}
|
||||||
|
segment.ForceNewline = true
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - 1)
|
||||||
return node, NoChildren
|
return node, NoChildren
|
||||||
|
|
@ -59,6 +60,7 @@ func (b *codeBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
preserveLeadingTabInCodeBlock(&segment, reader, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
segment.ForceNewline = true
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - 1)
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,12 @@ func (d *Delimiter) Dump(source []byte, level int) {
|
||||||
|
|
||||||
var kindDelimiter = ast.NewNodeKind("Delimiter")
|
var kindDelimiter = ast.NewNodeKind("Delimiter")
|
||||||
|
|
||||||
// Kind implements Node.Kind
|
// Kind implements Node.Kind.
|
||||||
func (d *Delimiter) Kind() ast.NodeKind {
|
func (d *Delimiter) Kind() ast.NodeKind {
|
||||||
return kindDelimiter
|
return kindDelimiter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text implements Node.Text
|
// Text implements Node.Text.
|
||||||
func (d *Delimiter) Text(source []byte) []byte {
|
func (d *Delimiter) Text(source []byte) []byte {
|
||||||
return d.Segment.Value(source)
|
return d.Segment.Value(source)
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +126,7 @@ func ScanDelimiter(line []byte, before rune, min int, processor DelimiterProcess
|
||||||
after = util.ToRune(line, j)
|
after = util.ToRune(line, j)
|
||||||
}
|
}
|
||||||
|
|
||||||
canOpen, canClose := false, false
|
var canOpen, canClose bool
|
||||||
beforeIsPunctuation := util.IsPunctRune(before)
|
beforeIsPunctuation := util.IsPunctRune(before)
|
||||||
beforeIsWhitespace := util.IsSpaceRune(before)
|
beforeIsWhitespace := util.IsSpaceRune(before)
|
||||||
afterIsPunctuation := util.IsPunctRune(after)
|
afterIsPunctuation := util.IsPunctRune(after)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
|
||||||
if line[len(line)-1] != '\n' {
|
if line[len(line)-1] != '\n' {
|
||||||
newline = 0
|
newline = 0
|
||||||
}
|
}
|
||||||
reader.Advance(segment.Stop - segment.Start - newline - segment.Padding)
|
reader.Advance(segment.Stop - segment.Start - newline + segment.Padding)
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +100,7 @@ func (b *fencedCodeBlockParser) Continue(node ast.Node, reader text.Reader, pc C
|
||||||
if padding != 0 {
|
if padding != 0 {
|
||||||
preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
|
preserveLeadingTabInCodeBlock(&seg, reader, fdata.indent)
|
||||||
}
|
}
|
||||||
|
seg.ForceNewline = true // EOF as newline
|
||||||
node.Lines().Append(seg)
|
node.Lines().Append(seg)
|
||||||
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
|
reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ var allowedBlockTags = map[string]bool{
|
||||||
"option": true,
|
"option": true,
|
||||||
"p": true,
|
"p": true,
|
||||||
"param": true,
|
"param": true,
|
||||||
|
"search": true,
|
||||||
"section": true,
|
"section": true,
|
||||||
"source": true,
|
|
||||||
"summary": true,
|
"summary": true,
|
||||||
"table": true,
|
"table": true,
|
||||||
"tbody": true,
|
"tbody": true,
|
||||||
|
|
@ -76,7 +76,7 @@ var allowedBlockTags = map[string]bool{
|
||||||
"ul": true,
|
"ul": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlBlockType1OpenRegexp = regexp.MustCompile(`(?i)^[ ]{0,3}<(script|pre|style|textarea)(?:\s.*|>.*|/>.*|)(?:\r\n|\n)?$`)
|
var htmlBlockType1OpenRegexp = regexp.MustCompile(`(?i)^[ ]{0,3}<(script|pre|style|textarea)(?:\s.*|>.*|/>.*|)(?:\r\n|\n)?$`) //nolint:golint,lll
|
||||||
var htmlBlockType1CloseRegexp = regexp.MustCompile(`(?i)^.*</(?:script|pre|style|textarea)>.*`)
|
var htmlBlockType1CloseRegexp = regexp.MustCompile(`(?i)^.*</(?:script|pre|style|textarea)>.*`)
|
||||||
|
|
||||||
var htmlBlockType2OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<!\-\-`)
|
var htmlBlockType2OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<!\-\-`)
|
||||||
|
|
@ -91,9 +91,9 @@ var htmlBlockType4Close = []byte{'>'}
|
||||||
var htmlBlockType5OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<\!\[CDATA\[`)
|
var htmlBlockType5OpenRegexp = regexp.MustCompile(`^[ ]{0,3}<\!\[CDATA\[`)
|
||||||
var htmlBlockType5Close = []byte{']', ']', '>'}
|
var htmlBlockType5Close = []byte{']', ']', '>'}
|
||||||
|
|
||||||
var htmlBlockType6Regexp = regexp.MustCompile(`^[ ]{0,3}<(?:/[ ]*)?([a-zA-Z]+[a-zA-Z0-9\-]*)(?:[ ].*|>.*|/>.*|)(?:\r\n|\n)?$`)
|
var htmlBlockType6Regexp = regexp.MustCompile(`^[ ]{0,3}<(?:/[ ]*)?([a-zA-Z]+[a-zA-Z0-9\-]*)(?:[ ].*|>.*|/>.*|)(?:\r\n|\n)?$`) //nolint:golint,lll
|
||||||
|
|
||||||
var htmlBlockType7Regexp = regexp.MustCompile(`^[ ]{0,3}<(/[ ]*)?([a-zA-Z]+[a-zA-Z0-9\-]*)(` + attributePattern + `*)[ ]*(?:>|/>)[ ]*(?:\r\n|\n)?$`)
|
var htmlBlockType7Regexp = regexp.MustCompile(`^[ ]{0,3}<(/[ ]*)?([a-zA-Z]+[a-zA-Z0-9\-]*)(` + attributePattern + `*)[ ]*(?:>|/>)[ ]*(?:\r\n|\n)?$`) //nolint:golint,lll
|
||||||
|
|
||||||
type htmlBlockParser struct {
|
type htmlBlockParser struct {
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +135,8 @@ func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
_, ok := allowedBlockTags[tagName]
|
_, ok := allowedBlockTags[tagName]
|
||||||
if ok {
|
if ok {
|
||||||
node = ast.NewHTMLBlock(ast.HTMLBlockType6)
|
node = ast.NewHTMLBlock(ast.HTMLBlockType6)
|
||||||
} else if tagName != "script" && tagName != "style" && tagName != "pre" && !ast.IsParagraph(last) && !(isCloseTag && hasAttr) { // type 7 can not interrupt paragraph
|
} else if tagName != "script" && tagName != "style" &&
|
||||||
|
tagName != "pre" && !ast.IsParagraph(last) && !(isCloseTag && hasAttr) { // type 7 can not interrupt paragraph
|
||||||
node = ast.NewHTMLBlock(ast.HTMLBlockType7)
|
node = ast.NewHTMLBlock(ast.HTMLBlockType7)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +150,7 @@ func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if node != nil {
|
if node != nil {
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - util.TrimRightSpaceLength(line))
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
return node, NoChildren
|
return node, NoChildren
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +173,7 @@ func (b *htmlBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
}
|
}
|
||||||
if htmlBlockType1CloseRegexp.Match(line) {
|
if htmlBlockType1CloseRegexp.Match(line) {
|
||||||
htmlBlock.ClosureLine = segment
|
htmlBlock.ClosureLine = segment
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - util.TrimRightSpaceLength(line))
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
case ast.HTMLBlockType2:
|
case ast.HTMLBlockType2:
|
||||||
|
|
@ -211,7 +212,7 @@ func (b *htmlBlockParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
reader.Advance(segment.Len() - 1)
|
reader.Advance(segment.Len() - util.TrimRightSpaceLength(line))
|
||||||
return Continue | NoChildren
|
return Continue | NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,13 +126,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
if line[0] == '!' {
|
if line[0] == '!' {
|
||||||
if len(line) > 1 && line[1] == '[' {
|
if len(line) > 1 && line[1] == '[' {
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
pc.Set(linkBottom, pc.LastDelimiter())
|
pushLinkBottom(pc)
|
||||||
return processLinkLabelOpen(block, segment.Start+1, true, pc)
|
return processLinkLabelOpen(block, segment.Start+1, true, pc)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if line[0] == '[' {
|
if line[0] == '[' {
|
||||||
pc.Set(linkBottom, pc.LastDelimiter())
|
pushLinkBottom(pc)
|
||||||
return processLinkLabelOpen(block, segment.Start, false, pc)
|
return processLinkLabelOpen(block, segment.Start, false, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -143,6 +143,7 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
}
|
}
|
||||||
last := tlist.(*linkLabelState).Last
|
last := tlist.(*linkLabelState).Last
|
||||||
if last == nil {
|
if last == nil {
|
||||||
|
_ = popLinkBottom(pc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
block.Advance(1)
|
block.Advance(1)
|
||||||
|
|
@ -151,11 +152,13 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
// > A link label can have at most 999 characters inside the square brackets.
|
// > A link label can have at most 999 characters inside the square brackets.
|
||||||
if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
|
if linkLabelStateLength(tlist.(*linkLabelState)) > 998 {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
|
_ = popLinkBottom(pc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
|
if !last.IsImage && s.containsLink(last) { // a link in a link text is not allowed
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
|
_ = popLinkBottom(pc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,6 +172,7 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
link, hasValue = s.parseReferenceLink(parent, last, block, pc)
|
link, hasValue = s.parseReferenceLink(parent, last, block, pc)
|
||||||
if link == nil && hasValue {
|
if link == nil && hasValue {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
|
_ = popLinkBottom(pc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -182,12 +186,14 @@ func (s *linkParser) Parse(parent ast.Node, block text.Reader, pc Context) ast.N
|
||||||
// > A link label can have at most 999 characters inside the square brackets.
|
// > A link label can have at most 999 characters inside the square brackets.
|
||||||
if len(maybeReference) > 999 {
|
if len(maybeReference) > 999 {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
|
_ = popLinkBottom(pc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
|
ref, ok := pc.Reference(util.ToLinkReference(maybeReference))
|
||||||
if !ok {
|
if !ok {
|
||||||
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
ast.MergeOrReplaceTextSegment(last.Parent(), last, last.Segment)
|
||||||
|
_ = popLinkBottom(pc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
link = ast.NewLink()
|
link = ast.NewLink()
|
||||||
|
|
@ -230,11 +236,7 @@ func processLinkLabelOpen(block text.Reader, pos int, isImage bool, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
|
func (s *linkParser) processLinkLabel(parent ast.Node, link *ast.Link, last *linkLabelState, pc Context) {
|
||||||
var bottom ast.Node
|
bottom := popLinkBottom(pc)
|
||||||
if v := pc.Get(linkBottom); v != nil {
|
|
||||||
bottom = v.(ast.Node)
|
|
||||||
}
|
|
||||||
pc.Set(linkBottom, nil)
|
|
||||||
ProcessDelimiters(bottom, pc)
|
ProcessDelimiters(bottom, pc)
|
||||||
for c := last.NextSibling(); c != nil; {
|
for c := last.NextSibling(); c != nil; {
|
||||||
next := c.NextSibling()
|
next := c.NextSibling()
|
||||||
|
|
@ -250,7 +252,8 @@ var linkFindClosureOptions text.FindClosureOptions = text.FindClosureOptions{
|
||||||
Advance: true,
|
Advance: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState, block text.Reader, pc Context) (*ast.Link, bool) {
|
func (s *linkParser) parseReferenceLink(parent ast.Node, last *linkLabelState,
|
||||||
|
block text.Reader, pc Context) (*ast.Link, bool) {
|
||||||
_, orgpos := block.Position()
|
_, orgpos := block.Position()
|
||||||
block.Advance(1) // skip '['
|
block.Advance(1) // skip '['
|
||||||
segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
|
segments, found := block.FindClosure('[', ']', linkFindClosureOptions)
|
||||||
|
|
@ -394,6 +397,43 @@ func parseLinkTitle(block text.Reader) ([]byte, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pushLinkBottom(pc Context) {
|
||||||
|
bottoms := pc.Get(linkBottom)
|
||||||
|
b := pc.LastDelimiter()
|
||||||
|
if bottoms == nil {
|
||||||
|
pc.Set(linkBottom, b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s, ok := bottoms.([]ast.Node); ok {
|
||||||
|
pc.Set(linkBottom, append(s, b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pc.Set(linkBottom, []ast.Node{bottoms.(ast.Node), b})
|
||||||
|
}
|
||||||
|
|
||||||
|
func popLinkBottom(pc Context) ast.Node {
|
||||||
|
bottoms := pc.Get(linkBottom)
|
||||||
|
if bottoms == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v, ok := bottoms.(ast.Node); ok {
|
||||||
|
pc.Set(linkBottom, nil)
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
s := bottoms.([]ast.Node)
|
||||||
|
v := s[len(s)-1]
|
||||||
|
n := s[0 : len(s)-1]
|
||||||
|
switch len(n) {
|
||||||
|
case 0:
|
||||||
|
pc.Set(linkBottom, nil)
|
||||||
|
case 1:
|
||||||
|
pc.Set(linkBottom, n[0])
|
||||||
|
default:
|
||||||
|
pc.Set(linkBottom, s[0:len(s)-1])
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
func (s *linkParser) CloseBlock(parent ast.Node, block text.Reader, pc Context) {
|
||||||
pc.Set(linkBottom, nil)
|
pc.Set(linkBottom, nil)
|
||||||
tlist := pc.Get(linkLabelStateKey)
|
tlist := pc.Get(linkLabelStateKey)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ var listItemFlagValue interface{} = true
|
||||||
|
|
||||||
// Same as
|
// Same as
|
||||||
// `^(([ ]*)([\-\*\+]))(\s+.*)?\n?$`.FindSubmatchIndex or
|
// `^(([ ]*)([\-\*\+]))(\s+.*)?\n?$`.FindSubmatchIndex or
|
||||||
// `^(([ ]*)(\d{1,9}[\.\)]))(\s+.*)?\n?$`.FindSubmatchIndex
|
// `^(([ ]*)(\d{1,9}[\.\)]))(\s+.*)?\n?$`.FindSubmatchIndex.
|
||||||
func parseListItem(line []byte) ([6]int, listItemType) {
|
func parseListItem(line []byte) ([6]int, listItemType) {
|
||||||
i := 0
|
i := 0
|
||||||
l := len(line)
|
l := len(line)
|
||||||
|
|
@ -89,7 +89,7 @@ func matchesListItem(source []byte, strict bool) ([6]int, listItemType) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func calcListOffset(source []byte, match [6]int) int {
|
func calcListOffset(source []byte, match [6]int) int {
|
||||||
offset := 0
|
var offset int
|
||||||
if match[4] < 0 || util.IsBlank(source[match[4]:]) { // list item starts with a blank line
|
if match[4] < 0 || util.IsBlank(source[match[4]:]) { // list item starts with a blank line
|
||||||
offset = 1
|
offset = 1
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -250,14 +250,14 @@ func (b *listParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
for c := node.FirstChild(); c != nil && list.IsTight; c = c.NextSibling() {
|
for c := node.FirstChild(); c != nil && list.IsTight; c = c.NextSibling() {
|
||||||
if c.FirstChild() != nil && c.FirstChild() != c.LastChild() {
|
if c.FirstChild() != nil && c.FirstChild() != c.LastChild() {
|
||||||
for c1 := c.FirstChild().NextSibling(); c1 != nil; c1 = c1.NextSibling() {
|
for c1 := c.FirstChild().NextSibling(); c1 != nil; c1 = c1.NextSibling() {
|
||||||
if bl, ok := c1.(ast.Node); ok && bl.HasBlankPreviousLines() {
|
if c1.HasBlankPreviousLines() {
|
||||||
list.IsTight = false
|
list.IsTight = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c != node.FirstChild() {
|
if c != node.FirstChild() {
|
||||||
if bl, ok := c.(ast.Node); ok && bl.HasBlankPreviousLines() {
|
if c.HasBlankPreviousLines() {
|
||||||
list.IsTight = false
|
list.IsTight = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ func (b *listItemParser) Continue(node ast.Node, reader text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
offset := lastOffset(node.Parent())
|
offset := lastOffset(node.Parent())
|
||||||
isEmpty := node.ChildCount() == 0
|
isEmpty := node.ChildCount() == 0 && pc.Get(emptyListItemWithBlankLines) != nil
|
||||||
indent, _ := util.IndentWidth(line, reader.LineOffset())
|
indent, _ := util.IndentWidth(line, reader.LineOffset())
|
||||||
if (isEmpty || indent < offset) && indent < 4 {
|
if (isEmpty || indent < offset) && indent < 4 {
|
||||||
_, typ := matchesListItem(line, true)
|
_, typ := matchesListItem(line, true)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package parser
|
||||||
import (
|
import (
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/text"
|
"github.com/yuin/goldmark/text"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type paragraphParser struct {
|
type paragraphParser struct {
|
||||||
|
|
@ -33,9 +34,8 @@ func (b *paragraphParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *paragraphParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
|
func (b *paragraphParser) Continue(node ast.Node, reader text.Reader, pc Context) State {
|
||||||
_, segment := reader.PeekLine()
|
line, segment := reader.PeekLine()
|
||||||
segment = segment.TrimLeftSpace(reader.Source())
|
if util.IsBlank(line) {
|
||||||
if segment.IsEmpty() {
|
|
||||||
return Close
|
return Close
|
||||||
}
|
}
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
|
|
@ -44,13 +44,14 @@ func (b *paragraphParser) Continue(node ast.Node, reader text.Reader, pc Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *paragraphParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
func (b *paragraphParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
parent := node.Parent()
|
|
||||||
if parent == nil {
|
|
||||||
// paragraph has been transformed
|
|
||||||
return
|
|
||||||
}
|
|
||||||
lines := node.Lines()
|
lines := node.Lines()
|
||||||
if lines.Len() != 0 {
|
if lines.Len() != 0 {
|
||||||
|
// trim leading spaces
|
||||||
|
for i := 0; i < lines.Len(); i++ {
|
||||||
|
l := lines.At(i)
|
||||||
|
lines.Set(i, l.TrimLeftSpace(reader.Source()))
|
||||||
|
}
|
||||||
|
|
||||||
// trim trailing spaces
|
// trim trailing spaces
|
||||||
length := lines.Len()
|
length := lines.Len()
|
||||||
lastLine := node.Lines().At(length - 1)
|
lastLine := node.Lines().At(length - 1)
|
||||||
|
|
|
||||||
|
|
@ -403,7 +403,8 @@ func (p *parseContext) IsInLinkLabel() bool {
|
||||||
type State int
|
type State int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
none State = 1 << iota
|
// None is a default value of the [State].
|
||||||
|
None State = 1 << iota
|
||||||
|
|
||||||
// Continue indicates parser can continue parsing.
|
// Continue indicates parser can continue parsing.
|
||||||
Continue
|
Continue
|
||||||
|
|
@ -430,6 +431,7 @@ type Config struct {
|
||||||
InlineParsers util.PrioritizedSlice /*<InlineParser>*/
|
InlineParsers util.PrioritizedSlice /*<InlineParser>*/
|
||||||
ParagraphTransformers util.PrioritizedSlice /*<ParagraphTransformer>*/
|
ParagraphTransformers util.PrioritizedSlice /*<ParagraphTransformer>*/
|
||||||
ASTTransformers util.PrioritizedSlice /*<ASTTransformer>*/
|
ASTTransformers util.PrioritizedSlice /*<ASTTransformer>*/
|
||||||
|
EscapedSpace bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new Config.
|
// NewConfig returns a new Config.
|
||||||
|
|
@ -635,6 +637,7 @@ type parser struct {
|
||||||
closeBlockers []CloseBlocker
|
closeBlockers []CloseBlocker
|
||||||
paragraphTransformers []ParagraphTransformer
|
paragraphTransformers []ParagraphTransformer
|
||||||
astTransformers []ASTTransformer
|
astTransformers []ASTTransformer
|
||||||
|
escapedSpace bool
|
||||||
config *Config
|
config *Config
|
||||||
initSync sync.Once
|
initSync sync.Once
|
||||||
}
|
}
|
||||||
|
|
@ -695,6 +698,18 @@ func WithASTTransformers(ps ...util.PrioritizedValue) Option {
|
||||||
return &withASTTransformers{ps}
|
return &withASTTransformers{ps}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type withEscapedSpace struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withEscapedSpace) SetParserOption(c *Config) {
|
||||||
|
c.EscapedSpace = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEscapedSpace is a functional option indicates that a '\' escaped half-space(0x20) should not trigger parsers.
|
||||||
|
func WithEscapedSpace() Option {
|
||||||
|
return &withEscapedSpace{}
|
||||||
|
}
|
||||||
|
|
||||||
type withOption struct {
|
type withOption struct {
|
||||||
name OptionName
|
name OptionName
|
||||||
value interface{}
|
value interface{}
|
||||||
|
|
@ -846,6 +861,7 @@ func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
|
||||||
for _, v := range p.config.ASTTransformers {
|
for _, v := range p.config.ASTTransformers {
|
||||||
p.addASTTransformer(v, p.config.Options)
|
p.addASTTransformer(v, p.config.Options)
|
||||||
}
|
}
|
||||||
|
p.escapedSpace = p.config.EscapedSpace
|
||||||
p.config = nil
|
p.config = nil
|
||||||
})
|
})
|
||||||
c := &ParseConfig{}
|
c := &ParseConfig{}
|
||||||
|
|
@ -866,6 +882,7 @@ func (p *parser) Parse(reader text.Reader, opts ...ParseOption) ast.Node {
|
||||||
for _, at := range p.astTransformers {
|
for _, at := range p.astTransformers {
|
||||||
at.Transform(root, reader, pc)
|
at.Transform(root, reader, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// root.Dump(reader.Source(), 0)
|
// root.Dump(reader.Source(), 0)
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
@ -884,11 +901,13 @@ func (p *parser) closeBlocks(from, to int, reader text.Reader, pc Context) {
|
||||||
blocks := pc.OpenedBlocks()
|
blocks := pc.OpenedBlocks()
|
||||||
for i := from; i >= to; i-- {
|
for i := from; i >= to; i-- {
|
||||||
node := blocks[i].Node
|
node := blocks[i].Node
|
||||||
blocks[i].Parser.Close(blocks[i].Node, reader, pc)
|
|
||||||
paragraph, ok := node.(*ast.Paragraph)
|
paragraph, ok := node.(*ast.Paragraph)
|
||||||
if ok && node.Parent() != nil {
|
if ok && node.Parent() != nil {
|
||||||
p.transformParagraph(paragraph, reader, pc)
|
p.transformParagraph(paragraph, reader, pc)
|
||||||
}
|
}
|
||||||
|
if node.Parent() != nil { // closes only if node has not been transformed
|
||||||
|
blocks[i].Parser.Close(blocks[i].Node, reader, pc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if from == len(blocks)-1 {
|
if from == len(blocks)-1 {
|
||||||
blocks = blocks[0:to]
|
blocks = blocks[0:to]
|
||||||
|
|
@ -1032,7 +1051,7 @@ func isBlankLine(lineNum, level int, stats []lineStat) bool {
|
||||||
func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
func (p *parser) parseBlocks(parent ast.Node, reader text.Reader, pc Context) {
|
||||||
pc.SetOpenedBlocks([]Block{})
|
pc.SetOpenedBlocks([]Block{})
|
||||||
blankLines := make([]lineStat, 0, 128)
|
blankLines := make([]lineStat, 0, 128)
|
||||||
isBlank := false
|
var isBlank bool
|
||||||
for { // process blocks separated by blank lines
|
for { // process blocks separated by blank lines
|
||||||
_, lines, ok := reader.SkipBlankLines()
|
_, lines, ok := reader.SkipBlankLines()
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -1135,18 +1154,23 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
lineLength := len(line)
|
lineLength := len(line)
|
||||||
var lineBreakFlags uint8 = 0
|
var lineBreakFlags uint8
|
||||||
hasNewLine := line[lineLength-1] == '\n'
|
hasNewLine := line[lineLength-1] == '\n'
|
||||||
if ((lineLength >= 3 && line[lineLength-2] == '\\' && line[lineLength-3] != '\\') || (lineLength == 2 && line[lineLength-2] == '\\')) && hasNewLine { // ends with \\n
|
if ((lineLength >= 3 && line[lineLength-2] == '\\' &&
|
||||||
|
line[lineLength-3] != '\\') || (lineLength == 2 && line[lineLength-2] == '\\')) && hasNewLine { // ends with \\n
|
||||||
lineLength -= 2
|
lineLength -= 2
|
||||||
lineBreakFlags |= lineBreakHard | lineBreakVisible
|
lineBreakFlags |= lineBreakHard | lineBreakVisible
|
||||||
} else if ((lineLength >= 4 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' && line[lineLength-4] != '\\') || (lineLength == 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r')) && hasNewLine { // ends with \\r\n
|
} else if ((lineLength >= 4 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r' &&
|
||||||
|
line[lineLength-4] != '\\') || (lineLength == 3 && line[lineLength-3] == '\\' && line[lineLength-2] == '\r')) &&
|
||||||
|
hasNewLine { // ends with \\r\n
|
||||||
lineLength -= 3
|
lineLength -= 3
|
||||||
lineBreakFlags |= lineBreakHard | lineBreakVisible
|
lineBreakFlags |= lineBreakHard | lineBreakVisible
|
||||||
} else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' && hasNewLine { // ends with [space][space]\n
|
} else if lineLength >= 3 && line[lineLength-3] == ' ' && line[lineLength-2] == ' ' &&
|
||||||
|
hasNewLine { // ends with [space][space]\n
|
||||||
lineLength -= 3
|
lineLength -= 3
|
||||||
lineBreakFlags |= lineBreakHard
|
lineBreakFlags |= lineBreakHard
|
||||||
} else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' && line[lineLength-2] == '\r' && hasNewLine { // ends with [space][space]\r\n
|
} else if lineLength >= 4 && line[lineLength-4] == ' ' && line[lineLength-3] == ' ' &&
|
||||||
|
line[lineLength-2] == '\r' && hasNewLine { // ends with [space][space]\r\n
|
||||||
lineLength -= 4
|
lineLength -= 4
|
||||||
lineBreakFlags |= lineBreakHard
|
lineBreakFlags |= lineBreakHard
|
||||||
} else if hasNewLine {
|
} else if hasNewLine {
|
||||||
|
|
@ -1163,9 +1187,9 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
isSpace := util.IsSpace(c)
|
isSpace := util.IsSpace(c) && c != '\r' && c != '\n'
|
||||||
isPunct := util.IsPunct(c)
|
isPunct := util.IsPunct(c)
|
||||||
if (isPunct && !escaped) || isSpace || i == 0 {
|
if (isPunct && !escaped) || isSpace && !(escaped && p.escapedSpace) || i == 0 {
|
||||||
parserChar := c
|
parserChar := c
|
||||||
if isSpace || (i == 0 && !isPunct) {
|
if isSpace || (i == 0 && !isPunct) {
|
||||||
parserChar = ' '
|
parserChar = ' '
|
||||||
|
|
@ -1233,4 +1257,5 @@ func (p *parser) parseBlock(block text.BlockReader, parent ast.Node, pc Context)
|
||||||
for _, ip := range p.closeBlockers {
|
for _, ip := range p.closeBlockers {
|
||||||
ip.CloseBlock(parent, block, pc)
|
ip.CloseBlock(parent, block, pc)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ type rawHTMLParser struct {
|
||||||
var defaultRawHTMLParser = &rawHTMLParser{}
|
var defaultRawHTMLParser = &rawHTMLParser{}
|
||||||
|
|
||||||
// NewRawHTMLParser return a new InlineParser that can parse
|
// NewRawHTMLParser return a new InlineParser that can parse
|
||||||
// inline htmls
|
// inline htmls.
|
||||||
func NewRawHTMLParser() InlineParser {
|
func NewRawHTMLParser() InlineParser {
|
||||||
return defaultRawHTMLParser
|
return defaultRawHTMLParser
|
||||||
}
|
}
|
||||||
|
|
@ -48,57 +48,48 @@ func (s *rawHTMLParser) Parse(parent ast.Node, block text.Reader, pc Context) as
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagnamePattern = `([A-Za-z][A-Za-z0-9-]*)`
|
var tagnamePattern = `([A-Za-z][A-Za-z0-9-]*)`
|
||||||
|
var spaceOrOneNewline = `(?:[ \t]|(?:\r\n|\n){0,1})`
|
||||||
var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)`
|
var attributePattern = `(?:[\r\n \t]+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:[\r\n \t]*=[\r\n \t]*(?:[^\"'=<>` + "`" + `\x00-\x20]+|'[^']*'|"[^"]*"))?)` //nolint:golint,lll
|
||||||
var openTagRegexp = regexp.MustCompile("^<" + tagnamePattern + attributePattern + `*[ \t]*/?>`)
|
var openTagRegexp = regexp.MustCompile("^<" + tagnamePattern + attributePattern + `*` + spaceOrOneNewline + `*/?>`)
|
||||||
var closeTagRegexp = regexp.MustCompile("^</" + tagnamePattern + `\s*>`)
|
var closeTagRegexp = regexp.MustCompile("^</" + tagnamePattern + spaceOrOneNewline + `*>`)
|
||||||
|
|
||||||
var openProcessingInstruction = []byte("<?")
|
var openProcessingInstruction = []byte("<?")
|
||||||
var closeProcessingInstruction = []byte("?>")
|
var closeProcessingInstruction = []byte("?>")
|
||||||
var openCDATA = []byte("<![CDATA[")
|
var openCDATA = []byte("<![CDATA[")
|
||||||
var closeCDATA = []byte("]]>")
|
var closeCDATA = []byte("]]>")
|
||||||
var closeDecl = []byte(">")
|
var closeDecl = []byte(">")
|
||||||
var emptyComment = []byte("<!---->")
|
var emptyComment1 = []byte("<!-->")
|
||||||
var invalidComment1 = []byte("<!-->")
|
var emptyComment2 = []byte("<!--->")
|
||||||
var invalidComment2 = []byte("<!--->")
|
|
||||||
var openComment = []byte("<!--")
|
var openComment = []byte("<!--")
|
||||||
var closeComment = []byte("-->")
|
var closeComment = []byte("-->")
|
||||||
var doubleHyphen = []byte("--")
|
|
||||||
|
|
||||||
func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
|
func (s *rawHTMLParser) parseComment(block text.Reader, pc Context) ast.Node {
|
||||||
savedLine, savedSegment := block.Position()
|
savedLine, savedSegment := block.Position()
|
||||||
node := ast.NewRawHTML()
|
node := ast.NewRawHTML()
|
||||||
line, segment := block.PeekLine()
|
line, segment := block.PeekLine()
|
||||||
if bytes.HasPrefix(line, emptyComment) {
|
if bytes.HasPrefix(line, emptyComment1) {
|
||||||
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment)))
|
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment1)))
|
||||||
block.Advance(len(emptyComment))
|
block.Advance(len(emptyComment1))
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
if bytes.HasPrefix(line, invalidComment1) || bytes.HasPrefix(line, invalidComment2) {
|
if bytes.HasPrefix(line, emptyComment2) {
|
||||||
return nil
|
node.Segments.Append(segment.WithStop(segment.Start + len(emptyComment2)))
|
||||||
|
block.Advance(len(emptyComment2))
|
||||||
|
return node
|
||||||
}
|
}
|
||||||
offset := len(openComment)
|
offset := len(openComment)
|
||||||
line = line[offset:]
|
line = line[offset:]
|
||||||
for {
|
for {
|
||||||
hindex := bytes.Index(line, doubleHyphen)
|
index := bytes.Index(line, closeComment)
|
||||||
if hindex > -1 {
|
if index > -1 {
|
||||||
hindex += offset
|
node.Segments.Append(segment.WithStop(segment.Start + offset + index + len(closeComment)))
|
||||||
}
|
block.Advance(offset + index + len(closeComment))
|
||||||
index := bytes.Index(line, closeComment) + offset
|
|
||||||
if index > -1 && hindex == index {
|
|
||||||
if index == 0 || len(line) < 2 || line[index-offset-1] != '-' {
|
|
||||||
node.Segments.Append(segment.WithStop(segment.Start + index + len(closeComment)))
|
|
||||||
block.Advance(index + len(closeComment))
|
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
}
|
offset = 0
|
||||||
if hindex > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
node.Segments.Append(segment)
|
node.Segments.Append(segment)
|
||||||
block.AdvanceLine()
|
block.AdvanceLine()
|
||||||
line, segment = block.PeekLine()
|
line, segment = block.PeekLine()
|
||||||
offset = 0
|
|
||||||
if line == nil {
|
if line == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -153,9 +144,8 @@ func (s *rawHTMLParser) parseMultiLineRegexp(reg *regexp.Regexp, block text.Read
|
||||||
if l == eline {
|
if l == eline {
|
||||||
block.Advance(end - start)
|
block.Advance(end - start)
|
||||||
break
|
break
|
||||||
} else {
|
|
||||||
block.AdvanceLine()
|
|
||||||
}
|
}
|
||||||
|
block.AdvanceLine()
|
||||||
}
|
}
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ func (b *setextHeadingParser) Close(node ast.Node, reader text.Reader, pc Contex
|
||||||
para.Lines().Append(segment)
|
para.Lines().Append(segment)
|
||||||
heading.Parent().InsertAfter(heading.Parent(), heading, para)
|
heading.Parent().InsertAfter(heading.Parent(), heading, para)
|
||||||
} else {
|
} else {
|
||||||
next.(ast.Node).Lines().Unshift(segment)
|
next.Lines().Unshift(segment)
|
||||||
}
|
}
|
||||||
heading.Parent().RemoveChild(heading.Parent(), heading)
|
heading.Parent().RemoveChild(heading.Parent(), heading)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
|
// Package html implements renderer that outputs HTMLs.
|
||||||
package html
|
package html
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/ast"
|
"github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/renderer"
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
|
@ -14,6 +17,7 @@ import (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Writer Writer
|
Writer Writer
|
||||||
HardWraps bool
|
HardWraps bool
|
||||||
|
EastAsianLineBreaks EastAsianLineBreaks
|
||||||
XHTML bool
|
XHTML bool
|
||||||
Unsafe bool
|
Unsafe bool
|
||||||
}
|
}
|
||||||
|
|
@ -23,6 +27,7 @@ func NewConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Writer: DefaultWriter,
|
Writer: DefaultWriter,
|
||||||
HardWraps: false,
|
HardWraps: false,
|
||||||
|
EastAsianLineBreaks: EastAsianLineBreaksNone,
|
||||||
XHTML: false,
|
XHTML: false,
|
||||||
Unsafe: false,
|
Unsafe: false,
|
||||||
}
|
}
|
||||||
|
|
@ -33,6 +38,8 @@ func (c *Config) SetOption(name renderer.OptionName, value interface{}) {
|
||||||
switch name {
|
switch name {
|
||||||
case optHardWraps:
|
case optHardWraps:
|
||||||
c.HardWraps = value.(bool)
|
c.HardWraps = value.(bool)
|
||||||
|
case optEastAsianLineBreaks:
|
||||||
|
c.EastAsianLineBreaks = value.(EastAsianLineBreaks)
|
||||||
case optXHTML:
|
case optXHTML:
|
||||||
c.XHTML = value.(bool)
|
c.XHTML = value.(bool)
|
||||||
case optUnsafe:
|
case optUnsafe:
|
||||||
|
|
@ -94,6 +101,99 @@ func WithHardWraps() interface {
|
||||||
return &withHardWraps{}
|
return &withHardWraps{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EastAsianLineBreaks is an option name used in WithEastAsianLineBreaks.
|
||||||
|
const optEastAsianLineBreaks renderer.OptionName = "EastAsianLineBreaks"
|
||||||
|
|
||||||
|
// A EastAsianLineBreaks is a style of east asian line breaks.
|
||||||
|
type EastAsianLineBreaks int
|
||||||
|
|
||||||
|
const (
|
||||||
|
//EastAsianLineBreaksNone renders line breaks as it is.
|
||||||
|
EastAsianLineBreaksNone EastAsianLineBreaks = iota
|
||||||
|
// EastAsianLineBreaksSimple follows east_asian_line_breaks in Pandoc.
|
||||||
|
EastAsianLineBreaksSimple
|
||||||
|
// EastAsianLineBreaksCSS3Draft follows CSS text level3 "Segment Break Transformation Rules" with some enhancements.
|
||||||
|
EastAsianLineBreaksCSS3Draft
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b EastAsianLineBreaks) softLineBreak(thisLastRune rune, siblingFirstRune rune) bool {
|
||||||
|
switch b {
|
||||||
|
case EastAsianLineBreaksNone:
|
||||||
|
return false
|
||||||
|
case EastAsianLineBreaksSimple:
|
||||||
|
return !(util.IsEastAsianWideRune(thisLastRune) && util.IsEastAsianWideRune(siblingFirstRune))
|
||||||
|
case EastAsianLineBreaksCSS3Draft:
|
||||||
|
return eastAsianLineBreaksCSS3DraftSoftLineBreak(thisLastRune, siblingFirstRune)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func eastAsianLineBreaksCSS3DraftSoftLineBreak(thisLastRune rune, siblingFirstRune rune) bool {
|
||||||
|
// Implements CSS text level3 Segment Break Transformation Rules with some enhancements.
|
||||||
|
// References:
|
||||||
|
// - https://www.w3.org/TR/2020/WD-css-text-3-20200429/#line-break-transform
|
||||||
|
// - https://github.com/w3c/csswg-drafts/issues/5086
|
||||||
|
|
||||||
|
// Rule1:
|
||||||
|
// If the character immediately before or immediately after the segment break is
|
||||||
|
// the zero-width space character (U+200B), then the break is removed, leaving behind the zero-width space.
|
||||||
|
if thisLastRune == '\u200B' || siblingFirstRune == '\u200B' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule2:
|
||||||
|
// Otherwise, if the East Asian Width property of both the character before and after the segment break is
|
||||||
|
// F, W, or H (not A), and neither side is Hangul, then the segment break is removed.
|
||||||
|
thisLastRuneEastAsianWidth := util.EastAsianWidth(thisLastRune)
|
||||||
|
siblingFirstRuneEastAsianWidth := util.EastAsianWidth(siblingFirstRune)
|
||||||
|
if (thisLastRuneEastAsianWidth == "F" ||
|
||||||
|
thisLastRuneEastAsianWidth == "W" ||
|
||||||
|
thisLastRuneEastAsianWidth == "H") &&
|
||||||
|
(siblingFirstRuneEastAsianWidth == "F" ||
|
||||||
|
siblingFirstRuneEastAsianWidth == "W" ||
|
||||||
|
siblingFirstRuneEastAsianWidth == "H") {
|
||||||
|
return unicode.Is(unicode.Hangul, thisLastRune) || unicode.Is(unicode.Hangul, siblingFirstRune)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule3:
|
||||||
|
// Otherwise, if either the character before or after the segment break belongs to
|
||||||
|
// the space-discarding character set and it is a Unicode Punctuation (P*) or U+3000,
|
||||||
|
// then the segment break is removed.
|
||||||
|
if util.IsSpaceDiscardingUnicodeRune(thisLastRune) ||
|
||||||
|
unicode.IsPunct(thisLastRune) ||
|
||||||
|
thisLastRune == '\u3000' ||
|
||||||
|
util.IsSpaceDiscardingUnicodeRune(siblingFirstRune) ||
|
||||||
|
unicode.IsPunct(siblingFirstRune) ||
|
||||||
|
siblingFirstRune == '\u3000' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rule4:
|
||||||
|
// Otherwise, the segment break is converted to a space (U+0020).
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type withEastAsianLineBreaks struct {
|
||||||
|
eastAsianLineBreaksStyle EastAsianLineBreaks
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withEastAsianLineBreaks) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[optEastAsianLineBreaks] = o.eastAsianLineBreaksStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withEastAsianLineBreaks) SetHTMLOption(c *Config) {
|
||||||
|
c.EastAsianLineBreaks = o.eastAsianLineBreaksStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
|
||||||
|
// between east asian wide characters should be ignored.
|
||||||
|
func WithEastAsianLineBreaks(e EastAsianLineBreaks) interface {
|
||||||
|
renderer.Option
|
||||||
|
Option
|
||||||
|
} {
|
||||||
|
return &withEastAsianLineBreaks{e}
|
||||||
|
}
|
||||||
|
|
||||||
// XHTML is an option name used in WithXHTML.
|
// XHTML is an option name used in WithXHTML.
|
||||||
const optXHTML renderer.OptionName = "XHTML"
|
const optXHTML renderer.OptionName = "XHTML"
|
||||||
|
|
||||||
|
|
@ -216,6 +316,7 @@ var GlobalAttributeFilter = util.NewBytesFilter(
|
||||||
[]byte("itemtype"),
|
[]byte("itemtype"),
|
||||||
[]byte("lang"),
|
[]byte("lang"),
|
||||||
[]byte("part"),
|
[]byte("part"),
|
||||||
|
[]byte("role"),
|
||||||
[]byte("slot"),
|
[]byte("slot"),
|
||||||
[]byte("spellcheck"),
|
[]byte("spellcheck"),
|
||||||
[]byte("style"),
|
[]byte("style"),
|
||||||
|
|
@ -224,15 +325,17 @@ var GlobalAttributeFilter = util.NewBytesFilter(
|
||||||
[]byte("translate"),
|
[]byte("translate"),
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderDocument(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeadingAttributeFilter defines attribute names which heading elements can have
|
// HeadingAttributeFilter defines attribute names which heading elements can have.
|
||||||
var HeadingAttributeFilter = GlobalAttributeFilter
|
var HeadingAttributeFilter = GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderHeading(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Heading)
|
n := node.(*ast.Heading)
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<h")
|
_, _ = w.WriteString("<h")
|
||||||
|
|
@ -249,12 +352,13 @@ func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockquoteAttributeFilter defines attribute names which blockquote elements can have
|
// BlockquoteAttributeFilter defines attribute names which blockquote elements can have.
|
||||||
var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend(
|
var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
[]byte("cite"),
|
[]byte("cite"),
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderBlockquote(
|
||||||
|
w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
_, _ = w.WriteString("<blockquote")
|
_, _ = w.WriteString("<blockquote")
|
||||||
|
|
@ -279,7 +383,8 @@ func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderFencedCodeBlock(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.FencedCodeBlock)
|
n := node.(*ast.FencedCodeBlock)
|
||||||
if entering {
|
if entering {
|
||||||
_, _ = w.WriteString("<pre><code")
|
_, _ = w.WriteString("<pre><code")
|
||||||
|
|
@ -297,7 +402,8 @@ func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node a
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderHTMLBlock(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.HTMLBlock)
|
n := node.(*ast.HTMLBlock)
|
||||||
if entering {
|
if entering {
|
||||||
if r.Unsafe {
|
if r.Unsafe {
|
||||||
|
|
@ -339,7 +445,7 @@ func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
_ = w.WriteByte('<')
|
_ = w.WriteByte('<')
|
||||||
_, _ = w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
if n.IsOrdered() && n.Start != 1 {
|
if n.IsOrdered() && n.Start != 1 {
|
||||||
fmt.Fprintf(w, " start=\"%d\"", n.Start)
|
_, _ = fmt.Fprintf(w, " start=\"%d\"", n.Start)
|
||||||
}
|
}
|
||||||
if n.Attributes() != nil {
|
if n.Attributes() != nil {
|
||||||
RenderAttributes(w, n, ListAttributeFilter)
|
RenderAttributes(w, n, ListAttributeFilter)
|
||||||
|
|
@ -399,7 +505,7 @@ func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node,
|
||||||
|
|
||||||
func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
if _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil {
|
if n.NextSibling() != nil && n.FirstChild() != nil {
|
||||||
_ = w.WriteByte('\n')
|
_ = w.WriteByte('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -415,7 +521,8 @@ var ThematicAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
[]byte("width"), // [Deprecated]
|
[]byte("width"), // [Deprecated]
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderThematicBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderThematicBreak(
|
||||||
|
w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +551,8 @@ var LinkAttributeFilter = GlobalAttributeFilter.Extend(
|
||||||
[]byte("target"),
|
[]byte("target"),
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderAutoLink(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.AutoLink)
|
n := node.(*ast.AutoLink)
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
|
|
@ -499,7 +607,8 @@ func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, e
|
||||||
// EmphasisAttributeFilter defines attribute names which emphasis elements can have.
|
// EmphasisAttributeFilter defines attribute names which emphasis elements can have.
|
||||||
var EmphasisAttributeFilter = GlobalAttributeFilter
|
var EmphasisAttributeFilter = GlobalAttributeFilter
|
||||||
|
|
||||||
func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderEmphasis(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
n := node.(*ast.Emphasis)
|
n := node.(*ast.Emphasis)
|
||||||
tag := "em"
|
tag := "em"
|
||||||
if n.Level == 2 {
|
if n.Level == 2 {
|
||||||
|
|
@ -571,7 +680,7 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
|
||||||
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||||
}
|
}
|
||||||
_, _ = w.WriteString(`" alt="`)
|
_, _ = w.WriteString(`" alt="`)
|
||||||
_, _ = w.Write(util.EscapeHTML(n.Text(source)))
|
r.renderTexts(w, source, n)
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
if n.Title != nil {
|
if n.Title != nil {
|
||||||
_, _ = w.WriteString(` title="`)
|
_, _ = w.WriteString(` title="`)
|
||||||
|
|
@ -589,7 +698,8 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, e
|
||||||
return ast.WalkSkipChildren, nil
|
return ast.WalkSkipChildren, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
func (r *Renderer) renderRawHTML(
|
||||||
|
w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkSkipChildren, nil
|
return ast.WalkSkipChildren, nil
|
||||||
}
|
}
|
||||||
|
|
@ -615,7 +725,8 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
if n.IsRaw() {
|
if n.IsRaw() {
|
||||||
r.Writer.RawWrite(w, segment.Value(source))
|
r.Writer.RawWrite(w, segment.Value(source))
|
||||||
} else {
|
} else {
|
||||||
r.Writer.Write(w, segment.Value(source))
|
value := segment.Value(source)
|
||||||
|
r.Writer.Write(w, value)
|
||||||
if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) {
|
if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) {
|
||||||
if r.XHTML {
|
if r.XHTML {
|
||||||
_, _ = w.WriteString("<br />\n")
|
_, _ = w.WriteString("<br />\n")
|
||||||
|
|
@ -623,9 +734,22 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
_, _ = w.WriteString("<br>\n")
|
_, _ = w.WriteString("<br>\n")
|
||||||
}
|
}
|
||||||
} else if n.SoftLineBreak() {
|
} else if n.SoftLineBreak() {
|
||||||
|
if r.EastAsianLineBreaks != EastAsianLineBreaksNone && len(value) != 0 {
|
||||||
|
sibling := node.NextSibling()
|
||||||
|
if sibling != nil && sibling.Kind() == ast.KindText {
|
||||||
|
if siblingText := sibling.(*ast.Text).Value(source); len(siblingText) != 0 {
|
||||||
|
thisLastRune := util.ToRune(value, len(value)-1)
|
||||||
|
siblingFirstRune, _ := utf8.DecodeRune(siblingText)
|
||||||
|
if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) {
|
||||||
_ = w.WriteByte('\n')
|
_ = w.WriteByte('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = w.WriteByte('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -646,6 +770,18 @@ func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) renderTexts(w util.BufWriter, source []byte, n ast.Node) {
|
||||||
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
|
if s, ok := c.(*ast.String); ok {
|
||||||
|
_, _ = r.renderString(w, source, s, true)
|
||||||
|
} else if t, ok := c.(*ast.Text); ok {
|
||||||
|
_, _ = r.renderText(w, source, t, true)
|
||||||
|
} else {
|
||||||
|
r.renderTexts(w, source, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var dataPrefix = []byte("data-")
|
var dataPrefix = []byte("data-")
|
||||||
|
|
||||||
// RenderAttributes renders given node's attributes.
|
// RenderAttributes renders given node's attributes.
|
||||||
|
|
@ -662,7 +798,14 @@ func RenderAttributes(w util.BufWriter, node ast.Node, filter util.BytesFilter)
|
||||||
_, _ = w.Write(attr.Name)
|
_, _ = w.Write(attr.Name)
|
||||||
_, _ = w.WriteString(`="`)
|
_, _ = w.WriteString(`="`)
|
||||||
// TODO: convert numeric values to strings
|
// TODO: convert numeric values to strings
|
||||||
_, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
|
var value []byte
|
||||||
|
switch typed := attr.Value.(type) {
|
||||||
|
case []byte:
|
||||||
|
value = typed
|
||||||
|
case string:
|
||||||
|
value = util.StringToReadOnlyBytes(typed)
|
||||||
|
}
|
||||||
|
_, _ = w.Write(util.EscapeHTML(value))
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -683,7 +826,33 @@ type Writer interface {
|
||||||
|
|
||||||
var replacementCharacter = []byte("\ufffd")
|
var replacementCharacter = []byte("\ufffd")
|
||||||
|
|
||||||
|
// A WriterConfig struct has configurations for the HTML based writers.
|
||||||
|
type WriterConfig struct {
|
||||||
|
// EscapedSpace is an option that indicates that a '\' escaped half-space(0x20) should not be rendered.
|
||||||
|
EscapedSpace bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// A WriterOption interface sets options for HTML based writers.
|
||||||
|
type WriterOption func(*WriterConfig)
|
||||||
|
|
||||||
|
// WithEscapedSpace is a WriterOption indicates that a '\' escaped half-space(0x20) should not be rendered.
|
||||||
|
func WithEscapedSpace() WriterOption {
|
||||||
|
return func(c *WriterConfig) {
|
||||||
|
c.EscapedSpace = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type defaultWriter struct {
|
type defaultWriter struct {
|
||||||
|
WriterConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWriter returns a new Writer.
|
||||||
|
func NewWriter(opts ...WriterOption) Writer {
|
||||||
|
w := &defaultWriter{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&w.WriterConfig)
|
||||||
|
}
|
||||||
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
func escapeRune(writer util.BufWriter, r rune) {
|
func escapeRune(writer util.BufWriter, r rune) {
|
||||||
|
|
@ -746,6 +915,12 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
||||||
escaped = false
|
escaped = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if d.EscapedSpace && c == ' ' {
|
||||||
|
d.RawWrite(writer, source[n:i-1])
|
||||||
|
n = i + 1
|
||||||
|
escaped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if c == '\x00' {
|
if c == '\x00' {
|
||||||
d.RawWrite(writer, source[n:i])
|
d.RawWrite(writer, source[n:i])
|
||||||
|
|
@ -811,32 +986,36 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
||||||
d.RawWrite(writer, source[n:])
|
d.RawWrite(writer, source[n:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultWriter is a default implementation of the Writer.
|
// DefaultWriter is a default instance of the Writer.
|
||||||
var DefaultWriter = &defaultWriter{}
|
var DefaultWriter = NewWriter()
|
||||||
|
|
||||||
var bDataImage = []byte("data:image/")
|
var bDataImage = []byte("data:image/")
|
||||||
var bPng = []byte("png;")
|
var bPng = []byte("png;")
|
||||||
var bGif = []byte("gif;")
|
var bGif = []byte("gif;")
|
||||||
var bJpeg = []byte("jpeg;")
|
var bJpeg = []byte("jpeg;")
|
||||||
var bWebp = []byte("webp;")
|
var bWebp = []byte("webp;")
|
||||||
var bSvg = []byte("svg;")
|
var bSvg = []byte("svg+xml;")
|
||||||
var bJs = []byte("javascript:")
|
var bJs = []byte("javascript:")
|
||||||
var bVb = []byte("vbscript:")
|
var bVb = []byte("vbscript:")
|
||||||
var bFile = []byte("file:")
|
var bFile = []byte("file:")
|
||||||
var bData = []byte("data:")
|
var bData = []byte("data:")
|
||||||
|
|
||||||
|
func hasPrefix(s, prefix []byte) bool {
|
||||||
|
return len(s) >= len(prefix) && bytes.Equal(bytes.ToLower(s[0:len(prefix)]), bytes.ToLower(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
// IsDangerousURL returns true if the given url seems a potentially dangerous url,
|
// IsDangerousURL returns true if the given url seems a potentially dangerous url,
|
||||||
// otherwise false.
|
// otherwise false.
|
||||||
func IsDangerousURL(url []byte) bool {
|
func IsDangerousURL(url []byte) bool {
|
||||||
if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
|
if hasPrefix(url, bDataImage) && len(url) >= 11 {
|
||||||
v := url[11:]
|
v := url[11:]
|
||||||
if bytes.HasPrefix(v, bPng) || bytes.HasPrefix(v, bGif) ||
|
if hasPrefix(v, bPng) || hasPrefix(v, bGif) ||
|
||||||
bytes.HasPrefix(v, bJpeg) || bytes.HasPrefix(v, bWebp) ||
|
hasPrefix(v, bJpeg) || hasPrefix(v, bWebp) ||
|
||||||
bytes.HasPrefix(v, bSvg) {
|
hasPrefix(v, bSvg) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return bytes.HasPrefix(url, bJs) || bytes.HasPrefix(url, bVb) ||
|
return hasPrefix(url, bJs) || hasPrefix(url, bVb) ||
|
||||||
bytes.HasPrefix(url, bFile) || bytes.HasPrefix(url, bData)
|
hasPrefix(url, bFile) || hasPrefix(url, bData)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ type Config struct {
|
||||||
NodeRenderers util.PrioritizedSlice
|
NodeRenderers util.PrioritizedSlice
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new Config
|
// NewConfig returns a new Config.
|
||||||
func NewConfig() *Config {
|
func NewConfig() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Options: map[OptionName]interface{}{},
|
Options: map[OptionName]interface{}{},
|
||||||
|
|
@ -78,7 +78,7 @@ type NodeRenderer interface {
|
||||||
RegisterFuncs(NodeRendererFuncRegisterer)
|
RegisterFuncs(NodeRendererFuncRegisterer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A NodeRendererFuncRegisterer registers
|
// A NodeRendererFuncRegisterer registers given NodeRendererFunc to this object.
|
||||||
type NodeRendererFuncRegisterer interface {
|
type NodeRendererFuncRegisterer interface {
|
||||||
// Register registers given NodeRendererFunc to this object.
|
// Register registers given NodeRendererFunc to this object.
|
||||||
Register(ast.NodeKind, NodeRendererFunc)
|
Register(ast.NodeKind, NodeRendererFunc)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package testutil provides utilities for unit tests.
|
||||||
package testutil
|
package testutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -65,7 +66,7 @@ type MarkdownTestCaseOptions struct {
|
||||||
const attributeSeparator = "//- - - - - - - - -//"
|
const attributeSeparator = "//- - - - - - - - -//"
|
||||||
const caseSeparator = "//= = = = = = = = = = = = = = = = = = = = = = = =//"
|
const caseSeparator = "//= = = = = = = = = = = = = = = = = = = = = = = =//"
|
||||||
|
|
||||||
var optionsRegexp *regexp.Regexp = regexp.MustCompile(`(?i)\s*options:(.*)`)
|
var optionsRegexp = regexp.MustCompile(`(?i)\s*options:(.*)`)
|
||||||
|
|
||||||
// ParseCliCaseArg parses -case command line args.
|
// ParseCliCaseArg parses -case command line args.
|
||||||
func ParseCliCaseArg() []int {
|
func ParseCliCaseArg() []int {
|
||||||
|
|
@ -90,7 +91,9 @@ func DoTestCaseFile(m goldmark.Markdown, filename string, t TestingT, no ...int)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer fp.Close()
|
defer func() {
|
||||||
|
_ = fp.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(fp)
|
scanner := bufio.NewScanner(fp)
|
||||||
c := MarkdownTestCase{
|
c := MarkdownTestCase{
|
||||||
|
|
|
||||||
2
text/package.go
Normal file
2
text/package.go
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// Package text provides functionalities to manipulate texts.
|
||||||
|
package text
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package text
|
package text
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
@ -75,7 +76,7 @@ type Reader interface {
|
||||||
FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool)
|
FindClosure(opener, closer byte, options FindClosureOptions) (*Segments, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindClosureOptions is options for Reader.FindClosure
|
// FindClosureOptions is options for Reader.FindClosure.
|
||||||
type FindClosureOptions struct {
|
type FindClosureOptions struct {
|
||||||
// CodeSpan is a flag for the FindClosure. If this is set to true,
|
// CodeSpan is a flag for the FindClosure. If this is set to true,
|
||||||
// FindClosure ignores closers in codespans.
|
// FindClosure ignores closers in codespans.
|
||||||
|
|
@ -153,7 +154,7 @@ func (r *reader) PeekLine() ([]byte, Segment) {
|
||||||
return nil, r.pos
|
return nil, r.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
// io.RuneReader interface
|
// io.RuneReader interface.
|
||||||
func (r *reader) ReadRune() (rune, int, error) {
|
func (r *reader) ReadRune() (rune, int, error) {
|
||||||
return readRuneReader(r)
|
return readRuneReader(r)
|
||||||
}
|
}
|
||||||
|
|
@ -353,7 +354,7 @@ func (r *blockReader) Value(seg Segment) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// io.RuneReader interface
|
// io.RuneReader interface.
|
||||||
func (r *blockReader) ReadRune() (rune, int, error) {
|
func (r *blockReader) ReadRune() (rune, int, error) {
|
||||||
return readRuneReader(r)
|
return readRuneReader(r)
|
||||||
}
|
}
|
||||||
|
|
@ -537,24 +538,30 @@ func matchReader(r Reader, reg *regexp.Regexp) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func findSubMatchReader(r Reader, reg *regexp.Regexp) [][]byte {
|
func findSubMatchReader(r Reader, reg *regexp.Regexp) [][]byte {
|
||||||
oldline, oldseg := r.Position()
|
oldLine, oldSeg := r.Position()
|
||||||
match := reg.FindReaderSubmatchIndex(r)
|
match := reg.FindReaderSubmatchIndex(r)
|
||||||
r.SetPosition(oldline, oldseg)
|
r.SetPosition(oldLine, oldSeg)
|
||||||
if match == nil {
|
if match == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
runes := make([]rune, 0, match[1]-match[0])
|
var bb bytes.Buffer
|
||||||
|
bb.Grow(match[1] - match[0])
|
||||||
for i := 0; i < match[1]; {
|
for i := 0; i < match[1]; {
|
||||||
r, size, _ := readRuneReader(r)
|
r, size, _ := readRuneReader(r)
|
||||||
i += size
|
i += size
|
||||||
runes = append(runes, r)
|
bb.WriteRune(r)
|
||||||
}
|
}
|
||||||
result := [][]byte{}
|
bs := bb.Bytes()
|
||||||
|
var result [][]byte
|
||||||
for i := 0; i < len(match); i += 2 {
|
for i := 0; i < len(match); i += 2 {
|
||||||
result = append(result, []byte(string(runes[match[i]:match[i+1]])))
|
if match[i] < 0 {
|
||||||
|
result = append(result, []byte{})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, bs[match[i]:match[i+1]])
|
||||||
}
|
}
|
||||||
|
|
||||||
r.SetPosition(oldline, oldseg)
|
r.SetPosition(oldLine, oldSeg)
|
||||||
r.Advance(match[1] - match[0])
|
r.Advance(match[1] - match[0])
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
text/reader_test.go
Normal file
16
text/reader_test.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package text
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindSubMatchReader(t *testing.T) {
|
||||||
|
s := "微笑"
|
||||||
|
r := NewReader([]byte(":" + s + ":"))
|
||||||
|
reg := regexp.MustCompile(`:(\p{L}+):`)
|
||||||
|
match := r.FindSubMatch(reg)
|
||||||
|
if len(match) != 2 || string(match[1]) != s {
|
||||||
|
t.Fatal("no match cjk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ package text
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"github.com/yuin/goldmark/util"
|
"github.com/yuin/goldmark/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,6 +19,20 @@ type Segment struct {
|
||||||
|
|
||||||
// Padding is a padding length of the segment.
|
// Padding is a padding length of the segment.
|
||||||
Padding int
|
Padding int
|
||||||
|
|
||||||
|
// ForceNewline is true if the segment should be ended with a newline.
|
||||||
|
// Some elements(i.e. CodeBlock, FencedCodeBlock) does not trim trailing
|
||||||
|
// newlines. Spec defines that EOF is treated as a newline, so we need to
|
||||||
|
// add a newline to the end of the segment if it is not empty.
|
||||||
|
//
|
||||||
|
// i.e.:
|
||||||
|
//
|
||||||
|
// ```go
|
||||||
|
// const test = "test"
|
||||||
|
//
|
||||||
|
// This code does not close the code block and ends with EOF. In this case,
|
||||||
|
// we need to add a newline to the end of the last line like `const test = "test"\n`.
|
||||||
|
ForceNewline bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSegment return a new Segment.
|
// NewSegment return a new Segment.
|
||||||
|
|
@ -40,12 +55,18 @@ func NewSegmentPadding(start, stop, n int) Segment {
|
||||||
|
|
||||||
// Value returns a value of the segment.
|
// Value returns a value of the segment.
|
||||||
func (t *Segment) Value(buffer []byte) []byte {
|
func (t *Segment) Value(buffer []byte) []byte {
|
||||||
|
var result []byte
|
||||||
if t.Padding == 0 {
|
if t.Padding == 0 {
|
||||||
return buffer[t.Start:t.Stop]
|
result = buffer[t.Start:t.Stop]
|
||||||
}
|
} else {
|
||||||
result := make([]byte, 0, t.Padding+t.Stop-t.Start+1)
|
result = make([]byte, 0, t.Padding+t.Stop-t.Start+1)
|
||||||
result = append(result, bytes.Repeat(space, t.Padding)...)
|
result = append(result, bytes.Repeat(space, t.Padding)...)
|
||||||
return append(result, buffer[t.Start:t.Stop]...)
|
result = append(result, buffer[t.Start:t.Stop]...)
|
||||||
|
}
|
||||||
|
if t.ForceNewline && len(result) > 0 && result[len(result)-1] != '\n' {
|
||||||
|
result = append(result, '\n')
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns a length of the segment.
|
// Len returns a length of the segment.
|
||||||
|
|
@ -207,3 +228,12 @@ func (s *Segments) Unshift(v Segment) {
|
||||||
s.values = append(s.values[0:1], s.values[0:]...)
|
s.values = append(s.values[0:1], s.values[0:]...)
|
||||||
s.values[0] = v
|
s.values[0] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Value returns a string value of the collection.
|
||||||
|
func (s *Segments) Value(buffer []byte) []byte {
|
||||||
|
var result []byte
|
||||||
|
for _, v := range s.values {
|
||||||
|
result = append(result, v.Value(buffer)...)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
//nolint:golint,lll,misspell
|
||||||
package util
|
package util
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
// An HTML5Entity struct represents HTML5 entitites.
|
// An HTML5Entity struct represents HTML5 entitites.
|
||||||
type HTML5Entity struct {
|
type HTML5Entity struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -8,13 +11,20 @@ type HTML5Entity struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookUpHTML5EntityByName returns (an HTML5Entity, true) if an entity named
|
// LookUpHTML5EntityByName returns (an HTML5Entity, true) if an entity named
|
||||||
// given name is found, otherwise (nil, false)
|
// given name is found, otherwise (nil, false).
|
||||||
func LookUpHTML5EntityByName(name string) (*HTML5Entity, bool) {
|
func LookUpHTML5EntityByName(name string) (*HTML5Entity, bool) {
|
||||||
v, ok := html5entities[name]
|
v, ok := html5entities()[name]
|
||||||
return v, ok
|
return v, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
var html5entities = map[string]*HTML5Entity{
|
var html5entitiesOnce sync.Once // TODO: uses sync.OnceValue for future
|
||||||
|
|
||||||
|
var _html5entities map[string]*HTML5Entity
|
||||||
|
|
||||||
|
func html5entities() map[string]*HTML5Entity {
|
||||||
|
html5entitiesOnce.Do(func() {
|
||||||
|
_html5entities =
|
||||||
|
map[string]*HTML5Entity{
|
||||||
"AElig": {Name: "AElig", CodePoints: []int{198}, Characters: []byte{0xc3, 0x86}},
|
"AElig": {Name: "AElig", CodePoints: []int{198}, Characters: []byte{0xc3, 0x86}},
|
||||||
"AMP": {Name: "AMP", CodePoints: []int{38}, Characters: []byte{0x26}},
|
"AMP": {Name: "AMP", CodePoints: []int{38}, Characters: []byte{0x26}},
|
||||||
"Aacute": {Name: "Aacute", CodePoints: []int{193}, Characters: []byte{0xc3, 0x81}},
|
"Aacute": {Name: "Aacute", CodePoints: []int{193}, Characters: []byte{0xc3, 0x81}},
|
||||||
|
|
@ -2139,4 +2149,7 @@ var html5entities = map[string]*HTML5Entity{
|
||||||
"zscr": {Name: "zscr", CodePoints: []int{120015}, Characters: []byte{0xf0, 0x9d, 0x93, 0x8f}},
|
"zscr": {Name: "zscr", CodePoints: []int{120015}, Characters: []byte{0xf0, 0x9d, 0x93, 0x8f}},
|
||||||
"zwj": {Name: "zwj", CodePoints: []int{8205}, Characters: []byte{0xe2, 0x80, 0x8d}},
|
"zwj": {Name: "zwj", CodePoints: []int{8205}, Characters: []byte{0xe2, 0x80, 0x8d}},
|
||||||
"zwnj": {Name: "zwnj", CodePoints: []int{8204}, Characters: []byte{0xe2, 0x80, 0x8c}},
|
"zwnj": {Name: "zwnj", CodePoints: []int{8204}, Characters: []byte{0xe2, 0x80, 0x8c}},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return _html5entities
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
44
util/util.go
44
util/util.go
|
|
@ -63,12 +63,13 @@ func (b *CopyOnWriteBuffer) AppendString(value string) {
|
||||||
|
|
||||||
// WriteByte writes the given byte to the buffer.
|
// WriteByte writes the given byte to the buffer.
|
||||||
// WriteByte allocate new buffer and clears it at the first time.
|
// WriteByte allocate new buffer and clears it at the first time.
|
||||||
func (b *CopyOnWriteBuffer) WriteByte(c byte) {
|
func (b *CopyOnWriteBuffer) WriteByte(c byte) error {
|
||||||
if !b.copied {
|
if !b.copied {
|
||||||
b.buffer = make([]byte, 0, len(b.buffer)+20)
|
b.buffer = make([]byte, 0, len(b.buffer)+20)
|
||||||
b.copied = true
|
b.copied = true
|
||||||
}
|
}
|
||||||
b.buffer = append(b.buffer, c)
|
b.buffer = append(b.buffer, c)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppendByte appends given bytes to the buffer.
|
// AppendByte appends given bytes to the buffer.
|
||||||
|
|
@ -150,7 +151,7 @@ func TabWidth(currentPos int) int {
|
||||||
// width: 1234 5678
|
// width: 1234 5678
|
||||||
//
|
//
|
||||||
// width=2 is in the tab character. In this case, IndentPosition returns
|
// width=2 is in the tab character. In this case, IndentPosition returns
|
||||||
// (pos=1, padding=2)
|
// (pos=1, padding=2).
|
||||||
func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
|
func IndentPosition(bs []byte, currentPos, width int) (pos, padding int) {
|
||||||
return IndentPositionPadding(bs, currentPos, 0, width)
|
return IndentPositionPadding(bs, currentPos, 0, width)
|
||||||
}
|
}
|
||||||
|
|
@ -165,7 +166,13 @@ func IndentPositionPadding(bs []byte, currentPos, paddingv, width int) (pos, pad
|
||||||
w := 0
|
w := 0
|
||||||
i := 0
|
i := 0
|
||||||
l := len(bs)
|
l := len(bs)
|
||||||
|
p := paddingv
|
||||||
for ; i < l; i++ {
|
for ; i < l; i++ {
|
||||||
|
if p > 0 {
|
||||||
|
p--
|
||||||
|
w++
|
||||||
|
continue
|
||||||
|
}
|
||||||
if bs[i] == '\t' && w < width {
|
if bs[i] == '\t' && w < width {
|
||||||
w += TabWidth(currentPos + w)
|
w += TabWidth(currentPos + w)
|
||||||
} else if bs[i] == ' ' && w < width {
|
} else if bs[i] == ' ' && w < width {
|
||||||
|
|
@ -424,7 +431,7 @@ func DoFullUnicodeCaseFolding(v []byte) []byte {
|
||||||
if c >= 0x41 && c <= 0x5a {
|
if c >= 0x41 && c <= 0x5a {
|
||||||
// A-Z to a-z
|
// A-Z to a-z
|
||||||
cob.Write(v[n:i])
|
cob.Write(v[n:i])
|
||||||
cob.WriteByte(c + 32)
|
_ = cob.WriteByte(c + 32)
|
||||||
n = i + 1
|
n = i + 1
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|
@ -521,7 +528,7 @@ func ToLinkReference(v []byte) string {
|
||||||
return string(ReplaceSpaces(v, ' '))
|
return string(ReplaceSpaces(v, ' '))
|
||||||
}
|
}
|
||||||
|
|
||||||
var htmlEscapeTable = [256][]byte{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("""), nil, nil, nil, []byte("&"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("<"), nil, []byte(">"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil}
|
var htmlEscapeTable = [256][]byte{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("""), nil, nil, nil, []byte("&"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, []byte("<"), nil, []byte(">"), nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil} //nolint:golint,lll
|
||||||
|
|
||||||
// EscapeHTMLByte returns HTML escaped bytes if the given byte should be escaped,
|
// EscapeHTMLByte returns HTML escaped bytes if the given byte should be escaped,
|
||||||
// otherwise nil.
|
// otherwise nil.
|
||||||
|
|
@ -557,7 +564,7 @@ func UnescapePunctuations(source []byte) []byte {
|
||||||
c := source[i]
|
c := source[i]
|
||||||
if i < limit-1 && c == '\\' && IsPunct(source[i+1]) {
|
if i < limit-1 && c == '\\' && IsPunct(source[i+1]) {
|
||||||
cob.Write(source[n:i])
|
cob.Write(source[n:i])
|
||||||
cob.WriteByte(source[i+1])
|
_ = cob.WriteByte(source[i+1])
|
||||||
i += 2
|
i += 2
|
||||||
n = i
|
n = i
|
||||||
continue
|
continue
|
||||||
|
|
@ -573,9 +580,9 @@ func UnescapePunctuations(source []byte) []byte {
|
||||||
// ResolveNumericReferences resolve numeric references like 'Ӓ" .
|
// ResolveNumericReferences resolve numeric references like 'Ӓ" .
|
||||||
func ResolveNumericReferences(source []byte) []byte {
|
func ResolveNumericReferences(source []byte) []byte {
|
||||||
cob := NewCopyOnWriteBuffer(source)
|
cob := NewCopyOnWriteBuffer(source)
|
||||||
buf := make([]byte, 6, 6)
|
buf := make([]byte, 6)
|
||||||
limit := len(source)
|
limit := len(source)
|
||||||
ok := false
|
var ok bool
|
||||||
n := 0
|
n := 0
|
||||||
for i := 0; i < limit; i++ {
|
for i := 0; i < limit; i++ {
|
||||||
if source[i] == '&' {
|
if source[i] == '&' {
|
||||||
|
|
@ -625,7 +632,7 @@ func ResolveNumericReferences(source []byte) []byte {
|
||||||
func ResolveEntityNames(source []byte) []byte {
|
func ResolveEntityNames(source []byte) []byte {
|
||||||
cob := NewCopyOnWriteBuffer(source)
|
cob := NewCopyOnWriteBuffer(source)
|
||||||
limit := len(source)
|
limit := len(source)
|
||||||
ok := false
|
var ok bool
|
||||||
n := 0
|
n := 0
|
||||||
for i := 0; i < limit; i++ {
|
for i := 0; i < limit; i++ {
|
||||||
if source[i] == '&' {
|
if source[i] == '&' {
|
||||||
|
|
@ -750,7 +757,7 @@ func FindURLIndex(b []byte) int {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
var emailDomainRegexp = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*`)
|
var emailDomainRegexp = regexp.MustCompile(`^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*`) //nolint:golint,lll
|
||||||
|
|
||||||
// FindEmailIndex returns a stop index value if the given bytes seem an email address.
|
// FindEmailIndex returns a stop index value if the given bytes seem an email address.
|
||||||
func FindEmailIndex(b []byte) int {
|
func FindEmailIndex(b []byte) int {
|
||||||
|
|
@ -781,18 +788,19 @@ func FindEmailIndex(b []byte) int {
|
||||||
|
|
||||||
var spaces = []byte(" \t\n\x0b\x0c\x0d")
|
var spaces = []byte(" \t\n\x0b\x0c\x0d")
|
||||||
|
|
||||||
var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
|
||||||
|
|
||||||
var punctTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var punctTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
|
||||||
|
|
||||||
// a-zA-Z0-9, ;/?:@&=+$,-_.!~*'()#
|
// a-zA-Z0-9, ;/?:@&=+$,-_.!~*'()#
|
||||||
var urlEscapeTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
||||||
|
|
||||||
var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99}
|
var urlEscapeTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
|
||||||
|
|
||||||
var urlTable = [256]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 5, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 0, 1, 0, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
|
var utf8lenTable = [256]int8{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 99, 99, 99, 99, 99, 99, 99, 99} //nolint:golint,lll
|
||||||
|
|
||||||
var emailTable = [256]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var urlTable = [256]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 1, 5, 5, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 0, 1, 0, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} //nolint:golint,lll
|
||||||
|
|
||||||
|
var emailTable = [256]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} //nolint:golint,lll
|
||||||
|
|
||||||
// UTF8Len returns a byte length of the utf-8 character.
|
// UTF8Len returns a byte length of the utf-8 character.
|
||||||
func UTF8Len(b byte) int8 {
|
func UTF8Len(b byte) int8 {
|
||||||
|
|
@ -806,7 +814,7 @@ func IsPunct(c byte) bool {
|
||||||
|
|
||||||
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
// IsPunctRune returns true if the given rune is a punctuation, otherwise false.
|
||||||
func IsPunctRune(r rune) bool {
|
func IsPunctRune(r rune) bool {
|
||||||
return int32(r) <= 256 && IsPunct(byte(r)) || unicode.IsPunct(r)
|
return unicode.IsSymbol(r) || unicode.IsPunct(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSpace returns true if the given character is a space, otherwise false.
|
// IsSpace returns true if the given character is a space, otherwise false.
|
||||||
|
|
@ -853,7 +861,7 @@ type PrioritizedValue struct {
|
||||||
Priority int
|
Priority int
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrioritizedSlice is a slice of the PrioritizedValues
|
// PrioritizedSlice is a slice of the PrioritizedValues.
|
||||||
type PrioritizedSlice []PrioritizedValue
|
type PrioritizedSlice []PrioritizedValue
|
||||||
|
|
||||||
// Sort sorts the PrioritizedSlice in ascending order.
|
// Sort sorts the PrioritizedSlice in ascending order.
|
||||||
|
|
@ -968,7 +976,7 @@ func (s *bytesFilter) Contains(b []byte) bool {
|
||||||
}
|
}
|
||||||
h := bytesHash(b) % uint64(len(s.slots))
|
h := bytesHash(b) % uint64(len(s.slots))
|
||||||
slot := s.slots[h]
|
slot := s.slots[h]
|
||||||
if slot == nil || len(slot) == 0 {
|
if len(slot) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for _, element := range slot {
|
for _, element := range slot {
|
||||||
|
|
|
||||||
469
util/util_cjk.go
Normal file
469
util/util_cjk.go
Normal file
|
|
@ -0,0 +1,469 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import "unicode"
|
||||||
|
|
||||||
|
var cjkRadicalsSupplement = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x2E80, 0x2EFF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var kangxiRadicals = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x2F00, 0x2FDF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ideographicDescriptionCharacters = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x2FF0, 0x2FFF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkSymbolsAndPunctuation = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x3000, 0x303F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var hiragana = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x3040, 0x309F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var katakana = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x30A0, 0x30FF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var kanbun = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x3130, 0x318F, 1},
|
||||||
|
{0x3190, 0x319F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkStrokes = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x31C0, 0x31EF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var katakanaPhoneticExtensions = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x31F0, 0x31FF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkCompatibility = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x3300, 0x33FF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionA = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x3400, 0x4DBF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographs = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0x4E00, 0x9FFF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var yiSyllables = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xA000, 0xA48F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var yiRadicals = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xA490, 0xA4CF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkCompatibilityIdeographs = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xF900, 0xFAFF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var verticalForms = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xFE10, 0xFE1F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkCompatibilityForms = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xFE30, 0xFE4F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var smallFormVariants = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xFE50, 0xFE6F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var halfwidthAndFullwidthForms = &unicode.RangeTable{
|
||||||
|
R16: []unicode.Range16{
|
||||||
|
{0xFF00, 0xFFEF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var kanaSupplement = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x1B000, 0x1B0FF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var kanaExtendedA = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x1B100, 0x1B12F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var smallKanaExtension = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x1B130, 0x1B16F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionB = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x20000, 0x2A6DF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionC = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x2A700, 0x2B73F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionD = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x2B740, 0x2B81F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionE = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x2B820, 0x2CEAF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionF = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x2CEB0, 0x2EBEF, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkCompatibilityIdeographsSupplement = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x2F800, 0x2FA1F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var cjkUnifiedIdeographsExtensionG = &unicode.RangeTable{
|
||||||
|
R32: []unicode.Range32{
|
||||||
|
{0x30000, 0x3134F, 1},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEastAsianWideRune returns trhe if the given rune is an east asian wide character, otherwise false.
|
||||||
|
func IsEastAsianWideRune(r rune) bool {
|
||||||
|
return unicode.Is(unicode.Hiragana, r) ||
|
||||||
|
unicode.Is(unicode.Katakana, r) ||
|
||||||
|
unicode.Is(unicode.Han, r) ||
|
||||||
|
unicode.Is(unicode.Lm, r) ||
|
||||||
|
unicode.Is(unicode.Hangul, r) ||
|
||||||
|
unicode.Is(cjkSymbolsAndPunctuation, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSpaceDiscardingUnicodeRune returns true if the given rune is space-discarding unicode character, otherwise false.
|
||||||
|
// See https://www.w3.org/TR/2020/WD-css-text-3-20200429/#space-discard-set
|
||||||
|
func IsSpaceDiscardingUnicodeRune(r rune) bool {
|
||||||
|
return unicode.Is(cjkRadicalsSupplement, r) ||
|
||||||
|
unicode.Is(kangxiRadicals, r) ||
|
||||||
|
unicode.Is(ideographicDescriptionCharacters, r) ||
|
||||||
|
unicode.Is(cjkSymbolsAndPunctuation, r) ||
|
||||||
|
unicode.Is(hiragana, r) ||
|
||||||
|
unicode.Is(katakana, r) ||
|
||||||
|
unicode.Is(kanbun, r) ||
|
||||||
|
unicode.Is(cjkStrokes, r) ||
|
||||||
|
unicode.Is(katakanaPhoneticExtensions, r) ||
|
||||||
|
unicode.Is(cjkCompatibility, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionA, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographs, r) ||
|
||||||
|
unicode.Is(yiSyllables, r) ||
|
||||||
|
unicode.Is(yiRadicals, r) ||
|
||||||
|
unicode.Is(cjkCompatibilityIdeographs, r) ||
|
||||||
|
unicode.Is(verticalForms, r) ||
|
||||||
|
unicode.Is(cjkCompatibilityForms, r) ||
|
||||||
|
unicode.Is(smallFormVariants, r) ||
|
||||||
|
unicode.Is(halfwidthAndFullwidthForms, r) ||
|
||||||
|
unicode.Is(kanaSupplement, r) ||
|
||||||
|
unicode.Is(kanaExtendedA, r) ||
|
||||||
|
unicode.Is(smallKanaExtension, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionB, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionC, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionD, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionE, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionF, r) ||
|
||||||
|
unicode.Is(cjkCompatibilityIdeographsSupplement, r) ||
|
||||||
|
unicode.Is(cjkUnifiedIdeographsExtensionG, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EastAsianWidth returns the east asian width of the given rune.
|
||||||
|
// See https://www.unicode.org/reports/tr11/tr11-36.html
|
||||||
|
func EastAsianWidth(r rune) string {
|
||||||
|
switch {
|
||||||
|
case r == 0x3000,
|
||||||
|
(0xFF01 <= r && r <= 0xFF60),
|
||||||
|
(0xFFE0 <= r && r <= 0xFFE6):
|
||||||
|
return "F"
|
||||||
|
|
||||||
|
case r == 0x20A9,
|
||||||
|
(0xFF61 <= r && r <= 0xFFBE),
|
||||||
|
(0xFFC2 <= r && r <= 0xFFC7),
|
||||||
|
(0xFFCA <= r && r <= 0xFFCF),
|
||||||
|
(0xFFD2 <= r && r <= 0xFFD7),
|
||||||
|
(0xFFDA <= r && r <= 0xFFDC),
|
||||||
|
(0xFFE8 <= r && r <= 0xFFEE):
|
||||||
|
return "H"
|
||||||
|
|
||||||
|
case (0x1100 <= r && r <= 0x115F),
|
||||||
|
(0x11A3 <= r && r <= 0x11A7),
|
||||||
|
(0x11FA <= r && r <= 0x11FF),
|
||||||
|
(0x2329 <= r && r <= 0x232A),
|
||||||
|
(0x2E80 <= r && r <= 0x2E99),
|
||||||
|
(0x2E9B <= r && r <= 0x2EF3),
|
||||||
|
(0x2F00 <= r && r <= 0x2FD5),
|
||||||
|
(0x2FF0 <= r && r <= 0x2FFB),
|
||||||
|
(0x3001 <= r && r <= 0x303E),
|
||||||
|
(0x3041 <= r && r <= 0x3096),
|
||||||
|
(0x3099 <= r && r <= 0x30FF),
|
||||||
|
(0x3105 <= r && r <= 0x312D),
|
||||||
|
(0x3131 <= r && r <= 0x318E),
|
||||||
|
(0x3190 <= r && r <= 0x31BA),
|
||||||
|
(0x31C0 <= r && r <= 0x31E3),
|
||||||
|
(0x31F0 <= r && r <= 0x321E),
|
||||||
|
(0x3220 <= r && r <= 0x3247),
|
||||||
|
(0x3250 <= r && r <= 0x32FE),
|
||||||
|
(0x3300 <= r && r <= 0x4DBF),
|
||||||
|
(0x4E00 <= r && r <= 0xA48C),
|
||||||
|
(0xA490 <= r && r <= 0xA4C6),
|
||||||
|
(0xA960 <= r && r <= 0xA97C),
|
||||||
|
(0xAC00 <= r && r <= 0xD7A3),
|
||||||
|
(0xD7B0 <= r && r <= 0xD7C6),
|
||||||
|
(0xD7CB <= r && r <= 0xD7FB),
|
||||||
|
(0xF900 <= r && r <= 0xFAFF),
|
||||||
|
(0xFE10 <= r && r <= 0xFE19),
|
||||||
|
(0xFE30 <= r && r <= 0xFE52),
|
||||||
|
(0xFE54 <= r && r <= 0xFE66),
|
||||||
|
(0xFE68 <= r && r <= 0xFE6B),
|
||||||
|
(0x1B000 <= r && r <= 0x1B001),
|
||||||
|
(0x1F200 <= r && r <= 0x1F202),
|
||||||
|
(0x1F210 <= r && r <= 0x1F23A),
|
||||||
|
(0x1F240 <= r && r <= 0x1F248),
|
||||||
|
(0x1F250 <= r && r <= 0x1F251),
|
||||||
|
(0x20000 <= r && r <= 0x2F73F),
|
||||||
|
(0x2B740 <= r && r <= 0x2FFFD),
|
||||||
|
(0x30000 <= r && r <= 0x3FFFD):
|
||||||
|
return "W"
|
||||||
|
|
||||||
|
case (0x0020 <= r && r <= 0x007E),
|
||||||
|
(0x00A2 <= r && r <= 0x00A3),
|
||||||
|
(0x00A5 <= r && r <= 0x00A6),
|
||||||
|
r == 0x00AC,
|
||||||
|
r == 0x00AF,
|
||||||
|
(0x27E6 <= r && r <= 0x27ED),
|
||||||
|
(0x2985 <= r && r <= 0x2986):
|
||||||
|
return "Na"
|
||||||
|
|
||||||
|
case (0x00A1 == r),
|
||||||
|
(0x00A4 == r),
|
||||||
|
(0x00A7 <= r && r <= 0x00A8),
|
||||||
|
(0x00AA == r),
|
||||||
|
(0x00AD <= r && r <= 0x00AE),
|
||||||
|
(0x00B0 <= r && r <= 0x00B4),
|
||||||
|
(0x00B6 <= r && r <= 0x00BA),
|
||||||
|
(0x00BC <= r && r <= 0x00BF),
|
||||||
|
(0x00C6 == r),
|
||||||
|
(0x00D0 == r),
|
||||||
|
(0x00D7 <= r && r <= 0x00D8),
|
||||||
|
(0x00DE <= r && r <= 0x00E1),
|
||||||
|
(0x00E6 == r),
|
||||||
|
(0x00E8 <= r && r <= 0x00EA),
|
||||||
|
(0x00EC <= r && r <= 0x00ED),
|
||||||
|
(0x00F0 == r),
|
||||||
|
(0x00F2 <= r && r <= 0x00F3),
|
||||||
|
(0x00F7 <= r && r <= 0x00FA),
|
||||||
|
(0x00FC == r),
|
||||||
|
(0x00FE == r),
|
||||||
|
(0x0101 == r),
|
||||||
|
(0x0111 == r),
|
||||||
|
(0x0113 == r),
|
||||||
|
(0x011B == r),
|
||||||
|
(0x0126 <= r && r <= 0x0127),
|
||||||
|
(0x012B == r),
|
||||||
|
(0x0131 <= r && r <= 0x0133),
|
||||||
|
(0x0138 == r),
|
||||||
|
(0x013F <= r && r <= 0x0142),
|
||||||
|
(0x0144 == r),
|
||||||
|
(0x0148 <= r && r <= 0x014B),
|
||||||
|
(0x014D == r),
|
||||||
|
(0x0152 <= r && r <= 0x0153),
|
||||||
|
(0x0166 <= r && r <= 0x0167),
|
||||||
|
(0x016B == r),
|
||||||
|
(0x01CE == r),
|
||||||
|
(0x01D0 == r),
|
||||||
|
(0x01D2 == r),
|
||||||
|
(0x01D4 == r),
|
||||||
|
(0x01D6 == r),
|
||||||
|
(0x01D8 == r),
|
||||||
|
(0x01DA == r),
|
||||||
|
(0x01DC == r),
|
||||||
|
(0x0251 == r),
|
||||||
|
(0x0261 == r),
|
||||||
|
(0x02C4 == r),
|
||||||
|
(0x02C7 == r),
|
||||||
|
(0x02C9 <= r && r <= 0x02CB),
|
||||||
|
(0x02CD == r),
|
||||||
|
(0x02D0 == r),
|
||||||
|
(0x02D8 <= r && r <= 0x02DB),
|
||||||
|
(0x02DD == r),
|
||||||
|
(0x02DF == r),
|
||||||
|
(0x0300 <= r && r <= 0x036F),
|
||||||
|
(0x0391 <= r && r <= 0x03A1),
|
||||||
|
(0x03A3 <= r && r <= 0x03A9),
|
||||||
|
(0x03B1 <= r && r <= 0x03C1),
|
||||||
|
(0x03C3 <= r && r <= 0x03C9),
|
||||||
|
(0x0401 == r),
|
||||||
|
(0x0410 <= r && r <= 0x044F),
|
||||||
|
(0x0451 == r),
|
||||||
|
(0x2010 == r),
|
||||||
|
(0x2013 <= r && r <= 0x2016),
|
||||||
|
(0x2018 <= r && r <= 0x2019),
|
||||||
|
(0x201C <= r && r <= 0x201D),
|
||||||
|
(0x2020 <= r && r <= 0x2022),
|
||||||
|
(0x2024 <= r && r <= 0x2027),
|
||||||
|
(0x2030 == r),
|
||||||
|
(0x2032 <= r && r <= 0x2033),
|
||||||
|
(0x2035 == r),
|
||||||
|
(0x203B == r),
|
||||||
|
(0x203E == r),
|
||||||
|
(0x2074 == r),
|
||||||
|
(0x207F == r),
|
||||||
|
(0x2081 <= r && r <= 0x2084),
|
||||||
|
(0x20AC == r),
|
||||||
|
(0x2103 == r),
|
||||||
|
(0x2105 == r),
|
||||||
|
(0x2109 == r),
|
||||||
|
(0x2113 == r),
|
||||||
|
(0x2116 == r),
|
||||||
|
(0x2121 <= r && r <= 0x2122),
|
||||||
|
(0x2126 == r),
|
||||||
|
(0x212B == r),
|
||||||
|
(0x2153 <= r && r <= 0x2154),
|
||||||
|
(0x215B <= r && r <= 0x215E),
|
||||||
|
(0x2160 <= r && r <= 0x216B),
|
||||||
|
(0x2170 <= r && r <= 0x2179),
|
||||||
|
(0x2189 == r),
|
||||||
|
(0x2190 <= r && r <= 0x2199),
|
||||||
|
(0x21B8 <= r && r <= 0x21B9),
|
||||||
|
(0x21D2 == r),
|
||||||
|
(0x21D4 == r),
|
||||||
|
(0x21E7 == r),
|
||||||
|
(0x2200 == r),
|
||||||
|
(0x2202 <= r && r <= 0x2203),
|
||||||
|
(0x2207 <= r && r <= 0x2208),
|
||||||
|
(0x220B == r),
|
||||||
|
(0x220F == r),
|
||||||
|
(0x2211 == r),
|
||||||
|
(0x2215 == r),
|
||||||
|
(0x221A == r),
|
||||||
|
(0x221D <= r && r <= 0x2220),
|
||||||
|
(0x2223 == r),
|
||||||
|
(0x2225 == r),
|
||||||
|
(0x2227 <= r && r <= 0x222C),
|
||||||
|
(0x222E == r),
|
||||||
|
(0x2234 <= r && r <= 0x2237),
|
||||||
|
(0x223C <= r && r <= 0x223D),
|
||||||
|
(0x2248 == r),
|
||||||
|
(0x224C == r),
|
||||||
|
(0x2252 == r),
|
||||||
|
(0x2260 <= r && r <= 0x2261),
|
||||||
|
(0x2264 <= r && r <= 0x2267),
|
||||||
|
(0x226A <= r && r <= 0x226B),
|
||||||
|
(0x226E <= r && r <= 0x226F),
|
||||||
|
(0x2282 <= r && r <= 0x2283),
|
||||||
|
(0x2286 <= r && r <= 0x2287),
|
||||||
|
(0x2295 == r),
|
||||||
|
(0x2299 == r),
|
||||||
|
(0x22A5 == r),
|
||||||
|
(0x22BF == r),
|
||||||
|
(0x2312 == r),
|
||||||
|
(0x2460 <= r && r <= 0x24E9),
|
||||||
|
(0x24EB <= r && r <= 0x254B),
|
||||||
|
(0x2550 <= r && r <= 0x2573),
|
||||||
|
(0x2580 <= r && r <= 0x258F),
|
||||||
|
(0x2592 <= r && r <= 0x2595),
|
||||||
|
(0x25A0 <= r && r <= 0x25A1),
|
||||||
|
(0x25A3 <= r && r <= 0x25A9),
|
||||||
|
(0x25B2 <= r && r <= 0x25B3),
|
||||||
|
(0x25B6 <= r && r <= 0x25B7),
|
||||||
|
(0x25BC <= r && r <= 0x25BD),
|
||||||
|
(0x25C0 <= r && r <= 0x25C1),
|
||||||
|
(0x25C6 <= r && r <= 0x25C8),
|
||||||
|
(0x25CB == r),
|
||||||
|
(0x25CE <= r && r <= 0x25D1),
|
||||||
|
(0x25E2 <= r && r <= 0x25E5),
|
||||||
|
(0x25EF == r),
|
||||||
|
(0x2605 <= r && r <= 0x2606),
|
||||||
|
(0x2609 == r),
|
||||||
|
(0x260E <= r && r <= 0x260F),
|
||||||
|
(0x2614 <= r && r <= 0x2615),
|
||||||
|
(0x261C == r),
|
||||||
|
(0x261E == r),
|
||||||
|
(0x2640 == r),
|
||||||
|
(0x2642 == r),
|
||||||
|
(0x2660 <= r && r <= 0x2661),
|
||||||
|
(0x2663 <= r && r <= 0x2665),
|
||||||
|
(0x2667 <= r && r <= 0x266A),
|
||||||
|
(0x266C <= r && r <= 0x266D),
|
||||||
|
(0x266F == r),
|
||||||
|
(0x269E <= r && r <= 0x269F),
|
||||||
|
(0x26BE <= r && r <= 0x26BF),
|
||||||
|
(0x26C4 <= r && r <= 0x26CD),
|
||||||
|
(0x26CF <= r && r <= 0x26E1),
|
||||||
|
(0x26E3 == r),
|
||||||
|
(0x26E8 <= r && r <= 0x26FF),
|
||||||
|
(0x273D == r),
|
||||||
|
(0x2757 == r),
|
||||||
|
(0x2776 <= r && r <= 0x277F),
|
||||||
|
(0x2B55 <= r && r <= 0x2B59),
|
||||||
|
(0x3248 <= r && r <= 0x324F),
|
||||||
|
(0xE000 <= r && r <= 0xF8FF),
|
||||||
|
(0xFE00 <= r && r <= 0xFE0F),
|
||||||
|
(0xFFFD == r),
|
||||||
|
(0x1F100 <= r && r <= 0x1F10A),
|
||||||
|
(0x1F110 <= r && r <= 0x1F12D),
|
||||||
|
(0x1F130 <= r && r <= 0x1F169),
|
||||||
|
(0x1F170 <= r && r <= 0x1F19A),
|
||||||
|
(0xE0100 <= r && r <= 0xE01EF),
|
||||||
|
(0xF0000 <= r && r <= 0xFFFFD),
|
||||||
|
(0x100000 <= r && r <= 0x10FFFD):
|
||||||
|
return "A"
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "N"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build appengine || js
|
||||||
// +build appengine js
|
// +build appengine js
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
// +build !appengine,!js
|
//go:build !appengine && !js && !go1.21
|
||||||
|
// +build !appengine,!js,!go1.21
|
||||||
|
|
||||||
package util
|
package util
|
||||||
|
|
||||||
18
util/util_unsafe_go121.go
Normal file
18
util/util_unsafe_go121.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
//go:build !appengine && !js && go1.21
|
||||||
|
// +build !appengine,!js,go1.21
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BytesToReadOnlyString returns a string converted from given bytes.
|
||||||
|
func BytesToReadOnlyString(b []byte) string {
|
||||||
|
return unsafe.String(unsafe.SliceData(b), len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringToReadOnlyBytes returns bytes converted from given string.
|
||||||
|
func StringToReadOnlyBytes(s string) []byte {
|
||||||
|
return unsafe.Slice(unsafe.StringData(s), len(s))
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue