Stapy

Static Site Generator

Documentation

EnglishFrench

Stapy is a Static Site Generator. It works with Python without any additional package.

Download Source

A question? Contact me!

Requirements

Requires Python 3.5 or newer on any operating system.

Installation

Create a project directory anywhere and download the last release from the Stapy repository.

mkdir stapy
cd stapy
wget https://codeberg.org/magentix/stapy/archive/last.tar.gz
tar zxvf last.tar.gz --strip 1
rm last.tar.gz

HTTP server

Run standalone HTTP server:

python3 server.py

On Windows, double-click on the server.py file (Python required, easily install from Microsoft Store if needed).

Then access the URL http://localhost:1985

Environments

Static files are generated in the web directory. This directory contains all the necessary environment directories (devel, prod...).

For the production, add a prod directory in the web directory. It will contain all pages and files you need to deploy (html, css, js, images...).

After you add a new environment, you must restart the server.

Route

When a page is open in the browser, the server search a json file in source/pages directory. The name of the json file is the same as the URL path. Examples:

URL Path Json file
/ index.html.json
/hello.html hello.html.json
/hello/world.html hello/world.html.json
/hello/world/ hello/world/index.html.json

If the json file does not exist, a 404 error is sent.

Configuration

The json file contains all the data required for generate the page:

{
  "title": "Page title",
  "description": "Page description",
  "template": "template/default.html",
  "content": "content/index.html"
}

The template key is required.

Set the environment variables with the environment suffix:

{
  "url.local": "http://localhost:1985/",
  "url.prod": "https://www.example.com/"
}

The environment suffix must have the same name as your environment directory. For local rendering, the suffix is always "local".

A variable can have a default value:

{
  "my_text": "All environments display this text",
  "my_text.local": "Except the local with this"
}

Layout

A file named html.json in the source/layout directory is used for the default html page configuration. It will be merged with the page's json file. This is useful for a global configuration.

layout/html.json

{
  "title": "Default title",
  "template": "template/default.html"
}

pages/index.html.json

{
  "title": "Home title",
  "content": "content/index.html"
}

layout/html.json + pages/index.html.json

{
  "title": "Home title",
  "template": "template/default.html",
  "content": "content/index.html"
}

You can add layout file for any file extensions you need:

A common.json config file is available for all pages (all extensions).

Finally, it is possible to add a layout file for the page subdirectories:

JSON Weight Required
layout/common.json 1 N
layout/html.json 2 N
layout/blog/common.json 3 N
layout/blog/html.json 4 N
layout/blog/2022/html.json 5 N
pages/blog/2022/my-first-post.html.json 6 Y

Json data with higher weight will override and extend lower weights.

Tip: Add _page/ before the path to fetch json merged data.

http://localhost:1985/_page/index.html
http://localhost:1985/_page/hello.html

Template

The template file is the skeleton of the page:

<!DOCTYPE html>
<html lang="fr">
    <head>
        <meta charset="utf-8">
        <title>{{ meta_title }}</title>
        <meta name="description" content="{{ meta_description }}" />
        <link rel="stylesheet" href="{{ url }}css/style.css" />
    </head>
    <body>
        <header>
            {% header %}
        </header>
        <main>
            {% content %}
        </main>
        <footer>
            {% footer %}
        </footer>
    </body>
</html>

Expressions

Tip: set empty string for a content file variable in the json file to replace it with empty text.

Child block

Use specific json data for the child content with a + separator (spaces are required):

{% post + my-first-post.html %}

The json/my-first-post.html.json data will be accessible in the post template, with a $ before the var:

<a href="{{ url }}{{ $_path }}">{{ $post_title }}</a>

To loop json data with a query, use ~ as separator (spaces are required):

{% post ~ {key:value} {key:dir} {start:end} %}

Example:

{% post ~ tags:post date:desc 1:10 %}

This query retrieves the 10 first pages with post value in tags, sorted by date. The tags and date vars must be present in the json data of the pages:

{
  "date": "2022-01-01",
  "tags": ["post"]
}

The json data will be accessible in the post template, with a $ before the var.

Reserved variables

List

Variable Description Example
_path Cleaned page path blog/
_full_path Full page path blog/index.html
_env Environment name prod

Examples

{{ url }}{{ _path }}
<!-- https://www.example.com/blog/ -->
{{ url }}{{ _full_path }}
<!-- https://www.example.com/blog/index.html -->
<script type="text/javascript" src="{{ url }}js/init.{{ _env }}.js"></script>
<!-- https://www.example.com/js/init.prod.js -->

Resources

All necessary resources like js, css or images are copied from the source/assets directory in all environment directories (e.g. web/prod).

Static files

The final static HTML files and resources are added or refreshed when the pages are opened in the browser. When /hello.html is open, the hello.html file is automatically generated in all environment directories (e.g. web/prod).

Plugins

Get official Stapy plugins.

A plugin allows you to add custom code when rendering the page.

Event Description Variable Args
file_content_opened Update any file content (html, json, md...) File content (str) path, file_system
page_data_merged Update the current page json data Current page data (dict) path, file_system
file_copy_before Do something before copying the asset file File path to copy (str) source, destination, file_system
file_copy_after Do something after the asset file was copied File path copied (str) source, destination, file_system
before_content_parsed Update the page template content before parsing Page content (str) data, env, path, file_system
after_content_parsed Update the page template content after parsing Page content (str) data, env, path, file_system
child_content_data Update child data before content generation Child data (dict) key, env, path, file_system
child_content_query_result Update data result before content generation All child data (list) key, env, path, file_system

A plugin is a python script in the plugins directory. Plugin files are loaded in alphabetical order.

For example, to convert Markdown to HTML, add a file markdown.py in the plugins directory with the following content:

import markdown


def file_content_opened(content: str, args: dict) -> str:
    if (args['file_system']).get_file_extension(args['path']) != 'md':
        return content
    return markdown.markdown(content)

pip3 install markdown

You can now use Markdown syntax in your page content:

Hello World!

[Back to home]({{ url }})

Crawler

The website can be regenerated with a crawler. Stapy gives a python crawler script in the tools directory.

python3 tools/crawler.py {delete} {copy} {crawl}

Launch crawler with no option perform delete, copy and crawl:

python3 tools/crawler.py

DELETE 200
PUT 200
HEAD 200 /index.html
HEAD 200 /hello.html
...

Deployment

If you are using an automated deployment tool like Netlify, Cloudflare Pages, Vercel or Render, configure the tool to deploy the environment directory from the git repository (e.g. web/prod).

To deploy with Github Pages, create a docs directory with a symlink from web/prod to docs. Commit and push the docs directory, then configure Github Pages to deploy docs.

Themes

Simple and minimal themes for Stapy: