my diamond-shape logo; quite minimalisitic. Liam Collod

Developer Documentation

The documentation that explains how this website is generated and modified.

This website is built using lxmsite, a custom made Python library that allow to convert a bunch of files to a static website.

lxmsite will generate a website that is made only of HTML and CSS. The "source files" are a mix of rst, HTML Jinja templates and other custom file formats.

You don't need to actually know Python to use lxmsite (unless you want to extends its feature).

Basics

The central concept for this website is that the source file structure represents the published site structure. It tries to be as explicit as possible.

source/                        >  published/
    index.rst                  >      index.html
    index.css                  >      index.css
    blog/                      >      blog/
        article1.rst           >          article1.html
        article1-image.jpg     >          article1-image.jpg

So let's say your website is published at https://site.com:

  • then https://site.com/blog/index.html implies there must be a /blog/index.rst file.

  • Same for https://site.com/images/art.jpg which implies images/art.jpg must exist in the source file structure.

When you have a basic file structure created, you may want to build the site. lxmsite comes with a build function but it is more convenient to use the build script (a CLI).

That build script only require on input: a path to the site config file.

The site config file is a python file that include a bunch of global variables that describe how the site must be built. This include what is the source directory (SRC_ROOT) or which directory you want to build it to (DST_ROOT).

.siteignore files

A .siteignore is a file that indicate which path must not be collected to build the site. It can be found at root but also in any sub-directory.

The file format works as follow:

  • each line defines a "path expression" which resolves to multiple paths to ignore.

  • a line can be empty

  • path expression follow the python glob syntax

  • path expression MUST be relative to the .siteignore file directory

  • path expression may resolve to non-existing paths

Example:

.siteignore
   **/.*.html
   *.txt
index.html
somestuff.txt
blog/
   .siteignore
      **/*.cpp
   index.html
   .template.html
   snippet.cpp
   resource.txt

In the above we have an expression at root that will ignore all html files that starts with a dot, the ** is a glob pattern which express recursion, meaning that blog/.template.html will be ignored. We will also ignore somestuff.txt but NOT resource.txt. We then ignore blog/snippet.cpp.

shelf feature

A shelf indicate a directory contains a bunch of page you want to "group" together. For example: a portfolio, a blog, a news-feed.

You create a shelf by simpy adding a .shelf file to the root directory.

Currently the shelf can be used in 2 ways:

  1. It allow to iterate through its children page from a Jinja template.

    You can retrieve a ShelfResource instance using the Shelf variable in your Jinja template context. The object proivides different method to browse its page, on which you can loop using Jinja {% for %} clause.

  2. It allow to auto-create an rss feed from all the children pages.

    RSS is the most naive way to allow visitor to "suscribe" to a website and get notified for updates. Here, adding a new page will add a new item to the RSS feed, which will notify suscribers a new page has been published.

The .shelf file acts as a config and have a few options to change the shelf behavior. Its content is a custom syntax which follow the given rules:

  • each lines defines an option to configure

  • an option CANNOT span multiple lines

  • a line might be empty

  • an option is specified as key: value with optional whitespace around the :.

    • key must be one of the available pre-defined option keys.

    • value must be a valid python object (so a string must be quoted for example).

And the following option keys are supported:

name

type

description

ignored_pages

list[str]

List of relative page url to not include in browse methods (relative to the shelf file).

disable_rss

bool

True to disable the auto-generation of an rss feed.

build process

This is how the source file structure is parsed the site final file structure:

  • collect all file paths in the source directory and ignore some paths using the .siteignore files.

  • read and convert rst file as pages

  • collect shelves

  • render pages with their template and write to disk

  • build redirection pages

  • build shelves rss feed

  • copy static resources

See lxmsite._build for the code implementation.

Creating a page

All pages MUST have an .rst file, even if it just have a title. You are then free to define its content using the standard rst syntax or to manually create the html with a template.

writing rst content

See https://docutils.sourceforge.io/docs/user/rst/quickref.html.

page metadata

This are the fields that are understood as page metadata:

name

description

authors

Comma separated list of person who authored the page. See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name

tags

Comma separated list of arbitrary labels matching the page topics

language

Language of the page. As standardized by https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/lang and https://www.w3.org/International/articles/language-tags/

title

Additional override if the rst file title is not desired. See https://ogp.me/#metadata

type

Caracterize the kind of content of the page. As standardized by https://ogp.me/#types

image

Relative file path to the image to use as cover for the page. See https://ogp.me/#metadata

image-alt

Alt text to describe the content of the image field.

description

Short, human-readable summary of the page content. See https://ogp.me/#optional

date-created

Date at which the page was created. Format is YYYY-MM-DDThh:mm. See https://en.wikipedia.org/wiki/ISO_8601

date-modified

Date at which the page was last modified. Format is YYYY-MM-DDThh:mm. See https://en.wikipedia.org/wiki/ISO_8601

template

Relative file path to the html template to use for rendering the page.

stylesheets

Comma separated list of stylesheet path relative to the page. Prefix with a + to inherit the parent stylesheets.

status

either published (no effect) or unlisted (will be excluded from being listed in its parent shelf)

Some extra fields may be used depending on the context (whose existence is only defined in some html template):

blog context:

name

description

category

(optional) which type of content is the page

cover

(optional) path to an image to display on top of the blog post.

cover-alt

(optional) the alt text for the cover image.

resources context:

name

description

category

(optional) which type of content is the page

A field is specified under the page title as :field-name: value. Example:

my page
=======

:description: this is quite a long summary that would be
   cool to wrap on 2 lines.

See lxmsite._page for the code implementation.

rst directives

In extent to the builtin rst directives ( https://docutils.sourceforge.io/docs/ref/rst/directives.html ), we provide additional directives, or edit the existing ones.

Here is a quick directive's glossary as reminder:

.. directivename:: argument1 argument2
    :option1:
    :option2:

    content

code, code-block

You can embed code snippets with the code and code-block directives. They use pygments to provide syntax highlighting.

Example:

.. code:: languageName
    :option1: optionValue

    your code
    in multiple lines

admonitions

Admonitions are builtin to rst and there is no changes to them.

admonition, attention, caution, danger, error, hint, important, note, tip, warning

If you want to render a specific admonition type with a custom title you can use the generic .. admonition:: and add the class option with the type. Example:

.. admonition:: πŸ• About pizza
    :class: warning

    Pineapple do belongs on them.

Will render:

highlight

It is however possible to have an admonition without a title using the custom directive .. highlight:::

.. highlight::
    :class: tip

    Look ma', no hands !

Will render:

url-preview

This is a customd directive which allow you to share links as "static embeds", meaning they have the box with rich content that is prettier than just a link, but you actually have to write all the rich content yourself instead of having fetch using javascript.

It required one mandatory argument which is the url to "prettify".

The directive have 4 options:

  • title: title to use for the preview

  • image: url to an image file (relative or absolute).

  • svg: relative url to a local svg file (relative to the page directory).

  • color: the css color of the svg.

  • svg-size: 1 or 2 number indicating the size of the svg. ex: '64' will set the svg to 64x64 px

The content of the directive will be used as description.

Example:

.. url-preview:: https://liamcollod.xyz
    :title: Website - Liam Collod
    :image: ../.static/images/cover-social.jpg

    Check my website & blog. VFX, imaging and software development.

Website - Liam Collod

Check my website & blog. VFX, imaging and software development.

image-grid

When needing to display a lot of image in a non-sequential layout (so as a grid), you can use the .. image-grid:: directive.

It accept no argument, neither options and all works based on its content.

Each line of the content is treated as an image. You group images into one row by separating them by a blank line. The line must start by the image uri, relative to the page its in and is optionally followed by the image caption.

It is possible the image caption span multiple line; in that case the following lines must start with a 2+ spaced indent.

Example:

.. image-grid::

    path/to/image1.jpg
    path/to/image2.jpg

    path/to/image3.jpg some caption that will be displayed under
    path/to/image4.jpg the caption can span
        multiple lines if it's too long.
    path/to/image5.jpg

.meta.json files

We see previously that each rst page can define some metadata at its top. However specifying everytime some of those fields is a repetitive task. To adress this issue you can use meta files.

Meta files are json files whose content specify default metadata value to use for all files that are next or children in the hierarchy of the meta file. The meta file hierachy is recursively merged so the meta file "closest" to your page will get priority.

Example:

.meta.json
index.rst
blog/
    .meta.json
    index.rst
    post1.rst
    post2.rst

In the above example .meta.json at root will affect index.rst but also all files in the blog/ directory. However the content of blog/.meta.json will take priority over the root one.

Meta file use standard JSON syntax, where a non-nested dict is expected. Each root key defines the name of the metadata to set, which is the same as you would use in the rst page. The value can either be a string or list of string.

List of strings are handled differently but allow merging, this mean that the child meta file will extend() the parent meta file list if it exists. When resolved in the rst file, lists are converted back to string by joining its items with a ,.

It's also totally possibel that for the same metadata key, switch between a list type or a str type. A str type will override any list value defined before, and a list value when the previous value was a string, will cast the previous value to a list automatically.

The code logic can be found in lmxsite._browse.

Writing page html templates

All html templates are processed with Jinja. Refers to their documentation for how to write Jinja templates.

In addition to the standard Jinja syntax, the following objects are available (some explained in details after):

filters:

  • slugify: make the string url-compatible

  • mksiteabs: Convert the given site-relative url to absolute.

  • mksiterel: make an internal link relative to the site root

  • mkpagerel: make an internal link relative to the current page

  • prettylink: remove the ".html" or "index.html" of internal links

variables:

  • Page: the page instance being rendered.

  • Config: the global site config used.

  • Context: additional variables specific to this build.

  • Shelf: optional parent shelf the page belongs to (can be None).

  • ShelfLibrary: collection of all shelves the site has.

  • include_script_output: function to include the output of a python script.

script system

The jinja syntax is not enough and you wish some part of the template was procedurally generated ? You can use the script include system to run an arbitrary python script that generates html (or actually anything).

To create a script, create a standard python file next to the template (can actually be stored anywhere but you need to specify its path relative to the template it is used in). Inside, you only need to declare one mandatory function:

def generate(template_renderer: lxmsite.TemplateRenderer) -> str:
    # your implementation here

The function when executed will return the text that need to be included in the template. The only argument template_renderer is a copy of the instance that is responsible of rendering the template that the script was called from. It allows in theory to recursively render another jinja template from the script or use its attributes for whatever you might need.

To use a script inside a template you use the include_script_output variable that is actually a function to call with the script path (relative to the template):

<div>
    {{ include_script_output("script_name.py") }}
</div>

cross-referencing

How to link to other html pages or static content ?

First, reminder that all relative urls are relative to the page they are on. This mean that if you want to link to a resources based on its site root location, like .static/icon/icon.svg you will need to make it relative to the page instead. This is easily done using the custom jinja filter mkpagerel.

Example:

<img src="{{ ".static/icons/icon.svg"|mkpagerel }}">

If you need the opposite you can also use mksiterel to make an page-relative url; relative to the site root instead.

And if you ever need an absolute url you can use mksiteabs that will prepend the site url but only on publish.

Then when linking pages or content, you must link a file, never a directory. While once published work/myproject/ might resolve fine by the server, locally it will not and you will need to link work/myproject/index.html instead. However just because this make links uglier you can use prettylink that will shorten the links on publish; best of both worlds !

Rss feeds

When creating a shelf, an rss feed will automatically be generated from that shelf as long as a template is specified in the site-config using RSS_FEED_TEMPLATE.

The template is a regular jinja2 file that have access to the same filters as the page templates, but different variables which are:

  • URL_PATH: the url path of the feed file; relative to the site root

  • Config: the global site config used.

  • Shelf: the shelf object to generated the feed from

The generated feed can be accessed at {shelf url}/{shelf name}.rss.xml.

Search feature

Implemented through https://pagefind.app/

Optimize images

You can have image automatically optimized at build time by using opti files:

  • the file must be named exactly as the image it optimize with the additional .opti suffix.

    • example: /src/img.png > /src/img.png.opti

  • the file content follow the JSON file format syntax

  • JSON content is a shallow tree of pre-defined keys that are defined below. All keys are optional.

name

description

convert_mode

Optional Pillow conversion mode to conver to. Usually "RGB" to convert RGBA pngs.

target_format

name of a format supported by Pillow to write the optimized file as.

target_file_suffix

suffix for the optimized file written to disk.

format_kwargs

Pillow compatible kwargs for the given target_format.

max_dimensions

Optional maximum pixel dimensions the image must not be larger than while.