From 2f839dc39a59b7b94546e780af7322b0db3598c8 Mon Sep 17 00:00:00 2001 From: Russ Ross Date: Tue, 28 Jun 2011 10:30:25 -0600 Subject: [PATCH] fenced code: ending marker must match beginning marker, tests for fenced code blocks --- block.go | 49 ++++++++++++++++++++++------------- block_test.go | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 18 deletions(-) diff --git a/block.go b/block.go index 11b5c4f..6323ab2 100644 --- a/block.go +++ b/block.go @@ -429,12 +429,13 @@ func isHRule(data []byte) bool { return n >= 3 } -func isFencedCode(data []byte, syntax **string) int { - i, n := 0, 0 +func isFencedCode(data []byte, syntax **string, oldmarker string) (skip int, marker string) { + i, size := 0, 0 + skip = 0 // skip initial spaces if len(data) < 3 { - return 0 + return } if data[0] == ' ' { i++ @@ -446,21 +447,28 @@ func isFencedCode(data []byte, syntax **string) int { } } - // look at the hrule char + // check for the marker characters: ~ or ` if i+2 >= len(data) || !(data[i] == '~' || data[i] == '`') { - return 0 + return } c := data[i] - // the whole line must be the char or whitespace + // the whole line must be the same char or whitespace for i < len(data) && data[i] == c { - n++ + size++ i++ } - if n < 3 { - return 0 + // the marker char must occur at least 3 times + if size < 3 { + return + } + marker = string(data[i-size : i]) + + // if this is the end marker, it must match the beginning marker + if oldmarker != "" && marker != oldmarker { + return } if syntax != nil { @@ -482,10 +490,10 @@ func isFencedCode(data []byte, syntax **string) int { } if i == len(data) || data[i] != '}' { - return 0 + return } - // string all whitespace at the beginning and the end + // strip all whitespace at the beginning and the end // of the {} block for syn > 0 && isspace(data[syntax_start]) { syntax_start++ @@ -508,19 +516,19 @@ func isFencedCode(data []byte, syntax **string) int { *syntax = &language } - for i < len(data) && data[i] != '\n' { + for ; i < len(data) && data[i] != '\n'; i++ { if !isspace(data[i]) { - return 0 + return } - i++ } - return i + 1 + skip = i + 1 + return } func blockFencedCode(out *bytes.Buffer, rndr *render, data []byte) int { var lang *string - beg := isFencedCode(data, &lang) + beg, marker := isFencedCode(data, &lang, "") if beg == 0 { return 0 } @@ -528,7 +536,7 @@ func blockFencedCode(out *bytes.Buffer, rndr *render, data []byte) int { var work bytes.Buffer for beg < len(data) { - fence_end := isFencedCode(data[beg:], nil) + fence_end, _ := isFencedCode(data[beg:], nil, marker) if fence_end != 0 { beg += fence_end break @@ -539,7 +547,7 @@ func blockFencedCode(out *bytes.Buffer, rndr *render, data []byte) int { } if beg < end { - // verbatim copy to the working buffer, escaping entities + // verbatim copy to the working buffer if isEmpty(data[beg:]) > 0 { work.WriteByte('\n') } else { @@ -547,6 +555,11 @@ func blockFencedCode(out *bytes.Buffer, rndr *render, data []byte) int { } } beg = end + + // did we find the end of the buffer without a closing marker? + if beg >= len(data) { + return 0 + } } if work.Len() > 0 && work.Bytes()[work.Len()-1] != '\n' { diff --git a/block_test.go b/block_test.go index 5e9f654..358ab0b 100644 --- a/block_test.go +++ b/block_test.go @@ -552,3 +552,74 @@ func TestPreformattedHtmlLax(t *testing.T) { } doTestsBlock(t, tests, EXTENSION_LAX_HTML_BLOCKS) } + +func TestFencedCodeBlock(t *testing.T) { + var tests = []string{ + "``` go\nfunc foo() bool {\n\treturn true;\n}\n```\n", + "
func foo() bool {\n    return true;\n}\n
\n", + + "``` c\n/* special & char < > \" escaping */\n```\n", + "
/* special & char < > " escaping */\n
\n", + + "``` c\nno *inline* processing ~~of text~~\n```\n", + "
no *inline* processing ~~of text~~\n
\n", + + "```\nNo language\n```\n", + "
No language\n
\n", + + "``` {ocaml}\nlanguage in braces\n```\n", + "
language in braces\n
\n", + + "``` {ocaml} \nwith extra whitespace\n```\n", + "
with extra whitespace\n
\n", + + "```{ ocaml }\nwith extra whitespace\n```\n", + "
with extra whitespace\n
\n", + + "~ ~~ java\nWith whitespace\n~~~\n", + "

~ ~~ java\nWith whitespace\n~~~

\n", + + "~~\nonly two\n~~\n", + "

~~\nonly two\n~~

\n", + + "```` python\nextra\n````\n", + "
extra\n
\n", + + "~~~ perl\nthree to start, four to end\n~~~~\n", + "

~~~ perl\nthree to start, four to end\n~~~~

\n", + + "~~~~ perl\nfour to start, three to end\n~~~\n", + "

~~~~ perl\nfour to start, three to end\n~~~

\n", + + "~~~ bash\ntildes\n~~~\n", + "
tildes\n
\n", + + "``` lisp\nno ending\n", + "

``` lisp\nno ending

\n", + + "~~~ lisp\nend with language\n~~~ lisp\n", + "

~~~ lisp\nend with language\n~~~ lisp

\n", + + "```\nmismatched begin and end\n~~~\n", + "

```\nmismatched begin and end\n~~~

\n", + + "~~~\nmismatched begin and end\n```\n", + "

~~~\nmismatched begin and end\n```

\n", + + " ``` oz\nleading spaces\n```\n", + "
leading spaces\n
\n", + + " ``` oz\nleading spaces\n ```\n", + "
leading spaces\n
\n", + + " ``` oz\nleading spaces\n ```\n", + "
leading spaces\n
\n", + + "``` oz\nleading spaces\n ```\n", + "
leading spaces\n
\n", + + " ``` oz\nleading spaces\n ```\n", + "
``` oz\n
\n\n

leading spaces\n ```

\n", + } + doTestsBlock(t, tests, EXTENSION_FENCED_CODE) +}