mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Compare commits
21 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04410ff159 | ||
|
|
f7b2d24c09 | ||
|
|
ba49c5c69d | ||
|
|
c05fb087a4 | ||
|
|
b39daae79e | ||
|
|
d9c03f07f0 | ||
|
|
65dcf6cd0a | ||
|
|
ad1565131a | ||
|
|
bc993b4f59 | ||
|
|
41273a4d07 | ||
|
|
d80ac9397c | ||
|
|
15000ac6a1 | ||
|
|
14d91f957f | ||
|
|
3847ca20c6 | ||
|
|
697e44ce88 | ||
|
|
fa88006eee | ||
|
|
fe34ea5d96 | ||
|
|
fd14edc9bc | ||
|
|
e367755421 | ||
|
|
e44645afbb | ||
|
|
2b845f2615 |
23 changed files with 2718 additions and 2175 deletions
|
|
@ -2,7 +2,7 @@ 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)
|
||||||
|
|
||||||
|
|
@ -493,7 +493,7 @@ Extensions
|
||||||
- [goldmark-d2](https://github.com/FurqanSoftware/goldmark-d2): Adds support for [D2](https://d2lang.com/) diagrams.
|
- [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-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-img64](https://github.com/tenkoh/goldmark-img64): Adds support for embedding images into the document as DataURL (base64 encoded).
|
||||||
- [goldmark-enclave](https://github.com/quail-ink/goldmark-enclave): Adds support for embedding youtube/bilibili video, X's [oembed tweet](https://publish.twitter.com/), [tradingview](https://www.tradingview.com/widget/)'s chart, [quail](https://quail.ink)'s widget into the document.
|
- [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-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()`.
|
- [goldmark-tgmd](https://github.com/Mad-Pixels/goldmark-tgmd): A Telegram markdown renderer that can be passed to `goldmark.WithRenderer()`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/88250/lute v1.7.5
|
github.com/88250/lute v1.7.5
|
||||||
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
|
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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -22,3 +22,4 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
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 => ../../
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -794,3 +795,51 @@ text" /></p>
|
||||||
//- - - - - - - - -//
|
//- - - - - - - - -//
|
||||||
<p><a href="b"><em>[a]</em></a></p>
|
<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>
|
||||||
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
15
ast/ast.go
15
ast/ast.go
|
|
@ -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,
|
||||||
|
|
@ -374,11 +380,18 @@ func (n *BaseNode) OwnerDocument() *Document {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
39
ast/block.go
39
ast/block.go
|
|
@ -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{
|
||||||
|
|
@ -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{
|
||||||
|
|
|
||||||
|
|
@ -143,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.
|
||||||
|
|
@ -258,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
|
||||||
}
|
}
|
||||||
|
|
@ -492,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.
|
||||||
|
|
@ -541,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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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>
|
||||||
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
//= = = = = = = = = = = = = = = = = = = = = = = =//
|
||||||
|
|
|
||||||
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
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -184,11 +184,11 @@ func (b *tableParagraphTransformer) Transform(node *gast.Paragraph, reader text.
|
||||||
func (b *tableParagraphTransformer) parseRow(segment text.Segment,
|
func (b *tableParagraphTransformer) parseRow(segment text.Segment,
|
||||||
alignments []ast.Alignment, isHeader bool, reader text.Reader, pc parser.Context) *ast.TableRow {
|
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++
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -882,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
|
||||||
}
|
}
|
||||||
|
|
@ -1256,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -680,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="`)
|
||||||
r.renderAttribute(w, source, n)
|
r.renderTexts(w, source, n)
|
||||||
_ = w.WriteByte('"')
|
_ = w.WriteByte('"')
|
||||||
if n.Title != nil {
|
if n.Title != nil {
|
||||||
_, _ = w.WriteString(` title="`)
|
_, _ = w.WriteString(` title="`)
|
||||||
|
|
@ -737,7 +737,7 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, en
|
||||||
if r.EastAsianLineBreaks != EastAsianLineBreaksNone && len(value) != 0 {
|
if r.EastAsianLineBreaks != EastAsianLineBreaksNone && len(value) != 0 {
|
||||||
sibling := node.NextSibling()
|
sibling := node.NextSibling()
|
||||||
if sibling != nil && sibling.Kind() == ast.KindText {
|
if sibling != nil && sibling.Kind() == ast.KindText {
|
||||||
if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 {
|
if siblingText := sibling.(*ast.Text).Value(source); len(siblingText) != 0 {
|
||||||
thisLastRune := util.ToRune(value, len(value)-1)
|
thisLastRune := util.ToRune(value, len(value)-1)
|
||||||
siblingFirstRune, _ := utf8.DecodeRune(siblingText)
|
siblingFirstRune, _ := utf8.DecodeRune(siblingText)
|
||||||
if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) {
|
if r.EastAsianLineBreaks.softLineBreak(thisLastRune, siblingFirstRune) {
|
||||||
|
|
@ -770,19 +770,14 @@ func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node,
|
||||||
return ast.WalkContinue, nil
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderAttribute(w util.BufWriter, source []byte, n ast.Node) {
|
func (r *Renderer) renderTexts(w util.BufWriter, source []byte, n ast.Node) {
|
||||||
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
for c := n.FirstChild(); c != nil; c = c.NextSibling() {
|
||||||
if s, ok := c.(*ast.String); ok {
|
if s, ok := c.(*ast.String); ok {
|
||||||
_, _ = r.renderString(w, source, s, true)
|
_, _ = r.renderString(w, source, s, true)
|
||||||
} else if t, ok := c.(*ast.String); ok {
|
} else if t, ok := c.(*ast.Text); ok {
|
||||||
_, _ = r.renderText(w, source, t, true)
|
_, _ = r.renderText(w, source, t, true)
|
||||||
} else if !c.HasChildren() {
|
|
||||||
r.Writer.Write(w, c.Text(source))
|
|
||||||
if t, ok := c.(*ast.Text); ok && t.SoftLineBreak() {
|
|
||||||
_ = w.WriteByte('\n')
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
r.renderAttribute(w, source, c)
|
r.renderTexts(w, source, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = append(result, bytes.Repeat(space, t.Padding)...)
|
||||||
|
result = append(result, buffer[t.Start:t.Stop]...)
|
||||||
}
|
}
|
||||||
result := make([]byte, 0, t.Padding+t.Stop-t.Start+1)
|
if t.ForceNewline && len(result) > 0 && result[len(result)-1] != '\n' {
|
||||||
result = append(result, bytes.Repeat(space, t.Padding)...)
|
result = append(result, '\n')
|
||||||
return append(result, buffer[t.Start:t.Stop]...)
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -166,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 {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//go:build !appengine && !js
|
//go:build !appengine && !js && !go1.21
|
||||||
// +build !appengine,!js
|
// +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