Go through HACKING docs with a fine tooth comb.

This commit is contained in:
Brit Butler 2014-05-02 16:25:39 -04:00
parent 4ae9634c52
commit 96d85c8e69
2 changed files with 112 additions and 130 deletions

View file

@ -7,6 +7,22 @@ the idea to each complete their half-dreamed wordpress replacement in
a week. Though it has evolved considerably since it's inception, like a week. Though it has evolved considerably since it's inception, like
any software some mess remains. any software some mess remains.
## Overall Structure
Conceptually, coleslaw processes a blog as follows:
1. Coleslaw loads the user's config, then reads the blog repo loading
any `.post` files or other content. CONTENT and INDEX objects are
created from those files.
2. The CONTENT and INDEX objects are then fed to the templating engine
to produce HTML files in a config-specified staging directory,
usually under `/tmp`.
3. A deploy method (possibly customized via plugins) is called with the
staging directory. It does whatever work is needed to make the
generated HTML files (and any static content) visible to the web.
## Core Concepts ## Core Concepts
### Data and Deployment ### Data and Deployment
@ -14,8 +30,7 @@ any software some mess remains.
**Coleslaw** is pretty fundamentally tied to the idea of git as both a **Coleslaw** is pretty fundamentally tied to the idea of git as both a
backing data store and a deployment method (via `git push`). The backing data store and a deployment method (via `git push`). The
consequence is that you need a bare repo somewhere with a post-recieve consequence is that you need a bare repo somewhere with a post-recieve
hook. That post-recieve hook hook. That post-recieve hook ([example][post_receive_hook])
([example](https://github.com/redline6561/coleslaw/blob/master/examples/example.post-receive))
will checkout the repo to a **$TMPDIR** and call `(coleslaw:main $TMPDIR)`. will checkout the repo to a **$TMPDIR** and call `(coleslaw:main $TMPDIR)`.
It is then coleslaw's job to load all of your content, your config and It is then coleslaw's job to load all of your content, your config and
@ -25,6 +40,60 @@ symlink. It is assumed a web server is set up to serve from that
symlink. However, there are plugins for deploying to Heroku, S3, and symlink. However, there are plugins for deploying to Heroku, S3, and
Github Pages. Github Pages.
### Plugins
**Coleslaw** strongly encourages extending functionality via plugins.
The Plugin API is well-documented and flexible enough for many use
cases. Do check the [API docs][api_docs] when contemplating a new
feature and see if a plugin would be appropriate.
### Templates and Theming
User configs are allowed to specify a theme. A theme consists of a
directory under "themes/" containing css, images, and at least
3 templates: Base, Index, and Post.
**Coleslaw** uses [cl-closure-template][closure_template]
exclusively for templating. **cl-closure-template** is a well
documented CL implementation of Google's Closure Templates. Each
template file should contain a namespace like
`coleslaw.theme.theme-name`.
Each template creates a lisp function in the theme's package when
loaded. These functions take a property list (or plist) as an argument
and return rendered HTML. **Coleslaw** defines a helper called
`theme-fn` for easy access to the template functions. Additionally,
there are RSS, ATOM, and sitemap templates *coleslaw* uses automatically.
No need for individual themes to reimplement a standard, after all!
### The Lifecycle of a Page
- `(load-content)`
A page starts, obviously, with a file. When *coleslaw* loads your
content, it iterates over a list of content types (i.e. subclasses of
CONTENT). For each content type, it iterates over all files in the
repo with a matching extension, e.g. ".post" for POSTs. Objects of the
appropriate class are created from each matching file and inserted
into the an in-memory data store. Then the INDEXes are created from
the loaded content and added to the data store.
- `(compile-blog dir)`
Compilation starts by ensuring the staging directory (`/tmp/coleslaw/`
by default) exists, cd'ing there, and copying over any necessary theme
assets. Then *coleslaw* iterates over all the content types and index
classes, rendering all of their instances and writing the HTML to disk.
After this, an 'index.html' symlink is created pointing to the first
reverse-chronological index.
- `(deploy dir)`
Finally, we move the staging directory to a timestamped path under the
the config's `:deploy-dir`, delete the directory pointed to by the old
'.prev' symlink, point '.curr' at '.prev', and point '.curr' at our
freshly built site.
### Blogs vs Sites ### Blogs vs Sites
**Coleslaw** is blogware. When I designed it, I only cared that it **Coleslaw** is blogware. When I designed it, I only cared that it
@ -35,12 +104,13 @@ collection of POSTs or other content. An INDEX really only serves to
group a set of content objects on a page, it isn't content itself. group a set of content objects on a page, it isn't content itself.
This isn't ideal if you're looking for a full-on static site This isn't ideal if you're looking for a full-on static site
generator. Content Types were added in 0.8 as a step towards making generator. Thankfully, Content Types were added in 0.8 as a step
*coleslaw* suitable for more use cases but still have some towards making *coleslaw* suitable for more use cases. Any subclass of
limitations. Any subclass of CONTENT that implements the *document CONTENT that implements the *document protocol* counts as a content
protocol* counts as a content type. However, only POSTs are currently type. However, only POSTs are currently included in the basic INDEXes
included on INDEXes since their isn't yet a formal relationship to since there isn't yet a formal relationship to determine which content
determine what content types should be included on which indexes. types should be included on which indexes. Users may easily implement
their own dedicated INDEX for new Content Types.
### The Document Protocol ### The Document Protocol
@ -55,27 +125,43 @@ It consists of 2 "class" methods, 2 instance methods, and an invariant.
There are also 4 helper functions provided that should prove useful in There are also 4 helper functions provided that should prove useful in
implementing new content types. implementing new content types.
**Class Methods**: **Class Methods**:
Since Common Lisp doesn't have explicit support for class methods, we Class Methods don't *really* exist in Common Lisp, as methods are
implement them by eql-specializing on the class, e.g. defined on generic functions and not on the class itself. But since
it's useful to think about a Class as being responsible for its
instances in the case of a blog, we implement class methods by
eql-specializing on the class, e.g.
```lisp ```lisp
(defmethod foo ((doc-type (eql (find-class 'bar)))) (defmethod foo ((doc-type (eql (find-class 'bar))))
... ) ... )
``` ```
- `discover`: Create instances for documents of the class and put them in - `discover`: Create instances for documents of the class and put them
in-memory database with `add-document`. If your class is a subclass of in the in-memory database with `add-document`.
CONTENT, there is a default method for this.
- `publish`: Iterate over all objects of the class For CONTENT, this means checking the blog repo for any files with a
matching extension and loading them from disk. If your class is a
subclass of CONTENT, it inherits a pleasant default method for this.
For INDEXes, this means iterating over any relevant CONTENT in the
database, and creating INDEXes in the database that include that
content.
- `publish`: Iterate over all instances of the class, rendering each
one to HTML and writing it out to the staging directory on disk.
**Instance Methods**: **Instance Methods**:
- `page-url`: Generate a unique, relative path for the object on the site - `page-url`: Generate a relative path for the object on the site,
sans file extension. An :around method adds that later. The `slug` slot usually sans file extension. If there is no extension, an :around
on the object is conventionally used to hold a portion of the unique method adds "html" later. The `slug` slot on the instance is
identifier. i.e. `(format nil "posts/~a" (content-slug object))`. conventionally used to hold a portion of the path that corresponds
to a unique Primary Key or Object ID.
- `render`: A method that calls the appropriate template with `theme-fn`, - `render`: A method that calls the appropriate template with `theme-fn`,
passing it any needed arguments and returning rendered HTML. passing it any needed arguments and returning rendered HTML.
@ -92,12 +178,15 @@ implement them by eql-specializing on the class, e.g.
database. It will error if the `page-url` of the document is not database. It will error if the `page-url` of the document is not
unique. Such a hash collision represents content on the site being unique. Such a hash collision represents content on the site being
shadowed/overwritten. This should be used in your `discover` method. shadowed/overwritten. This should be used in your `discover` method.
- `write-document`: Write the document out to disk as HTML. It takes - `write-document`: Write the document out to disk as HTML. It takes
an optional template name and render-args to pass to the template. an optional template name and render-args to pass to the template.
This should be used in your `publish` method. This should be used in your `publish` method.
- `find-all`: Return a list of all documents of the requested class. - `find-all`: Return a list of all documents of the requested class.
This is often used in the `publish` method to iterate over documents This is often used in the `publish` method to iterate over documents
of a given type. of a given type.
- `purge-all`: Remove all instances of the requested class from the DB. - `purge-all`: Remove all instances of the requested class from the DB.
This is primarily used at the REPL or for debugging but it is also This is primarily used at the REPL or for debugging but it is also
used in a `:before` method on `discover` to keep it idempotent. used in a `:before` method on `discover` to keep it idempotent.
@ -109,62 +198,7 @@ NUMERIC-INDEX, FEED, and TAG-FEED. Respectively, they support
grouping content by tags, publishing date, and reverse chronological grouping content by tags, publishing date, and reverse chronological
order. Feeds exist to special case RSS and ATOM generation. order. Feeds exist to special case RSS and ATOM generation.
Currently, there is only 1 content type: POST, for blog entries. Currently, there is only 1 content type: POST, for blog entries.
PAGE, a content type for static page support, is available as a plugin.
### Templates and Theming
User configs are allowed to specify a theme, otherwise the default is
used. A theme consists of a directory under "themes/" containing css,
images, and at least 3 templates: Base, Index, and Post.
**Coleslaw** uses
[cl-closure-template](https://github.com/archimag/cl-closure-template)
exclusively for templating. **cl-closure-template** is a well
documented CL implementation of Google's Closure Templates. Each
template file should contain a namespace like
`coleslaw.theme.theme-name`.
Each template creates a lisp function in the theme's package when
loaded. These functions take a property list (or plist) as an argument
and return rendered HTML. **Coleslaw** defines a helper called
`theme-fn` for easy access to the template functions. Additionally,
there are RSS, ATOM, and sitemap templates *coleslaw* uses automatically.
No need for individual themes to reimplement a standard, after all!
### Plugins
**Coleslaw** also encourages extending functionality via plugins. The Plugin
API is well-documented and flexible enough for many use cases. Do check the
[API docs](https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md)
when contemplating a new feature and see if a plugin would be appropriate.
### The Lifecycle of a Page
- `(load-content)`
A page starts, obviously, with a file. When *coleslaw* loads your
content, it iterates over a list of content types (i.e. subclasses of
CONTENT). For each content type, it iterates over all files in the
repo with a matching extension, e.g. ".post" for POSTs. Objects of the
appropriate class are created from each matching file and inserted
into the an in-memory data store. Then the INDEXes are created by
iterating over the POSTs and inserted into the data store.
- `(compile-blog dir)`
Compilation starts by ensuring the staging directory (`/tmp/coleslaw/`
by default) exists, cd'ing there, and copying over any necessary theme
assets. Then *coleslaw* iterates over the content types and index
classes, calling the `publish` method on each one. Publish iterates
over the class instances, rendering each one and writing the result
out to disk with `write-file`. After this, an 'index.html' symlink is
created to point to the first index.
- `(deploy dir)`
Finally, we move the staging directory to a timestamped path under the
the config's `:deploy-dir`, delete the directory pointed to by the old
'.prev' symlink, point '.curr' at '.prev', and point '.curr' at our
freshly built site.
## Areas for Improvement ## Areas for Improvement
@ -220,3 +254,7 @@ reasonable concern. Also, every numbered INDEX would have to be
regenerated along with any tag or month indexes matching the regenerated along with any tag or month indexes matching the
modified files. If incremental compilation is a goal, simply modified files. If incremental compilation is a goal, simply
disabling the indexes may be appropriate for certain users. disabling the indexes may be appropriate for certain users.
[post_receive_hook]: https://github.com/redline6561/coleslaw/blob/master/examples/example.post-receive
[closure_template]: https://github.com/archimag/cl-closure-template
[api_docs]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md

View file

@ -1,56 +0,0 @@
## Overall Structure
Conceptually, coleslaw processes a blog as follows:
1. Coleslaw loads the user's config, then reads a directory containing
`.post` files and processes them into POST and INDEX objects.
2. The POST and INDEX objects are then fed to the templating engine
to produce HTML files.
3. A deploy method is called (possibly customized with plugins) to make
the resulting HTML files (and any static content) visible to the web.
## What files are generated anyway?
Before we dive into other details, it is useful to know the directory
structure of the generated content, so you know how the relative path
different content resides at.
The blog's toplevel looks like this:
```
index.html
posts/
date/
tag/
css/
static/ (optional)
```
### index.html
This file is the blog homepage, as you'd expect. It contains a list of
the most recent posts and has links to the different archives.
### posts directory
This directory contains an `.html` file per post. The name of the file
is the post's `slug`.
### date directory
This directory contains an `.html` file per month, for each month with
published content. The name of the file is of the form `yyyy-mm.html`.
### tag directory
This directory contains an `.html` file per tag, each containing all
posts with that tag. The name of the file is the tag's `slug`.
### css directory
This directory is a copy of the `css/` folder of the theme.
### static directory (optional)
This directory is a copy of the `static/` directory of the blog's git repo.