Skip to content

Commit

Permalink
Add full info string in fenced code blocks (#449)
Browse files Browse the repository at this point in the history
* Add full info string in fenced code blocks

According to common mark, the info string for a fenced code block can be any
non-whitespace string, so adjust the code to read a full string instead of
just the syntax name.

Fixes #410 in v2.

* run go fmt
  • Loading branch information
garfieldnate authored and rtfb committed Apr 28, 2018
1 parent 6aeb241 commit 3420fef
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 51 deletions.
50 changes: 25 additions & 25 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"bytes"
"html"
"regexp"
"strings"

"github.com/shurcooL/sanitized_anchor_name"
)
Expand Down Expand Up @@ -568,8 +569,8 @@ func (*Markdown) isHRule(data []byte) bool {

// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data,
// and returns the end index if so, or 0 otherwise. It also returns the marker found.
// If syntax is not nil, it gets set to the syntax specified in the fence line.
func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker string) {
// If info is not nil, it gets set to the syntax specified in the fence line.
func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) {
i, size := 0, 0

// skip up to three spaces
Expand Down Expand Up @@ -605,9 +606,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
}

// TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here
// into one, always get the syntax, and discard it if the caller doesn't care.
if syntax != nil {
syn := 0
// into one, always get the info string, and discard it if the caller doesn't care.
if info != nil {
infoLength := 0
i = skipChar(data, i, ' ')

if i >= len(data) {
Expand All @@ -617,14 +618,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
return 0, ""
}

syntaxStart := i
infoStart := i

if data[i] == '{' {
i++
syntaxStart++
infoStart++

for i < len(data) && data[i] != '}' && data[i] != '\n' {
syn++
infoLength++
i++
}

Expand All @@ -634,31 +635,30 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker

// strip all whitespace at the beginning and the end
// of the {} block
for syn > 0 && isspace(data[syntaxStart]) {
syntaxStart++
syn--
for infoLength > 0 && isspace(data[infoStart]) {
infoStart++
infoLength--
}

for syn > 0 && isspace(data[syntaxStart+syn-1]) {
syn--
for infoLength > 0 && isspace(data[infoStart+infoLength-1]) {
infoLength--
}

i++
i = skipChar(data, i, ' ')
} else {
for i < len(data) && !isspace(data[i]) {
syn++
for i < len(data) && !isverticalspace(data[i]) {
infoLength++
i++
}
}

*syntax = string(data[syntaxStart : syntaxStart+syn])
*info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength]))
}

i = skipChar(data, i, ' ')
if i >= len(data) || data[i] != '\n' {
if i == len(data) {
return i, marker
}
if i == len(data) {
return i, marker
}
if i > len(data) || data[i] != '\n' {
return 0, ""
}
return i + 1, marker // Take newline into account.
Expand All @@ -668,14 +668,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string) (end int, marker
// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects.
// If doRender is true, a final newline is mandatory to recognize the fenced code block.
func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int {
var syntax string
beg, marker := isFenceLine(data, &syntax, "")
var info string
beg, marker := isFenceLine(data, &info, "")
if beg == 0 || beg >= len(data) {
return 0
}

var work bytes.Buffer
work.Write([]byte(syntax))
work.Write([]byte(info))
work.WriteByte('\n')

for {
Expand Down
70 changes: 45 additions & 25 deletions block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,6 +945,9 @@ func TestFencedCodeBlock(t *testing.T) {
"``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n",
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",

"``` go foo bar\nfunc foo() bool {\n\treturn true;\n}\n```\n",
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",

"``` c\n/* special & char < > \" escaping */\n```\n",
"<pre><code class=\"language-c\">/* special &amp; char &lt; &gt; &quot; escaping */\n</code></pre>\n",

Expand Down Expand Up @@ -1403,6 +1406,9 @@ func TestFencedCodeBlock_EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK(t *testing.T) {
"``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n",
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",

"``` go foo bar\nfunc foo() bool {\n\treturn true;\n}\n```\n",
"<pre><code class=\"language-go\">func foo() bool {\n\treturn true;\n}\n</code></pre>\n",

"``` c\n/* special & char < > \" escaping */\n```\n",
"<pre><code class=\"language-c\">/* special &amp; char &lt; &gt; &quot; escaping */\n</code></pre>\n",

Expand Down Expand Up @@ -1640,11 +1646,11 @@ func TestCompletePage(t *testing.T) {

func TestIsFenceLine(t *testing.T) {
tests := []struct {
data []byte
syntaxRequested bool
wantEnd int
wantMarker string
wantSyntax string
data []byte
infoRequested bool
wantEnd int
wantMarker string
wantInfo string
}{
{
data: []byte("```"),
Expand All @@ -1657,45 +1663,59 @@ func TestIsFenceLine(t *testing.T) {
wantMarker: "```",
},
{
data: []byte("```\nstuff here\n"),
syntaxRequested: true,
wantEnd: 4,
wantMarker: "```",
data: []byte("```\nstuff here\n"),
infoRequested: true,
wantEnd: 4,
wantMarker: "```",
},
{
data: []byte("stuff here\n```\n"),
wantEnd: 0,
},
{
data: []byte("```"),
syntaxRequested: true,
wantEnd: 3,
wantMarker: "```",
data: []byte("```"),
infoRequested: true,
wantEnd: 3,
wantMarker: "```",
},
{
data: []byte("``` go"),
infoRequested: true,
wantEnd: 6,
wantMarker: "```",
wantInfo: "go",
},
{
data: []byte("``` go foo bar"),
infoRequested: true,
wantEnd: 14,
wantMarker: "```",
wantInfo: "go foo bar",
},
{
data: []byte("``` go"),
syntaxRequested: true,
wantEnd: 6,
wantMarker: "```",
wantSyntax: "go",
data: []byte("``` go foo bar "),
infoRequested: true,
wantEnd: 16,
wantMarker: "```",
wantInfo: "go foo bar",
},
}

for _, test := range tests {
var syntax *string
if test.syntaxRequested {
syntax = new(string)
var info *string
if test.infoRequested {
info = new(string)
}
end, marker := isFenceLine(test.data, syntax, "```")
end, marker := isFenceLine(test.data, info, "```")
if got, want := end, test.wantEnd; got != want {
t.Errorf("got end %v, want %v", got, want)
}
if got, want := marker, test.wantMarker; got != want {
t.Errorf("got marker %q, want %q", got, want)
}
if test.syntaxRequested {
if got, want := *syntax, test.wantSyntax; got != want {
t.Errorf("got syntax %q, want %q", got, want)
if test.infoRequested {
if got, want := *info, test.wantInfo; got != want {
t.Errorf("got info string %q, want %q", got, want)
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,17 @@ func ispunct(c byte) bool {

// Test if a character is a whitespace character.
func isspace(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'
return ishorizontalspace(c) || isverticalspace(c)
}

// Test if a character is a horizontal whitespace character.
func ishorizontalspace(c byte) bool {
return c == ' ' || c == '\t'
}

// Test if a character is a vertical character.
func isverticalspace(c byte) bool {
return c == '\n' || c == '\r' || c == '\f' || c == '\v'
}

// Test if a character is letter.
Expand Down

0 comments on commit 3420fef

Please sign in to comment.