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
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

View File

@ -13,6 +13,7 @@
package main
import (
"flag"
"fmt"
"io/ioutil"
"github.com/russross/blackfriday"
@ -20,22 +21,55 @@ import (
)
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 @@ func main() {
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>")
}
}

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 {
out.WriteString("mailto:")
}
out.Write(link)
attrEscape(out, link)
out.WriteString("\">")
/*
@ -504,17 +504,13 @@ func htmlLink(out *bytes.Buffer, link []byte, title []byte, content []byte, opaq
}
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
}

View File

@ -124,7 +124,7 @@ func inlineCodeSpan(out *bytes.Buffer, rndr *render, data []byte, offset int) in
}
if i < nb && end >= len(data) {
out.WriteByte('`')
out.WriteByte('`')
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
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 @@ func inlineLink(out *bytes.Buffer, rndr *render, data []byte, offset int) int {
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 inlineEntity(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 ':'
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
}

View File

@ -141,6 +141,7 @@ type render struct {
flags uint32
nesting int
maxNesting int
insideLink bool
}
@ -165,6 +166,7 @@ func Markdown(input []byte, renderer *Renderer, extensions uint32) []byte {
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 @@ func expandTabs(out *bytes.Buffer, line []byte) {
// 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' {

View File

@ -8,10 +8,10 @@
<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>

View File

@ -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>