all repos — grayfriday @ f9b03f67fbe685393bf24c1fd18fbc89da420561

blackfriday fork with a few changes

output validates, command-line tool has useful options
Russ Ross russ@russross.com
Fri, 24 Jun 2011 11:50:03 -0600
commit

f9b03f67fbe685393bf24c1fd18fbc89da420561

parent

157bb44c05ebc6de03292485277d14ce96d9bd78

M README.mdREADME.md

@@ -53,17 +53,13 @@ errors that were present in the C code).

* Good performance. I have not done rigorous benchmarking, but informal testing suggests it is around 3.5x slower than upskirt. - This is an ugly, direct translation from the C code, so - the difference is unlikely to be related to differences in - coding style. There is a lot of bounds checking that is - duplicated (by user code for the application and again by code - the compiler generates) and there is some additional memory - management overhead, since I allocate and garbage collect - buffers instead of explicitly managing them as upskirt does. * Minimal dependencies. blackfriday only depends on standard library packages in Go. The source code is pretty self-contained, so it is easy to add to any project. + +* Output successfully validates using the W3C validation tool for + HTML 4.01 and XHTML 1.0 Transitional. Extensions

@@ -83,15 +79,12 @@ LaTeX Output

------------ A rudimentary LaTeX rendering backend is also included. To see an -example of its usage, comment out this link in `main.go`: +example of its usage, see `main.go`: - renderer := blackfriday.HtmlRenderer(html_flags) - -and uncomment this line: - - renderer := blackfriday.LatexRenderer(0) - -It renders some basic documents, but is only experimental at this point. +It renders some basic documents, but is only experimental at this +point. In particular, it does not do any inline escaping, so input +that happens to look like LaTeX code will be passed through without +modification. Todo
M example/main.goexample/main.go

@@ -13,6 +13,7 @@

package main import ( + "flag" "fmt" "io/ioutil" "github.com/russross/blackfriday"

@@ -20,22 +21,55 @@ "os"

) func main() { + // parse command-line options + var page, xhtml, latex, smartypants bool + var css string + var repeat int + flag.BoolVar(&page, "page", false, + "Generate a standalone HTML page (implies -latex=false)") + flag.BoolVar(&xhtml, "xhtml", true, + "Use XHTML-style tags in HTML output") + flag.BoolVar(&latex, "latex", false, + "Generate LaTeX output instead of HTML") + flag.BoolVar(&smartypants, "smartypants", false, + "Apply smartypants-style substitutions") + flag.StringVar(&css, "css", "", + "Link to a CSS stylesheet (implies -page)") + flag.IntVar(&repeat, "repeat", 1, + "Process the input multiple times (for benchmarking)") + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage:\n"+ + " %s [options] [inputfile [outputfile]]\n\n"+ + "Options:\n", os.Args[0]) + flag.PrintDefaults() + } + flag.Parse() + + // enforce implied options + if css != "" { + page = true + } + if page { + latex = false + } + // read the input var input []byte var err os.Error - switch len(os.Args) { - case 1: + args := flag.Args() + switch len(args) { + case 0: if input, err = ioutil.ReadAll(os.Stdin); err != nil { fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err) os.Exit(-1) } - case 2, 3: - if input, err = ioutil.ReadFile(os.Args[1]); err != nil { - fmt.Fprintln(os.Stderr, "Error reading from", os.Args[1], ":", err) + case 1, 2: + if input, err = ioutil.ReadFile(args[0]); err != nil { + fmt.Fprintln(os.Stderr, "Error reading from", args[0], ":", err) os.Exit(-1) } default: - fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[inputfile [outputfile]]") + flag.Usage() os.Exit(-1) }

@@ -48,33 +82,70 @@ extensions |= blackfriday.EXTENSION_AUTOLINK

extensions |= blackfriday.EXTENSION_STRIKETHROUGH extensions |= blackfriday.EXTENSION_SPACE_HEADERS - html_flags := 0 - html_flags |= blackfriday.HTML_USE_XHTML - // note: uncomment the following line to enable smartypants - // it is commented out by default so that markdown - // compatibility tests pass - //html_flags |= blackfriday.HTML_USE_SMARTYPANTS - html_flags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS - html_flags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES + var renderer *blackfriday.Renderer + if latex { + // render the data into LaTeX + renderer = blackfriday.LatexRenderer(0) + } else { + // render the data into HTML + html_flags := 0 + if xhtml { + html_flags |= blackfriday.HTML_USE_XHTML + } + if smartypants { + html_flags |= blackfriday.HTML_USE_SMARTYPANTS + html_flags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS + html_flags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES + } + renderer = blackfriday.HtmlRenderer(html_flags) + } - // render the data into HTML (comment this out to deselect HTML) - renderer := blackfriday.HtmlRenderer(html_flags) - - // render the data into LaTeX (uncomment to select LaTeX) - //renderer := blackfriday.LatexRenderer(0) - - output := blackfriday.Markdown(input, renderer, extensions) + // parse and render + var output []byte + for i := 0; i < repeat; i++ { + output = blackfriday.Markdown(input, renderer, extensions) + } // output the result - if len(os.Args) == 3 { - if err = ioutil.WriteFile(os.Args[2], output, 0644); err != nil { - fmt.Fprintln(os.Stderr, "Error writing to", os.Args[2], ":", err) + var out *os.File + if len(args) == 2 { + if out, err = os.Create(args[1]); err != nil { + fmt.Fprintf(os.Stderr, "Error creating %s: %v", args[1], err) os.Exit(-1) } + defer out.Close() } else { - if _, err = os.Stdout.Write(output); err != nil { - fmt.Fprintln(os.Stderr, "Error writing to Stdout:", err) - os.Exit(-1) + out = os.Stdout + } + + if page { + ending := "" + if xhtml { + fmt.Fprint(out, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ") + fmt.Fprintln(out, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">") + fmt.Fprintln(out, "<html xmlns=\"http://www.w3.org/1999/xhtml\">") + ending = " /" + } else { + fmt.Fprint(out, "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\" ") + fmt.Fprintln(out, "\"http://www.w3.org/TR/html4/strict.dtd\">") + fmt.Fprintln(out, "<html>") } + fmt.Fprintln(out, "<head>") + fmt.Fprintln(out, " <title></title>") + fmt.Fprintf(out, " <meta name=\"GENERATOR\" content=\"Blackfriday markdown processor\"%s>\n", ending) + fmt.Fprintf(out, " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"%s>\n", ending) + if css != "" { + fmt.Fprintf(out, " <link rel=\"stylesheet\" type=\"text/css\" href=\"%s\" />\n", css) + } + fmt.Fprintln(out, "</head>") + fmt.Fprintln(out, "<body>") + } + if _, err = out.Write(output); err != nil { + fmt.Fprintln(os.Stderr, "Error writing output:", err) + os.Exit(-1) + } + if page { + fmt.Fprintln(out, "</body>") + fmt.Fprintln(out, "</html>") } }
M html.gohtml.go

@@ -419,7 +419,7 @@ out.WriteString("<a href=\"")

if kind == LINK_TYPE_EMAIL { out.WriteString("mailto:") } - out.Write(link) + attrEscape(out, link) out.WriteString("\">") /*

@@ -504,17 +504,13 @@ return 0

} out.WriteString("<a href=\"") - if len(link) > 0 { - out.Write(link) - } + attrEscape(out, link) if len(title) > 0 { out.WriteString("\" title=\"") attrEscape(out, title) } out.WriteString("\">") - if len(content) > 0 { - out.Write(content) - } + out.Write(content) out.WriteString("</a>") return 1 }
M inline.goinline.go

@@ -124,7 +124,7 @@ }

} if i < nb && end >= len(data) { - out.WriteByte('`') + out.WriteByte('`') return 0 // no matching delimiter }

@@ -185,6 +185,11 @@ }

// '[': parse a link or an image func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { + // no links allowed inside other links + if rndr.insideLink { + return 0 + } + isImg := offset > 0 && data[offset-1] == '!' data = data[offset:]

@@ -410,7 +415,11 @@ if txt_e > 1 {

if isImg { content.Write(data[1:txt_e]) } else { + // links cannot contain other links, so turn off link parsing temporarily + insideLink := rndr.insideLink + rndr.insideLink = true parseInline(&content, rndr, data[1:txt_e]) + rndr.insideLink = insideLink } }

@@ -539,7 +548,7 @@ }

func inlineAutoLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { // quick check to rule out most false hits on ':' - if len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' { + if rndr.insideLink || len(data) < offset+3 || data[offset+1] != '/' || data[offset+2] != '/' { return 0 }
M markdown.gomarkdown.go

@@ -141,6 +141,7 @@ inline [256]inlineParser

flags uint32 nesting int maxNesting int + insideLink bool }

@@ -165,6 +166,7 @@ rndr.mk = renderer

rndr.flags = extensions rndr.refs = make(map[string]*reference) rndr.maxNesting = 16 + rndr.insideLink = false // register inline parsers if rndr.mk.Emphasis != nil || rndr.mk.DoubleEmphasis != nil || rndr.mk.TripleEmphasis != nil {

@@ -464,7 +466,7 @@

// the slow case: we need to count runes to figure out how // many spaces to insert for each tab column := 0 - i = 0 + i = 0 for i < len(line) { start := i for i < len(line) && line[i] != '\t' {
M upskirtref/Amps and angle encoding.htmlupskirtref/Amps and angle encoding.html

@@ -8,10 +8,10 @@ <p>4 &lt; 5.</p>

<p>6 &gt; 5.</p> -<p>Here's a <a href="http://example.com/?foo=1&bar=2">link</a> with an ampersand in the URL.</p> +<p>Here's a <a href="http://example.com/?foo=1&amp;bar=2">link</a> with an ampersand in the URL.</p> <p>Here's a link with an amersand in the link text: <a href="http://att.com/" title="AT&amp;T">AT&amp;T</a>.</p> -<p>Here's an inline <a href="/script?foo=1&bar=2">link</a>.</p> +<p>Here's an inline <a href="/script?foo=1&amp;bar=2">link</a>.</p> -<p>Here's an inline <a href="/script?foo=1&bar=2">link</a>.</p> +<p>Here's an inline <a href="/script?foo=1&amp;bar=2">link</a>.</p>
M upskirtref/Auto links.htmlupskirtref/Auto links.html

@@ -1,6 +1,6 @@

<p>Link: <a href="http://example.com/">http://example.com/</a>.</p> -<p>With an ampersand: <a href="http://example.com/?foo=1&bar=2">http://example.com/?foo=1&amp;bar=2</a></p> +<p>With an ampersand: <a href="http://example.com/?foo=1&amp;bar=2">http://example.com/?foo=1&amp;bar=2</a></p> <ul> <li>In a list?</li>