new API: p := NewParser(&opts), p.Markdown(r, f) replaces d := Parse(r, opts), d.WriteHtml(w)

The new api allows to create a parser instance that can be
reused between calls to Markdown. Also, an interface `Formatter'
replaces the tight coupling between former Doc type and HTML
generation.
This commit is contained in:
Michael Teichgräber 2012-04-28 22:57:55 +02:00
parent 2670cccafb
commit f721084df0
6 changed files with 5299 additions and 5258 deletions

View File

@ -31,11 +31,12 @@ func main() {
r = f r = f
} }
p := markdown.NewParser(&opt)
startPProf() startPProf()
defer stopPProf() defer stopPProf()
doc := markdown.Parse(r, opt)
w := bufio.NewWriter(os.Stdout) w := bufio.NewWriter(os.Stdout)
doc.WriteHtml(w) p.Markdown(r, markdown.ToHTML(w))
w.Flush() w.Flush()
} }

6
doc.go
View File

@ -6,16 +6,16 @@ Usage example:
package main package main
import ( import (
md "github.com/knieriem/markdown" "github.com/knieriem/markdown"
"os" "os"
"bufio" "bufio"
) )
func main() { func main() {
doc := md.Parse(os.Stdin, md.Options{Smart: true}) p := markdown.NewParser(&markdown.Options{Smart: true})
w := bufio.NewWriter(os.Stdout) w := bufio.NewWriter(os.Stdout)
doc.WriteHtml(w) p.Markdown(os.Stdin, markdown.ToHTML(w))
w.Flush() w.Flush()
} }

View File

@ -17,8 +17,6 @@
package markdown package markdown
// implements Parse()
import ( import (
"bytes" "bytes"
"io" "io"
@ -35,46 +33,73 @@ type Options struct {
Dlists bool Dlists bool
} }
// Parse converts a Markdown document into a tree for later output processing. type Parser struct {
func Parse(r io.Reader, opt Options) *Doc { yy yyParser
d := new(Doc) preformatBuf *bytes.Buffer
d.extension = opt
d.parser = new(yyParser)
d.parser.Doc = d
d.parser.Init()
s := preformat(r)
d.parseRule(ruleReferences, s)
if opt.Notes {
d.parseRule(ruleNotes, s)
}
raw := d.parseMarkdown(s)
d.tree = d.processRawBlocks(raw)
return d
} }
func (d *Doc) parseRule(rule int, s string) { // NewParser creates an instance of a parser. It can be reused
m := d.parser // so that stacks and buffers need not be allocated anew for
if m.ResetBuffer(s) != "" { // each Markdown call.
func NewParser(opt *Options) (p *Parser) {
p = new(Parser)
if opt != nil {
p.yy.state.extension = *opt
}
p.yy.Init()
p.preformatBuf = bytes.NewBuffer(make([]byte, 0, 32768))
return
}
type Formatter interface {
FormatBlock(*element)
Finish()
}
// Markdown parses input from an io.Reader into a tree, and sends
// parsed blocks to a Formatter
func (p *Parser) Markdown(src io.Reader, f Formatter) {
s := p.preformat(src)
p.parseRule(ruleReferences, s)
if p.yy.extension.Notes {
p.parseRule(ruleNotes, s)
}
L:
for {
tree := p.parseRule(ruleDocblock, s)
s = p.yy.ResetBuffer("")
tree = p.processRawBlocks(tree)
f.FormatBlock(tree)
switch s {
case "", "\n", "\n\n":
break L
}
}
f.Finish()
}
func (p *Parser) parseRule(rule int, s string) (tree *element) {
if p.yy.ResetBuffer(s) != "" {
log.Fatalf("Buffer not empty") log.Fatalf("Buffer not empty")
} }
if err := m.Parse(rule); err != nil { if err := p.yy.Parse(rule); err != nil {
log.Fatalln("markdown:", err) log.Fatalln("markdown:", err)
} }
} switch rule {
case ruleDoc, ruleDocblock:
func (d *Doc) parseMarkdown(text string) *element { tree = p.yy.state.tree
d.parseRule(ruleDoc, text) p.yy.state.tree = nil
return d.tree }
return
} }
/* process_raw_blocks - traverses an element list, replacing any RAW elements with /* process_raw_blocks - traverses an element list, replacing any RAW elements with
* the result of parsing them as markdown text, and recursing into the children * the result of parsing them as markdown text, and recursing into the children
* of parent elements. The result should be a tree of elements without any RAWs. * of parent elements. The result should be a tree of elements without any RAWs.
*/ */
func (d *Doc) processRawBlocks(input *element) *element { func (p *Parser) processRawBlocks(input *element) *element {
for current := input; current != nil; current = current.next { for current := input; current != nil; current = current.next {
if current.key == RAW { if current.key == RAW {
@ -86,7 +111,7 @@ func (d *Doc) processRawBlocks(input *element) *element {
current.children = nil current.children = nil
listEnd := &current.children listEnd := &current.children
for _, contents := range strings.Split(current.contents.str, "\001") { for _, contents := range strings.Split(current.contents.str, "\001") {
if list := d.parseMarkdown(contents); list != nil { if list := p.parseRule(ruleDoc, contents); list != nil {
*listEnd = list *listEnd = list
for list.next != nil { for list.next != nil {
list = list.next list = list.next
@ -97,7 +122,7 @@ func (d *Doc) processRawBlocks(input *element) *element {
current.contents.str = "" current.contents.str = ""
} }
if current.children != nil { if current.children != nil {
current.children = d.processRawBlocks(current.children) current.children = p.processRawBlocks(current.children)
} }
} }
return input return input
@ -110,11 +135,12 @@ const (
/* preformat - allocate and copy text buffer while /* preformat - allocate and copy text buffer while
* performing tab expansion. * performing tab expansion.
*/ */
func preformat(r io.Reader) (s string) { func (p *Parser) preformat(r io.Reader) (s string) {
charstotab := TABSTOP charstotab := TABSTOP
buf := make([]byte, 32768) buf := make([]byte, 32768)
b := bytes.NewBuffer(make([]byte, 0, 32768)) b := p.preformatBuf
b.Reset()
for { for {
n, err := r.Read(buf) n, err := r.Read(buf)
if err != nil { if err != nil {

View File

@ -27,6 +27,7 @@ import (
) )
type Writer interface { type Writer interface {
Write([]byte) (int, error)
WriteString(string) (int, error) WriteString(string) (int, error)
WriteRune(rune) (int, error) WriteRune(rune) (int, error)
WriteByte(byte) error WriteByte(byte) error
@ -41,19 +42,21 @@ type htmlOut struct {
endNotes []*element /* List of endnotes to print after main content. */ endNotes []*element /* List of endnotes to print after main content. */
} }
// WriteHtml prints a document tree in HTML format using the specified Writer. func ToHTML(w Writer) Formatter {
// f := new(htmlOut)
func (d *Doc) WriteHtml(w Writer) int { f.Writer = w
out := new(htmlOut) f.padded = 2
out.Writer = w return f
out.padded = 2 }
out.elist(d.tree) func (f *htmlOut) FormatBlock(tree *element) {
if len(out.endNotes) != 0 { f.elist(tree)
out.pad(2) }
out.printEndnotes() func (f *htmlOut) Finish() {
if len(f.endNotes) != 0 {
f.pad(2)
f.printEndnotes()
} }
out.WriteByte('\n') f.WriteByte('\n')
return 0
} }
// pad - add newlines if needed // pad - add newlines if needed

View File

@ -91,10 +91,8 @@ const (
numVAL numVAL
) )
type Doc struct { type state struct {
parser *yyParser
extension Options extension Options
tree *element /* Results of parse. */ tree *element /* Results of parse. */
references *element /* List of link references found. */ references *element /* List of link references found. */
notes *element /* List of footnotes found. */ notes *element /* List of footnotes found. */
@ -102,7 +100,9 @@ type Doc struct {
%} %}
%userstate *Doc %userstate state
%noexport
%YYSTYPE *element %YYSTYPE *element
@ -110,6 +110,8 @@ Doc = a:StartList ( Block { a = cons($$, a) } )*
{ p.tree = reverse(a) } { p.tree = reverse(a) }
commit commit
Docblock = Block { p.tree = $$ } commit
Block = BlankLine* Block = BlankLine*
( BlockQuote ( BlockQuote
| Verbatim | Verbatim
@ -965,8 +967,8 @@ func match_inlines(l1, l2 *element) bool {
/* find_reference - return true if link found in references matching label. /* find_reference - return true if link found in references matching label.
* 'link' is modified with the matching url and title. * 'link' is modified with the matching url and title.
*/ */
func (d *Doc) findReference(label *element) (*link, bool) { func (p *yyParser) findReference(label *element) (*link, bool) {
for cur := d.references; cur != nil; cur = cur.next { for cur := p.references; cur != nil; cur = cur.next {
l := cur.contents.link l := cur.contents.link
if match_inlines(label, l.label) { if match_inlines(label, l.label) {
return l, true return l, true
@ -979,8 +981,8 @@ func (d *Doc) findReference(label *element) (*link, bool) {
/* find_note - return true if note found in notes matching label. /* find_note - return true if note found in notes matching label.
* if found, 'result' is set to point to matched note. * if found, 'result' is set to point to matched note.
*/ */
func (d *Doc) find_note(label string) (*element, bool) { func (p *yyParser) find_note(label string) (*element, bool) {
for el := d.notes; el != nil; el = el.next { for el := p.notes; el != nil; el = el.next {
if label == el.contents.str { if label == el.contents.str {
return el, true return el, true
} }

File diff suppressed because it is too large Load Diff