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:
parent
2670cccafb
commit
f721084df0
@ -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
6
doc.go
@ -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()
|
||||
}
|
||||
|
||||
|
94
markdown.go
94
markdown.go
@ -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 := ¤t.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 {
|
||||
|
27
output.go
27
output.go
@ -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
|
||||
|
18
parser.leg
18
parser.leg
@ -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
|
||||
}
|
||||
|
10425
parser.leg.go
10425
parser.leg.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user