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
|
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
6
doc.go
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
94
markdown.go
94
markdown.go
@ -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 := ¤t.children
|
listEnd := ¤t.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 {
|
||||||
|
27
output.go
27
output.go
@ -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
|
||||||
|
18
parser.leg
18
parser.leg
@ -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
|
||||||
}
|
}
|
||||||
|
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