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
}
p := markdown.NewParser(&opt)
startPProf()
defer stopPProf()
doc := markdown.Parse(r, opt)
w := bufio.NewWriter(os.Stdout)
doc.WriteHtml(w)
p.Markdown(r, markdown.ToHTML(w))
w.Flush()
}

6
doc.go
View File

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

View File

@ -17,8 +17,6 @@
package markdown
// implements Parse()
import (
"bytes"
"io"
@ -35,46 +33,73 @@ type Options struct {
Dlists bool
}
// Parse converts a Markdown document into a tree for later output processing.
func Parse(r io.Reader, opt Options) *Doc {
d := new(Doc)
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
type Parser struct {
yy yyParser
preformatBuf *bytes.Buffer
}
func (d *Doc) parseRule(rule int, s string) {
m := d.parser
if m.ResetBuffer(s) != "" {
// NewParser creates an instance of a parser. It can be reused
// so that stacks and buffers need not be allocated anew for
// 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")
}
if err := m.Parse(rule); err != nil {
if err := p.yy.Parse(rule); err != nil {
log.Fatalln("markdown:", err)
}
}
func (d *Doc) parseMarkdown(text string) *element {
d.parseRule(ruleDoc, text)
return d.tree
switch rule {
case ruleDoc, ruleDocblock:
tree = p.yy.state.tree
p.yy.state.tree = nil
}
return
}
/* 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
* 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 {
if current.key == RAW {
@ -86,7 +111,7 @@ func (d *Doc) processRawBlocks(input *element) *element {
current.children = nil
listEnd := &current.children
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
for list.next != nil {
list = list.next
@ -97,7 +122,7 @@ func (d *Doc) processRawBlocks(input *element) *element {
current.contents.str = ""
}
if current.children != nil {
current.children = d.processRawBlocks(current.children)
current.children = p.processRawBlocks(current.children)
}
}
return input
@ -110,11 +135,12 @@ const (
/* preformat - allocate and copy text buffer while
* performing tab expansion.
*/
func preformat(r io.Reader) (s string) {
func (p *Parser) preformat(r io.Reader) (s string) {
charstotab := TABSTOP
buf := make([]byte, 32768)
b := bytes.NewBuffer(make([]byte, 0, 32768))
b := p.preformatBuf
b.Reset()
for {
n, err := r.Read(buf)
if err != nil {

View File

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

View File

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

File diff suppressed because it is too large Load Diff