From 56ecb2d4014c2f6594f6fd343830c2410cade7d7 Mon Sep 17 00:00:00 2001 From: Niklas Fasching Date: Wed, 2 Jan 2019 19:14:49 +0100 Subject: [PATCH] Add some documentation & split Document into Configuration+Document --- etc/_wasm.go | 2 +- main.go | 2 +- org/document.go | 102 ++++++++++++++++++++++++++-------------- org/fuzz.go | 5 +- org/html_writer.go | 1 + org/html_writer_test.go | 2 +- org/keyword.go | 2 +- org/org_writer.go | 3 +- org/org_writer_test.go | 2 +- 9 files changed, 77 insertions(+), 44 deletions(-) diff --git a/etc/_wasm.go b/etc/_wasm.go index e668a9f..e94e0b9 100644 --- a/etc/_wasm.go +++ b/etc/_wasm.go @@ -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("
%s
", err)) } else { diff --git a/main.go b/main.go index f72db9d..6500a95 100644 --- a/main.go +++ b/main.go @@ -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()) diff --git a/org/document.go b/org/document.go index 0d9ab8f..657919f 100644 --- a/org/document.go +++ b/org/document.go @@ -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 - tokens []token - Nodes []Node - Footnotes Footnotes - Outline Outline - MaxEmphasisNewLines int - AutoLink bool - BufferSettings map[string]string - DefaultSettings map[string]string - Error error - Log *log.Logger + *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 // 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 } +// 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"]) { diff --git a/org/fuzz.go b/org/fuzz.go index 58e0d62..48ab440 100644 --- a/org/fuzz.go +++ b/org/fuzz.go @@ -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") } diff --git a/org/html_writer.go b/org/html_writer.go index 040bb68..75320ed 100644 --- a/org/html_writer.go +++ b/org/html_writer.go @@ -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 diff --git a/org/html_writer_test.go b/org/html_writer_test.go index 298b206..f1890d8 100644 --- a/org/html_writer_test.go +++ b/org/html_writer_test.go @@ -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 diff --git a/org/keyword.go b/org/keyword.go index 06450c7..f8ffa4c 100644 --- a/org/keyword.go +++ b/org/keyword.go @@ -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 diff --git a/org/org_writer.go b/org/org_writer.go index 44e6518..e624ce4 100644 --- a/org/org_writer.go +++ b/org/org_writer.go @@ -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 } diff --git a/org/org_writer_test.go b/org/org_writer_test.go index c6bb8e6..6fe51af 100644 --- a/org/org_writer_test.go +++ b/org/org_writer_test.go @@ -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