/website

personal website

Primary LanguageScheme

Static Site Generator

I have written a custom static site generator using Guile Scheme to process my personal website. To build this repository, run:

$ guile doclisp.scm && ./sync.sh user@server_address

doclisp.scm will make the website by running make.scm with the Doclisp syntax enabled. sync.sh will send the files to the server removing any old files and reloading the Nginx instance tuning there.

WARNING: sync.sh will overwrite anything in the /data directory of the server as well as /etc/nginx/nginx.conf.

Doclisp

The doclisp file defines a special syntax for reading scheme code and a function for converting S-expressions to HTML.

Usage

At present doclisp.scm will automatically load the file make.scm with the correct reader as this is the easiest way to set the reader in a file.

Reader

The function dl-load will load a file using doclisp-reader, which is a function similar to the built in read but has additional syntax for writing text using curly brackets. Below is a demonstration of this syntax and what it maps to in normal scheme.

{a {b} ('`,c. ) #d #"#;," #@e}
;; is the same as
`("a" ("b") "('`,c." ")" ,d "#;," ,@e)

Within curly braces, all ordinary syntax is disabled and text is split by spaces then converted to strings. (As a result of this, white-space is not significant.) Curly braces can be nested without causing issues with quoting. The macros # and #@ are the equivalent of ​,​ and ​,@​ from quasiquote, they will read an ordinary scheme form put the result of evaluating it into the list. Comments will still work within the curly braces. The unquoting # can also be used to escape these characters unquoting a string literal.

Escaping

When the doclisp reader encounters a \ (backslash) character, it will treat the subsequent character literally. This can be used to write special charters (eg {}#\;) into symbols.

Converting to HTML

The function sexp->html converts a tree of strings to a string of HTML. The basic syntax used is to take the head of the list and use that as an HTML tag, then join the rest of the list with spaces and put those in the body of the tag. Escape characters are not yet dealt with, so they will need to be replaced with the corresponding entity when they appear.

(sexp->html {p Paragraph with {b bold text}.})
;; => <p>Paragraph with <b>bold text</b>.</p>

If the head of a list is itself a list, then its first element is the tag name and the rest are either pairs of an attribute and its value, or single strings inserted directly. The value of a pair will first be processed by (sexp->html (cons "just" (cdr pair))), so the list will be joined using spaces between each string if more than one item is given. The result of this conversion is inserted directly after the attribute and surrounded by quotes.

(sexp->html {{script {src /path/to/script name} defer}})
;; => <script src="/path/to/script name" defer></script>

Special Tags

If the tag is just, then the body will not be surrounded by a tag. join is similar, but the body will not have spaces added between its elements.

(sexp->html {just Paragraph with {join super {sup script}}.})
;; => Paragraph with super<sup>script</sup>.

These can both be escaped using the attribute syntax without listing any attributes.

(sexp->html {{just} Paragraph with {{join} super {sup script}}.})
;; => <just>Paragraph with <join>super <sup>script</sup></join></just>

Implicit Join

The symbol join is inserted into Doclisp code automatically when forms touch in a list not separated by space. Hence, the following are equivalent.

{p Paragraph with {b bold text}.}
{p Paragraph with {b bold text} #'join .}
`("p" "Paragraph" "with" ("b" "bold" "text") ,'join ".")

This inserted join will also inhibit the implicit inserting of spaces. Without this, there would be an unwanted space between the “.” and the bold text.

Ignored Values

#f is ignored and will not have spaces inserted on either side of this it. I personally find it nicer than splicing an empty list if you want to include content conditionally.

{p Example of conditional content: #(and include-content? {just content})}