Compare commits
11 commits
d4f684b248
...
b9f94aafed
Author | SHA1 | Date | |
---|---|---|---|
b9f94aafed | |||
|
ed88fea76d | ||
|
6d8701e84d | ||
|
f8bd23d282 | ||
|
bb5515f225 | ||
|
417b814e85 | ||
|
075f3bab6d | ||
|
8e4a992dbd | ||
|
6cfae31132 | ||
|
7bc6100fc7 | ||
|
f537c0bda3 |
16 changed files with 77 additions and 84 deletions
45
README.org
45
README.org
|
@ -1,44 +1,5 @@
|
|||
* go-org
|
||||
An Org mode parser and static site generator in go.
|
||||
Take a look at github pages
|
||||
- for [[https://niklasfasching.github.io/go-org/][org to html conversion]] examples
|
||||
- for a [[https://niklasfasching.github.io/go-org/blorg][static site]] generated by blorg
|
||||
- to [[https://niklasfasching.github.io/go-org/convert.html][try it out live]] in your browser
|
||||
* go-org-orgwiki
|
||||
|
||||
[[https://raw.githubusercontent.com/niklasfasching/go-org/master/etc/example.png]]
|
||||
Thanks to: [[https://github.com/niklasfasching/go-org][go-org]]
|
||||
|
||||
Please note
|
||||
- the goal for the html export is to produce sensible html output, not to exactly reproduce the output of =org-html-export=.
|
||||
- the goal for the parser is to support a reasonable subset of Org mode. Org mode is *huge* and I like to follow the 80/20 rule.
|
||||
* usage
|
||||
** command line
|
||||
#+begin_src bash
|
||||
$ go-org
|
||||
Usage: go-org COMMAND [ARGS]...
|
||||
Commands:
|
||||
- render [FILE] FORMAT
|
||||
FORMAT: org, html, html-chroma
|
||||
Instead of specifying a file, org mode content can also be passed on stdin
|
||||
- blorg
|
||||
- blorg init
|
||||
- blorg build
|
||||
- blorg serve
|
||||
#+end_src
|
||||
** as a library
|
||||
see [[https://github.com/niklasfasching/go-org/blob/master/main.go][main.go]] and hugo [[https://github.com/gohugoio/hugo/blob/master/markup/org/convert.go][org/convert.go]]
|
||||
* development
|
||||
1. =make setup=
|
||||
2. change things
|
||||
3. =make preview= (regenerates fixtures & shows output in a browser)
|
||||
|
||||
in general, have a look at the Makefile - it's short enough.
|
||||
* resources
|
||||
- test files
|
||||
- [[https://raw.githubusercontent.com/kaushalmodi/ox-hugo/master/test/site/content-org/all-posts.org][ox-hugo all-posts.org]]
|
||||
- https://ox-hugo.scripter.co/doc/examples/
|
||||
- https://orgmode.org/manual/
|
||||
- https://orgmode.org/worg/dev/org-syntax.html
|
||||
- https://code.orgmode.org/bzg/org-mode/src/master/lisp/org.el
|
||||
- https://code.orgmode.org/bzg/org-mode/src/master/lisp/org-element.el
|
||||
- mostly those & ox-html.el, but yeah, all of [[https://code.orgmode.org/bzg/org-mode/src/master/lisp/]]
|
||||
- existing Org mode implementations: [[https://github.com/emacsmirror/org][org]], [[https://github.com/bdewey/org-ruby/blob/master/spec/html_examples][org-ruby]], [[https://github.com/chaseadamsio/goorgeous/][goorgeous]], [[https://github.com/jgm/pandoc/][pandoc]]
|
||||
This fork is used exclusively by [[https://git.fz0x1.wtf/fz0x1/orgwiki][orgwiki]] and includes a few minor modifications tailored for it.
|
||||
|
|
|
@ -133,7 +133,7 @@ echo "$index" > docs/index.html
|
|||
echo "$convert" > docs/convert.html
|
||||
cp etc/_wasm.go docs/wasm.go
|
||||
GOOS=js GOARCH=wasm go build -o docs/main.wasm docs/wasm.go
|
||||
cp $(go env GOROOT)/misc/wasm/wasm_exec.js docs/wasm_exec.js
|
||||
cp $(go env GOROOT)/lib/wasm/wasm_exec.js docs/wasm_exec.js
|
||||
|
||||
mkdir -p docs/blorg
|
||||
cp -r blorg/testdata/public/* docs/blorg/
|
||||
|
|
6
go.mod
6
go.mod
|
@ -1,11 +1,13 @@
|
|||
module github.com/niklasfasching/go-org
|
||||
|
||||
go 1.18
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.2
|
||||
|
||||
require (
|
||||
github.com/alecthomas/chroma/v2 v2.5.0
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
golang.org/x/net v0.23.0
|
||||
golang.org/x/net v0.38.0
|
||||
)
|
||||
|
||||
require github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -7,5 +7,5 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k
|
|||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
|
|
10
org/block.go
10
org/block.go
|
@ -157,7 +157,15 @@ func splitParameters(s string) []string {
|
|||
parameters, parts := []string{}, strings.Split(s, " :")
|
||||
lang, rest := strings.TrimSpace(parts[0]), parts[1:]
|
||||
if lang != "" {
|
||||
parameters = append(parameters, lang)
|
||||
xs := strings.Fields(lang)
|
||||
parameters = append(parameters, xs[0])
|
||||
for i := 1; i < len(xs); i++ {
|
||||
k, v := xs[i], ""
|
||||
if i+1 < len(xs) && xs[i+1][0] != '-' {
|
||||
v, i = xs[i+1], i+1
|
||||
}
|
||||
parameters = append(parameters, k, v)
|
||||
}
|
||||
}
|
||||
for _, p := range rest {
|
||||
kv := strings.SplitN(p+" ", " ", 2)
|
||||
|
|
|
@ -29,6 +29,7 @@ type Configuration struct {
|
|||
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.
|
||||
ReadFile func(filename string) ([]byte, error) // ReadFile is used to read e.g. #+INCLUDE files.
|
||||
ResolveLink func(protocol string, description []Node, link string) Node
|
||||
}
|
||||
|
||||
// Document contains the parsing results and a pointer to the Configuration.
|
||||
|
@ -93,6 +94,9 @@ func New() *Configuration {
|
|||
},
|
||||
Log: log.New(os.Stderr, "go-org: ", 0),
|
||||
ReadFile: ioutil.ReadFile,
|
||||
ResolveLink: func(protocol string, description []Node, link string) Node {
|
||||
return RegularLink{protocol, description, link, false}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +264,9 @@ func (d *Document) parseMany(i int, stop stopFn) (int, []Node) {
|
|||
func (d *Document) addHeadline(headline *Headline) int {
|
||||
current := &Section{Headline: headline}
|
||||
d.Outline.last.add(current)
|
||||
d.Outline.count++
|
||||
if !headline.IsExcluded(d) {
|
||||
d.Outline.count++
|
||||
}
|
||||
d.Outline.last = current
|
||||
return d.Outline.count
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ type Headline struct {
|
|||
Index int
|
||||
Lvl int
|
||||
Status string
|
||||
IsComment bool
|
||||
Priority string
|
||||
Properties *PropertyDrawer
|
||||
Title []Node
|
||||
|
@ -43,9 +44,6 @@ func lexHeadline(line string) (token, bool) {
|
|||
func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
|
||||
t, headline := d.tokens[i], Headline{}
|
||||
headline.Lvl = len(t.matches[1])
|
||||
|
||||
headline.Index = d.addHeadline(&headline)
|
||||
|
||||
text := t.content
|
||||
todoKeywords := trimFastTags(
|
||||
strings.FieldsFunc(d.Get("TODO"), func(r rune) bool { return unicode.IsSpace(r) || r == '|' }),
|
||||
|
@ -62,12 +60,15 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
|
|||
headline.Priority = text[2:3]
|
||||
text = strings.TrimSpace(text[4:])
|
||||
}
|
||||
|
||||
if strings.HasPrefix(text, "COMMENT ") {
|
||||
headline.IsComment = true
|
||||
text = strings.TrimPrefix(text, "COMMENT ")
|
||||
}
|
||||
if m := tagRegexp.FindStringSubmatch(text); m != nil {
|
||||
text = m[1]
|
||||
headline.Tags = strings.FieldsFunc(m[2], func(r rune) bool { return r == ':' })
|
||||
}
|
||||
|
||||
headline.Index = d.addHeadline(&headline)
|
||||
headline.Title = d.parseInline(text)
|
||||
|
||||
stop := func(d *Document, i int) bool {
|
||||
|
@ -107,6 +108,9 @@ func (h Headline) ID() string {
|
|||
}
|
||||
|
||||
func (h Headline) IsExcluded(d *Document) bool {
|
||||
if h.IsComment {
|
||||
return true
|
||||
}
|
||||
for _, excludedTag := range strings.Fields(d.Get("EXCLUDE_TAGS")) {
|
||||
for _, tag := range h.Tags {
|
||||
if tag == excludedTag {
|
||||
|
|
|
@ -393,16 +393,16 @@ func (w *HTMLWriter) WriteRegularLink(l RegularLink) {
|
|||
if l.Protocol == "file" {
|
||||
url = url[len("file:"):]
|
||||
}
|
||||
if isRelative := l.Protocol == "file" || l.Protocol == ""; isRelative && w.PrettyRelativeLinks {
|
||||
if !strings.HasPrefix(url, "/") {
|
||||
url = "../" + url
|
||||
}
|
||||
if strings.HasSuffix(url, ".org") {
|
||||
url = strings.TrimSuffix(url, ".org") + "/"
|
||||
}
|
||||
} else if isRelative && strings.HasSuffix(url, ".org") {
|
||||
url = strings.TrimSuffix(url, ".org") + ".html"
|
||||
}
|
||||
// if isRelative := l.Protocol == "file" || l.Protocol == ""; isRelative && w.PrettyRelativeLinks {
|
||||
// if !strings.HasPrefix(url, "/") {
|
||||
// url = "../" + url
|
||||
// }
|
||||
// if strings.HasSuffix(url, ".org") {
|
||||
// url = strings.TrimSuffix(url, ".org") + "/"
|
||||
// }
|
||||
// } else if isRelative && strings.HasSuffix(url, ".org") {
|
||||
// url = strings.TrimSuffix(url, ".org") + ".html"
|
||||
// }
|
||||
if prefix := w.document.Links[l.Protocol]; prefix != "" {
|
||||
if tag := strings.TrimPrefix(l.URL, l.Protocol+":"); strings.Contains(prefix, "%s") || strings.Contains(prefix, "%h") {
|
||||
url = html.EscapeString(strings.ReplaceAll(strings.ReplaceAll(prefix, "%s", tag), "%h", u.QueryEscape(tag)))
|
||||
|
|
|
@ -73,7 +73,7 @@ var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d
|
|||
var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`)
|
||||
var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`)
|
||||
var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`)
|
||||
var inlineBlockRegexp = regexp.MustCompile(`src_(\w+)(\[(.*)\])?{(.*)}`)
|
||||
var inlineBlockRegexp = regexp.MustCompile(`src_(\w+)(\[([^\]]*)\])?{([^}]*)}`)
|
||||
var inlineExportBlockRegexp = regexp.MustCompile(`@@(\w+):(.*?)@@`)
|
||||
var macroRegexp = regexp.MustCompile(`{{{(.*)\((.*)\)}}}`)
|
||||
|
||||
|
@ -329,7 +329,7 @@ func (d *Document) parseRegularLink(input string, start int) (int, Node) {
|
|||
if len(linkParts) == 2 {
|
||||
protocol = linkParts[0]
|
||||
}
|
||||
return consumed, RegularLink{protocol, description, link, false}
|
||||
return consumed, d.ResolveLink(protocol, description, link)
|
||||
}
|
||||
|
||||
func (d *Document) parseTimestamp(input string, start int) (int, Node) {
|
||||
|
|
32
org/testdata/headlines.html
vendored
32
org/testdata/headlines.html
vendored
|
@ -10,9 +10,9 @@
|
|||
</li>
|
||||
<li><a href="#headline-5">headline with custom status</a>
|
||||
</li>
|
||||
<li><a href="#headline-7">malformed property drawer</a>
|
||||
<li><a href="#headline-6">malformed property drawer</a>
|
||||
</li>
|
||||
<li><a href="#headline-8">level limit for headlines to be included in the table of contents</a>
|
||||
<li><a href="#headline-7">level limit for headlines to be included in the table of contents</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
@ -75,40 +75,40 @@ headline with custom status
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="outline-container-headline-7" class="outline-2">
|
||||
<h2 id="headline-7">
|
||||
<div id="outline-container-headline-6" class="outline-2">
|
||||
<h2 id="headline-6">
|
||||
malformed property drawer
|
||||
</h2>
|
||||
<div id="outline-text-headline-7" class="outline-text-2">
|
||||
<div id="outline-text-headline-6" class="outline-text-2">
|
||||
<p>:PROPERTIES:
|
||||
not a property</p>
|
||||
<p>:END:</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="outline-container-headline-8" class="outline-2">
|
||||
<h2 id="headline-8">
|
||||
<div id="outline-container-headline-7" class="outline-2">
|
||||
<h2 id="headline-7">
|
||||
level limit for headlines to be included in the table of contents
|
||||
</h2>
|
||||
<div id="outline-text-headline-8" class="outline-text-2">
|
||||
<div id="outline-text-headline-7" class="outline-text-2">
|
||||
<p>The toc option allows setting a <a href="https://orgmode.org/manual/Export-settings.html">level limit</a>. For this file we set it to 1 - which means that the following headlines
|
||||
won't be included in the table of contents.</p>
|
||||
<div id="outline-container-headline-9" class="outline-3">
|
||||
<h3 id="headline-9">
|
||||
<div id="outline-container-headline-8" class="outline-3">
|
||||
<h3 id="headline-8">
|
||||
headline 2 not in toc
|
||||
</h3>
|
||||
<div id="outline-text-headline-9" class="outline-text-3">
|
||||
<div id="outline-container-headline-10" class="outline-4">
|
||||
<h4 id="headline-10">
|
||||
<div id="outline-text-headline-8" class="outline-text-3">
|
||||
<div id="outline-container-headline-9" class="outline-4">
|
||||
<h4 id="headline-9">
|
||||
headline 3 not in toc
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="outline-container-headline-11" class="outline-3">
|
||||
<h3 id="headline-11">
|
||||
<div id="outline-container-headline-10" class="outline-3">
|
||||
<h3 id="headline-10">
|
||||
anoter headline 2 not in toc
|
||||
</h3>
|
||||
<div id="outline-text-headline-11" class="outline-text-3">
|
||||
<div id="outline-text-headline-10" class="outline-text-3">
|
||||
<p>you get the gist…</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
2
org/testdata/headlines.org
vendored
2
org/testdata/headlines.org
vendored
|
@ -28,6 +28,8 @@ it's possible to use =#+SETUPFILE= - in this case the setup file contains the fo
|
|||
this headline and it's content are not exported as it is marked with an =EXCLUDE_TAGS= tag.
|
||||
By default =EXCLUDE_TAGS= is just =:noexport:=.
|
||||
|
||||
* TODO [#A] COMMENT commented headline
|
||||
this headline is commented out. see [[https://orgmode.org/manual/Comment-Lines.html][comment lines]]
|
||||
* malformed property drawer
|
||||
:PROPERTIES:
|
||||
not a property
|
||||
|
|
2
org/testdata/headlines.pretty_org
vendored
2
org/testdata/headlines.pretty_org
vendored
|
@ -28,6 +28,8 @@ it's possible to use =#+SETUPFILE= - in this case the setup file contains the fo
|
|||
this headline and it's content are not exported as it is marked with an =EXCLUDE_TAGS= tag.
|
||||
By default =EXCLUDE_TAGS= is just =:noexport:=.
|
||||
|
||||
* TODO [#A] commented headline
|
||||
this headline is commented out. see [[https://orgmode.org/manual/Comment-Lines.html][comment lines]]
|
||||
* malformed property drawer
|
||||
:PROPERTIES:
|
||||
not a property
|
||||
|
|
6
org/testdata/inline.html
vendored
6
org/testdata/inline.html
vendored
|
@ -18,6 +18,12 @@ also hard line breaks not followed by a newline get ignored, see \\</li>
|
|||
<h1>hello</h1>
|
||||
</pre>
|
||||
</div>
|
||||
</div> and this <div class="src src-inline src-schema">
|
||||
<div class="highlight-inline">
|
||||
<pre>
|
||||
world
|
||||
</pre>
|
||||
</div>
|
||||
</div></li>
|
||||
<li>inline export blocks <h1>hello</h1></li>
|
||||
<li><code class="verbatim">multiline emphasis is
|
||||
|
|
2
org/testdata/inline.org
vendored
2
org/testdata/inline.org
vendored
|
@ -11,7 +11,7 @@
|
|||
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
|
||||
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+
|
||||
- *bold string with an *asterisk inside*
|
||||
- inline source blocks like src_html[:eval no]{<h1>hello</h1>}
|
||||
- inline source blocks like src_html[:eval no]{<h1>hello</h1>} and this src_schema[:eval no]{world}
|
||||
- inline export blocks @@html:<h1>hello</h1>@@
|
||||
- =multiline emphasis is
|
||||
supported - and respects MaxEmphasisNewLines (default: 1)=
|
||||
|
|
2
org/testdata/inline.pretty_org
vendored
2
org/testdata/inline.pretty_org
vendored
|
@ -11,7 +11,7 @@
|
|||
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
|
||||
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+
|
||||
- *bold string with an *asterisk inside*
|
||||
- inline source blocks like src_html[:eval no]{<h1>hello</h1>}
|
||||
- inline source blocks like src_html[:eval no]{<h1>hello</h1>} and this src_schema[:eval no]{world}
|
||||
- inline export blocks @@html:<h1>hello</h1>@@
|
||||
- =multiline emphasis is
|
||||
supported - and respects MaxEmphasisNewLines (default: 1)=
|
||||
|
|
2
org/testdata/misc.html
vendored
2
org/testdata/misc.html
vendored
|
@ -171,6 +171,8 @@ it's possible to use =#+SETUPFILE= - in this case the setup file contains th
|
|||
this headline and it's content are not exported as it is marked with an =EXCLUDE_TAGS= tag.
|
||||
By default =EXCLUDE_TAGS= is just =:noexport:=.
|
||||
|
||||
* TODO [#A] COMMENT commented headline
|
||||
this headline is commented out. see [[https://orgmode.org/manual/Comment-Lines.html][comment lines]]
|
||||
* malformed property drawer
|
||||
:PROPERTIES:
|
||||
not a property
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue