diff --git a/README.org b/README.org index 7d8bec7..c604b18 100644 --- a/README.org +++ b/README.org @@ -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 - hopefully add reasonable org-mode support to hugo - sadly [[https://github.com/chaseadamsio/goorgeous][goorgeous]] is broken & abandoned * 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/46][#46]]: Support for symbols like ndash and mdash - 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 *** TODO [[https://github.com/chaseadamsio/goorgeous/issues/33][#33]]: Wrong output when mixing html with org-mode * 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 - 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 - table colgroups https://orgmode.org/worg/org-tutorials/tables.html - table pretty printing diff --git a/org/html.go b/org/html.go index 0bb3879..db3ee6b 100644 --- a/org/html.go +++ b/org/html.go @@ -8,6 +8,7 @@ import ( type HTMLWriter struct { stringBuilder + document *Document HighlightCodeBlock func(source, lang string) string } @@ -44,7 +45,10 @@ func (w *HTMLWriter) emptyClone() *HTMLWriter { return &wcopy } -func (w *HTMLWriter) before(d *Document) {} +func (w *HTMLWriter) before(d *Document) { + w.document = d +} + func (w *HTMLWriter) after(d *Document) { w.writeFootnotes(d) } @@ -54,6 +58,8 @@ func (w *HTMLWriter) writeNodes(ns ...Node) { switch n := n.(type) { case Keyword, Comment: continue + case NodeWithMeta: + w.writeNodeWithMeta(n) case Headline: w.writeHeadline(n) case Block: @@ -242,6 +248,19 @@ func (w *HTMLWriter) writeHorizontalRule(h HorizontalRule) { w.WriteString("
\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 := `

` + "\n" + captionW.String() + "\n

\n" + nodeString = `
` + "\n" + nodeString + caption + `
` + "\n" + } + w.WriteString(nodeString) +} + func (w *HTMLWriter) writeTable(t Table) { w.WriteString("\n") w.writeNodes(t.Header) diff --git a/org/keyword.go b/org/keyword.go index 3eb8c65..68359c0 100644 --- a/org/keyword.go +++ b/org/keyword.go @@ -10,10 +10,16 @@ type Keyword struct { Value string } +type NodeWithMeta struct { + Node Node + Meta map[string]string +} + type Comment struct{ Content string } var keywordRegexp = regexp.MustCompile(`^(\s*)#\+([^:]+):\s(.*)`) var commentRegexp = regexp.MustCompile(`^(\s*)#(.*)`) +var affiliatedKeywordRegexp = regexp.MustCompile(`^(CAPTION)$`) func lexKeywordOrComment(line string) (token, bool) { 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) { - t := d.tokens[i] - k, v := t.matches[2], t.matches[3] - d.BufferSettings[k] = strings.Join([]string{d.BufferSettings[k], v}, "\n") - return 1, Keyword{k, v} + k := parseKeyword(d.tokens[i]) + if affiliatedKeywordRegexp.MatchString(k.Key) { + consumed, node := d.parseAffiliated(i, stop) + 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) { 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} +} diff --git a/org/org.go b/org/org.go index 4b77b43..752412d 100644 --- a/org/org.go +++ b/org/org.go @@ -48,6 +48,8 @@ func (w *OrgWriter) writeNodes(ns ...Node) { w.writeComment(n) case Keyword: w.writeKeyword(n) + case NodeWithMeta: + w.writeNodeWithMeta(n) case Headline: w.writeHeadline(n) 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)) } +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) { w.WriteString(w.indent + "#" + c.Content) } diff --git a/org/testdata/example.html b/org/testdata/example.html index 902af7c..da65ee7 100644 --- a/org/testdata/example.html +++ b/org/testdata/example.html @@ -188,8 +188,35 @@ auto link, i.e. not inside \[[square brackets]\] < +

Captions

+

+Anything can be captioned. Also captions are not real, correct captions but just a paragraph below the element (bothe wrapped into a div) +

+
+
echo "i have a caption!"
+

+captioned soure block +

+
+
+ +

+captioned link (video in this case) +

+
+

+note that only that one line is captioned, not the whole paragraph +

+

+also, normal text lines can't be captioned +

blocks

+
echo a bash source block
+

+bleck! +

+
a source block without a language
 and a second line
 and a third one
diff --git a/org/testdata/example.org b/org/testdata/example.org index 2e77050..a7d37f8 100644 --- a/org/testdata/example.org +++ b/org/testdata/example.org @@ -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]] 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 + +#+CAPTION: bleck! #+BEGIN_SRC bash echo a bash source block #+END_SRC diff --git a/org/util.go b/org/util.go index 0ee6a97..eb23f16 100644 --- a/org/util.go +++ b/org/util.go @@ -17,3 +17,10 @@ func isEmptyLineParagraph(n Node) bool { } return false } + +func isImageOrVideoLink(n Node) bool { + if l, ok := n.(RegularLink); ok && l.Kind() == "video" || l.Kind() == "image" { + return true + } + return false +}