Add some documentation & split Document into Configuration+Document

This commit is contained in:
Niklas Fasching 2019-01-02 19:14:49 +01:00
parent c98cdea4f0
commit 56ecb2d401
9 changed files with 77 additions and 44 deletions

View file

@ -17,7 +17,7 @@ func main() {
js.Global().Set("run", js.NewCallback(func([]js.Value) {
in := strings.NewReader(in.Get("value").String())
html, err := org.NewDocument().Parse(in).Write(org.NewHTMLWriter())
html, err := org.New().Parse(in, "").Write(org.NewHTMLWriter())
if err != nil {
out.Set("innerHTML", fmt.Sprintf("<pre>%s</pre>", err))
} else {

View file

@ -27,7 +27,7 @@ func main() {
log.Fatal(err)
}
out, err := "", nil
d := org.NewDocument().SetPath(path).Parse(bytes.NewReader(bs))
d := org.New().Parse(bytes.NewReader(bs), path)
switch strings.ToLower(os.Args[2]) {
case "org":
out, err = d.Write(org.NewOrgWriter())

View file

@ -1,3 +1,15 @@
// Package org is an Org mode syntax processor.
//
// It parses plain text into an AST and can export it as HTML or pretty printed Org mode syntax.
// Further export formats can be defined using the Writer interface.
//
// You probably want to start with something like this:
// input := strings.NewReader("Your Org mode input")
// html, err := org.New().Parse(input, "./").Write(org.NewHTMLWriter())
// if err != nil {
// log.Fatalf("Something went wrong: %s", err)
// }
// log.Print(html)
package org
import (
@ -10,27 +22,34 @@ import (
"strings"
)
type Configuration struct {
MaxEmphasisNewLines int // Maximum number of newlines inside an emphasis. See org-emphasis-regexp-components newline.
AutoLink bool // Try to convert text passages that look like hyperlinks into hyperlinks.
DefaultSettings map[string]string // Default values for settings that are overriden by setting the same key in BufferSettings.
Log *log.Logger // Log is used to print warnings during parsing.
}
// Document contains the parsing results and a pointer to the Configuration.
type Document struct {
Path string
*Configuration
Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE).
tokens []token
Nodes []Node
Footnotes Footnotes
Outline Outline
MaxEmphasisNewLines int
AutoLink bool
BufferSettings map[string]string
DefaultSettings map[string]string
Outline Outline // Outline is a Table Of Contents for the document and contains all sections (headline + content).
BufferSettings map[string]string // Settings contains all settings that were parsed from keywords.
Error error
Log *log.Logger
}
// Writer is the interface that is used to export a parsed document into a new format. See Document.Write().
type Writer interface {
Before(*Document)
After(*Document)
WriteNodes(...Node)
String() string
Before(*Document) // Before is called before any nodes are passed to the writer.
After(*Document) // After is called after all nodes have been passed to the writer.
WriteNodes(...Node) // WriteNodes is called with the nodes of the parsed document.
String() string // String is called at the very end to retrieve the final output.
}
// Node represents a parsed node of the document. It's an empty interface and can be ignored.
type Node interface{}
type lexFn = func(line string) (t token, ok bool)
@ -59,17 +78,11 @@ var lexFns = []lexFn{
var nilToken = token{"nil", -1, "", nil}
func NewDocument() *Document {
outlineSection := &Section{}
return &Document{
Footnotes: Footnotes{
Title: "Footnotes",
Definitions: map[string]*FootnoteDefinition{},
},
// New returns a new Configuration with (hopefully) sane defaults.
func New() *Configuration {
return &Configuration{
AutoLink: true,
MaxEmphasisNewLines: 1,
Outline: Outline{outlineSection, outlineSection, 0},
BufferSettings: map[string]string{},
DefaultSettings: map[string]string{
"TODO": "TODO | DONE",
"EXCLUDE_TAGS": "noexport",
@ -79,6 +92,7 @@ func NewDocument() *Document {
}
}
// Write is called after with an instance of the Writer interface to export a parsed Document into another format.
func (d *Document) Write(w Writer) (out string, err error) {
defer func() {
if recovered := recover(); recovered != nil {
@ -96,8 +110,20 @@ func (d *Document) Write(w Writer) (out string, err error) {
return w.String(), err
}
func (dIn *Document) Parse(input io.Reader) (d *Document) {
d = dIn
// Parse parses the input into an AST (and some other helpful fields like Outline).
// To allow method chaining, errors are stored in document.Error rather than being returned.
func (c *Configuration) Parse(input io.Reader, path string) (d *Document) {
outlineSection := &Section{}
d = &Document{
Configuration: c,
Footnotes: Footnotes{
Title: "Footnotes",
Definitions: map[string]*FootnoteDefinition{},
},
Outline: Outline{outlineSection, outlineSection, 0},
BufferSettings: map[string]string{},
Path: path,
}
defer func() {
if recovered := recover(); recovered != nil {
d.Error = fmt.Errorf("could not parse input: %v", recovered)
@ -112,15 +138,10 @@ func (dIn *Document) Parse(input io.Reader) (d *Document) {
return d
}
func (d *Document) SetPath(path string) *Document {
d.Path = path
d.Log.SetPrefix(fmt.Sprintf("%s(%s): ", d.Log.Prefix(), path))
return d
}
func (d *Document) Silent() *Document {
d.Log = log.New(ioutil.Discard, "", 0)
return d
// Silent disables all logging of warnings during parsing.
func (c *Configuration) Silent() *Configuration {
c.Log = log.New(ioutil.Discard, "", 0)
return c
}
func (d *Document) tokenize(input io.Reader) {
@ -134,6 +155,7 @@ func (d *Document) tokenize(input io.Reader) {
}
}
// Get returns the value for key in BufferSettings or DefaultSettings if key does not exist in the former
func (d *Document) Get(key string) string {
if v, ok := d.BufferSettings[key]; ok {
return v
@ -144,7 +166,15 @@ func (d *Document) Get(key string) string {
return ""
}
// see https://orgmode.org/manual/Export-settings.html
// GetOption returns the value associated to the export option key
// Currently supported options:
// - e (export org entities)
// - f (export footnotes)
// - toc (export table of content)
// - todo (export headline todo status)
// - pri (export headline priority)
// - tags (export headline tags)
// see https://orgmode.org/manual/Export-settings.html for more information
func (d *Document) GetOption(key string) bool {
get := func(settings map[string]string) string {
for _, field := range strings.Fields(settings["OPTIONS"]) {

View file

@ -9,7 +9,8 @@ import (
// Fuzz function to be used by https://github.com/dvyukov/go-fuzz
func Fuzz(input []byte) int {
d := NewDocument().Silent().Parse(bytes.NewReader(input))
conf := New().Silent()
d := conf.Parse(bytes.NewReader(input), "")
orgOutput, err := d.Write(NewOrgWriter())
if err != nil {
panic(err)
@ -18,7 +19,7 @@ func Fuzz(input []byte) int {
if err != nil {
panic(err)
}
htmlOutputB, err := NewDocument().Silent().Parse(strings.NewReader(orgOutput)).Write(NewHTMLWriter())
htmlOutputB, err := conf.Parse(strings.NewReader(orgOutput)).Write(NewHTMLWriter())
if htmlOutputA != htmlOutputB {
panic("rendered org results in different html than original input")
}

View file

@ -12,6 +12,7 @@ import (
"golang.org/x/net/html/atom"
)
// HTMLWriter exports an org document into a html document.
type HTMLWriter struct {
stringBuilder
document *Document

View file

@ -9,7 +9,7 @@ func TestHTMLWriter(t *testing.T) {
for _, path := range orgTestFiles() {
expected := fileString(path[:len(path)-len(".org")] + ".html")
reader, writer := strings.NewReader(fileString(path)), NewHTMLWriter()
actual, err := NewDocument().Silent().SetPath(path).Parse(reader).Write(writer)
actual, err := New().Silent().Parse(reader, path).Write(writer)
if err != nil {
t.Errorf("%s\n got error: %s", path, err)
continue

View file

@ -148,7 +148,7 @@ func (d *Document) loadSetupFile(k Keyword) (int, Node) {
d.Log.Printf("Bad setup file: %#v: %s", k, err)
return 1, k
}
setupDocument := NewDocument().Parse(bytes.NewReader(bs))
setupDocument := d.Configuration.Parse(bytes.NewReader(bs), path)
if err := setupDocument.Error; err != nil {
d.Log.Printf("Bad setup file: %#v: %s", k, err)
return 1, k

View file

@ -9,8 +9,9 @@ import (
type stringBuilder = strings.Builder
// OrgWriter export an org document into pretty printed org document.
type OrgWriter struct {
TagsColumn int // see org-tags-column
TagsColumn int
stringBuilder
indent string
}

View file

@ -14,7 +14,7 @@ func TestOrgWriter(t *testing.T) {
for _, path := range orgTestFiles() {
expected := fileString(path[:len(path)-len(".org")] + ".pretty_org")
reader, writer := strings.NewReader(fileString(path)), NewOrgWriter()
actual, err := NewDocument().Silent().SetPath(path).Parse(reader).Write(writer)
actual, err := New().Silent().Parse(reader, path).Write(writer)
if err != nil {
t.Errorf("%s\n got error: %s", path, err)
continue