output validates, command-line tool has useful options

This commit is contained in:
Russ Ross 2011-06-24 11:50:03 -06:00
parent 157bb44c05
commit f9b03f67fb
7 changed files with 127 additions and 56 deletions

View File

@ -53,18 +53,14 @@ All features of upskirt are supported, including:
* Good performance. I have not done rigorous benchmarking, but * Good performance. I have not done rigorous benchmarking, but
informal testing suggests it is around 3.5x slower than upskirt. 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 * Minimal dependencies. blackfriday only depends on standard
library packages in Go. The source code is pretty library packages in Go. The source code is pretty
self-contained, so it is easy to add to any project. 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 Extensions
---------- ----------
@ -83,15 +79,12 @@ LaTeX Output
------------ ------------
A rudimentary LaTeX rendering backend is also included. To see an 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) It renders some basic documents, but is only experimental at this
point. In particular, it does not do any inline escaping, so input
and uncomment this line: that happens to look like LaTeX code will be passed through without
modification.
renderer := blackfriday.LatexRenderer(0)
It renders some basic documents, but is only experimental at this point.
Todo Todo

View File

@ -13,6 +13,7 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"github.com/russross/blackfriday" "github.com/russross/blackfriday"
@ -20,22 +21,55 @@ import (
) )
func main() { 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 // read the input
var input []byte var input []byte
var err os.Error var err os.Error
switch len(os.Args) { args := flag.Args()
case 1: switch len(args) {
case 0:
if input, err = ioutil.ReadAll(os.Stdin); err != nil { if input, err = ioutil.ReadAll(os.Stdin); err != nil {
fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err) fmt.Fprintln(os.Stderr, "Error reading from Stdin:", err)
os.Exit(-1) os.Exit(-1)
} }
case 2, 3: case 1, 2:
if input, err = ioutil.ReadFile(os.Args[1]); err != nil { if input, err = ioutil.ReadFile(args[0]); err != nil {
fmt.Fprintln(os.Stderr, "Error reading from", os.Args[1], ":", err) fmt.Fprintln(os.Stderr, "Error reading from", args[0], ":", err)
os.Exit(-1) os.Exit(-1)
} }
default: default:
fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[inputfile [outputfile]]") flag.Usage()
os.Exit(-1) os.Exit(-1)
} }
@ -48,33 +82,70 @@ func main() {
extensions |= blackfriday.EXTENSION_STRIKETHROUGH extensions |= blackfriday.EXTENSION_STRIKETHROUGH
extensions |= blackfriday.EXTENSION_SPACE_HEADERS extensions |= blackfriday.EXTENSION_SPACE_HEADERS
html_flags := 0 var renderer *blackfriday.Renderer
html_flags |= blackfriday.HTML_USE_XHTML if latex {
// note: uncomment the following line to enable smartypants // render the data into LaTeX
// it is commented out by default so that markdown renderer = blackfriday.LatexRenderer(0)
// compatibility tests pass } else {
//html_flags |= blackfriday.HTML_USE_SMARTYPANTS // render the data into HTML
html_flags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS html_flags := 0
html_flags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES 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) // parse and render
renderer := blackfriday.HtmlRenderer(html_flags) var output []byte
for i := 0; i < repeat; i++ {
// render the data into LaTeX (uncomment to select LaTeX) output = blackfriday.Markdown(input, renderer, extensions)
//renderer := blackfriday.LatexRenderer(0) }
output := blackfriday.Markdown(input, renderer, extensions)
// output the result // output the result
if len(os.Args) == 3 { var out *os.File
if err = ioutil.WriteFile(os.Args[2], output, 0644); err != nil { if len(args) == 2 {
fmt.Fprintln(os.Stderr, "Error writing to", os.Args[2], ":", err) if out, err = os.Create(args[1]); err != nil {
fmt.Fprintf(os.Stderr, "Error creating %s: %v", args[1], err)
os.Exit(-1) os.Exit(-1)
} }
defer out.Close()
} else { } else {
if _, err = os.Stdout.Write(output); err != nil { out = os.Stdout
fmt.Fprintln(os.Stderr, "Error writing to Stdout:", err) }
os.Exit(-1)
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>")
} }
} }

10
html.go
View File

@ -419,7 +419,7 @@ func htmlAutoLink(out *bytes.Buffer, link []byte, kind int, opaque interface{})
if kind == LINK_TYPE_EMAIL { if kind == LINK_TYPE_EMAIL {
out.WriteString("mailto:") out.WriteString("mailto:")
} }
out.Write(link) attrEscape(out, link)
out.WriteString("\">") out.WriteString("\">")
/* /*
@ -504,17 +504,13 @@ func htmlLink(out *bytes.Buffer, link []byte, title []byte, content []byte, opaq
} }
out.WriteString("<a href=\"") out.WriteString("<a href=\"")
if len(link) > 0 { attrEscape(out, link)
out.Write(link)
}
if len(title) > 0 { if len(title) > 0 {
out.WriteString("\" title=\"") out.WriteString("\" title=\"")
attrEscape(out, title) attrEscape(out, title)
} }
out.WriteString("\">") out.WriteString("\">")
if len(content) > 0 { out.Write(content)
out.Write(content)
}
out.WriteString("</a>") out.WriteString("</a>")
return 1 return 1
} }

View File

@ -124,7 +124,7 @@ func inlineCodeSpan(out *bytes.Buffer, rndr *render, data []byte, offset int) in
} }
if i < nb && end >= len(data) { if i < nb && end >= len(data) {
out.WriteByte('`') out.WriteByte('`')
return 0 // no matching delimiter return 0 // no matching delimiter
} }
@ -185,6 +185,11 @@ func inlineLineBreak(out *bytes.Buffer, rndr *render, data []byte, offset int) i
// '[': parse a link or an image // '[': parse a link or an image
func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { 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] == '!' isImg := offset > 0 && data[offset-1] == '!'
data = data[offset:] data = data[offset:]
@ -410,7 +415,11 @@ func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
if isImg { if isImg {
content.Write(data[1:txt_e]) content.Write(data[1:txt_e])
} else { } 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]) parseInline(&content, rndr, data[1:txt_e])
rndr.insideLink = insideLink
} }
} }
@ -539,7 +548,7 @@ func inlineEntity(out *bytes.Buffer, rndr *render, data []byte, offset int) int
func inlineAutoLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int { func inlineAutoLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
// quick check to rule out most false hits on ':' // 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 return 0
} }

View File

@ -141,6 +141,7 @@ type render struct {
flags uint32 flags uint32
nesting int nesting int
maxNesting int maxNesting int
insideLink bool
} }
@ -165,6 +166,7 @@ func Markdown(input []byte, renderer *Renderer, extensions uint32) []byte {
rndr.flags = extensions rndr.flags = extensions
rndr.refs = make(map[string]*reference) rndr.refs = make(map[string]*reference)
rndr.maxNesting = 16 rndr.maxNesting = 16
rndr.insideLink = false
// register inline parsers // register inline parsers
if rndr.mk.Emphasis != nil || rndr.mk.DoubleEmphasis != nil || rndr.mk.TripleEmphasis != nil { if rndr.mk.Emphasis != nil || rndr.mk.DoubleEmphasis != nil || rndr.mk.TripleEmphasis != nil {
@ -464,7 +466,7 @@ func expandTabs(out *bytes.Buffer, line []byte) {
// the slow case: we need to count runes to figure out how // the slow case: we need to count runes to figure out how
// many spaces to insert for each tab // many spaces to insert for each tab
column := 0 column := 0
i = 0 i = 0
for i < len(line) { for i < len(line) {
start := i start := i
for i < len(line) && line[i] != '\t' { for i < len(line) && line[i] != '\t' {

View File

@ -8,10 +8,10 @@
<p>6 &gt; 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 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>

View File

@ -1,6 +1,6 @@
<p>Link: <a href="http://example.com/">http://example.com/</a>.</p> <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> <ul>
<li>In a list?</li> <li>In a list?</li>