Welcome to GeekCMS’s documentation!

GeekCMS is a lightweight framework for static site development, with properties as follow:

  • Well defined protocols to facilitate the process of development and deployment.
  • Plugin-base and pipe-and-filter architecture.
  • Strict rules of files organization.
  • Implemented in Python3.

Execution Model

From the view of theme(in short, theme = theme settings + plugins) developers, GeekCMS is a framework with protocols and helpful libraries, to:

  • control the behavior of each individual plugin.
  • customize the calling sequence of multiple plugins.

For users, GeekCMS is a theme driven tool, means that without plugins, GeekCMS can do nothing!

There are two kinds of plugin execution procedures defined in GeekCMS:

  • Default procedure: Procedure consists of several runtime components. A runtime component is an area to place plugins to be executed.
  • Extended procedure: Besides, there are kinds of behaviors can not be classified into components, for instance, automatically pushing static pages to a remote git repo. Such behaviors could be implemented as independent extended procedures, and triggered by CLI command by user.

Default Procedure

Default procedure is divided into nine runtime components:

  1. pre_load, in_load, post_load
  2. pre_process, in_process, post_process
  3. pre_write, in_write, post_write

which would be sequentially executed by GeekCMS. Each runtime component could contain zero or more plugins, details of that would be covered later.

The components can be classified into three layers, load/process/write. Layers are exectured in order, load –> process –> write, which is simple and intuitive. Notice that the distinction of components is vague, for instance, a plugin transforming markdown to html can be placed in in_load, post_load or even post_write, depending on developers understanding of components’ semantics. By dividing a layer into three components, theme developer could well control the sequence of plugin execution. Plugin execution order within a layer is a little bit complicated, and would be introduce later.

Extended Procedure

GeekCMS allow developer to define extended procedure for special usage. For more information: Extended Procedure Related.

Utilities Classes

All following classes are defined in geekcms.utils.

Data Share

class ShareData
classmethod get(cls, search_key)

search_key is a key for search Share section of theme and project settings.

search_key could be in the pattern of ‘theme_name.key’. In such pattern, GeekCMS would lookup the settings file of theme theme_name for key. If such key do not exist in the settings file of theme theme_name, None would be return. If found, a string bound with key would be return.

Besides, search_key could be presented in the pattern of ‘key’(with no ‘.’ in the search_key). In such case, GeekCMS would lookup all settings files of themes and project, the first match value would be return. If not found, None would be return.

Before executing default and extended procedures, GeekCMS would parse all settings file of project and registered themes, and ShareData would load up all the key-value pairs in Share section of the settings files. Since ShareData.get judges the pattern of search_key by finding a ‘dot’, It’s not a good idea to defines a key along with a ‘dot’.

Path Resolve

class PathResolver
project_path

The path of project.

classmethod set_project_path(cls, path)

Set the project_path with path.

classmethod inputs(cls, *, ensure_exist=False)

Return the path of inputs directory of project. If ensure_exist is True and the directory of inputs do not exist, then an empty directory would be created.

classmethod outputs(cls, *, ensure_exist=False)

Return the path of outputs directory of project. If ensure_exist is True and the directory of outputs do not exist, then an empty directory would be created.

classmethod themes(cls, *, ensure_exist=False)

Return the path of themes directory of project. If ensure_exist is True and the directory of themes do not exist, then an empty directory would be created.

classmethod states(cls, *, ensure_exist=False)

Return the path of states directory of project. If ensure_exist is True and the directory of states do not exist, then an empty directory would be created.

classmethod theme_state(cls, theme_name, *, ensure_exist=False)

Return the path of directory contains state of theme. Such path is generated by joining cls.states() and theme_name. If ensure_exist is True and the directory of theme’s state do not exist, then an empty directory would be created.

classmethod theme_dir(cls, theme_name, *, ensure_exist=False)

Return the path of directory contains code of theme. Such path is generated by joining cls.themes() and theme_name. If ensure_exist is True and the directory of theme’s dir do not exist, then an empty directory would be created.

PathResolver can be helpful for development, with which developer could easily get the path of specific directory, and create specific directory if such directory does not exist.

Project Organization

Structure Of Project

GeekCMS would maintained a directory containing all files required to generate a website, such directory is organized as a projcet.

Sturcture of a project is as follow:

example_project/
                themes/
                    ...
                states/
                    ...
                inputs/
                    ...
                outputs/
                    ...
                settings

Brief explanations of above file and direcroties:

themes

A directory where all the code of theme exists.

states

A directory for themes to place its intermediate data.

inputs

A direcroty contains all input files.

outputs

A directory contains all generated files.

settings

A text file named project settings, in which defines registered themes and global shared data.

The names of above file and direcroties is hardcoded in GeekCMS.

Structure Of Theme

A theme should be organized as a python package, structure is as follow:

example_project/
                themes/
                       theme_A/
                           __init__.py
                           settings       # theme settings
                           ...
                       theme_B/
                           __init__.py
                           settings       # theme settings
                           ...
                states/
                    ...
                inputs/
                    ...
                outputs/
                    ...
                settings                  # project settings

All themes should be placed in themes directory. As you can see, there is settings file exists in each theme package. Such settings file is named theme settings.

Settings File

GeekCMS’s behavior is guided by project settings and theme settings. Format of settings is described in configparser [1].

projcet settings should defines a RegisterTheme(case-sensitive) section. Names of themes(the name of theme’s directory) to be loaded by GeekCMS should be seperated by whitespaces and set as the value of themes key(case-insensitive). Example is as follow:

# project settings.
[RegisterTheme]
themes: simple git_upload

where directories simple and git_upload are registered.

themes settings should defines a RegisterPlugin(case-sensitive) section. Keys in the section should be one of [pre_load, in_load, post_load, pre_process, in_process, post_process, pre_write, in_write, post_write] and [cli_extend]. All avaliable keys except cli_extend is discussed in Default Procedure, and cli_extend is a key for registering extended procedure. An example for demonstration:

# settings of simple.
[RegisterPlugin]

in_load:
        load_inputs_static
        load_article
        load_about
        load_index
        load_theme_static

in_process:
        md_to_html << gen_article_page

post_process:
        gen_about_page
        gen_index_page
        gen_time_line_page
        gen_archive_page

pre_write:
        clean

in_write:
        write_static
        write_page

post_write:
        cname

# settings of git_upload
[RegisterPlugin]

cli_extend: GitUploader

Both projcet settings and theme settings can define a Share section. Key-value pairs defined in Share section can be retrived by ShareData. An example for demonstration:

# settings of simple.
[Share]
# special pages
index_page: index.html
time_line_page: speical/time_line.html
about_page: speical/about.html
archive_page: speical/archive.html

where the value of index_page can be retrived by ShareData.get('simple.index_page').

Plugin Registration

Execution order of plugins within the same runtime component is defined by plugin registration syntax. The syntax is:

runtime_component    ::=  component_name (':' | '=') [NEWLINE] plugin_relation*
plugin_relation      ::=  binary_relation_expr | unary_relation_expr NEWLINE
binary_relation_expr ::=  plugin_name (left_relation | right_relation) plugin_name
unary_plugin_expr    ::=  plugin_name [left_relation]
                          | [right_relation] plugin_name
left_relation        ::=  '<<' [decimalinteger]
right_relation       ::=  [decimalinteger] '>>'
component_name       ::=  identifier
plugin_name          ::=  identifier

where identifier, decimalinteger and NEWLINE are corresponding to the definitions in Python Lexical Analysis [2].

Semantics:

  1. pre_load: my_loader

    register plugin my_loader to component pre_load.

  2. pre_load: my_loader << my_filter

    register plugins my_loader and my_filter to component pre_load, with my_loader being executed before my_filter.

  3. pre_load: my_filter >> my_loader

    has the same meaning as pre_load: my_loader << my_filter.

  4. pre_load: loader_a <<0 loader_b NEWLINE loader_c <<1 loader_b

    the execution order would be loader_c –> loader_a –> loader_b. << is equivalent to <<0, and << decimalinteger is equivalent to decimalinteger >>.

  5. pre_load: my_loader <<

    means my_loader would be executed before the other plugins within a component, unless another relation such as anther_loader <<1 is established.

  6. pre_load: >> my_filter

    reverse meaning of pre_load: my_loader <<.

Notice that the plugin_name should be presented in the pattern of ‘theme_name.plugin_name’. ‘theme_name.’ can be omitted, as presented in above example, if plugin_name points to a plugin exists in current theme directory.

GeekCMS would automatically import the __init__ module of registered theme packages. Besides writing a theme settings, developer should import the module(s) that defines plugin(s) in __init__. An example is given for demonstration:

# ../git_upload/__init__.py

# necessary!
from . import plugin


# ../git_upload/plugin.py

"""
Usage:
    geekcms gitupload

"""

from datetime import datetime
import subprocess
import os

from geekcms.protocol import BaseExtendedProcedure
from geekcms.utils import PathResolver


class CWDContextManager:

    def __enter__(self):
        os.chdir(PathResolver.outputs())

    def __exit__(self, *args, **kwargs):
        os.chdir(PathResolver.project_path)


class GitUploader(BaseExtendedProcedure):

    def get_command_and_explanation(self):
        return ('gitupload',
                'Automatically commit and push all files of outputs.')

    def get_doc(self):
        return __doc__

    def run(self, args):
        commit_text = 'GeekCMS Update, {}'.format(
            datetime.now().strftime('%c'),
        )
        commands = [
            ['git', 'add', '--all', '.'],
            ['git', 'commit', '-m', commit_text],
            ['git', 'push'],
        ]
        with CWDContextManager():
            for command in commands:
                subprocess.check_call(command)

GeekCMS would automatically loaded GitUploader in above example.

[1]http://docs.python.org/3/library/configparser.html
[2]http://docs.python.org/3/reference/lexical_analysis.html

Deploy And Download Themes

Command Line Interface

GeekCMS provides a friendly CLI interface of usage. CLI of GeekCMS is implemented by using docopt [1].

For code sharing, developer could package their codes as a template. A template is organized in as a project.

Download

If you type geekcms in your prompt and current working directory is not a project, then the shell would presents:

$ geekcms
Usage:
    geekcms startproject <template_name>

Entering startproject option with <template_name> would automatically download a directory with the name of <template_name>, which should be an empty project, from GeekCMS-Themes [2] to current working directory:

$ ls
$ geekcms startproject simple
A    simple/inputs
A    simple/inputs/about
A    simple/inputs/about/about.md
A    simple/inputs/article
A    simple/inputs/article/test
A    simple/inputs/article/test/codetest.md
A    simple/inputs/article/test/longlong.md
A    simple/inputs/article/test/top-level.md
A    simple/inputs/index
A    simple/inputs/index/welcome.md
A    simple/inputs/static
A    simple/inputs/static/delete it.
A    simple/settings
A    simple/themes
A    simple/themes/git_upload
A    simple/themes/git_upload/__init__.py
A    simple/themes/git_upload/plugin.py
A    simple/themes/git_upload/settings
A    simple/themes/simple
A    simple/themes/simple/__init__.py
A    simple/themes/simple/assets.py
A    simple/themes/simple/load.py
A    simple/themes/simple/process.py
A    simple/themes/simple/settings
A    simple/themes/simple/static
A    simple/themes/simple/static/css
A    simple/themes/simple/static/css/github.css
A    simple/themes/simple/templates
A    simple/themes/simple/templates/archive.html
A    simple/themes/simple/templates/article.html
A    simple/themes/simple/templates/base.html
A    simple/themes/simple/templates/time_line.html
A    simple/themes/simple/utils.py
A    simple/themes/simple/write.py
Checked out revision 15.
$ ls
simple

After downloading such directory, GeekCMS would ensure project settings and themes, states, inputs, outputs exists, so developer should not consider pushing an empty directory to git repo.

Deploy

If you want to share your code with the others, just push your code to GeekCMS-Themes [2].

[1]https://github.com/docopt/docopt
[2](1, 2) https://github.com/haoxun/GeekCMS-Themes

Example Of Development

A simple [1] template is developed for demonstraction. The template simply renders markdown files and generates a static site.

[1]https://github.com/haoxun/GeekCMS-Themes/tree/master/simple