0cms

Meet 0cms

0cms (spelled zero-cms) is a static website generator. You can pronounce it “Occams” and think of it as Occam’s Razor (“Entities should not be multiplied without necessity”) for a website content management system.

0cms was developed and is maintained by Jared Henley

0cms is licenced GPLv3

Simple

Powerful

Respectful

0cms is built on the principle that the most maintainable code is the code you don’t write. 0cms does the least it possibly can to get your site to build. As a result, it has no build system – instead it uses Ninja.

0cms has no built-in code for blogs, feeds, or sitemaps. It ships with no templates or themes or other paraphernalia. This doesn’t mean that these things are impossible – they are totally possible, but you create and maintain them yourself. This also means that the maintenance schedule is yours: 0cms won’t unexpectedly break or change your site when you upgrade.

Get 0cms

It feels all wrong, but I am not versioning 0cms at this stage. You can always grab the source from bitbucket

Use 0cms

Let’s make a really minimal website. First install 0cms, or download it somewhere and put that directory in your $PATH.

Create a directory for your website. In that directory create another directory named src, and a file 0cms-config. In the src directory create two files: index.html.0p and 0cms-template. Now populate the files with the following:

0cms-config:

source = "src"
target = "html"

0cms-template:

Template("""<!DOCTYPE html>
<html>
<head>
<title>{title}</title>
</head>
<body>
{document}
</body>
</html>
""")

index.html:

Replacements(
    title="My first 0cms website"
)

WebPage(Markdown("""
# Hello World

I've just created my first 0cms website!
"""))

Now open a shell in your website directory and run

% 0cms

0cms should build your site, and you can examine the output in the html directory.

Learn 0cms

Don’t want to read the manual? Take a look at some working examples.

In the Use 0cms section above you learned how to set up an 0cms website. But what else can you put in these files?

0cms-config

0cms-config is Python code. 0cms can make use of the following variables in this file – the defaults are shown below:

site_source = 'src'
site_target = 'html'
ignore_source = []
ignore_target = []

0cms uses a foolproof method to determine the files that appear in the built website: everything in the source website. So you structure your source the way you want, and that structure is mirrored in the target. There are no magic targets that get built automatically, and no source files that disappear – with one exception: 0cms-template files are built to 0cms-template.built files in the source tree, and neither appears in the target tree.

site_source (string)
the directory relative to the 0cms-config file that contains the 0cms source files for the website.
site_target (string)
the directory relative to the 0cms-config file that the website is to be generated into.
ignore_source (list of glob patterns)
paths in the source directory to completely ignore.
ignore_target (list of glob patterns)
paths in the target directory to completely ignore. 0cms removes files from the target that are not in the source; this variable lists patterns that should not be removed. This could be used for git files ([".git", ".git/*"]) that are necessary if you’re publishing to github pages, for example.

ignore_source and ignore_target are used as arguments to fnmatch – see the documentation for fnmatch for more information about that.

Remember that 0cms-config is just Python code: you can put anything in there. If you need some fancy logic to help determine what to put into these variabes, write it. If you need to adjust sys.path as I do, do it. (Note that if you adjust sys.path, also change the environment variable $PYTHONPATH if you expect the changes to be accessible to each page as it’s built).

0cms-template

0cms-template is Python code. You do need to call the function Template() to define your template. There won’t be much output if you don’t.

Template(template)

You supply a string to Template(). This string may contain replacement fields that are processed using Python’s standard str.format() function. A template should contain a {document} field. This is replaced by the content defined in the WebPage() function for each page. You may also use as many other fields as you want. If you make multiple calls to this function, only the last is applied.

Replacements()

Replacements() takes a keyword argument list of replacement fields and their values. It is documented in more detail below.

Class methods for defaults

At the moment, only the Markdown class has any of these.

Markdown.default_extensions(extensions)
extensions is a list of strings, each one being the name of a python-markdown extension that you want to use in all pages affected by this template. See the python-markdown documentation for more information.

Template cascading / inheritance

Templates are applied to the directory they are in, and to all subdirectories. You may define templates in subdirectories too. These work in a very simple way: they are simply concatenated together. Thus a subdirectory template may overwrite the variables of its parent, define a new Template(), etc.

GetTemplate()

This function returns the currently-defined template. It exists so that a subdirectory’s 0cms-template file may easily access the template applied by its parent. This could be done in other ways, but it helps to keep template files clean in simple cases.

.0p, .0s and .0e files

There is a tiny bit of “magic” here. First of all, the .0? extension is removed from the target when these files when they are built.

.0p (page) file
These files depend only upon themselves and their directory’s template.
.0s (summary) file
These files depend upon themselves, their directory’s template, and any number of other files specified by a call to the Dependencies() function. They are designed for things like feeds and sitemaps.
.0e (external) file
These files are always rebuilt every time 0cms is run. The .0e extension is used to indicate that the file depends on files or other information outside the 0cms website tree. You could use this to list files somewhere else on the system, or to query a database or get server status, etc.
.html.0? files
Source files that target .html are treated differently from every other kind of target extension. It is expected that the WebPage() function will be called to trigger the application of a template, creation of some predefined replacements, replacement of all replacement fields, and output of html.
.*.0? files
Source files that target something other than html are expected to call the Data() function with the data that is to be written to the target file.

All other files

All other files are simply copied to the target directory.

Functions and Classes available to 0cms-template and *.0? files

Markdown class

You use this to use Markdown-formatted text in a page. For example:

WebPage(Markdown("""# Hello World"""))

Use Markdown.default_extensions() to specify the python-markdown extensions you want to use. When this is used in a template, the extensions apply to all pages using that template. When used in a page, they only apply to that page. For example:

Markdown.default_extensions(['smarty', 'fenced_code'])

Markdown extensions can also be specified each time you create a Markdown object. For example:

Markdown("""
# Here’s some code

```
#!/usr/bin/python

print("Hello World")
```
""", extensions=['fenced_code'], overwrite=True)

The overwrite argument defaults to False, meaning that the extensions in the list are added to the defaults. If you supply overwrite=True, the extensions in the list are the only ones that are used.

Asciidoc class

You use this to use Asciidoc-formatted text in a page. For example:

WebPage(Asciidoc("""# Hello World"""))

Asciidoc is called with the following options:

asciidoc -b html5 -s

This generates html5 output and only outputs a HTML fragment, not a complete page.

HtmlDiv class

You use this to insert a HTML div element, as these are not well-supported by many markup languages. Use it like this:

HtmlDiv(Markdown("""# Hello World"""), htmlid="myid", htmlclass="myclass")

This usage allows your page source to mirror the generated HTML, where content is wrapped within a div tag. The arguments htmlid and htmlclass are optional, and allow you to add id and class attributes to the div tag.

You may use any number of content arguments to HtmlDiv.

HtmlImage class

You use this to insert a HTML image tag, as these are not well-supported by many markup languages. Use it like this:

HtmlImage("path/to/image.jpg", alt="Picture of a tree", htmlid="myid", htmlclass="myclass")

The alt text is optional. The arguments htmlid and htmlclass are optional, and allow you to add id and class attributes to the image tag.

It’s not possible to insert images directly into headings or lists, etc. in many markup languages. However, you can assign images to replacement fields, and use the field in the heading or list like this:

Replacements(
    img=HtmlImage("path/to/image.jpg")
)

WebPage(Markdown("""# See, a heading with an image {img}"""))

Html class

You can use this to insert plain HTML, if you ever need to do that.

DirListing class

This class can be used in .0s files for for generating feeds and the like. Use it like this:

d = DirListing(pattern='*.html.0p')

DirListing returns a tuple of SiteItems. SiteItem has a .source() method that returns the complete path to the source file, and a .url() method that returns the absolute URL of the page, minus the protocol and domain parts.

Dependencies() function

You use this function do specify the dependencies of a .0s file. You pass it a tuple of the full paths of files that the page depends on.

Replacements() function

You use this function like this:

Replacements(
    title="My cool page title"
    description="This will turn up in page summaries reported by search engines"
)

Pass in any replacement fields that you’ve used in your template or page as keyword arguments.

Two replacements are supplied for you by 0cms: now (the build time) and mtime (the last modification time of the source file). If you want these formatted in a particular way, define strftime format strings in your replacements, named now_format to format now, and mtime_format to format mtime.

WebPage() function

This function declares the web page that will be created. Pass in any number of arguments. They should be strings or something that can be converted to strings (eg. Markdown, Asciidoc, HtmlDiv etc).

Data() function

This function is used by non-html pages to set up the content that will be written to the target file.

Template() function

This function is used to set up the template. It takes one argument, that is a string.

GetTemplate() function

This function returns the template that was specified by Template(). It is intended to be used by templates in subdirectories that wish to modify the parent template.

source_path() function

This is only usable within a page. Returns the path to the source file being processed.

template_path() function

This is only usable within a page. Returns the path to the template file being applied.

target_path() function

This is only usable within a page. Returns the path to the target file that will be written.

source_root() function

This is only usable within a page. Returns the path to the root of the source tree for the website.

Importer class

Use this class like:

page = Importer(path_to_source_file)

This provides you access to parts of a page, from some other page (for example, creating a feed that needs to know the title and description of each page). The following methods are available from an imported page object:

replacements() function

Returns the replacements that have been specified by a call to Replacements().

get_replacement() function

Returns a particular replacement:

title = page.get_replacement('title')

dependencies() function

Returns the dependecies that have been specified by a call to Dependencies().

html() function

Returns the html for the page, doing conversions on all objects that have been passed to the WebPage() function.

data() function

Returns the data for the page that has been passed to the Data() function.

template() function

Returns the template that was declared by a call to Template() in a template file.

Build functions

Items passed to WebPage(), page components such as Markdown(), and also Template() may also be callables. This allows you to pass in a function so that heavy processing can be done only once instead of every time the page is imported, and it can be deferred to the moment when the html is actually required.

The 0build program

Calling 0cms produces a build.ninja file that is used by ninja to coordinate the build. If you examine this file, you will find a program named 0build. 0build is a small program that is used as follows:

% 0build <source_file> <template_file> [other dependencies...] <target file>

You will probably never need to use 0build directly.

Extend

Do you need to write some helper classes to do things like

or some other repetitive thing?

The way I handle this is to first add the following to 0cms-config:

# Put the pymodules directory in the python path
import os
import sys
modulepath = os.path.join(os.getcwd(), "pymodules/")
sys.path.insert(0, modulepath)
try:
    pypath = [os.environ['PYTHONPATH'],]
except KeyError:
    pypath = []
pypath.append(modulepath)
os.environ['PYTHONPATH'] = ':'.join(pypath)

This adds another directory, at the same level as src and html, to your python path and allows you to import and run python code from there.

Now you can write code to include in your pages.

The basic template to follow is this:

from zcms.zcms import *

class ActivityList(PageComponent):
    def __init__(self, *content):
        # Your initialisation code here.
        super().__init__(content)

    def __str__(self):
        super().__str__()
        return("The HTML that you want to turn up in your web page")

The from zcms.zcms import * is only necessary if you intend to inherit from classes that ship with 0cms.

And that about covers it. If you need more detail, check out the 0cms source code, or the live examples at

Collaborate

I’ve set up an issue tracker on bitbucket