Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accept info strings in code fences #448

Merged
merged 3 commits into from
Apr 28, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Accept info strings in code fences
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 #410 (in v1).
  • Loading branch information
garfieldnate committed Apr 21, 2018
commit 04fca4d0ef1e6816233ccafc4101d4d5427d2235
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor nit: I think it would make sense to get rid of the C-like naming, let's change that to isHorizontalSpace (and same for vertical).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. For this PR, though, I kept the style consistent with what's already in there. Fixing the style everywhere would be a separate task/commit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you would like me to fix it in another commit, though, I can. I would really like both of these PR's merged as soon as possible so I can start working on Hugo again.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only meant these two new funcs, but it's a minor nit anyway, so in it goes! Shoot the other PR if you feel up to that.

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