Compare commits

..

No commits in common. "b9f94aafed2df95b04b567d1dff4f56821956a74" and "d4f684b24872e4c0bfbaea0526f6f773cd2d8281" have entirely different histories.

16 changed files with 84 additions and 77 deletions

View file

@ -1,5 +1,44 @@
* go-org-orgwiki * 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
Thanks to: [[https://github.com/niklasfasching/go-org][go-org]] [[https://raw.githubusercontent.com/niklasfasching/go-org/master/etc/example.png]]
This fork is used exclusively by [[https://git.fz0x1.wtf/fz0x1/orgwiki][orgwiki]] and includes a few minor modifications tailored for it. 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]]

View file

@ -133,7 +133,7 @@ echo "$index" > docs/index.html
echo "$convert" > docs/convert.html echo "$convert" > docs/convert.html
cp etc/_wasm.go docs/wasm.go cp etc/_wasm.go docs/wasm.go
GOOS=js GOARCH=wasm go build -o docs/main.wasm docs/wasm.go GOOS=js GOARCH=wasm go build -o docs/main.wasm docs/wasm.go
cp $(go env GOROOT)/lib/wasm/wasm_exec.js docs/wasm_exec.js cp $(go env GOROOT)/misc/wasm/wasm_exec.js docs/wasm_exec.js
mkdir -p docs/blorg mkdir -p docs/blorg
cp -r blorg/testdata/public/* docs/blorg/ cp -r blorg/testdata/public/* docs/blorg/

6
go.mod
View file

@ -1,13 +1,11 @@
module github.com/niklasfasching/go-org module github.com/niklasfasching/go-org
go 1.23.0 go 1.18
toolchain go1.24.2
require ( require (
github.com/alecthomas/chroma/v2 v2.5.0 github.com/alecthomas/chroma/v2 v2.5.0
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0
golang.org/x/net v0.38.0 golang.org/x/net v0.23.0
) )
require github.com/dlclark/regexp2 v1.4.0 // indirect require github.com/dlclark/regexp2 v1.4.0 // indirect

4
go.sum
View file

@ -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/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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=

View file

@ -157,15 +157,7 @@ func splitParameters(s string) []string {
parameters, parts := []string{}, strings.Split(s, " :") parameters, parts := []string{}, strings.Split(s, " :")
lang, rest := strings.TrimSpace(parts[0]), parts[1:] lang, rest := strings.TrimSpace(parts[0]), parts[1:]
if lang != "" { if lang != "" {
xs := strings.Fields(lang) parameters = append(parameters, 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 { for _, p := range rest {
kv := strings.SplitN(p+" ", " ", 2) kv := strings.SplitN(p+" ", " ", 2)

View file

@ -29,7 +29,6 @@ type Configuration struct {
DefaultSettings map[string]string // Default values for settings that are overriden by setting the same key in BufferSettings. 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. 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. 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. // Document contains the parsing results and a pointer to the Configuration.
@ -94,9 +93,6 @@ func New() *Configuration {
}, },
Log: log.New(os.Stderr, "go-org: ", 0), Log: log.New(os.Stderr, "go-org: ", 0),
ReadFile: ioutil.ReadFile, ReadFile: ioutil.ReadFile,
ResolveLink: func(protocol string, description []Node, link string) Node {
return RegularLink{protocol, description, link, false}
},
} }
} }
@ -264,9 +260,7 @@ func (d *Document) parseMany(i int, stop stopFn) (int, []Node) {
func (d *Document) addHeadline(headline *Headline) int { func (d *Document) addHeadline(headline *Headline) int {
current := &Section{Headline: headline} current := &Section{Headline: headline}
d.Outline.last.add(current) d.Outline.last.add(current)
if !headline.IsExcluded(d) {
d.Outline.count++ d.Outline.count++
}
d.Outline.last = current d.Outline.last = current
return d.Outline.count return d.Outline.count
} }

View file

@ -23,7 +23,6 @@ type Headline struct {
Index int Index int
Lvl int Lvl int
Status string Status string
IsComment bool
Priority string Priority string
Properties *PropertyDrawer Properties *PropertyDrawer
Title []Node Title []Node
@ -44,6 +43,9 @@ func lexHeadline(line string) (token, bool) {
func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) { func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
t, headline := d.tokens[i], Headline{} t, headline := d.tokens[i], Headline{}
headline.Lvl = len(t.matches[1]) headline.Lvl = len(t.matches[1])
headline.Index = d.addHeadline(&headline)
text := t.content text := t.content
todoKeywords := trimFastTags( todoKeywords := trimFastTags(
strings.FieldsFunc(d.Get("TODO"), func(r rune) bool { return unicode.IsSpace(r) || r == '|' }), strings.FieldsFunc(d.Get("TODO"), func(r rune) bool { return unicode.IsSpace(r) || r == '|' }),
@ -60,15 +62,12 @@ func (d *Document) parseHeadline(i int, parentStop stopFn) (int, Node) {
headline.Priority = text[2:3] headline.Priority = text[2:3]
text = strings.TrimSpace(text[4:]) 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 { if m := tagRegexp.FindStringSubmatch(text); m != nil {
text = m[1] text = m[1]
headline.Tags = strings.FieldsFunc(m[2], func(r rune) bool { return r == ':' }) headline.Tags = strings.FieldsFunc(m[2], func(r rune) bool { return r == ':' })
} }
headline.Index = d.addHeadline(&headline)
headline.Title = d.parseInline(text) headline.Title = d.parseInline(text)
stop := func(d *Document, i int) bool { stop := func(d *Document, i int) bool {
@ -108,9 +107,6 @@ func (h Headline) ID() string {
} }
func (h Headline) IsExcluded(d *Document) bool { func (h Headline) IsExcluded(d *Document) bool {
if h.IsComment {
return true
}
for _, excludedTag := range strings.Fields(d.Get("EXCLUDE_TAGS")) { for _, excludedTag := range strings.Fields(d.Get("EXCLUDE_TAGS")) {
for _, tag := range h.Tags { for _, tag := range h.Tags {
if tag == excludedTag { if tag == excludedTag {

View file

@ -393,16 +393,16 @@ func (w *HTMLWriter) WriteRegularLink(l RegularLink) {
if l.Protocol == "file" { if l.Protocol == "file" {
url = url[len("file:"):] url = url[len("file:"):]
} }
// if isRelative := l.Protocol == "file" || l.Protocol == ""; isRelative && w.PrettyRelativeLinks { if isRelative := l.Protocol == "file" || l.Protocol == ""; isRelative && w.PrettyRelativeLinks {
// if !strings.HasPrefix(url, "/") { if !strings.HasPrefix(url, "/") {
// url = "../" + url url = "../" + url
// } }
// if strings.HasSuffix(url, ".org") { if strings.HasSuffix(url, ".org") {
// url = strings.TrimSuffix(url, ".org") + "/" url = strings.TrimSuffix(url, ".org") + "/"
// } }
// } else if isRelative && strings.HasSuffix(url, ".org") { } else if isRelative && strings.HasSuffix(url, ".org") {
// url = strings.TrimSuffix(url, ".org") + ".html" url = strings.TrimSuffix(url, ".org") + ".html"
// } }
if prefix := w.document.Links[l.Protocol]; prefix != "" { if prefix := w.document.Links[l.Protocol]; prefix != "" {
if tag := strings.TrimPrefix(l.URL, l.Protocol+":"); strings.Contains(prefix, "%s") || strings.Contains(prefix, "%h") { 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))) url = html.EscapeString(strings.ReplaceAll(strings.ReplaceAll(prefix, "%s", tag), "%h", u.QueryEscape(tag)))

View file

@ -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 footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`)
var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`) var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`)
var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`) 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 inlineExportBlockRegexp = regexp.MustCompile(`@@(\w+):(.*?)@@`)
var macroRegexp = regexp.MustCompile(`{{{(.*)\((.*)\)}}}`) var macroRegexp = regexp.MustCompile(`{{{(.*)\((.*)\)}}}`)
@ -329,7 +329,7 @@ func (d *Document) parseRegularLink(input string, start int) (int, Node) {
if len(linkParts) == 2 { if len(linkParts) == 2 {
protocol = linkParts[0] protocol = linkParts[0]
} }
return consumed, d.ResolveLink(protocol, description, link) return consumed, RegularLink{protocol, description, link, false}
} }
func (d *Document) parseTimestamp(input string, start int) (int, Node) { func (d *Document) parseTimestamp(input string, start int) (int, Node) {

View file

@ -10,9 +10,9 @@
</li> </li>
<li><a href="#headline-5">headline with custom status</a> <li><a href="#headline-5">headline with custom status</a>
</li> </li>
<li><a href="#headline-6">malformed property drawer</a> <li><a href="#headline-7">malformed property drawer</a>
</li> </li>
<li><a href="#headline-7">level limit for headlines to be included in the table of contents</a> <li><a href="#headline-8">level limit for headlines to be included in the table of contents</a>
</li> </li>
</ul> </ul>
</nav> </nav>
@ -75,40 +75,40 @@ headline with custom status
</div> </div>
</div> </div>
</div> </div>
<div id="outline-container-headline-6" class="outline-2"> <div id="outline-container-headline-7" class="outline-2">
<h2 id="headline-6"> <h2 id="headline-7">
malformed property drawer malformed property drawer
</h2> </h2>
<div id="outline-text-headline-6" class="outline-text-2"> <div id="outline-text-headline-7" class="outline-text-2">
<p>:PROPERTIES: <p>:PROPERTIES:
not a property</p> not a property</p>
<p>:END:</p> <p>:END:</p>
</div> </div>
</div> </div>
<div id="outline-container-headline-7" class="outline-2"> <div id="outline-container-headline-8" class="outline-2">
<h2 id="headline-7"> <h2 id="headline-8">
level limit for headlines to be included in the table of contents level limit for headlines to be included in the table of contents
</h2> </h2>
<div id="outline-text-headline-7" class="outline-text-2"> <div id="outline-text-headline-8" 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 <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&#39;t be included in the table of contents.</p> won&#39;t be included in the table of contents.</p>
<div id="outline-container-headline-8" class="outline-3"> <div id="outline-container-headline-9" class="outline-3">
<h3 id="headline-8"> <h3 id="headline-9">
headline 2 not in toc headline 2 not in toc
</h3> </h3>
<div id="outline-text-headline-8" class="outline-text-3"> <div id="outline-text-headline-9" class="outline-text-3">
<div id="outline-container-headline-9" class="outline-4"> <div id="outline-container-headline-10" class="outline-4">
<h4 id="headline-9"> <h4 id="headline-10">
headline 3 not in toc headline 3 not in toc
</h4> </h4>
</div> </div>
</div> </div>
</div> </div>
<div id="outline-container-headline-10" class="outline-3"> <div id="outline-container-headline-11" class="outline-3">
<h3 id="headline-10"> <h3 id="headline-11">
anoter headline 2 not in toc anoter headline 2 not in toc
</h3> </h3>
<div id="outline-text-headline-10" class="outline-text-3"> <div id="outline-text-headline-11" class="outline-text-3">
<p>you get the gist…</p> <p>you get the gist…</p>
</div> </div>
</div> </div>

View file

@ -28,8 +28,6 @@ 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. 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:=. 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 * malformed property drawer
:PROPERTIES: :PROPERTIES:
not a property not a property

View file

@ -28,8 +28,6 @@ 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. 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:=. 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 * malformed property drawer
:PROPERTIES: :PROPERTIES:
not a property not a property

View file

@ -18,12 +18,6 @@ also hard line breaks not followed by a newline get ignored, see \\</li>
&lt;h1&gt;hello&lt;/h1&gt; &lt;h1&gt;hello&lt;/h1&gt;
</pre> </pre>
</div> </div>
</div> and this <div class="src src-inline src-schema">
<div class="highlight-inline">
<pre>
world
</pre>
</div>
</div></li> </div></li>
<li>inline export blocks <h1>hello</h1></li> <li>inline export blocks <h1>hello</h1></li>
<li><code class="verbatim">multiline emphasis is <li><code class="verbatim">multiline emphasis is

View file

@ -11,7 +11,7 @@
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/ - links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+ - _underlined_ *bold* =verbatim= ~code~ +strikethrough+
- *bold string with an *asterisk inside* - *bold string with an *asterisk inside*
- inline source blocks like src_html[:eval no]{<h1>hello</h1>} and this src_schema[:eval no]{world} - inline source blocks like src_html[:eval no]{<h1>hello</h1>}
- inline export blocks @@html:<h1>hello</h1>@@ - inline export blocks @@html:<h1>hello</h1>@@
- =multiline emphasis is - =multiline emphasis is
supported - and respects MaxEmphasisNewLines (default: 1)= supported - and respects MaxEmphasisNewLines (default: 1)=

View file

@ -11,7 +11,7 @@
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/ - links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+ - _underlined_ *bold* =verbatim= ~code~ +strikethrough+
- *bold string with an *asterisk inside* - *bold string with an *asterisk inside*
- inline source blocks like src_html[:eval no]{<h1>hello</h1>} and this src_schema[:eval no]{world} - inline source blocks like src_html[:eval no]{<h1>hello</h1>}
- inline export blocks @@html:<h1>hello</h1>@@ - inline export blocks @@html:<h1>hello</h1>@@
- =multiline emphasis is - =multiline emphasis is
supported - and respects MaxEmphasisNewLines (default: 1)= supported - and respects MaxEmphasisNewLines (default: 1)=

View file

@ -171,8 +171,6 @@ it&#39;s possible to use =#+SETUPFILE= - in this case the setup file contains th
this headline and it&#39;s content are not exported as it is marked with an =EXCLUDE_TAGS= tag. this headline and it&#39;s content are not exported as it is marked with an =EXCLUDE_TAGS= tag.
By default =EXCLUDE_TAGS= is just =:noexport:=. 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 * malformed property drawer
:PROPERTIES: :PROPERTIES:
not a property not a property