Skip to content

Commit

Permalink
Accept info strings in code fences
Browse files Browse the repository at this point in the history
According to the common mark standard, code fence info strings can be anything,
not just single words. Update the tests and parser accordingly.

The formatter already expected an info string with a language and HTML classes,
so this does not need to change. Update the LaTeX formatter to take the first
word of the info string as the language.

Fixes russross#410 (in v1).
  • Loading branch information
garfieldnate committed Apr 21, 2018
1 parent 55d61fa commit 04fca4d
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 39 deletions.
39 changes: 20 additions & 19 deletions block.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package blackfriday

import (
"bytes"
"strings"
"unicode"
)

Expand Down Expand Up @@ -92,7 +93,7 @@ func (p *parser) block(out *bytes.Buffer, data []byte) {

// fenced code block:
//
// ``` go
// ``` go info string here
// func fact(n int) int {
// if n <= 1 {
// return n
Expand Down Expand Up @@ -562,7 +563,7 @@ func (*parser) isHRule(data []byte) bool {
// 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.
// A final newline is mandatory to recognize the fence line, unless newlineOptional is true.
func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional bool) (end int, marker string) {
func isFenceLine(data []byte, info *string, oldmarker string, newlineOptional bool) (end int, marker string) {
i, size := 0, 0

// skip up to three spaces
Expand Down Expand Up @@ -598,9 +599,9 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
}

// 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 @@ -610,14 +611,14 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
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 @@ -627,24 +628,24 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional

// 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++
} 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, ' ')
Expand All @@ -662,8 +663,8 @@ func isFenceLine(data []byte, syntax *string, oldmarker string, newlineOptional
// 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 *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool) int {
var syntax string
beg, marker := isFenceLine(data, &syntax, "", false)
var infoString string
beg, marker := isFenceLine(data, &infoString, "", false)
if beg == 0 || beg >= len(data) {
return 0
}
Expand Down Expand Up @@ -697,7 +698,7 @@ func (p *parser) fencedCodeBlock(out *bytes.Buffer, data []byte, doRender bool)
}

if doRender {
p.r.BlockCode(out, work.Bytes(), syntax)
p.r.BlockCode(out, work.Bytes(), infoString)
}

return beg
Expand Down
45 changes: 32 additions & 13 deletions block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,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 foo bar\">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 @@ -1646,11 +1649,11 @@ func TestCDATA(t *testing.T) {
func TestIsFenceLine(t *testing.T) {
tests := []struct {
data []byte
syntaxRequested bool
infoRequested bool
newlineOptional bool
wantEnd int
wantMarker string
wantSyntax string
wantInfo string
}{
{
data: []byte("```"),
Expand All @@ -1663,7 +1666,7 @@ func TestIsFenceLine(t *testing.T) {
},
{
data: []byte("```\nstuff here\n"),
syntaxRequested: true,
infoRequested: true,
wantEnd: 4,
wantMarker: "```",
},
Expand All @@ -1679,36 +1682,52 @@ func TestIsFenceLine(t *testing.T) {
},
{
data: []byte("```"),
syntaxRequested: true,
infoRequested: true,
newlineOptional: true,
wantEnd: 3,
wantMarker: "```",
},
{
data: []byte("``` go"),
syntaxRequested: true,
infoRequested: true,
newlineOptional: true,
wantEnd: 6,
wantMarker: "```",
wantSyntax: "go",
wantInfo: "go",
},
{
data: []byte("``` go foo bar"),
infoRequested: true,
newlineOptional: true,
wantEnd: 14,
wantMarker: "```",
wantInfo: "go foo bar",
},
{
data: []byte("``` go foo bar "),
infoRequested: true,
newlineOptional: 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, "```", test.newlineOptional)
end, marker := isFenceLine(test.data, info, "```", test.newlineOptional)
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 %q, want %q", got, want)
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions html.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,12 @@ func (options *Html) HRule(out *bytes.Buffer) {
out.WriteByte('\n')
}

func (options *Html) BlockCode(out *bytes.Buffer, text []byte, lang string) {
func (options *Html) BlockCode(out *bytes.Buffer, text []byte, info string) {
doubleSpace(out)

// parse out the language names/classes
count := 0
for _, elt := range strings.Fields(lang) {
for _, elt := range strings.Fields(info) {
if elt[0] == '.' {
elt = elt[1:]
}
Expand Down
8 changes: 5 additions & 3 deletions latex.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package blackfriday

import (
"bytes"
"strings"
)

// Latex is a type that implements the Renderer interface for LaTeX output.
Expand All @@ -39,16 +40,17 @@ func (options *Latex) GetFlags() int {
}

// render code chunks using verbatim, or listings if we have a language
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, lang string) {
if lang == "" {
func (options *Latex) BlockCode(out *bytes.Buffer, text []byte, info string) {
if info == "" {
out.WriteString("\n\\begin{verbatim}\n")
} else {
lang := strings.Fields(info)[0]
out.WriteString("\n\\begin{lstlisting}[language=")
out.WriteString(lang)
out.WriteString("]\n")
}
out.Write(text)
if lang == "" {
if info == "" {
out.WriteString("\n\\end{verbatim}\n")
} else {
out.WriteString("\n\\end{lstlisting}\n")
Expand Down
14 changes: 12 additions & 2 deletions markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ var blockTags = map[string]struct{}{
// Currently Html and Latex implementations are provided
type Renderer interface {
// block-level callbacks
BlockCode(out *bytes.Buffer, text []byte, lang string)
BlockCode(out *bytes.Buffer, text []byte, infoString string)
BlockQuote(out *bytes.Buffer, text []byte)
BlockHtml(out *bytes.Buffer, text []byte)
Header(out *bytes.Buffer, text func() bool, level int, id string)
Expand Down Expand Up @@ -804,7 +804,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 whitespace 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 04fca4d

Please sign in to comment.