A few days ago I asked Thibaut Lienart how to setup Franklin.jl to work with Norg.jl. Norg.jl is a Julia package to parse norg files, and Franklin.jl is a static website generator written in Julia. He kindly answered and told me to try making it work with Xranklin.jl, the next version of Franklin.jl. If you are reading this, it means it worked! Here are the steps to make it work!
This is far from perfect (and you can see there is work to do if we want a proper integration), but this is still doable, and that means we can create a glue package for (X|F)ranklin.jl integration!
You can also check how the Norg specification is currently rendered or visit the GitHub repository for this website.
First things first, you need a working website setup. The easiest way to do that is to create it using Franklin.jl and then switch to Xranklin.jl.
using Franklin
newsite("NorgInXranklin")
Then, you can switch to Xranklin.jl. The website generated is not yet compatible with the new library so for now simply delete menu1.md
, menu2.md
, menu3.md
and most of the content in index.md
. We don't need them anyway. You can then check that the website works. If it doesn't, keep deleting unnecessary things.
using Xranklin
serve(debug=true)
Great! now we can install Norg.jl. The latest version is not released yet, so we will install the developpment version.
Pkg.add(url="https://github.com/Klafyvel/Norg.jl", rev="main")
You can quickly check that Norg.jl works as intended.
using Norg
norg"* Hello, world!"
You should get the following result:
(K"NorgDocument", 1, 9)
└─ (K"Heading1", 2, 9)
└─ (K"ParagraphSegment", 4, 8)
├─ Hello
├─ ,
├─
├─ world
└─ !
Now is time to invoke Norg.jl to generate an HTML page from existing .norg
files. This should happen within utils.jl
. We need a lx_norg
function there, based on the literate.jl template.
I ended up with the following code (a bit hacky):
"""
\\norg{rpath}
Try to find a norg file, resolve it and return it.
## Notes
1. the `rpath` is taken relative to the website folder
2. the `rpath` must end with `.norg` and must not start with a `/`
"""
function lx_norg(p::Vector{String})::String
lc = cur_lc()
c = Xranklin._lx_check_nargs(:norg, p, 1)
isempty(c) || return c
rpath = Xranklin.unixify(strip(p[1]))
if !endswith(rpath, ".norg")
@warn """
\\norg{...}
The relative path
<$rpath>
does not end with '.norg'.
"""
return Xranklin.failed_lxc("norg", p)
end
# try to form the full path to the norg file and check it's there
fpath = Xranklin.path(:folder) / rpath
if !isfile(fpath)
@warn """
\\norg{...}
Couldn't find a norg file at path
<$fpath>
(resolved from '$rpath').
"""
return Xranklin.failed_lxc("norg", p)
end
# here fpath is the full path to an existing literate script
return _process_norg_file(lc, rpath, fpath)
end
function is_meta(ast, node)
if Norg.kind(node) ≠ Norg.K"Verbatim"
return false
end
c1,c2 = first(children(node), 2)
Norg.textify(ast, c1) == "document" && Norg.textify(ast, c2) == "meta"
end
function parse_meta(ast, metanode)
d = Dict{Symbol, String}()
metacontent = Norg.textify(ast, last(children(metanode)))
for metaline in filter(!isempty, split(metacontent, '\n'))
k,v = split(metaline, r"\s*:\s*")
d[Symbol(k)] = v
end
d
end
"""
_process_norg_file(rpath, fpath)
Helper function to process a norg file located at `rpath` (`fpath`).
We pass `fpath` because it's already been resolved.
"""
function _process_norg_file(
lc::Xranklin.LocalContext,
rpath::String,
fpath::String
)::String
# Step 1: read the .norg file
s = open(fpath) do io
read(io, String)
end
# Step 2: parse the .norg file
ast = norg(s)
# Step 3: dirty trick to parse the `@document.meta` tag.
metanode = first(filter(x->is_meta(ast, x), collect(PreOrderDFS(ast.root))))
if !isnothing(metanode)
metadict = parse_meta(ast, metanode)
for (k,v) in metadict
setlvar!(k, v)
end
end
# Step 4: generate HTML
return string(Norg.codegen(HTMLTarget(), ast))
end