Merge pull request #284 from russross/fix-lint

Fix most of lint errors on v2
This commit is contained in:
Vytautas Šaltenis 2016-07-28 19:45:39 +03:00 committed by GitHub
commit ca4bf013e8
9 changed files with 142 additions and 107 deletions

View File

@ -22,13 +22,13 @@ import (
) )
const ( const (
Entity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});" charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});"
Escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]" escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]"
) )
var ( var (
reBackslashOrAmp = regexp.MustCompile("[\\&]") reBackslashOrAmp = regexp.MustCompile("[\\&]")
reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + Escapable + "|" + Entity) reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity)
reTrailingWhitespace = regexp.MustCompile("(\n *)+$") reTrailingWhitespace = regexp.MustCompile("(\n *)+$")
) )
@ -279,9 +279,8 @@ func (p *parser) isUnderlinedHeader(data []byte) int {
i = skipChar(data, i, ' ') i = skipChar(data, i, ' ')
if data[i] == '\n' { if data[i] == '\n' {
return 1 return 1
} else {
return 0
} }
return 0
} }
// test of level 2 header // test of level 2 header
@ -290,9 +289,8 @@ func (p *parser) isUnderlinedHeader(data []byte) int {
i = skipChar(data, i, ' ') i = skipChar(data, i, ' ')
if data[i] == '\n' { if data[i] == '\n' {
return 2 return 2
} else {
return 0
} }
return 0
} }
return 0 return 0
@ -414,20 +412,20 @@ func (p *parser) html(data []byte, doRender bool) int {
for end > 0 && data[end-1] == '\n' { for end > 0 && data[end-1] == '\n' {
end-- end--
} }
finalizeHtmlBlock(p.addBlock(HTMLBlock, data[:end])) finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
} }
return i return i
} }
func finalizeHtmlBlock(block *Node) { func finalizeHTMLBlock(block *Node) {
block.Literal = reTrailingWhitespace.ReplaceAll(block.content, []byte{}) block.Literal = reTrailingWhitespace.ReplaceAll(block.content, []byte{})
block.content = []byte{} block.content = []byte{}
} }
// HTML comment, lax form // HTML comment, lax form
func (p *parser) htmlComment(data []byte, doRender bool) int { func (p *parser) htmlComment(data []byte, doRender bool) int {
i := p.inlineHtmlComment(data) i := p.inlineHTMLComment(data)
// needs to end with a blank line // needs to end with a blank line
if j := p.isEmpty(data[i:]); j > 0 { if j := p.isEmpty(data[i:]); j > 0 {
size := i + j size := i + j
@ -438,7 +436,7 @@ func (p *parser) htmlComment(data []byte, doRender bool) int {
end-- end--
} }
block := p.addBlock(HTMLBlock, data[:end]) block := p.addBlock(HTMLBlock, data[:end])
finalizeHtmlBlock(block) finalizeHTMLBlock(block)
} }
return size return size
} }
@ -470,7 +468,7 @@ func (p *parser) htmlHr(data []byte, doRender bool) int {
for end > 0 && data[end-1] == '\n' { for end > 0 && data[end-1] == '\n' {
end-- end--
} }
finalizeHtmlBlock(p.addBlock(HTMLBlock, data[:end])) finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end]))
} }
return size return size
} }
@ -729,9 +727,8 @@ func unescapeChar(str []byte) []byte {
func unescapeString(str []byte) []byte { func unescapeString(str []byte) []byte {
if reBackslashOrAmp.Match(str) { if reBackslashOrAmp.Match(str) {
return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar) return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar)
} else {
return str
} }
return str
} }
func finalizeCodeBlock(block *Node) { func finalizeCodeBlock(block *Node) {

View File

@ -1572,7 +1572,7 @@ func TestCompletePage(t *testing.T) {
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title></title> <title></title>
<meta name="GENERATOR" content="Blackfriday Markdown Processor v1.4" /> <meta name="GENERATOR" content="Blackfriday Markdown Processor v2.0" />
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
<body> <body>

View File

@ -54,7 +54,7 @@ func doTests(t *testing.T, tests []string) {
doTestsParam(t, tests, TestParams{ doTestsParam(t, tests, TestParams{
Options: DefaultOptions, Options: DefaultOptions,
HTMLRendererParameters: HTMLRendererParameters{ HTMLRendererParameters: HTMLRendererParameters{
Flags: CommonHtmlFlags, Flags: CommonHTMLFlags,
Extensions: CommonExtensions, Extensions: CommonExtensions,
}, },
}) })
@ -106,7 +106,7 @@ func doLinkTestsInline(t *testing.T, tests []string) {
HTMLRendererParameters: params, HTMLRendererParameters: params,
}) })
doTestsInlineParam(t, transformTests, TestParams{ doTestsInlineParam(t, transformTests, TestParams{
HTMLFlags: CommonHtmlFlags, HTMLFlags: CommonHTMLFlags,
HTMLRendererParameters: params, HTMLRendererParameters: params,
}) })
} }

24
html.go
View File

@ -24,6 +24,7 @@ import (
"strings" "strings"
) )
// HTMLFlags control optional behavior of HTML renderer.
type HTMLFlags int type HTMLFlags int
// HTML renderer configuration options. // HTML renderer configuration options.
@ -63,6 +64,8 @@ var (
htmlTagRe = regexp.MustCompile("(?i)^" + HTMLTag) htmlTagRe = regexp.MustCompile("(?i)^" + HTMLTag)
) )
// HTMLRendererParameters is a collection of supplementary parameters tweaking
// the behavior of various parts of HTML renderer.
type HTMLRendererParameters struct { type HTMLRendererParameters struct {
// Prepend this text to each relative URL. // Prepend this text to each relative URL.
AbsolutePrefix string AbsolutePrefix string
@ -126,8 +129,8 @@ func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
} }
} }
func isHtmlTag(tag []byte, tagname string) bool { func isHTMLTag(tag []byte, tagname string) bool {
found, _ := findHtmlTagPos(tag, tagname) found, _ := findHTMLTagPos(tag, tagname)
return found return found
} }
@ -154,7 +157,7 @@ func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
return start return start
} }
func findHtmlTagPos(tag []byte, tagname string) (bool, int) { func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
i := 0 i := 0
if i < len(tag) && tag[0] != '<' { if i < len(tag) && tag[0] != '<' {
return false, -1 return false, -1
@ -384,6 +387,16 @@ func (r *HTMLRenderer) cr(w io.Writer) {
} }
} }
// RenderNode is a default renderer of a single node of a syntax tree. For
// block nodes it will be called twice: first time with entering=true, second
// time with entering=false, so that it could know when it's working on an open
// tag and when on close. It writes the result to w.
//
// The return value is a way to tell the calling walker to adjust its walk
// pattern: e.g. it can terminate the traversal by returning Terminate. Or it
// can ask the walker to skip a subtree of this node by returning SkipChildren.
// The typical behavior is to return GoToNext, which asks for the usual
// traversal to the next node.
func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
attrs := []string{} attrs := []string{}
switch node.Type { switch node.Type {
@ -420,7 +433,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt
if r.Flags&SkipHTML != 0 { if r.Flags&SkipHTML != 0 {
break break
} }
if r.Flags&SkipStyle != 0 && isHtmlTag(node.Literal, "style") { if r.Flags&SkipStyle != 0 && isHTMLTag(node.Literal, "style") {
break break
} }
//if options.safe { //if options.safe {
@ -714,7 +727,7 @@ func (r *HTMLRenderer) writeDocumentHeader(w *bytes.Buffer, sr *SPRenderer) {
} }
w.WriteString("</title>\n") w.WriteString("</title>\n")
w.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v") w.WriteString(" <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
w.WriteString(VERSION) w.WriteString(Version)
w.WriteString("\"") w.WriteString("\"")
w.WriteString(ending) w.WriteString(ending)
w.WriteString(">\n") w.WriteString(">\n")
@ -739,6 +752,7 @@ func (r *HTMLRenderer) writeDocumentFooter(w *bytes.Buffer) {
w.WriteString("\n</body>\n</html>\n") w.WriteString("\n</body>\n</html>\n")
} }
// Render walks the specified syntax (sub)tree and returns a HTML document.
func (r *HTMLRenderer) Render(ast *Node) []byte { func (r *HTMLRenderer) Render(ast *Node) []byte {
//println("render_Blackfriday") //println("render_Blackfriday")
//dump(ast) //dump(ast)

View File

@ -266,13 +266,13 @@ func link(p *parser, data []byte, offset int) int {
// ![alt] == image // ![alt] == image
case offset >= 0 && data[offset] == '!': case offset >= 0 && data[offset] == '!':
t = linkImg t = linkImg
offset += 1 offset++
// ^[text] == inline footnote // ^[text] == inline footnote
// [^refId] == deferred footnote // [^refId] == deferred footnote
case p.flags&Footnotes != 0: case p.flags&Footnotes != 0:
if offset >= 0 && data[offset] == '^' { if offset >= 0 && data[offset] == '^' {
t = linkInlineFootnote t = linkInlineFootnote
offset += 1 offset++
} else if len(data)-1 > offset && data[offset+1] == '^' { } else if len(data)-1 > offset && data[offset+1] == '^' {
t = linkDeferredFootnote t = linkDeferredFootnote
} }
@ -285,7 +285,7 @@ func link(p *parser, data []byte, offset int) int {
var ( var (
i = 1 i = 1
noteId int noteID int
title, link, altContent []byte title, link, altContent []byte
textHasNl = false textHasNl = false
) )
@ -501,7 +501,7 @@ func link(p *parser, data []byte, offset int) int {
if t == linkInlineFootnote { if t == linkInlineFootnote {
// create a new reference // create a new reference
noteId = len(p.notes) + 1 noteID = len(p.notes) + 1
var fragment []byte var fragment []byte
if len(id) > 0 { if len(id) > 0 {
@ -512,11 +512,11 @@ func link(p *parser, data []byte, offset int) int {
} }
copy(fragment, slugify(id)) copy(fragment, slugify(id))
} else { } else {
fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteId))...) fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteID))...)
} }
ref := &reference{ ref := &reference{
noteId: noteId, noteID: noteID,
hasBlock: false, hasBlock: false,
link: fragment, link: fragment,
title: id, title: id,
@ -534,7 +534,7 @@ func link(p *parser, data []byte, offset int) int {
} }
if t == linkDeferredFootnote { if t == linkDeferredFootnote {
lr.noteId = len(p.notes) + 1 lr.noteID = len(p.notes) + 1
p.notes = append(p.notes, lr) p.notes = append(p.notes, lr)
} }
@ -542,7 +542,7 @@ func link(p *parser, data []byte, offset int) int {
link = lr.link link = lr.link
// if inline footnote, title == footnote contents // if inline footnote, title == footnote contents
title = lr.title title = lr.title
noteId = lr.noteId noteID = lr.noteID
} }
// rewind the whitespace // rewind the whitespace
@ -590,13 +590,13 @@ func link(p *parser, data []byte, offset int) int {
linkNode.Title = title linkNode.Title = title
p.currBlock.appendChild(linkNode) p.currBlock.appendChild(linkNode)
linkNode.appendChild(text(data[1:txtE])) linkNode.appendChild(text(data[1:txtE]))
i += 1 i++
case linkInlineFootnote, linkDeferredFootnote: case linkInlineFootnote, linkDeferredFootnote:
linkNode := NewNode(Link) linkNode := NewNode(Link)
linkNode.Destination = link linkNode.Destination = link
linkNode.Title = title linkNode.Title = title
linkNode.NoteID = noteId linkNode.NoteID = noteID
p.currBlock.appendChild(linkNode) p.currBlock.appendChild(linkNode)
if t == linkInlineFootnote { if t == linkInlineFootnote {
i++ i++
@ -609,7 +609,7 @@ func link(p *parser, data []byte, offset int) int {
return i return i
} }
func (p *parser) inlineHtmlComment(data []byte) int { func (p *parser) inlineHTMLComment(data []byte) int {
if len(data) < 5 { if len(data) < 5 {
return 0 return 0
} }
@ -643,7 +643,7 @@ func leftAngle(p *parser, data []byte, offset int) int {
data = data[offset:] data = data[offset:]
altype := LinkTypeNotAutolink altype := LinkTypeNotAutolink
end := tagLength(data, &altype) end := tagLength(data, &altype)
if size := p.inlineHtmlComment(data); size > 0 { if size := p.inlineHTMLComment(data); size > 0 {
end = size end = size
} }
if end > 2 { if end > 2 {
@ -1026,9 +1026,8 @@ func isMailtoAutoLink(data []byte) int {
case '>': case '>':
if nb == 1 { if nb == 1 {
return i + 1 return i + 1
} else {
return 0
} }
return 0
default: default:
return 0 return 0
} }
@ -1091,9 +1090,8 @@ func helperFindEmphChar(data []byte, c byte) int {
if data[i] != '[' && data[i] != '(' { // not a link if data[i] != '[' && data[i] != '(' { // not a link
if tmpI > 0 { if tmpI > 0 {
return tmpI return tmpI
} else {
continue
} }
continue
} }
cc := data[i] cc := data[i]
i++ i++
@ -1218,17 +1216,15 @@ func helperTripleEmphasis(p *parser, data []byte, offset int, c byte) int {
length = helperEmphasis(p, origData[offset-2:], c) length = helperEmphasis(p, origData[offset-2:], c)
if length == 0 { if length == 0 {
return 0 return 0
} else {
return length - 2
} }
return length - 2
default: default:
// single symbol found, hand over to emph2 // single symbol found, hand over to emph2
length = helperDoubleEmphasis(p, origData[offset-1:], c) length = helperDoubleEmphasis(p, origData[offset-1:], c)
if length == 0 { if length == 0 {
return 0 return 0
} else {
return length - 1
} }
return length - 1
} }
} }
return 0 return 0

View File

@ -311,7 +311,7 @@ func (r *Latex) DocumentHeader() {
r.w.WriteString(" pdfstartview=FitH,%\n") r.w.WriteString(" pdfstartview=FitH,%\n")
r.w.WriteString(" breaklinks=true,%\n") r.w.WriteString(" breaklinks=true,%\n")
r.w.WriteString(" pdfauthor={Blackfriday Markdown Processor v") r.w.WriteString(" pdfauthor={Blackfriday Markdown Processor v")
r.w.WriteString(VERSION) r.w.WriteString(Version)
r.w.WriteString("}}\n") r.w.WriteString("}}\n")
r.w.WriteString("\n") r.w.WriteString("\n")
r.w.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n") r.w.WriteString("\\newcommand{\\HRule}{\\rule{\\linewidth}{0.5mm}}\n")

View File

@ -13,7 +13,7 @@
// //
// //
// Blackfriday markdown processor. // Package blackfriday is a markdown processor.
// //
// Translates plain text with simple formatting rules into HTML or LaTeX. // Translates plain text with simple formatting rules into HTML or LaTeX.
package blackfriday package blackfriday
@ -26,8 +26,11 @@ import (
"unicode/utf8" "unicode/utf8"
) )
const VERSION = "1.4" // Version string of the package.
const Version = "2.0"
// Extensions is a bitwise or'ed collection of enabled Blackfriday's
// extensions.
type Extensions int type Extensions int
// These are the supported markdown parsing extensions. // These are the supported markdown parsing extensions.
@ -58,7 +61,7 @@ const (
TOC // Generate a table of contents TOC // Generate a table of contents
OmitContents // Skip the main contents (for a standalone table of contents) OmitContents // Skip the main contents (for a standalone table of contents)
CommonHtmlFlags HTMLFlags = UseXHTML CommonHTMLFlags HTMLFlags = UseXHTML
CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode |
Autolink | Strikethrough | SpaceHeaders | HeaderIDs | Autolink | Strikethrough | SpaceHeaders | HeaderIDs |
@ -66,10 +69,13 @@ const (
SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes
) )
// DefaultOptions is a convenience variable with all the options that are
// enabled by default.
var DefaultOptions = Options{ var DefaultOptions = Options{
Extensions: CommonExtensions, Extensions: CommonExtensions,
} }
// TODO: this should probably be unexported. Or moved to node.go
type LinkType int type LinkType int
// These are the possible flag values for the link renderer. // These are the possible flag values for the link renderer.
@ -81,6 +87,7 @@ const (
LinkTypeEmail LinkTypeEmail
) )
// ListType contains bitwise or'ed flags for list and list item objects.
type ListType int type ListType int
// These are the possible flag values for the ListItem renderer. // These are the possible flag values for the ListItem renderer.
@ -96,6 +103,7 @@ const (
ListItemEndOfList ListItemEndOfList
) )
// CellAlignFlags holds a type of alignment in a table cell.
type CellAlignFlags int type CellAlignFlags int
// These are the possible flag values for the table cell renderer. // These are the possible flag values for the table cell renderer.
@ -213,7 +221,7 @@ func (p *parser) getRef(refid string) (ref *reference, found bool) {
return &reference{ return &reference{
link: []byte(r.Link), link: []byte(r.Link),
title: []byte(r.Title), title: []byte(r.Title),
noteId: 0, noteID: 0,
hasBlock: false, hasBlock: false,
text: []byte(r.Text)}, true text: []byte(r.Text)}, true
} }
@ -312,9 +320,8 @@ func MarkdownBasic(input []byte) []byte {
return Markdown(input, renderer, Options{}) return Markdown(input, renderer, Options{})
} }
// Call Markdown with most useful extensions enabled // MarkdownCommon is a convenience function for simple rendering. It calls
// MarkdownCommon is a convenience function for simple rendering. // Markdown with most useful extensions enabled, including:
// It processes markdown input with common extensions enabled, including:
// //
// * Smartypants processing with smart fractions and LaTeX dashes // * Smartypants processing with smart fractions and LaTeX dashes
// //
@ -334,7 +341,7 @@ func MarkdownBasic(input []byte) []byte {
func MarkdownCommon(input []byte) []byte { func MarkdownCommon(input []byte) []byte {
// set up the HTML renderer // set up the HTML renderer
renderer := NewHTMLRenderer(HTMLRendererParameters{ renderer := NewHTMLRenderer(HTMLRendererParameters{
Flags: CommonHtmlFlags, Flags: CommonHTMLFlags,
Extensions: CommonExtensions, Extensions: CommonExtensions,
}) })
return Markdown(input, renderer, DefaultOptions) return Markdown(input, renderer, DefaultOptions)
@ -354,6 +361,10 @@ func Markdown(input []byte, renderer Renderer, options Options) []byte {
return renderer.Render(Parse(input, options)) return renderer.Render(Parse(input, options))
} }
// Parse is an entry point to the parsing part of Blackfriday. It takes an
// input markdown document and produces a syntax tree for its contents. This
// tree can then be rendered with a default or custom renderer, or
// analyzed/transformed by the caller to whatever non-standard needs they have.
func Parse(input []byte, opts Options) *Node { func Parse(input []byte, opts Options) *Node {
extensions := opts.Extensions extensions := opts.Extensions
@ -488,7 +499,7 @@ func (p *parser) parseRefsToAST() {
return return
} }
p.tip = p.doc p.tip = p.doc
finalizeHtmlBlock(p.addBlock(HTMLBlock, []byte(`<div class="footnotes">`))) finalizeHTMLBlock(p.addBlock(HTMLBlock, []byte(`<div class="footnotes">`)))
p.addBlock(HorizontalRule, nil) p.addBlock(HorizontalRule, nil)
block := p.addBlock(List, nil) block := p.addBlock(List, nil)
block.ListFlags = ListTypeOrdered block.ListFlags = ListTypeOrdered
@ -514,7 +525,7 @@ func (p *parser) parseRefsToAST() {
above := block.Parent above := block.Parent
finalizeList(block) finalizeList(block)
p.tip = above p.tip = above
finalizeHtmlBlock(p.addBlock(HTMLBlock, []byte("</div>"))) finalizeHTMLBlock(p.addBlock(HTMLBlock, []byte("</div>")))
block.Walk(func(node *Node, entering bool) WalkStatus { block.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Paragraph || node.Type == Header { if node.Type == Paragraph || node.Type == Header {
p.currBlock = node p.currBlock = node
@ -592,7 +603,7 @@ func secondPass(p *parser, input []byte) {
if p.flags&Footnotes != 0 && len(p.notes) > 0 { if p.flags&Footnotes != 0 && len(p.notes) > 0 {
flags := ListItemBeginningOfList flags := ListItemBeginningOfList
for i := 0; i < len(p.notes); i += 1 { for i := 0; i < len(p.notes); i++ {
ref := p.notes[i] ref := p.notes[i]
if ref.hasBlock { if ref.hasBlock {
flags |= ListItemContainsBlock flags |= ListItemContainsBlock
@ -642,14 +653,14 @@ func secondPass(p *parser, input []byte) {
type reference struct { type reference struct {
link []byte link []byte
title []byte title []byte
noteId int // 0 if not a footnote ref noteID int // 0 if not a footnote ref
hasBlock bool hasBlock bool
text []byte text []byte
} }
func (r *reference) String() string { func (r *reference) String() string {
return fmt.Sprintf("{link: %q, title: %q, text: %q, noteId: %d, hasBlock: %v}", return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}",
r.link, r.title, r.text, r.noteId, r.hasBlock) r.link, r.title, r.text, r.noteID, r.hasBlock)
} }
// Check whether or not data starts with a reference link. // Check whether or not data starts with a reference link.
@ -667,7 +678,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
i++ i++
} }
noteId := 0 noteID := 0
// id part: anything but a newline between brackets // id part: anything but a newline between brackets
if data[i] != '[' { if data[i] != '[' {
@ -678,7 +689,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
if i < len(data) && data[i] == '^' { if i < len(data) && data[i] == '^' {
// we can set it to anything here because the proper noteIds will // we can set it to anything here because the proper noteIds will
// be assigned later during the second pass. It just has to be != 0 // be assigned later during the second pass. It just has to be != 0
noteId = 1 noteID = 1
i++ i++
} }
} }
@ -721,7 +732,7 @@ func isReference(p *parser, data []byte, tabSize int) int {
hasBlock bool hasBlock bool
) )
if p.flags&Footnotes != 0 && noteId != 0 { if p.flags&Footnotes != 0 && noteID != 0 {
linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize)
lineEnd = linkEnd lineEnd = linkEnd
} else { } else {
@ -734,11 +745,11 @@ func isReference(p *parser, data []byte, tabSize int) int {
// a valid ref has been found // a valid ref has been found
ref := &reference{ ref := &reference{
noteId: noteId, noteID: noteID,
hasBlock: hasBlock, hasBlock: hasBlock,
} }
if noteId > 0 { if noteID > 0 {
// reusing the link field for the id since footnotes don't have links // reusing the link field for the id since footnotes don't have links
ref.link = data[idOffset:idEnd] ref.link = data[idOffset:idEnd]
// if footnote, it's not really a title, it's the contained text // if footnote, it's not really a title, it's the contained text

52
node.go
View File

@ -5,8 +5,12 @@ import (
"fmt" "fmt"
) )
// NodeType specifies a type of a single node of a syntax tree. Usually one
// node (and its type) corresponds to a single markdown feature, e.g. emphasis
// or code block.
type NodeType int type NodeType int
// Constants for identifying different types of nodes. See NodeType.
const ( const (
Document NodeType = iota Document NodeType = iota
BlockQuote BlockQuote
@ -65,6 +69,7 @@ func (t NodeType) String() string {
return nodeTypeNames[t] return nodeTypeNames[t]
} }
// ListData contains fields relevant to a List node type.
type ListData struct { type ListData struct {
ListFlags ListType ListFlags ListType
Tight bool // Skip <p>s around list item data if true Tight bool // Skip <p>s around list item data if true
@ -73,12 +78,14 @@ type ListData struct {
RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering
} }
// LinkData contains fields relevant to a Link node type.
type LinkData struct { type LinkData struct {
Destination []byte Destination []byte
Title []byte Title []byte
NoteID int NoteID int
} }
// CodeBlockData contains fields relevant to a CodeBlock node type.
type CodeBlockData struct { type CodeBlockData struct {
IsFenced bool // Specifies whether it's a fenced code block or an indented one IsFenced bool // Specifies whether it's a fenced code block or an indented one
Info []byte // This holds the info string Info []byte // This holds the info string
@ -87,11 +94,13 @@ type CodeBlockData struct {
FenceOffset int FenceOffset int
} }
// TableCellData contains fields relevant to a TableCell node type.
type TableCellData struct { type TableCellData struct {
IsHeader bool // This tells if it's under the header row IsHeader bool // This tells if it's under the header row
Align CellAlignFlags // This holds the value for align attribute Align CellAlignFlags // This holds the value for align attribute
} }
// HeaderData contains fields relevant to a Header node type.
type HeaderData struct { type HeaderData struct {
Level int // This holds the heading level number Level int // This holds the heading level number
HeaderID string // This might hold header ID, if present HeaderID string // This might hold header ID, if present
@ -111,16 +120,17 @@ type Node struct {
Literal []byte // Text contents of the leaf nodes Literal []byte // Text contents of the leaf nodes
HeaderData // Populated if Type == Header HeaderData // Populated if Type is Header
ListData // Populated if Type == List ListData // Populated if Type is List
CodeBlockData // Populated if Type == CodeBlock CodeBlockData // Populated if Type is CodeBlock
LinkData // Populated if Type == Link LinkData // Populated if Type is Link
TableCellData // Populated if Type == TableCell TableCellData // Populated if Type is TableCell
content []byte // Markdown content of the block nodes content []byte // Markdown content of the block nodes
open bool // Specifies an open block node that has not been finished to process yet open bool // Specifies an open block node that has not been finished to process yet
} }
// NewNode allocates a node of a specified type.
func NewNode(typ NodeType) *Node { func NewNode(typ NodeType) *Node {
return &Node{ return &Node{
Type: typ, Type: typ,
@ -218,7 +228,6 @@ func (n *Node) isContainer() bool {
default: default:
return false return false
} }
return false
} }
func (n *Node) canContain(t NodeType) bool { func (n *Node) canContain(t NodeType) bool {
@ -246,9 +255,12 @@ func (n *Node) canContain(t NodeType) bool {
type WalkStatus int type WalkStatus int
const ( const (
GoToNext WalkStatus = iota // The default traversal of every node. // GoToNext is the default traversal of every node.
SkipChildren // Skips all children of current node. GoToNext WalkStatus = iota
Terminate // Terminates the traversal. // SkipChildren tells walker to skip all children of current node.
SkipChildren
// Terminate tells walker to terminate the traversal.
Terminate
) )
// NodeVisitor is a callback to be called when traversing the syntax tree. // NodeVisitor is a callback to be called when traversing the syntax tree.
@ -256,8 +268,10 @@ const (
// first visited, then with entering=false after all the children are done. // first visited, then with entering=false after all the children are done.
type NodeVisitor func(node *Node, entering bool) WalkStatus type NodeVisitor func(node *Node, entering bool) WalkStatus
func (root *Node) Walk(visitor NodeVisitor) { // Walk is a convenience method that instantiates a walker and starts a
walker := NewNodeWalker(root) // traversal of subtree rooted at n.
func (n *Node) Walk(visitor NodeVisitor) {
walker := newNodeWalker(n)
node, entering := walker.next() node, entering := walker.next()
for node != nil { for node != nil {
status := visitor(node, entering) status := visitor(node, entering)
@ -272,21 +286,21 @@ func (root *Node) Walk(visitor NodeVisitor) {
} }
} }
type NodeWalker struct { type nodeWalker struct {
current *Node current *Node
root *Node root *Node
entering bool entering bool
} }
func NewNodeWalker(root *Node) *NodeWalker { func newNodeWalker(root *Node) *nodeWalker {
return &NodeWalker{ return &nodeWalker{
current: root, current: root,
root: nil, root: nil,
entering: true, entering: true,
} }
} }
func (nw *NodeWalker) next() (*Node, bool) { func (nw *nodeWalker) next() (*Node, bool) {
if nw.current == nil { if nw.current == nil {
return nil, false return nil, false
} }
@ -314,7 +328,7 @@ func (nw *NodeWalker) next() (*Node, bool) {
return nw.current, nw.entering return nw.current, nw.entering
} }
func (nw *NodeWalker) resumeAt(node *Node, entering bool) (*Node, bool) { func (nw *nodeWalker) resumeAt(node *Node, entering bool) (*Node, bool) {
nw.current = node nw.current = node
nw.entering = entering nw.entering = entering
return nw.next() return nw.next()
@ -324,7 +338,7 @@ func dump(ast *Node) {
fmt.Println(dumpString(ast)) fmt.Println(dumpString(ast))
} }
func dump_r(ast *Node, depth int) string { func dumpR(ast *Node, depth int) string {
if ast == nil { if ast == nil {
return "" return ""
} }
@ -335,11 +349,11 @@ func dump_r(ast *Node, depth int) string {
} }
result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content) result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content)
for n := ast.FirstChild; n != nil; n = n.Next { for n := ast.FirstChild; n != nil; n = n.Next {
result += dump_r(n, depth+1) result += dumpR(n, depth+1)
} }
return result return result
} }
func dumpString(ast *Node) string { func dumpString(ast *Node) string {
return dump_r(ast, 0) return dumpR(ast, 0)
} }

View File

@ -19,6 +19,7 @@ import (
"bytes" "bytes"
) )
// SPRenderer is a struct containing state of a Smartypants renderer.
type SPRenderer struct { type SPRenderer struct {
inSingleQuote bool inSingleQuote bool
inDoubleQuote bool inDoubleQuote bool
@ -108,7 +109,7 @@ func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote
return true return true
} }
func (smrt *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 { if len(text) >= 2 {
t1 := tolower(text[1]) t1 := tolower(text[1])
@ -117,7 +118,7 @@ func (smrt *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, t
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) {
return 1 return 1
} }
} }
@ -142,7 +143,7 @@ func (smrt *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, t
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if smartQuoteHelper(out, previousChar, nextChar, 's', &smrt.inSingleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote) {
return 0 return 0
} }
@ -150,7 +151,7 @@ func (smrt *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, t
return 0 return 0
} }
func (smrt *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 { if len(text) >= 3 {
t1 := tolower(text[1]) t1 := tolower(text[1])
t2 := tolower(text[2]) t2 := tolower(text[2])
@ -175,7 +176,7 @@ func (smrt *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text [
return 0 return 0
} }
func (smrt *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 { if len(text) >= 2 {
if text[1] == '-' { if text[1] == '-' {
out.WriteString("&mdash;") out.WriteString("&mdash;")
@ -192,7 +193,7 @@ func (smrt *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []b
return 0 return 0
} }
func (smrt *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '-' && text[2] == '-' { if len(text) >= 3 && text[1] == '-' && text[2] == '-' {
out.WriteString("&mdash;") out.WriteString("&mdash;")
return 2 return 2
@ -206,13 +207,13 @@ func (smrt *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, tex
return 0 return 0
} }
func (smrt *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
if bytes.HasPrefix(text, []byte("&quot;")) { if bytes.HasPrefix(text, []byte("&quot;")) {
nextChar := byte(0) nextChar := byte(0)
if len(text) >= 7 { if len(text) >= 7 {
nextChar = text[6] nextChar = text[6]
} }
if smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote) {
return 5 return 5
} }
} }
@ -225,15 +226,15 @@ func (smrt *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, te
return 0 return 0
} }
func (smrt *SPRenderer) smartAmp(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartAmp(out *bytes.Buffer, previousChar byte, text []byte) int {
return smrt.smartAmpVariant(out, previousChar, text, 'd') return r.smartAmpVariant(out, previousChar, text, 'd')
} }
func (smrt *SPRenderer) smartAmpAngledQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartAmpAngledQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return smrt.smartAmpVariant(out, previousChar, text, 'a') return r.smartAmpVariant(out, previousChar, text, 'a')
} }
func (smrt *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 3 && text[1] == '.' && text[2] == '.' { if len(text) >= 3 && text[1] == '.' && text[2] == '.' {
out.WriteString("&hellip;") out.WriteString("&hellip;")
return 2 return 2
@ -248,13 +249,13 @@ func (smrt *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text [
return 0 return 0
} }
func (smrt *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int {
if len(text) >= 2 && text[1] == '`' { if len(text) >= 2 && text[1] == '`' {
nextChar := byte(0) nextChar := byte(0)
if len(text) >= 3 { if len(text) >= 3 {
nextChar = text[2] nextChar = text[2]
} }
if smartQuoteHelper(out, previousChar, nextChar, 'd', &smrt.inDoubleQuote) { if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote) {
return 1 return 1
} }
} }
@ -263,7 +264,7 @@ func (smrt *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text
return 0 return 0
} }
func (smrt *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
// is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b
// note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8) // note: check for regular slash (/) or fraction slash (, 0x2044, or 0xe2 81 84 in utf-8)
@ -305,7 +306,7 @@ func (smrt *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte,
return 0 return 0
} }
func (smrt *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int {
if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 {
if text[0] == '1' && text[1] == '/' && text[2] == '2' { if text[0] == '1' && text[1] == '/' && text[2] == '2' {
if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' {
@ -333,27 +334,27 @@ func (smrt *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text [
return 0 return 0
} }
func (smrt *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int {
nextChar := byte(0) nextChar := byte(0)
if len(text) > 1 { if len(text) > 1 {
nextChar = text[1] nextChar = text[1]
} }
if !smartQuoteHelper(out, previousChar, nextChar, quote, &smrt.inDoubleQuote) { if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote) {
out.WriteString("&quot;") out.WriteString("&quot;")
} }
return 0 return 0
} }
func (smrt *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return smrt.smartDoubleQuoteVariant(out, previousChar, text, 'd') return r.smartDoubleQuoteVariant(out, previousChar, text, 'd')
} }
func (smrt *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int {
return smrt.smartDoubleQuoteVariant(out, previousChar, text, 'a') return r.smartDoubleQuoteVariant(out, previousChar, text, 'a')
} }
func (smrt *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int { func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int {
i := 0 i := 0
for i < len(text) && text[i] != '>' { for i < len(text) && text[i] != '>' {
@ -366,6 +367,7 @@ func (smrt *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, tex
type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int
// NewSmartypantsRenderer constructs a Smartypants renderer object.
func NewSmartypantsRenderer(flags Extensions) *SPRenderer { func NewSmartypantsRenderer(flags Extensions) *SPRenderer {
var r SPRenderer var r SPRenderer
if flags&SmartypantsAngledQuotes == 0 { if flags&SmartypantsAngledQuotes == 0 {
@ -398,13 +400,14 @@ func NewSmartypantsRenderer(flags Extensions) *SPRenderer {
return &r return &r
} }
func (sr *SPRenderer) Process(text []byte) []byte { // Process is the entry point of the Smartypants renderer.
func (r *SPRenderer) Process(text []byte) []byte {
var buff bytes.Buffer var buff bytes.Buffer
// first do normal entity escaping // first do normal entity escaping
text = esc(text) text = esc(text)
mark := 0 mark := 0
for i := 0; i < len(text); i++ { for i := 0; i < len(text); i++ {
if action := sr.callbacks[text[i]]; action != nil { if action := r.callbacks[text[i]]; action != nil {
if i > mark { if i > mark {
buff.Write(text[mark:i]) buff.Write(text[mark:i])
} }