- Write S-expressions,
- Run
l2h
, - Use generated HTML file(s).
Here's a small example of s-exp
input and HTML
output:
Input
(html
(body
(h1 :disabled Hello world!)
(div :class="alert"
So long, and (b thanks) for all the (em fish))))
Output
<html>
<body>
<h1 disabled>Hello world!</h1>
<div class="alert">So long, and <b>thanks</b> for all the <em>fish</em></div></body></html>
Note
The output is actually tab-indented; in the interests of brevity I have substituted 2x spaces for each tab.
There are only two main rules to remember, both of which are very simple1:
- The first symbol of each s-expression is the HTML tag.
- Attributes for an HTML tag must be preceded with a
:
character.
There's one additional rule:
- Tagnames starting with a period are all reserved for
l2h
builtin functions.
Here's how it looks in practice:
Input
(MyTag Some random content goes here)Output
<MyTag>Some random content goes here</MyTag>
Input
(MyTag :class="alert" :readonly Some random text)Output
<MyTag class="alert" readonly>Some random text</MyTag>
That's pretty much all you need to know2 to generate HTML.
Newlines in the source are respected as far as possible so that the generated HTML matches the visual structure of the source s-expression.
Input
(html (body (h1 :disabled Hello world!)))
Output
<html> <body><h1 disabled>Hello world!</h1></body></html>
Spaces have significance to ensure that results like He<b>ll</b>o
and
Good <b>Morning</b> world
come out as expected. In particular:
- Spaces are significant prior to a starting tag.
- Spaces are significant prior to an ending tag.
- Spaces are significant after an ending tag.
Input
He(b ll)o Good (b Morning) World Good (b Morning )World
Output
He<b>ll</b>o Good <b>Morning</b> World Good <b>Morning </b>World
You will eventually need to include parentheses in content. To do this use the
special builtin tagname .
like this:
Input
(p This is how (. an aside parenthetical) should look)Output
<p>This is how (an aside parenthetical) should look</p>
As this is meant to be part of my workflow, speed is one of the more important
criteria, especially complete duration (which includes startup speed). I use
inotify tools to watch a directory for changes and then apply l2h -r
which
processes all named directories recursively.
In practice, the delay is imperceptible to me.
For most projects, I will be surprised if you notice l2h
added to your
workflow just by looking at build times.
For large amounts of input content, it can be noticeable. I imagine that when my filecounts grow that large I'd make some attempt at optimisation.
On my VirtualBox instance (4 cores, 6GB RAM), the speed test script produced the following data at different input data sizes (when processing recursively).
Files | Dirs | Size | Create (secs) | Convert (secs) |
---|---|---|---|---|
100 | 111 | 14M | 0.29 | 0.99 |
400 | 421 | 55M | 1.16 | 3.88 |
900 | 931 | 123M | 2.60 | 9.37 |
1600 | 1641 | 219M | 4.72 | 16.08 |
2500 | 2551 | 341M | 7.29 | 25.42 |
3600 | 3661 | 491M | 10.59 | 36.87 |
4900 | 4971 | 668M | 14.17 | 50.28 |
6400 | 6481 | 873M | 19.73 | 64.00 |
8100 | 8191 | 1.1G | 26.55 | 82.88 |
The
:
character must be escaped whenever it occurs as the first piece of content after a tagname, else it will be parsed as an attribute.
For example, here is how the bug manifests, and how escaping fixes it.
Input
(tag :attr1 :not-attr the :not-attr is content, not tag) (tag :attr1 \:not-attr the :not-attr is content, not tag)
Output
<tag attr1 not-attr>the :not-attr is content, not tag</tag> <tag attr1>not-attr the :not-attr is content, not tag</tag>
Either grab the pre-compiled package (for Linux/x64 only, for now) or download
the single ./l2h_main.c
file and compile it (tested with gcc
, clang
and
tcc
).
Note
While this is Linux-only right now, I'll add Windows support if anyone ever asks for it. Same for *BSDs. I dunno about Mac as I don't have one anymore. I'm also working on the assumption that there's a github actions runner for whatever platform is being requested.
Feel free to log issues. Starting the program with l2h --help
prints out all
the options available, and all the flags supported.
Lisp2Html: Convert lisp-ish s-expressions to HTML tag trees
Usage:
l2h [options] PATH1 PATH2 ... PATHn
Each path must be a filename of the form '*.html.lisp' or a directory
name. When PATH is a filename, the file is converted and the results
are written to '*.html' (i.e. the '.lisp' is removed from the output
filename). When PATH is a directory it is scanned for all files matching
the pattern '*.html.lisp' and each file is converted, with the results
stored in '*.html'
Unless the option '-r' or '--recurse' is specified, directories are
not recursively processed. If the option '-s' or '--stdio' is specified
then input is read from stdin and written to stdout. All pathnames are
ignored when '-s' or '--stdio' is specified.
The following options are recognised. Unrecognised options produce an
error message without any processing of files or data.
-r | --recurse Recursively process any directories specified
-s | --stdio Read input from stdin and write the output to stdout
-v | --verbose Produce extra informational messages
-V | --version Print the program version, then continue as normal
-h | --help Display this message and exit
Footnotes
-
I was aiming for intuitive. I may have misjudged. ↩
-
Caveats apply, see the BUGS section. ↩