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) { js.Global().Set("run", js.NewCallback(func([]js.Value) {
in := strings.NewReader(in.Get("value").String()) 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 { if err != nil {
out.Set("innerHTML", fmt.Sprintf("<pre>%s</pre>", err)) out.Set("innerHTML", fmt.Sprintf("<pre>%s</pre>", err))
} else { } else {

View file

@ -27,7 +27,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
out, err := "", nil 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]) { switch strings.ToLower(os.Args[2]) {
case "org": case "org":
out, err = d.Write(org.NewOrgWriter()) 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 package org
import ( import (
@ -10,27 +22,34 @@ import (
"strings" "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 { type Document struct {
Path string *Configuration
tokens []token Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE).
Nodes []Node tokens []token
Footnotes Footnotes Nodes []Node
Outline Outline Footnotes Footnotes
MaxEmphasisNewLines int Outline Outline // Outline is a Table Of Contents for the document and contains all sections (headline + content).
AutoLink bool BufferSettings map[string]string // Settings contains all settings that were parsed from keywords.
BufferSettings map[string]string Error error
DefaultSettings map[string]string
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 { type Writer interface {
Before(*Document) Before(*Document) // Before is called before any nodes are passed to the writer.
After(*Document) After(*Document) // After is called after all nodes have been passed to the writer.
WriteNodes(...Node) WriteNodes(...Node) // WriteNodes is called with the nodes of the parsed document.
String() string 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 Node interface{}
type lexFn = func(line string) (t token, ok bool) type lexFn = func(line string) (t token, ok bool)
@ -59,17 +78,11 @@ var lexFns = []lexFn{
var nilToken = token{"nil", -1, "", nil} var nilToken = token{"nil", -1, "", nil}
func NewDocument() *Document { // New returns a new Configuration with (hopefully) sane defaults.
outlineSection := &Section{} func New() *Configuration {
return &Document{ return &Configuration{
Footnotes: Footnotes{
Title: "Footnotes",
Definitions: map[string]*FootnoteDefinition{},
},
AutoLink: true, AutoLink: true,
MaxEmphasisNewLines: 1, MaxEmphasisNewLines: 1,
Outline: Outline{outlineSection, outlineSection, 0},
BufferSettings: map[string]string{},
DefaultSettings: map[string]string{ DefaultSettings: map[string]string{
"TODO": "TODO | DONE", "TODO": "TODO | DONE",
"EXCLUDE_TAGS": "noexport", "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) { func (d *Document) Write(w Writer) (out string, err error) {
defer func() { defer func() {
if recovered := recover(); recovered != nil { if recovered := recover(); recovered != nil {
@ -96,8 +110,20 @@ func (d *Document) Write(w Writer) (out string, err error) {
return w.String(), err return w.String(), err
} }
func (dIn *Document) Parse(input io.Reader) (d *Document) { // Parse parses the input into an AST (and some other helpful fields like Outline).
d = dIn // 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() { defer func() {
if recovered := recover(); recovered != nil { if recovered := recover(); recovered != nil {
d.Error = fmt.Errorf("could not parse input: %v", recovered) 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 return d
} }
func (d *Document) SetPath(path string) *Document { // Silent disables all logging of warnings during parsing.
d.Path = path func (c *Configuration) Silent() *Configuration {
d.Log.SetPrefix(fmt.Sprintf("%s(%s): ", d.Log.Prefix(), path)) c.Log = log.New(ioutil.Discard, "", 0)
return d return c
}
func (d *Document) Silent() *Document {
d.Log = log.New(ioutil.Discard, "", 0)
return d
} }
func (d *Document) tokenize(input io.Reader) { 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 { func (d *Document) Get(key string) string {
if v, ok := d.BufferSettings[key]; ok { if v, ok := d.BufferSettings[key]; ok {
return v return v
@ -144,7 +166,15 @@ func (d *Document) Get(key string) string {
return "" 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 { func (d *Document) GetOption(key string) bool {
get := func(settings map[string]string) string { get := func(settings map[string]string) string {
for _, field := range strings.Fields(settings["OPTIONS"]) { 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 // Fuzz function to be used by https://github.com/dvyukov/go-fuzz
func Fuzz(input []byte) int { 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()) orgOutput, err := d.Write(NewOrgWriter())
if err != nil { if err != nil {
panic(err) panic(err)
@ -18,7 +19,7 @@ func Fuzz(input []byte) int {
if err != nil { if err != nil {
panic(err) panic(err)
} }
htmlOutputB, err := NewDocument().Silent().Parse(strings.NewReader(orgOutput)).Write(NewHTMLWriter()) htmlOutputB, err := conf.Parse(strings.NewReader(orgOutput)).Write(NewHTMLWriter())
if htmlOutputA != htmlOutputB { if htmlOutputA != htmlOutputB {
panic("rendered org results in different html than original input") panic("rendered org results in different html than original input")
} }

View file

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

View file

@ -9,7 +9,7 @@ func TestHTMLWriter(t *testing.T) {
for _, path := range orgTestFiles() { for _, path := range orgTestFiles() {
expected := fileString(path[:len(path)-len(".org")] + ".html") expected := fileString(path[:len(path)-len(".org")] + ".html")
reader, writer := strings.NewReader(fileString(path)), NewHTMLWriter() 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 { if err != nil {
t.Errorf("%s\n got error: %s", path, err) t.Errorf("%s\n got error: %s", path, err)
continue 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) d.Log.Printf("Bad setup file: %#v: %s", k, err)
return 1, k return 1, k
} }
setupDocument := NewDocument().Parse(bytes.NewReader(bs)) setupDocument := d.Configuration.Parse(bytes.NewReader(bs), path)
if err := setupDocument.Error; err != nil { if err := setupDocument.Error; err != nil {
d.Log.Printf("Bad setup file: %#v: %s", k, err) d.Log.Printf("Bad setup file: %#v: %s", k, err)
return 1, k return 1, k

View file

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

View file

@ -14,7 +14,7 @@ func TestOrgWriter(t *testing.T) {
for _, path := range orgTestFiles() { for _, path := range orgTestFiles() {
expected := fileString(path[:len(path)-len(".org")] + ".pretty_org") expected := fileString(path[:len(path)-len(".org")] + ".pretty_org")
reader, writer := strings.NewReader(fileString(path)), NewOrgWriter() 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 { if err != nil {
t.Errorf("%s\n got error: %s", path, err) t.Errorf("%s\n got error: %s", path, err)
continue continue