mirror of
https://github.com/russross/blackfriday.git
synced 2024-03-22 13:40:34 +08:00
output validates, command-line tool has useful options
This commit is contained in:
parent
157bb44c05
commit
f9b03f67fb
23
README.md
23
README.md
|
@ -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
|
||||||
|
|
125
example/main.go
125
example/main.go
|
@ -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
10
html.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
13
inline.go
13
inline.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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' {
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
|
|
||||||
<p>6 > 5.</p>
|
<p>6 > 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&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&T">AT&T</a>.</p>
|
<p>Here's a link with an amersand in the link text: <a href="http://att.com/" title="AT&T">AT&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&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&bar=2">link</a>.</p>
|
||||||
|
|
|
@ -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&bar=2</a></p>
|
<p>With an ampersand: <a href="http://example.com/?foo=1&bar=2">http://example.com/?foo=1&bar=2</a></p>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li>In a list?</li>
|
<li>In a list?</li>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user