From 9f7e8a8fbd20c8fe66a5a1c7fa3ccb068f09a983 Mon Sep 17 00:00:00 2001
From: Niklas Fasching
Date: Fri, 17 Apr 2020 16:41:06 +0200
Subject: [PATCH] Implement #+MACRO
---
org/document.go | 2 ++
org/html_writer.go | 13 +++++++++++++
org/inline.go | 16 ++++++++++++++++
org/keyword.go | 5 +++++
org/org_writer.go | 4 ++++
org/testdata/inline.html | 8 ++++++++
org/testdata/inline.org | 2 ++
org/testdata/inline.pretty_org | 2 ++
org/writer.go | 3 +++
9 files changed, 55 insertions(+)
diff --git a/org/document.go b/org/document.go
index 9c464e1..510fe51 100644
--- a/org/document.go
+++ b/org/document.go
@@ -36,6 +36,7 @@ type Document struct {
Path string // Path of the file containing the parse input - used to resolve relative paths during parsing (e.g. INCLUDE).
tokens []token
baseLvl int
+ Macros map[string]string
Links map[string]string
Nodes []Node
NamedNodes map[string]Node
@@ -123,6 +124,7 @@ func (c *Configuration) Parse(input io.Reader, path string) (d *Document) {
BufferSettings: map[string]string{},
NamedNodes: map[string]Node{},
Links: map[string]string{},
+ Macros: map[string]string{},
Path: path,
}
defer func() {
diff --git a/org/html_writer.go b/org/html_writer.go
index e0290b1..6c230a8 100644
--- a/org/html_writer.go
+++ b/org/html_writer.go
@@ -355,6 +355,19 @@ func (w *HTMLWriter) WriteRegularLink(l RegularLink) {
}
}
+func (w *HTMLWriter) WriteMacro(m Macro) {
+ if macro := w.document.Macros[m.Name]; macro != "" {
+ for i, param := range m.Parameters {
+ macro = strings.Replace(macro, fmt.Sprintf("$%d", i+1), param, -1)
+ }
+ macroDocument := w.document.Parse(strings.NewReader(macro), w.document.Path)
+ if macroDocument.Error != nil {
+ w.log.Printf("bad macro: %s -> %s: %v", m.Name, macro, macroDocument.Error)
+ }
+ WriteNodes(w, macroDocument.Nodes...)
+ }
+}
+
func (w *HTMLWriter) WriteList(l List) {
tags, ok := listTags[l.Kind]
if !ok {
diff --git a/org/inline.go b/org/inline.go
index 566af0f..ede3355 100644
--- a/org/inline.go
+++ b/org/inline.go
@@ -54,6 +54,11 @@ type RegularLink struct {
AutoLink bool
}
+type Macro struct {
+ Name string
+ Parameters []string
+}
+
var validURLCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;="
var autolinkProtocols = regexp.MustCompile(`^(https?|ftp|file)$`)
var imageExtensionRegexp = regexp.MustCompile(`^[.](png|gif|jpe?g|svg|tiff?)$`)
@@ -66,6 +71,7 @@ var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`)
var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`)
var inlineBlockRegexp = regexp.MustCompile(`src_(\w+)(\[(.*)\])?{(.*)}`)
var inlineExportBlockRegexp = regexp.MustCompile(`@@(\w+):(.*?)@@`)
+var macroRegexp = regexp.MustCompile(`{{{(.*)\((.*)\)}}}`)
var timestampFormat = "2006-01-02 Mon 15:04"
var datestampFormat = "2006-01-02 Mon"
@@ -94,6 +100,8 @@ func (d *Document) parseInline(input string) (nodes []Node) {
consumed, node = d.parseEmphasis(input, current, true)
case '[':
consumed, node = d.parseOpeningBracket(input, current)
+ case '{':
+ consumed, node = d.parseMacro(input, current)
case '<':
consumed, node = d.parseTimestamp(input, current)
case '\\':
@@ -238,6 +246,13 @@ func (d *Document) parseOpeningBracket(input string, start int) (int, Node) {
return 0, nil
}
+func (d *Document) parseMacro(input string, start int) (int, Node) {
+ if m := macroRegexp.FindStringSubmatch(input[start:]); m != nil {
+ return len(m[0]), Macro{m[1], strings.Split(m[2], ",")}
+ }
+ return 0, nil
+}
+
func (d *Document) parseFootnoteReference(input string, start int) (int, Node) {
if m := footnoteRegexp.FindStringSubmatch(input[start:]); m != nil {
name, definition := m[1], m[3]
@@ -389,4 +404,5 @@ func (n InlineBlock) String() string { return orgWriter.WriteNodesAsString
func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) }
func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) }
func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) }
+func (n Macro) String() string { return orgWriter.WriteNodesAsString(n) }
func (n Timestamp) String() string { return orgWriter.WriteNodesAsString(n) }
diff --git a/org/keyword.go b/org/keyword.go
index 774e9c0..d4bd233 100644
--- a/org/keyword.go
+++ b/org/keyword.go
@@ -67,6 +67,11 @@ func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) {
d.Links[parts[0]] = parts[1]
}
return 1, k
+ case "MACRO":
+ if parts := strings.Split(k.Value, " "); len(parts) >= 2 {
+ d.Macros[parts[0]] = parts[1]
+ }
+ return 1, k
case "CAPTION", "ATTR_HTML":
consumed, node := d.parseAffiliated(i, stop)
if consumed != 0 {
diff --git a/org/org_writer.go b/org/org_writer.go
index e22a40c..4b4d2a4 100644
--- a/org/org_writer.go
+++ b/org/org_writer.go
@@ -360,3 +360,7 @@ func (w *OrgWriter) WriteRegularLink(l RegularLink) {
w.WriteString(fmt.Sprintf("[[%s][%s]]", l.URL, w.WriteNodesAsString(l.Description...)))
}
}
+
+func (w *OrgWriter) WriteMacro(m Macro) {
+ w.WriteString(fmt.Sprintf("{{{%s(%s)}}}", m.Name, strings.Join(m.Parameters, ",")))
+}
diff --git a/org/testdata/inline.html b/org/testdata/inline.html
index 89afa2f..74ef1c4 100644
--- a/org/testdata/inline.html
+++ b/org/testdata/inline.html
@@ -168,6 +168,14 @@ timestamps
#+LINK
based links: https://www.example.com/foobar
+
+
+
+
+#+MACROs
:
+
yolo
+
+
diff --git a/org/testdata/inline.org b/org/testdata/inline.org
index e2a075f..188e38c 100644
--- a/org/testdata/inline.org
+++ b/org/testdata/inline.org
@@ -39,3 +39,5 @@
- <2019-01-06 18:00 +1w>
- =#+LINK= based links: [[example:foobar]]
#+LINK: example https://www.example.com/
+- =#+MACROs=: {{{headline(yolo)}}}
+ #+MACRO: headline @@html:$1
@@
diff --git a/org/testdata/inline.pretty_org b/org/testdata/inline.pretty_org
index 695c39a..60fe598 100644
--- a/org/testdata/inline.pretty_org
+++ b/org/testdata/inline.pretty_org
@@ -39,3 +39,5 @@
- <2019-01-06 Sun 18:00 +1w>
- =#+LINK= based links: [[example:foobar]]
#+LINK: example https://www.example.com/
+- =#+MACROs=: {{{headline(yolo)}}}
+ #+MACRO: headline @@html:$1
@@
diff --git a/org/writer.go b/org/writer.go
index 9a50237..29b64bd 100644
--- a/org/writer.go
+++ b/org/writer.go
@@ -36,6 +36,7 @@ type Writer interface {
WriteExplicitLineBreak(ExplicitLineBreak)
WriteLineBreak(LineBreak)
WriteRegularLink(RegularLink)
+ WriteMacro(Macro)
WriteTimestamp(Timestamp)
WriteFootnoteLink(FootnoteLink)
WriteFootnoteDefinition(FootnoteDefinition)
@@ -95,6 +96,8 @@ func WriteNodes(w Writer, nodes ...Node) {
w.WriteLineBreak(n)
case RegularLink:
w.WriteRegularLink(n)
+ case Macro:
+ w.WriteMacro(n)
case Timestamp:
w.WriteTimestamp(n)
case FootnoteLink: