|
@@ -7,6 +7,22 @@ the idea to each complete their half-dreamed wordpress replacement in
|
7
|
7
|
a week. Though it has evolved considerably since it's inception, like
|
8
|
8
|
any software some mess remains.
|
9
|
9
|
|
|
10
|
+## Overall Structure
|
|
11
|
+
|
|
12
|
+Conceptually, coleslaw processes a blog as follows:
|
|
13
|
+
|
|
14
|
+1. Coleslaw loads the user's config, then reads the blog repo loading
|
|
15
|
+ any `.post` files or other content. CONTENT and INDEX objects are
|
|
16
|
+ created from those files.
|
|
17
|
+
|
|
18
|
+2. The CONTENT and INDEX objects are then fed to the templating engine
|
|
19
|
+ to produce HTML files in a config-specified staging directory,
|
|
20
|
+ usually under `/tmp`.
|
|
21
|
+
|
|
22
|
+3. A deploy method (possibly customized via plugins) is called with the
|
|
23
|
+ staging directory. It does whatever work is needed to make the
|
|
24
|
+ generated HTML files (and any static content) visible to the web.
|
|
25
|
+
|
10
|
26
|
## Core Concepts
|
11
|
27
|
|
12
|
28
|
### Data and Deployment
|
|
@@ -14,8 +30,7 @@ any software some mess remains.
|
14
|
30
|
**Coleslaw** is pretty fundamentally tied to the idea of git as both a
|
15
|
31
|
backing data store and a deployment method (via `git push`). The
|
16
|
32
|
consequence is that you need a bare repo somewhere with a post-recieve
|
17
|
|
-hook. That post-recieve hook
|
18
|
|
-([example](https://github.com/redline6561/coleslaw/blob/master/examples/example.post-receive))
|
|
33
|
+hook. That post-recieve hook ([example][post_receive_hook])
|
19
|
34
|
will checkout the repo to a **$TMPDIR** and call `(coleslaw:main $TMPDIR)`.
|
20
|
35
|
|
21
|
36
|
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
|
25
|
40
|
symlink. However, there are plugins for deploying to Heroku, S3, and
|
26
|
41
|
Github Pages.
|
27
|
42
|
|
|
43
|
+### Plugins
|
|
44
|
+
|
|
45
|
+**Coleslaw** strongly encourages extending functionality via plugins.
|
|
46
|
+The Plugin API is well-documented and flexible enough for many use
|
|
47
|
+cases. Do check the [API docs][api_docs] when contemplating a new
|
|
48
|
+feature and see if a plugin would be appropriate.
|
|
49
|
+
|
|
50
|
+### Templates and Theming
|
|
51
|
+
|
|
52
|
+User configs are allowed to specify a theme. A theme consists of a
|
|
53
|
+directory under "themes/" containing css, images, and at least
|
|
54
|
+3 templates: Base, Index, and Post.
|
|
55
|
+
|
|
56
|
+**Coleslaw** uses [cl-closure-template][closure_template]
|
|
57
|
+exclusively for templating. **cl-closure-template** is a well
|
|
58
|
+documented CL implementation of Google's Closure Templates. Each
|
|
59
|
+template file should contain a namespace like
|
|
60
|
+`coleslaw.theme.theme-name`.
|
|
61
|
+
|
|
62
|
+Each template creates a lisp function in the theme's package when
|
|
63
|
+loaded. These functions take a property list (or plist) as an argument
|
|
64
|
+and return rendered HTML. **Coleslaw** defines a helper called
|
|
65
|
+`theme-fn` for easy access to the template functions. Additionally,
|
|
66
|
+there are RSS, ATOM, and sitemap templates *coleslaw* uses automatically.
|
|
67
|
+No need for individual themes to reimplement a standard, after all!
|
|
68
|
+
|
|
69
|
+### The Lifecycle of a Page
|
|
70
|
+
|
|
71
|
+- `(load-content)`
|
|
72
|
+
|
|
73
|
+A page starts, obviously, with a file. When *coleslaw* loads your
|
|
74
|
+content, it iterates over a list of content types (i.e. subclasses of
|
|
75
|
+CONTENT). For each content type, it iterates over all files in the
|
|
76
|
+repo with a matching extension, e.g. ".post" for POSTs. Objects of the
|
|
77
|
+appropriate class are created from each matching file and inserted
|
|
78
|
+into the an in-memory data store. Then the INDEXes are created from
|
|
79
|
+the loaded content and added to the data store.
|
|
80
|
+
|
|
81
|
+- `(compile-blog dir)`
|
|
82
|
+
|
|
83
|
+Compilation starts by ensuring the staging directory (`/tmp/coleslaw/`
|
|
84
|
+by default) exists, cd'ing there, and copying over any necessary theme
|
|
85
|
+assets. Then *coleslaw* iterates over all the content types and index
|
|
86
|
+classes, rendering all of their instances and writing the HTML to disk.
|
|
87
|
+After this, an 'index.html' symlink is created pointing to the first
|
|
88
|
+reverse-chronological index.
|
|
89
|
+
|
|
90
|
+- `(deploy dir)`
|
|
91
|
+
|
|
92
|
+Finally, we move the staging directory to a timestamped path under the
|
|
93
|
+the config's `:deploy-dir`, delete the directory pointed to by the old
|
|
94
|
+'.prev' symlink, point '.curr' at '.prev', and point '.curr' at our
|
|
95
|
+freshly built site.
|
|
96
|
+
|
28
|
97
|
### Blogs vs Sites
|
29
|
98
|
|
30
|
99
|
**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
|
35
|
104
|
group a set of content objects on a page, it isn't content itself.
|
36
|
105
|
|
37
|
106
|
This isn't ideal if you're looking for a full-on static site
|
38
|
|
-generator. Content Types were added in 0.8 as a step towards making
|
39
|
|
-*coleslaw* suitable for more use cases but still have some
|
40
|
|
-limitations. Any subclass of CONTENT that implements the *document
|
41
|
|
-protocol* counts as a content type. However, only POSTs are currently
|
42
|
|
-included on INDEXes since their isn't yet a formal relationship to
|
43
|
|
-determine what content types should be included on which indexes.
|
|
107
|
+generator. Thankfully, Content Types were added in 0.8 as a step
|
|
108
|
+towards making *coleslaw* suitable for more use cases. Any subclass of
|
|
109
|
+CONTENT that implements the *document protocol* counts as a content
|
|
110
|
+type. However, only POSTs are currently included in the basic INDEXes
|
|
111
|
+since there isn't yet a formal relationship to determine which content
|
|
112
|
+types should be included on which indexes. Users may easily implement
|
|
113
|
+their own dedicated INDEX for new Content Types.
|
44
|
114
|
|
45
|
115
|
### The Document Protocol
|
46
|
116
|
|
|
@@ -55,27 +125,43 @@ It consists of 2 "class" methods, 2 instance methods, and an invariant.
|
55
|
125
|
There are also 4 helper functions provided that should prove useful in
|
56
|
126
|
implementing new content types.
|
57
|
127
|
|
|
128
|
+
|
58
|
129
|
**Class Methods**:
|
59
|
130
|
|
60
|
|
-Since Common Lisp doesn't have explicit support for class methods, we
|
61
|
|
-implement them by eql-specializing on the class, e.g.
|
|
131
|
+Class Methods don't *really* exist in Common Lisp, as methods are
|
|
132
|
+defined on generic functions and not on the class itself. But since
|
|
133
|
+it's useful to think about a Class as being responsible for its
|
|
134
|
+instances in the case of a blog, we implement class methods by
|
|
135
|
+eql-specializing on the class, e.g.
|
|
136
|
+
|
62
|
137
|
```lisp
|
63
|
138
|
(defmethod foo ((doc-type (eql (find-class 'bar))))
|
64
|
139
|
... )
|
65
|
140
|
```
|
66
|
141
|
|
67
|
|
-- `discover`: Create instances for documents of the class and put them in
|
68
|
|
- in-memory database with `add-document`. If your class is a subclass of
|
69
|
|
- CONTENT, there is a default method for this.
|
70
|
|
-- `publish`: Iterate over all objects of the class
|
|
142
|
+- `discover`: Create instances for documents of the class and put them
|
|
143
|
+ in the in-memory database with `add-document`.
|
|
144
|
+
|
|
145
|
+ For CONTENT, this means checking the blog repo for any files with a
|
|
146
|
+ matching extension and loading them from disk. If your class is a
|
|
147
|
+ subclass of CONTENT, it inherits a pleasant default method for this.
|
|
148
|
+
|
|
149
|
+ For INDEXes, this means iterating over any relevant CONTENT in the
|
|
150
|
+ database, and creating INDEXes in the database that include that
|
|
151
|
+ content.
|
|
152
|
+
|
|
153
|
+- `publish`: Iterate over all instances of the class, rendering each
|
|
154
|
+ one to HTML and writing it out to the staging directory on disk.
|
71
|
155
|
|
72
|
156
|
|
73
|
157
|
**Instance Methods**:
|
74
|
158
|
|
75
|
|
-- `page-url`: Generate a unique, relative path for the object on the site
|
76
|
|
- sans file extension. An :around method adds that later. The `slug` slot
|
77
|
|
- on the object is conventionally used to hold a portion of the unique
|
78
|
|
- identifier. i.e. `(format nil "posts/~a" (content-slug object))`.
|
|
159
|
+- `page-url`: Generate a relative path for the object on the site,
|
|
160
|
+ usually sans file extension. If there is no extension, an :around
|
|
161
|
+ method adds "html" later. The `slug` slot on the instance is
|
|
162
|
+ conventionally used to hold a portion of the path that corresponds
|
|
163
|
+ to a unique Primary Key or Object ID.
|
|
164
|
+
|
79
|
165
|
- `render`: A method that calls the appropriate template with `theme-fn`,
|
80
|
166
|
passing it any needed arguments and returning rendered HTML.
|
81
|
167
|
|
|
@@ -92,12 +178,15 @@ implement them by eql-specializing on the class, e.g.
|
92
|
178
|
database. It will error if the `page-url` of the document is not
|
93
|
179
|
unique. Such a hash collision represents content on the site being
|
94
|
180
|
shadowed/overwritten. This should be used in your `discover` method.
|
|
181
|
+
|
95
|
182
|
- `write-document`: Write the document out to disk as HTML. It takes
|
96
|
183
|
an optional template name and render-args to pass to the template.
|
97
|
184
|
This should be used in your `publish` method.
|
|
185
|
+
|
98
|
186
|
- `find-all`: Return a list of all documents of the requested class.
|
99
|
187
|
This is often used in the `publish` method to iterate over documents
|
100
|
188
|
of a given type.
|
|
189
|
+
|
101
|
190
|
- `purge-all`: Remove all instances of the requested class from the DB.
|
102
|
191
|
This is primarily used at the REPL or for debugging but it is also
|
103
|
192
|
used in a `:before` method on `discover` to keep it idempotent.
|
|
@@ -109,62 +198,7 @@ NUMERIC-INDEX, FEED, and TAG-FEED. Respectively, they support
|
109
|
198
|
grouping content by tags, publishing date, and reverse chronological
|
110
|
199
|
order. Feeds exist to special case RSS and ATOM generation.
|
111
|
200
|
Currently, there is only 1 content type: POST, for blog entries.
|
112
|
|
-
|
113
|
|
-### Templates and Theming
|
114
|
|
-
|
115
|
|
-User configs are allowed to specify a theme, otherwise the default is
|
116
|
|
-used. A theme consists of a directory under "themes/" containing css,
|
117
|
|
-images, and at least 3 templates: Base, Index, and Post.
|
118
|
|
-
|
119
|
|
-**Coleslaw** uses
|
120
|
|
-[cl-closure-template](https://github.com/archimag/cl-closure-template)
|
121
|
|
-exclusively for templating. **cl-closure-template** is a well
|
122
|
|
-documented CL implementation of Google's Closure Templates. Each
|
123
|
|
-template file should contain a namespace like
|
124
|
|
-`coleslaw.theme.theme-name`.
|
125
|
|
-
|
126
|
|
-Each template creates a lisp function in the theme's package when
|
127
|
|
-loaded. These functions take a property list (or plist) as an argument
|
128
|
|
-and return rendered HTML. **Coleslaw** defines a helper called
|
129
|
|
-`theme-fn` for easy access to the template functions. Additionally,
|
130
|
|
-there are RSS, ATOM, and sitemap templates *coleslaw* uses automatically.
|
131
|
|
-No need for individual themes to reimplement a standard, after all!
|
132
|
|
-
|
133
|
|
-### Plugins
|
134
|
|
-
|
135
|
|
-**Coleslaw** also encourages extending functionality via plugins. The Plugin
|
136
|
|
-API is well-documented and flexible enough for many use cases. Do check the
|
137
|
|
-[API docs](https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md)
|
138
|
|
-when contemplating a new feature and see if a plugin would be appropriate.
|
139
|
|
-
|
140
|
|
-### The Lifecycle of a Page
|
141
|
|
-
|
142
|
|
-- `(load-content)`
|
143
|
|
-
|
144
|
|
-A page starts, obviously, with a file. When *coleslaw* loads your
|
145
|
|
-content, it iterates over a list of content types (i.e. subclasses of
|
146
|
|
-CONTENT). For each content type, it iterates over all files in the
|
147
|
|
-repo with a matching extension, e.g. ".post" for POSTs. Objects of the
|
148
|
|
-appropriate class are created from each matching file and inserted
|
149
|
|
-into the an in-memory data store. Then the INDEXes are created by
|
150
|
|
-iterating over the POSTs and inserted into the data store.
|
151
|
|
-
|
152
|
|
-- `(compile-blog dir)`
|
153
|
|
-
|
154
|
|
-Compilation starts by ensuring the staging directory (`/tmp/coleslaw/`
|
155
|
|
-by default) exists, cd'ing there, and copying over any necessary theme
|
156
|
|
-assets. Then *coleslaw* iterates over the content types and index
|
157
|
|
-classes, calling the `publish` method on each one. Publish iterates
|
158
|
|
-over the class instances, rendering each one and writing the result
|
159
|
|
-out to disk with `write-file`. After this, an 'index.html' symlink is
|
160
|
|
-created to point to the first index.
|
161
|
|
-
|
162
|
|
-- `(deploy dir)`
|
163
|
|
-
|
164
|
|
-Finally, we move the staging directory to a timestamped path under the
|
165
|
|
-the config's `:deploy-dir`, delete the directory pointed to by the old
|
166
|
|
-'.prev' symlink, point '.curr' at '.prev', and point '.curr' at our
|
167
|
|
-freshly built site.
|
|
201
|
+PAGE, a content type for static page support, is available as a plugin.
|
168
|
202
|
|
169
|
203
|
## Areas for Improvement
|
170
|
204
|
|
|
@@ -220,3 +254,7 @@ reasonable concern. Also, every numbered INDEX would have to be
|
220
|
254
|
regenerated along with any tag or month indexes matching the
|
221
|
255
|
modified files. If incremental compilation is a goal, simply
|
222
|
256
|
disabling the indexes may be appropriate for certain users.
|
|
257
|
+
|
|
258
|
+[post_receive_hook]: https://github.com/redline6561/coleslaw/blob/master/examples/example.post-receive
|
|
259
|
+[closure_template]: https://github.com/archimag/cl-closure-template
|
|
260
|
+[api_docs]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md
|