Permalink
Browse files

output validates, command-line tool has useful options

  • Loading branch information...
russross committed Jun 24, 2011
1 parent 157bb44 commit f9b03f67fbe685393bf24c1fd18fbc89da420561
Showing with 127 additions and 56 deletions.
  1. +8 −15 README.md
  2. +98 −27 example/main.go
  3. +3 −7 html.go
  4. +11 −2 inline.go
  5. +3 −1 markdown.go
  6. +3 −3 upskirtref/Amps and angle encoding.html
  7. +1 −1 upskirtref/Auto links.html
View
@@ -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`:
-
- renderer := blackfriday.HtmlRenderer(html_flags)
-
-and uncomment this line:
-
- renderer := blackfriday.LatexRenderer(0)
+example of its usage, see `main.go`:
-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
@@ -13,29 +13,63 @@
package main
import (
+ "flag"
"fmt"
"io/ioutil"
"github.com/russross/blackfriday"
"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 @@ 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
-
- // 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)
+ 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)
+ }
- 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>")
}
}
View
10 html.go
@@ -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
@@ -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
@@ -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' {
@@ -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>
@@ -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>

0 comments on commit f9b03f6

Please sign in to comment.