Add minimal affiliated keyword support. Just captions for now
This commit is contained in:
parent
2399fec2eb
commit
043095e672
7 changed files with 138 additions and 8 deletions
|
@ -3,9 +3,6 @@ A basic org-mode parser in go
|
||||||
- have a org-mode AST to play around with building an org-mode language server
|
- have a org-mode AST to play around with building an org-mode language server
|
||||||
- hopefully add reasonable org-mode support to hugo - sadly [[https://github.com/chaseadamsio/goorgeous][goorgeous]] is broken & abandoned
|
- hopefully add reasonable org-mode support to hugo - sadly [[https://github.com/chaseadamsio/goorgeous][goorgeous]] is broken & abandoned
|
||||||
* next
|
* next
|
||||||
- handle #+RESULTS: raw and stuff
|
|
||||||
- hugo frontmatter - see https://gohugo.io/content-management/front-matter/
|
|
||||||
- captions: images, tables & blocks
|
|
||||||
*** TODO [[https://github.com/chaseadamsio/goorgeous/issues/72][#72:]] Support for #+ATTR_HTML
|
*** TODO [[https://github.com/chaseadamsio/goorgeous/issues/72][#72:]] Support for #+ATTR_HTML
|
||||||
*** TODO [[https://github.com/chaseadamsio/goorgeous/issues/46][#46]]: Support for symbols like ndash and mdash
|
*** TODO [[https://github.com/chaseadamsio/goorgeous/issues/46][#46]]: Support for symbols like ndash and mdash
|
||||||
- see org-entities replacement: see org-entities-help
|
- see org-entities replacement: see org-entities-help
|
||||||
|
@ -15,8 +12,11 @@ A basic org-mode parser in go
|
||||||
- see https://orgmode.org/manual/Include-files.html
|
- see https://orgmode.org/manual/Include-files.html
|
||||||
*** TODO [[https://github.com/chaseadamsio/goorgeous/issues/33][#33]]: Wrong output when mixing html with org-mode
|
*** TODO [[https://github.com/chaseadamsio/goorgeous/issues/33][#33]]: Wrong output when mixing html with org-mode
|
||||||
* later
|
* later
|
||||||
|
- hugo frontmatter - see https://gohugo.io/content-management/front-matter/ -> actually seems to be handled by hugo itself
|
||||||
|
- handle #+RESULTS: raw and stuff
|
||||||
- affiliated keywords: see org-element.el - org-element-affiliated-keywords
|
- affiliated keywords: see org-element.el - org-element-affiliated-keywords
|
||||||
- keywords: support both multi (e.g. LINK, TODO) & normal (e.g. AUTHOR, TITLE) keywords
|
- keywords: support both multi (e.g. LINK, TODO) & normal (e.g. AUTHOR, TITLE) keywords
|
||||||
|
https://orgmode.org/manual/In_002dbuffer-settings.html
|
||||||
- links based on #+LINK
|
- links based on #+LINK
|
||||||
- table colgroups https://orgmode.org/worg/org-tutorials/tables.html
|
- table colgroups https://orgmode.org/worg/org-tutorials/tables.html
|
||||||
- table pretty printing
|
- table pretty printing
|
||||||
|
|
21
org/html.go
21
org/html.go
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
type HTMLWriter struct {
|
type HTMLWriter struct {
|
||||||
stringBuilder
|
stringBuilder
|
||||||
|
document *Document
|
||||||
HighlightCodeBlock func(source, lang string) string
|
HighlightCodeBlock func(source, lang string) string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +45,10 @@ func (w *HTMLWriter) emptyClone() *HTMLWriter {
|
||||||
return &wcopy
|
return &wcopy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *HTMLWriter) before(d *Document) {}
|
func (w *HTMLWriter) before(d *Document) {
|
||||||
|
w.document = d
|
||||||
|
}
|
||||||
|
|
||||||
func (w *HTMLWriter) after(d *Document) {
|
func (w *HTMLWriter) after(d *Document) {
|
||||||
w.writeFootnotes(d)
|
w.writeFootnotes(d)
|
||||||
}
|
}
|
||||||
|
@ -54,6 +58,8 @@ func (w *HTMLWriter) writeNodes(ns ...Node) {
|
||||||
switch n := n.(type) {
|
switch n := n.(type) {
|
||||||
case Keyword, Comment:
|
case Keyword, Comment:
|
||||||
continue
|
continue
|
||||||
|
case NodeWithMeta:
|
||||||
|
w.writeNodeWithMeta(n)
|
||||||
case Headline:
|
case Headline:
|
||||||
w.writeHeadline(n)
|
w.writeHeadline(n)
|
||||||
case Block:
|
case Block:
|
||||||
|
@ -242,6 +248,19 @@ func (w *HTMLWriter) writeHorizontalRule(h HorizontalRule) {
|
||||||
w.WriteString("<hr>\n")
|
w.WriteString("<hr>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *HTMLWriter) writeNodeWithMeta(m NodeWithMeta) {
|
||||||
|
nodeW := w.emptyClone()
|
||||||
|
nodeW.writeNodes(m.Node)
|
||||||
|
nodeString := nodeW.String()
|
||||||
|
if rawCaption, ok := m.Meta["CAPTION"]; ok {
|
||||||
|
nodes, captionW := w.document.parseInline(rawCaption), w.emptyClone()
|
||||||
|
captionW.writeNodes(nodes...)
|
||||||
|
caption := `<p class="caption">` + "\n" + captionW.String() + "\n</p>\n"
|
||||||
|
nodeString = `<div class="captioned">` + "\n" + nodeString + caption + `</div>` + "\n"
|
||||||
|
}
|
||||||
|
w.WriteString(nodeString)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *HTMLWriter) writeTable(t Table) {
|
func (w *HTMLWriter) writeTable(t Table) {
|
||||||
w.WriteString("<table>\n")
|
w.WriteString("<table>\n")
|
||||||
w.writeNodes(t.Header)
|
w.writeNodes(t.Header)
|
||||||
|
|
|
@ -10,10 +10,16 @@ type Keyword struct {
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NodeWithMeta struct {
|
||||||
|
Node Node
|
||||||
|
Meta map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
type Comment struct{ Content string }
|
type Comment struct{ Content string }
|
||||||
|
|
||||||
var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):\s(.*)`)
|
var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):\s(.*)`)
|
||||||
var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`)
|
var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`)
|
||||||
|
var affiliatedKeywordRegexp = regexp.MustCompile(`^(CAPTION)$`)
|
||||||
|
|
||||||
func lexKeywordOrComment(line string) (token, bool) {
|
func lexKeywordOrComment(line string) (token, bool) {
|
||||||
if m := keywordRegexp.FindStringSubmatch(line); m != nil {
|
if m := keywordRegexp.FindStringSubmatch(line); m != nil {
|
||||||
|
@ -25,12 +31,55 @@ func lexKeywordOrComment(line string) (token, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) {
|
func (d *Document) parseKeyword(i int, stop stopFn) (int, Node) {
|
||||||
t := d.tokens[i]
|
k := parseKeyword(d.tokens[i])
|
||||||
k, v := t.matches[2], t.matches[3]
|
if affiliatedKeywordRegexp.MatchString(k.Key) {
|
||||||
d.BufferSettings[k] = strings.Join([]string{d.BufferSettings[k], v}, "\n")
|
consumed, node := d.parseAffiliated(i, stop)
|
||||||
return 1, Keyword{k, v}
|
if consumed != 0 {
|
||||||
|
return consumed, node
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.BufferSettings[k.Key] = strings.Join([]string{d.BufferSettings[k.Key], k.Value}, "\n")
|
||||||
|
}
|
||||||
|
return 1, k
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) parseComment(i int, stop stopFn) (int, Node) {
|
func (d *Document) parseComment(i int, stop stopFn) (int, Node) {
|
||||||
return 1, Comment{d.tokens[i].content}
|
return 1, Comment{d.tokens[i].content}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) parseAffiliated(i int, stop stopFn) (int, Node) {
|
||||||
|
start, meta := i, map[string]string{}
|
||||||
|
for ; !stop(d, i) && d.tokens[i].kind == "keyword"; i++ {
|
||||||
|
k := parseKeyword(d.tokens[i])
|
||||||
|
if !affiliatedKeywordRegexp.MatchString(k.Key) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
if value, ok := meta[k.Key]; ok {
|
||||||
|
meta[k.Key] = value + " " + k.Value
|
||||||
|
} else {
|
||||||
|
meta[k.Key] = k.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stop(d, i) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
consumed, node := 0, (Node)(nil)
|
||||||
|
if t := d.tokens[i]; t.kind == "text" {
|
||||||
|
if nodes := d.parseInline(t.content); len(nodes) == 1 && isImageOrVideoLink(nodes[0]) {
|
||||||
|
consumed, node = 1, Line{nodes[:1]}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
consumed, node = d.parseOne(i, stop)
|
||||||
|
}
|
||||||
|
if consumed == 0 || node == nil {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
i += consumed
|
||||||
|
return i - start, NodeWithMeta{node, meta}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKeyword(t token) Keyword {
|
||||||
|
k, v := t.matches[2], t.matches[3]
|
||||||
|
k = strings.ToUpper(k)
|
||||||
|
return Keyword{k, v}
|
||||||
|
}
|
||||||
|
|
|
@ -48,6 +48,8 @@ func (w *OrgWriter) writeNodes(ns ...Node) {
|
||||||
w.writeComment(n)
|
w.writeComment(n)
|
||||||
case Keyword:
|
case Keyword:
|
||||||
w.writeKeyword(n)
|
w.writeKeyword(n)
|
||||||
|
case NodeWithMeta:
|
||||||
|
w.writeNodeWithMeta(n)
|
||||||
case Headline:
|
case Headline:
|
||||||
w.writeHeadline(n)
|
w.writeHeadline(n)
|
||||||
case Block:
|
case Block:
|
||||||
|
@ -164,6 +166,13 @@ func (w *OrgWriter) writeKeyword(k Keyword) {
|
||||||
w.WriteString(w.indent + fmt.Sprintf("#+%s: %s\n", k.Key, k.Value))
|
w.WriteString(w.indent + fmt.Sprintf("#+%s: %s\n", k.Key, k.Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *OrgWriter) writeNodeWithMeta(m NodeWithMeta) {
|
||||||
|
for k, v := range m.Meta {
|
||||||
|
w.writeNodes(Keyword{k, v})
|
||||||
|
}
|
||||||
|
w.writeNodes(m.Node)
|
||||||
|
}
|
||||||
|
|
||||||
func (w *OrgWriter) writeComment(c Comment) {
|
func (w *OrgWriter) writeComment(c Comment) {
|
||||||
w.WriteString(w.indent + "#" + c.Content)
|
w.WriteString(w.indent + "#" + c.Content)
|
||||||
}
|
}
|
||||||
|
|
27
org/testdata/example.html
vendored
27
org/testdata/example.html
vendored
|
@ -188,8 +188,35 @@ auto link, i.e. not inside <code class="verbatim">\[[square brackets]\]</code> <
|
||||||
</ol>
|
</ol>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<h2>Captions</h2>
|
||||||
|
<p>
|
||||||
|
Anything can be captioned. Also captions are not real, correct captions but just a paragraph below the element (bothe wrapped into a div)
|
||||||
|
</p>
|
||||||
|
<div class="captioned">
|
||||||
|
<div class="highlight"><pre>echo "i have a caption!"</pre></div>
|
||||||
|
<p class="caption">
|
||||||
|
captioned soure block
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="captioned">
|
||||||
|
<video src="my-video.mp4" title="my-video.mp4">my-video.mp4</video>
|
||||||
|
<p class="caption">
|
||||||
|
captioned link (video in this case)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
note that only that one line is captioned, not the whole paragraph
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
also, normal text lines can't be captioned
|
||||||
|
</p>
|
||||||
<h2>blocks</h2>
|
<h2>blocks</h2>
|
||||||
|
<div class="captioned">
|
||||||
<div class="highlight"><pre>echo a bash source block</pre></div>
|
<div class="highlight"><pre>echo a bash source block</pre></div>
|
||||||
|
<p class="caption">
|
||||||
|
bleck!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="highlight"><pre>a source block without a language
|
<div class="highlight"><pre>a source block without a language
|
||||||
and a second line
|
and a second line
|
||||||
and a third one</pre></div>
|
and a third one</pre></div>
|
||||||
|
|
19
org/testdata/example.org
vendored
19
org/testdata/example.org
vendored
|
@ -63,7 +63,26 @@ this one is cheating a little as tags are ALWAYS printed right aligned to a give
|
||||||
6. regular link to https (image) [[https://www.example.com/my-img.png]]
|
6. regular link to https (image) [[https://www.example.com/my-img.png]]
|
||||||
7. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com
|
7. auto link, i.e. not inside =\[[square brackets]\]= https://www.example.com
|
||||||
|
|
||||||
|
** Captions
|
||||||
|
|
||||||
|
Anything can be captioned. Also captions are not real, correct captions but just a paragraph below the element (bothe wrapped into a div)
|
||||||
|
|
||||||
|
#+CAPTION: captioned soure block
|
||||||
|
#+BEGIN_SRC sh
|
||||||
|
echo "i have a caption!"
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
#+CAPTION: captioned link (video in this case)
|
||||||
|
[[my-video.mp4]]
|
||||||
|
note that only that one line is captioned, not the whole paragraph
|
||||||
|
|
||||||
|
#+CAPTION: not happening!
|
||||||
|
also, normal text lines can't be captioned
|
||||||
|
|
||||||
|
|
||||||
** blocks
|
** blocks
|
||||||
|
|
||||||
|
#+CAPTION: bleck!
|
||||||
#+BEGIN_SRC bash
|
#+BEGIN_SRC bash
|
||||||
echo a bash source block
|
echo a bash source block
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
|
@ -17,3 +17,10 @@ func isEmptyLineParagraph(n Node) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isImageOrVideoLink(n Node) bool {
|
||||||
|
if l, ok := n.(RegularLink); ok && l.Kind() == "video" || l.Kind() == "image" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue