Lilac Developer Guide
- 1. Development environment (Nix shell)
- 2. Makefile
- 3. Tangling (generating the source code)
- 4. Weaving (generating the HTML)
- 4.1. Emacs customizations (
lilac.el
) - 4.2. Fix non-determinism
- 4.3. Top-level publishing function (
lilac-publish
) - 4.4. Org modifications
- 4.5. HTML modifications
- 4.6. JavaScript
- 4.7. Autogenerate CSS for syntax highlighting of source code blocks
- 4.8. Misc settings
- 4.9. Imports
- 4.10. Additional (hand-tweaked) CSS
- 4.10.1. Colors and fonts
- 4.10.2. General body text
- 4.10.3. Headline font sizes
- 4.10.4. Tables
- 4.10.5. Description lists
- 4.10.6. Images
- 4.10.7. Monospaced
- 4.10.8. Links
- 4.10.9. Source code block body
- 4.10.10. Source code block captions
- 4.10.11. Links to child source block from parent
- 4.10.12. Sidenotes
- 4.10.13. Printer-friendly styling
- 4.10.14. Adjustments for mobile (touch) screens
- 4.11. Create
lilac.theme
file - 4.12. Ignore woven HTML from
git diff
- 4.13. gitignore
- 4.1. Emacs customizations (
- 5. Tests
- 6. Glossary
- 7. References
All source code is generated by tangling this Org file
(developer-guide.org
). This file is the single source of truth for basically
everything. Some other things like COPYRIGHT and LICENSE do not come from this
file, but they are exceptions.
Tangling is done by loading this file into Emacs and then running
(org-babel-tangle)
. This file is also part of the woven HTML documentation
as developer-guide.html
, which is referenced by the introductory docs at
README.html
. The HTML is generated by invoking (lilac-publish)
. The outputs
of both tangling and weaving are checked into version control.
The Makefile
in this repo is used as the main "driver" for both tangling and
weaving. Typically, you would have a browser pointed to README.html
or
developer-guide.html
(whichever one you are working on) and refresh it after
editing the corresponding Org file. After every change to the Org file, you can
run make
to tangle, weave, and run unit tests.
1. Development environment (Nix shell)
This is the main development shell and brings in all of our dependencies to
build all of our code. Taken from here. The Makefile
is meant to be executed
from within this environment.
For Emacs, we use the -nox
version to avoid GUI dependencies (because we
always invoke Emacs in batch mode in the terminal without ever using it in an
interactive manner).
2. Makefile
We have a top-level Makefile
so that we can run some make
commands on the
command line. The overall idea is to tangle and weave, while also running any
associated tests.
Note that we make use of the fake file tangle
, so that we can write the
top-level test
rule as test: tangle
, which reads more naturally than the
equivalent test: Makefile
or test: lilac.el
.
Weaving just depends on the main README.html
and developer-guide
files being
generated. Before we call (lilac-publish)
, we have to first call
(lilac-gen-css-and-exit)
because otherwise the source code blocks do not get
any syntax highlighting.
Tangling is pretty straightforward — we just need to call
(org-babel-tangle)
on developer-guide.org
(the README.org
does not contain
any code we need to run to make this work). This generates a number of files,
such as the Makefile
and shell.nix
.
The key here is to enumerate these generated files, because we need to tell the
make
utility that it should run the rule if developer-guide.org
has a newer
modification timestamp than any of the generated files. Technically speaking,
because all of the tangled files are tangled together at once with
(org-babel-tangle)
, we could just list one of them such as Makefile
(instead
of enumerating all of them). However we still enumerate them all here for
completeness.
The run_emacs
function is used for both weaving and tangling. The main thing
of interest here is that it loads the lilac.el
(tangled) file before
evaluating the given expression.
We use niv
to update the dependencies sourced by shell.nix
. Niv uses two
sources of truth: the niv repository itself on GitHub, and a branch of
nixpkgs
. The former tracks the master
branch, and the latter tracks the
stable channels (example: nixos-24.05
branch). Whenever we run niv update
,
niv
will update the HEAD commit SHA of these branches.
One problem with the nixpkgs
stable channel is that it will eventually become
obsolete as newer stable channels get created. So we have to manually track
these channels ourselves.
2.1. Linting
2.1.1. Spell checker
We use typos-cli to check for spelling errors. Below we configure it to only check the original source material — Org files.
Here we have the Makefile rules for linting, which include this spellchecker.
3. Tangling (generating the source code)
Tangling is simply the act of collecting the #+begin_src ... #+end_src
blocks
and arranging them into the various target (source code) files. Every source
code block is given a unique name.
We simply tangle the developer-guide.org
file to get all the code we need.
3.1. Standardized Noweb reference syntax
Lilac places the following requirements for naming Noweb references:
- the reference must start with a
__NREF__
prefix - the reference must then start with a letter, followed by letters, numbers,
dashes, underscores, or periods, and may terminate with
(...)
where the "…" may be an empty string or some other argument.
These rules help Lilac detect Noweb references easily.
3.2. Remove trailing whitespace
When the __NREF__...
is indented, and that reference has blank lines, those
blank lines will inherit the indent in the parent block. This results in
trailing whitespace in the tangled output, which is messy. Delete such lines
from the tangled output.
3.3. Allow evaluation of code blocks
Some code blocks are generated by evaluating code in other blocks. This way, you can use all the power of Org mode (as well as any supported programming language) as a kind of meta-programming system.
Orgmode by default disables automatic evaluation of source code blocks, because it is a big security risk. For us, we know that we want to allow code evaluation, so we disable the evaluation confirmation check. This way, evaluation can still work even in batch mode.
The following is needed to evaluate the example for source code block evaluation in Monoblock (evaluation result's value), which evaluates Python. We don't need to add in Emacs lisp here because it's already supported by default (which we take advantage of in 4.11).
In addition to Python, we add in some additional languages that might be of use.
3.4. Faster tangling
These settings are stolen from https://github.com/doomemacs/doomemacs/commit/295ab7ed3a20ba4619a142be15f5f2ef08d2adcf.
4. Weaving (generating the HTML)
Weaving is conceptually simpler than tangling because there is no extra step — the output is an HTML page and that is something that we can use directly (unlike program source code, which may require additional compilation into a binary, depending on the language). We limit ourselves to HTML output because HTML support is ubiquitous; plus we don't have to worry about page breaks such as in PDF output.
Although weaving is conceptually simple, most of the code in lilac.el
have to
do with weaving because the default infrastructure that ships with Org mode is
too rigid for our needs. For example, we make heavy use of Noweb-style
[1] references, but also add in extensive HTML links to allow
the reader to jump around code easily because Org does not cross-link these
references by default.
Weaving currently requires the following dependencies:
Dependency | Why |
---|---|
GNU Make | to run "make" |
GNU Emacs | for tangling and weaving |
Note that all of the above can be brought in by using the Nix package manager.
This is why we provide a shell.nix
file in this repo.
4.1. Emacs customizations (lilac.el
)
Below is the overall structure of lilac.el
. The gc-cons-threshold
setting is to prevent emacs
from entering garbage collection pauses, because
we invoke emacs
from the command line in a non-interactive manner.
4.2. Fix non-determinism
Nondeterminism is problematic because it results in a different HTML file
every time we run org-babel-tangle
, even if the Org files have not changed.
Here we take care to set things right so that we can have reproducible, stable
HTML output.
4.2.1. Do not insert current time as HTML comment
Org mode also injects an HTML comment (not visible to the user) to record the time that the HTML was generated. We disable this because it breaks deterministic output. See this link for more info.
4.2.2. Do not insert current Org mode version
By default Org mode appends visible metadata at the bottom of the HTML document, including the Org version used to generate the document. We suppress this information.
4.2.3. Do not use random numbers for the HTML id
attribute
Stop randomized IDs from being generated every time. Instead count from 0 and work our way up.
See https://www.reddit.com/r/orgmode/comments/aagmfh/comment/hk6upbf.
4.3. Top-level publishing function (lilac-publish
)
The top-level function is lilac-publish
. This actually publishes to HTML
twice, with two separate calls to org-html-export-to-html
. The reason we
publish twice is because we need to examine the HTML output twice in order to
build up a database of parent/child source code block links (which is then used
to link between these parent/child source code blocks).
Also note that we do some modifications to the Org buffer directly before
exporting to HTML. The main reason is so that the source code blocks that are
named __NREF__...
get an automatic #+caption: ...
text to go along with it
(because for these Noweb-style blocks, the captions should always look uniform).
The full code listing for lilac-publish
is below.
Now let's go through lilac-publish
in detail.
Do not hardcode colors into the HTML. Instead refer to CSS class names, to be stylized by an external CSS file.
Here we modify the Org mode buffer, by using org-export-before-parsing-hook
.
This takes a list of functions that are free to modify the Org mode buffer
before each Org element in the buffer gets converted into HTML.
As for the actual modifications, see:
lilac-UID-for-all-src-blocks
(4.4.1)lilac-insert-noweb-source-code-block-captions
(4.4.2)lilac-UID-for-all-headlines
(4.4.3)
In brief, the lilac-UID-for-all-*
functions make it so that the links to
headlines and source code blocks are both deterministic and human-readable. The
lilac-insert-noweb-source-code-block-captions
function
Now we start modifying the HTML.
This is useful for adding in final tweaks to the HTML that is difficult to accomplish at the Org-mode buffer level.
Phase 1: In the first phase, we use the generated HTML data to populate the
lilac-child-HTML_ID-hash-table
. This data structure is used to link to child
blocks from parent blocks. We also populate the
lilac-org_id-human_id-hash-table
which is used to convert HTML IDs to be more
human-readable.
In between these phases we have to call
because of some internal housekeeping we have to do.
Phase 2: In this phase we perform the linking from parent blocks to child blocks
(lilac-link-to-children-from-parent-body
), and also convert the child source
code captions to look prettier (lilac-prettify-source-code-captions
).
After publishing to HTML, we have to clear the intermediate hash tables used for
the export, because we could be invoking lilac-publish
multiple times from the
same emacs session (such as during unit tests).
The final bits are
- the deletion of the "Table of Contents" text from the autogenerated Table of Contents section, which we'll convert into a sidebar; and
- the cleaning up of some inline CSS styles that get injected into the HTML as part of citation styles.
4.3.1. Helper functions
These are for modifying the Org buffer.
These are for modifying the HTML.
4.4. Org modifications
4.4.1. Give all source code blocks a #+name: ...
field (HTML ID)
Only source code blocks that have a #+name: ...
field (org name field) get an
HTML ID (org ID) assigned to it. The problem with polyblocks is that they are
not assigned an org name field by default.
Of course, we still want all polyblock to have an HTML ID, which can then be
extracted by lilac-get-src-block-HTML_ID
to build up the
lilac-child-HTML_ID-hash-table
in 4.5.3. If we don't do this then parent source code blocks won't
be able to link to the polyblock at all (or vice versa).
Monoblocks with a #+name: ...
field get a unique HTML ID assigned to it in
the form orgN
where N
is a hexadecimal number. By default Org generates a
random number for N
, but we use a simple counter that increments, starting
from 0 (see 4.2.3).
Some source code blocks may not even be monoblocks, because a #+name: ...
field may simply be missing.
What we can do is inject a #+name: ___anonymous-src-block-N
line (where N
is
an incrementing number) into the beginning of the source code section of all
source code blocks that need it. Then we can construct an HTML link to any
source code block.
Note that the actual name __anonymous-src-block-N
is not important, because it
gets erased and replaced with an orgN
ID during HTML export. At that point we
make these orgN
strings human-readable in 4.5.1.
4.4.3. Human-readable UIDs (Headings, aka headlines)
By default Org does a terrible job of naming HTML id
fields for headings. By
default it uses a randomly-generated number. In 4.2.3 we tweak this behavior to use a deterministic,
incrementing number starting from 0. However while this solution gets rid of the
nondeterminism, it still results in human-unfriendly id
attributes because
they are all numeric (e.g. org00000a1
, org00000f3
, etc).
For headings, we can do better because in practice they already mostly have
unique contents, which should work most of the time to act as an id
. In other
words, we want all headings to have HTML IDs that are patterned after their
contents. This way we can have IDs like some-heading-name-1
(where the
trailing -1
is only used to disambiguate against another heading of the same
name) instead of org00000a1
(numeric hex).
For each heading, we insert a CUSTOM_ID
property. This makes Org refer to this
CUSTOM_ID
instead of the numeric org...
link names. We append this headline
property just below every headline we find in the buffer. The actual
construction of the CUSTOM_ID
(headline-UID
in the code below) is done by
lilac-get-unique-id
.
lilac-get-unique-id
converts a given headline to its canonical form (every
non-word character converted to a dash) and performs a lookup against the hash
table. If the entry exists, it looks up a entry-N
value in a loop with N
increasing until it sees that no such key exists (at which point we know that we
have a unique ID).
4.5. HTML modifications
4.5.1. Use human-readable HTML IDs for source code links
Recall that there are 2 types of source code blocks: monoblocks and polyblocks.
Polyblocks do get a name field attached to them during the Org
modification stage, in the format ___anonymous-src-block-N
. These names are
for HTML link generation only, because the user won't see them — they will
instead just see org000012
or some such. In fact, all monoblocks are also
given these random-looking (and unstable) org...
HTML IDs.
And therein lies the problem: if a user decides to bookmark a particular source
code block, whether a monoblock or polyblock, they will link to an
org...
-style ID and chances are that this link will break over time.
This is exactly the same problem we have for headlines. For headlines we solved
the problem with a hash table, and we need to do the same thing here. The major
difference, though, is that unlike headlines which can accept a CUSTOM_ID
Org
property, source code blocks have no such facility. So instead of modifying the
buffer (as we do for headlines), we have to modify the final HTML output
instead.
The solution is to simply look at all source code block links, then modify the
id=...
part so that it looks like a more human-readable ID. We can extract the
human-readable ID by looking at the smart captions inside the
<label>...</label>
area for both monoblocks and polyblocks. And then it's just
a matter of doing a basic search-and-replace across the entire buffer (HTML
file).
We have to do a search-and-replace across the entire file because we may also have manual links to source code blocks (although — maybe it's just not worth it because we can't refer to polyblocks anyway by name).
4.5.3. Link noweb references (link to child block from parent block)
In 4.5.2 we tweaked the HTML so that source code blocks could link back up to their parents. In this section we are concerned with the opposite — linking to child blocks from the parents. For example, consider the following code:
What we want to do is to make the __NREF__child-block-1
and
__NREF__child-block-2
references inside parent-block
to link to their
definitions, so that the reader can just click on them to go to see how they're
defined. Unfortunately Org mode doesn't do this by default so we have to do this
ourselves.
In the case of __NREF__child-block-2
, it is defined in multiple blocks so we
would want to link to the very first block.
We cannot use a org-export-before-parsing-hook
like we did in 4.3 because at that stage of processing, we
are dealing with Org mode syntax. Any modifications we make to the parent
source code block will be treated as text upon HTML export. Thankfully Org mode
allows customizations on generated HTML through the
org-export-filter-src-block-functions
variable. This variable is analogous to
org-export-before-parsing-hook
, but operates at the HTML level (not at the
Org syntax level) for source code blocks, which is exactly what we need.
So we have to craft valid HTML links (not Org links) to the child source code
blocks. For this we need the actual id
part of the HTML <pre>...
block that
will hold the source code. That is, the algorithm should be something like:
- for every parent source code block,
- for every child block (noweb) referenced in the body, insert an HTML link to
the child block (lookup in
lilac-child-HTML_ID-hash-table
).
The only thing remaining is the construction of
lilac-child-HTML_ID-hash-table
. We can construct this by mapping through all
source code blocks and getting the name which can be just drawn from the <label
...>
HTML tag, thanks to the smart captions we inserted for all child blocks
earlier in 4.4.2. This hash table
will hold mappings from child source block names to their HTML ID's.
Now that we have a high-level understanding, let's walk through the
implementation. First here's the code to populate the
lilac-child-HTML_ID-hash-table
.
We need to get the source block name (the name of the child) from the HTML. We
do this in lilac-get-src-block-name-from-html
below. Either there is a
__NREF__...
text in the <code>
tag within a <label>
tag (from 4.4.2), or there is just a plain <label>
as
a result of a manually-written #+caption: ...
bit. We use either one. Note
that the latter category assumes that the user used unique caption labels for
all blocks; if the user manually creates non-unique captions, this will probably
break.
If the user used manual captions, we have to remove the "Listing N: …" text that Org automatically inserts.
Now we have everything we need to add HTML links into the body of the source
code block directly. We search for the child name (which begins with a
__NREF__...
, which is defined in lilac-nref-rx
), and perform a string
replacement, adding in the link to the child block.
4.5.4. Search and replace hardcoded things
Org's HTML export hardcodes some things. We have to do some manual surgery to set things right. First let's define a generic search-and-replace function. This function is based on this example.
Here's a basic wrapper to perform the string replacement for an HTML file based on the current buffer name.
Now we do some basic search-and-replace commands to clean up the exported HTML
file from Emacs (instead of invoking sed
).
4.5.4.1. MathJax with line breaking support
Surprisingly, MathJax v3 (which Org ships with) does not support manual line breaks. However, line breaks are supported in the new v4 alpha version. So use that.
We have to include the trailing (and somewhat redundant) \">
so that the
function does not replace the text above as well as the intended raw HTML
(hardcoded) bits that we want to replace.
4.5.4.2. Bibliography (citations)
This cleans up the inline styles for citations. Again we include some additional
characters in the pattern (notably the left angle bracket (<
)) so that the
text below itself does not get recognized and replaced.
We also do some replacements for the bibliography entries themselves. Namely, each entry has the following (odd) structure by default:
<div class="csl-entry"><a id="citeproc_bib_item_1"></a> ... </div>
and instead we delete the content-less link anchor and instead move the ID to the parent div, like this:
<div class="csl-entry" id="citeproc_bib_item_1"> ... </div>
This way we can target this surrounding parent div for the active
HTML class
attribute (instead of the empty link anchor) to style the entire entry when we
click a link to go to it.
And here we style these entries.
4.5.4.3. Glossary (description lists)
Similar to the bibliography entries, the description lists in HTML have empty
link anchors in them, because of the way we insert the link anchors manually in
the Org text (this is a convention we follow; see the 6 for examples).
We get rid of these anchors and instead create a surrounding div around it, so
that we can highlight the enclosed <dt>
(description term) and <dd>
(description details).
And here we style it.
4.5.5. Custom HTML "head" section
If a user wants to use a custom Google web font, they have to make the HTML page
pull it in in the <head>
part of the page. This requires modifying the HTML.
In order to facilitate this, we provide a replaceable piece of text that can be
swapped out for the value that the user can provide. Specifically, we inject the
line <!-- LILAC_HTML_HEAD -->
into the HTML, and this can be replaced by the
value of the lilac-html-head
variable in Emacs Lisp (which can be provided by
the user when they invoke the lilac-publish
function).
4.6. JavaScript
We use JavaScript to make the HTML document more useful. The primary concern is to make it easier to navigate around the various intra-document links.
In thelilac.theme
file, we include lilac.js
and also jQuery
because we depend on it.
4.6.1. Self-linked headlines
Every HTML element h2
to h6
(which encode the Org mode headlines) already
come with a unique ID, but they are not linked to themselves. We add the
self-links here, which makes it easy for users to link to them directly when
they're reading the page.
The JavaScript below is taken from here.
Linking itself makes all the headlines blue (the default color for links). This is a bit distracting, so make them black.
Lastly, show a link anchor icon when the user hovers over a headline. We have
the icon present at all times; we only make it visible when we hover over the
heading. This way, there is no "jumping" of any kind when the heading is about
the same size as the width of the enclosing div
(it can jump when displaying
the icon results in a new line break).
4.6.3. Highlight and scroll to just-clicked-on item
When we click on any link (typically a code block but it can also be a headline or some other intra-document link destination), the browser shifts the page there. But sometimes we are already near the link destination so the page doesn't move. Other times we get moved all the way to the top or the bottom of the page, so by the time the browser finishes moving there, the user can be confused as to know which destination the browser wanted to go to. This can be somewhat disorienting.
The solution is to highlight the just-clicked-on link's destination element. Every time we click on anything, we add a class to the destination element. Then from CSS we can make this visually compelling. This way we give the user a visual cue when clicking on links that navigate to a destination within the same document.
We adapt the code in 4.6.2.3 to do what we need here. The
main difference is that while the code there is concerned with adding and
removing the active
class from the #text-table-of-contents
div, here we want
to do the same but for the outline-2
div which contains the outline text
(There can be multiple outline-2
divs, but this is immaterial.)
We also use the scrollIntoViewIfNeeded
function to scroll the item into view,
but only if we need to. This way we minimize the need to scroll, resulting in a
much less "jumpy" experience.
Now we just need to style the active element.
For headlines, we draw a border around it.
For span nodes, we also draw a border around it. Spans can be the destination when we link to a line inside a source code block.
Different destination elements need different selectors. We cover most of them here.
4.6.4. Scroll to history item
Normally, browsers only treat links across URLs as new points in history; this
means that for links within the page, their history is not saved. We make sure
to save it explicitly with history.pushState()
though in HISTORY_PUSHSTATE
.
So then every time we want to go back in history (by pressing the "back" arrow
button in the browser), we just need to scroll to it. We already scroll to the
element we click on when we push on a new history item into the stack, so
there's no need to keep it symmetric here.
We have to activate the restored history item (to re-highlight it with the
active
class), so we do that also.
Lastly, setting the scrollRestoration
property is critical because otherwise
the browser will want to restore the custom scroll position (instead of going to
the history item location we've saved).
4.7. Autogenerate CSS for syntax highlighting of source code blocks
Generate syntax-highlighting.css
and quit emacs. This function is designed to
be run from the command line on a fresh emacs instance (dedicated OS process).
Unfortunately, it can only be run in interactive mode (without the --batch
flag to emacs).
If we use the workaround from here, we can generate a CSS file with colors from batch mode. However, the hackiness is not worth it.
4.8. Misc settings
4.8.1. Use HTML5 export, not XML (to un-break MathJax)
By default on Org 9.6, MathJax settings (JavaScript snippet) gets wrapped in a
CDATA tag, and we run into the same problem described on this email that has
gone unanswered:
https://www.mail-archive.com/emacs-orgmode@gnu.org/msg140821.html. It appears
that this is because the document is exported as XML, not HTML. Setting the
document type to html5
, as below, appears to make the CDATA tag magically
disappear.
4.8.2. Set citeproc styles folder
For some reason we cannot specify citeproc styles based on a relative path in
our Org file. The solution is to set the org-cite-csl-styles-dir
variable. See
this post.
4.8.3. Code references
4.8.3.1. Define CodeHighlightOn
and CodeHighlightOff
If we don't do this, we get an error because the "coderef" links (the links
inside code blocks, for example ;ref:NSCB_NAME
) will still try to run the
CodeHighlightOn
and CodeHighlightOff
JavaScript functions. Turning this
setting on here injects the definitions of these functions into the HTML.
4.8.3.2. Do not highlight coderefs
4.8.4. Preserve leading whitespace characters on export
4.8.5. Set tab width to 4 spaces
This matters for languages that require tabs, such as Makefiles.
4.8.6. Disable backups
Disable backup files for lilac.el
(that look like lilac.el~
) when we invoke
Emacs from the Makefile.
4.8.7. Profiling
We don't use this very often, but it's mainly for determining cost centers for weaving and tangling.
4.9. Imports
If we want syntax-highlighting to work for a code block, that code block's major mode (language) must be loaded in here.
4.10. Additional (hand-tweaked) CSS
We add some additional CSS tweaks on top of what we get from Org.
4.10.1. Colors and fonts
We define colors and fonts in one place and reuse them throughout the rest of
the CSS, with the var()
function.
This would be an excellent candidate to copy/paste/tweak into your own
lilac-override.css
file to tweak any of the colors or fonts used by Lilac.
4.10.3. Headline font sizes
4.10.3.1. Title font
4.10.4. Tables
4.10.5. Description lists
4.10.6. Images
4.10.7. Monospaced
Also make the background color of the programming language hover text the same as what we have elsewhere. This hover text comes with Org mode's HTML export of source code blocks.
4.10.8. Links
Force all links to have a blue color. This is mainly to override whatever Org gives us by default.
4.10.9. Source code block body
4.10.11. Links to child source block from parent
4.10.12. Sidenotes
Sidenotes are small blurbs of text that are displayed "out-of-band", on the right margin. This right margin is good for presenting smaller ideas that shouldn't necessarily sit in the main body text.
The CSS below is drawn primarily from here, with some modifications.
4.10.13. Printer-friendly styling
When printing, we don't display the sidebar because it unnecessarily narrows the width of the main text area.
4.10.14. Adjustments for mobile (touch) screens
See https://stackoverflow.com/a/74215894 for the any-pointer: coarse
tip.
4.11. Create lilac.theme
file
Allow HTML exports of Org files (including this one) to pull in CSS and JavaScript that we've defined for Lilac by referring to a single theme file. The inspiration for this setup comes from https://gitlab.com/OlMon/org-themes.
For the default fonts, we break up the definition over multiple lines here using Emacs Lisp for readability.
Now we get the result of evaluating the above with __NREF__fonts-to-load()
(note the trailing parentheses ()
, which evaluates the referenced code block
before injecting its evaluated value).
Also note that we pull in both the lilac.css
file which we tangle in 4.10, but this can be expanded by customizing the
value of lilac-html-head
, per 4.5.5. For example, you
could make this variable link to a separate lilac-override.css
file to
override any of the values we have hardcoded in lilac.css
.
4.12. Ignore woven HTML from git diff
Typically we only need to look at the rendered HTML output in a web browser as
the raw HTML diff output is extremely difficult to parse as a human. So by
default we ask Git to exclude it from git diff
by treating them as binary
data.
In order to still show the HTML textual diff, we can run git diff --text
.
4.12.1. git add -p
Note that the above setting to treat HTML files as binary data prevents them
from being considered for git add -p
. In order to add them, use git add -u
instead.
4.13. gitignore
5. Tests
We use ERT, the Emacs Lisp Regression Testing tool for our unit tests. Pure
functions that take all of their inputs explicitly ("dependency-injected") are
easy to test because we just provide the various inputs and expect the function
to produce certain outputs. For functions that operate on an Emacs buffer, we
use with-temp-buffer
to create a temporary buffer first before invoking the
functions under test.
Some functions we test expect Org mode to be active (so that certain Org mode
functions are available), so we turn it on here by calling (org-mode)
.
5.1. Test helpers
5.1.1. Setup and tear-down fixture for HTML tests
The Emacs manual for ERT defines fixtures as environments which provide setup and tear-down.
When testing HTML output (behavior of (lilac-publish)
), it's useful to create
a temporary Org file and to generate the HTML output (as part of "setup"). Then
we'd run the tests, and finally delete the temporary files (as part of
"tear-down").
We use (lilac-publish-fixture)
to do the aforementioned setup and tear-down
for us. In between setup and tear-down, we execute the test
function with a
funcall
.
5.3. Link to child block from parent block
5.3.1. One (same) child node block referenced in two different parent blocks
Expect to find a link in the HTML from the parent blocks to the same child block.
5.3.2. Nested child blocks
A child block can itself be a parent (and link to the nested child within it). Expect to find a link to the nested child block from within the first child block.
6. Glossary
- literate document
- A file or collection of
files that include both source code and prose to explain it. Well-known
formats include Noweb files (
*.nw
) and Org mode files (*.org
). - monoblock
- an Org mode source code block with a
#+name: ...
field. This block is an independent block and there are no other blocks with the same name. - Noweb
- A literate programming tool from 1989 that still works and from which Org mode borrows heavily using Noweb-style references. See Wikipedia.
- noweb-ref
- aka "Noweb-style reference". A Noweb-style reference is just a name (string) that refers to a monoblock or polyblock. See the Org manual.
- Org mode
- An Emacs major mode for
*.org
files, where "major mode" means that it provides things like syntax highlighting and keyboard shortcuts for*.org
text files if you are using Emacs. For Lilac, the important thing is that we use Org mode as a literate programming tool. See Org mode. - polyblock
- an Org mode source code block without a
#+name: ...
field, but which has a#+header: :noweb-ref ...
field. Other blocks with the same Noweb-ref name are concatenated together when they are tangled. Polyblocks are used in cases where we would like to break up a single block into smaller pieces for explanatory purposes. In all other cases, monoblocks are preferable, unless the source code block is not to be tangled and is only for explanatory purposes in the woven output. - source code block
- An Org mode facility
that allows you to enclose a multiline text (typically source code) with
#+begin_src ...
and#+end_src
lines. They are enclosed in a separate background color in the HTML output, and are often used for illustrating source code listings. The format is#+begin_src LANGUAGE_NAME
whereLANGUAGE_NAME
is the name of the programming language used for the listing. If the name is a recognized name, it will get syntax highlighting in the output automatically. - tangling
- The act of extracting source code from a raw literate document.
- weaving
- The act of converting a raw literate document to a richer format such as PDF or HTML. This allows fancier output, such as for mathematical formulas, which are easier to read versus the original literate document.