Recently Written · git

coleslaw

Emacs mode for the "Coleslaw" site generator written in Common Lisp.

git clone https://github.com/equwal/coleslaw

Log | Files | Refs


docs/hacking.md (17306 bytes)

1 ## Coleslaw: A Hacker's Guide
2 
3 Here we'll provide an overview of key concepts and technical decisions
4 in *coleslaw* and a few suggestions about future directions. Please
5 keep in mind that *coleslaw* was written on a lark when 3 friends had
6 the idea to each complete their half-dreamed wordpress replacement in
7 a week. Though it has evolved considerably since it's inception, like
8 any software some mess remains.
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 
26 ## A Note on Performance
27 
28 A recent test on my Core i5 touting Thinkpad generated my 430-post blog
29 in 2.7 seconds. This averages out to about 6-7 milliseconds per piece of
30 content. However, about 400 of those 430 items were HTML posts from a
31 wordpress export, not markdown posts that require parsing with 3bmd.
32 I expect that 3bmd would be the main bottleneck on a larger site. It
33 would be worthwhile to see how well [cl-markdown][clmd] performs as
34 a replacement if this becomes an issue for users though we would lose
35 source highlighting from [colorize][clrz] and should also investigate
36 [pygments][pyg] as a replacement. Using the new [incremental][incf] plugin
37 reduced runtime to 1.36 seconds, almost cutting it in half.
38 
39 ## Core Concepts
40 
41 ### Plugins
42 
43 **Coleslaw** strongly encourages extending functionality via plugins.
44 The Plugin API is well-documented and flexible enough for many use
45 cases. Do check the [API docs][api_docs] when contemplating a new
46 feature and see if a plugin would be appropriate.
47 
48 ### Templates and Theming
49 
50 User configs are allowed to specify a theme. A theme consists of a
51 directory under "themes/" containing css, images, and at least
52 3 templates: Base, Index, and Post.
53 
54 **Coleslaw** uses [cl-closure-template][closure_template]
55 exclusively for templating. **cl-closure-template** is a well
56 documented CL implementation of Google's Closure Templates. Each
57 template file should contain a namespace like
58 `coleslaw.theme.theme-name`.
59 
60 Each template creates a lisp function in the theme's package when
61 loaded. These functions take a property list (or plist) as an argument
62 and return rendered HTML.  **Coleslaw** defines a helper called
63 `theme-fn` for easy access to the template functions. Additionally,
64 there are RSS, ATOM, and sitemap templates *coleslaw* uses automatically.
65 No need for individual themes to reimplement a standard, after all!
66 
67 Unfortunately, it is not very pleasant to debug broken templates.
68 Efforts to remedy this are being pursued for the next release.
69 Two particular issues to note are transposed Closure commands,
70 e.g. "${foo}" instead of "{$foo}", and trying to use nonexistent
71 keys or slots which fails silently instead of producing an error.
72 
73 ### The Lifecycle of a Page
74 
75 - `(progn
76      (load-config "/my/blog/repo/path")
77      (compile-theme (theme *config*)))`
78 
79 Coleslaw first needs the config loaded and theme compiled,
80 as neither the blog location, the theme to use, and other
81 crucial information are not yet known.
82 
83 - `(load-content)`
84 
85 A page starts, obviously, with a file. When *coleslaw* loads your
86 content, it iterates over a list of content types (i.e. subclasses of
87 CONTENT).  For each content type, it iterates over all files in the
88 repo with a matching extension, e.g. ".post" for POSTs. Objects of the
89 appropriate class are created from each matching file and inserted
90 into the an in-memory data store. Then the INDEXes are created from
91 the loaded content and added to the data store.
92 
93 - `(compile-blog dir)`
94 
95 Compilation starts by ensuring the staging directory (`/tmp/coleslaw/`
96 by default) exists, cd'ing there, and copying over any necessary theme
97 assets. Then *coleslaw* iterates over all the content types and index
98 classes, rendering all of their instances and writing the HTML to disk.
99 After this, an 'index.html' symlink is created pointing to the first
100 reverse-chronological index.
101 
102 - `(deploy dir)`
103 
104 Finally, we move the staging directory to a path under the config's
105 `:deploy-dir`. If the versioned plugin is enabled, it is a timestamped
106 path and we delete the directory pointed to by the old '.prev' symlink,
107 point '.curr' at '.prev', and point '.curr' at our freshly built site.
108 
109 ### Blogs vs Sites
110 
111 **Coleslaw** is blogware. When I designed it, I only cared that it
112 could replace my server's wordpress install. As a result, the code
113 until very recently was structured in terms of POSTs and
114 INDEXes. Roughly speaking, a POST is a blog entry and an INDEX is a
115 collection of POSTs or other content. An INDEX really only serves to
116 group a set of content objects on a page, it isn't content itself.
117 
118 Content Types were added in 0.8 as a step towards making *coleslaw*
119 suitable for more use cases. Any subclass of CONTENT that implements
120 the *document protocol* counts as a content type. However, only POSTs
121 are currently included in the bundled INDEXes since there isn't yet a
122 formal relationship to determine which content types should be
123 included on which indexes. It is straightforward for users to implement
124 their own dedicated INDEX for new Content Types.
125 
126 ### The Document Protocol
127 
128 The *document protocol* was born during a giant refactoring in 0.9.3.
129 Any object that will be rendered to HTML should adhere to the protocol.
130 Subclasses of CONTENT (content types) that implement the protocol will
131 be seamlessly picked up by *coleslaw* and included on the rendered site.
132 
133 All current Content Types and Indexes implement the protocol faithfully.
134 It consists of 2 "class" methods, 2 instance methods, and an invariant.
135 
136 There are also 5 helper functions provided that should prove useful in
137 implementing new content types.
138 
139 
140 **Class Methods**:
141 
142 Class Methods don't *really* exist in Common Lisp, as methods are
143 defined on generic functions and not on the class itself. But since
144 it's useful to think about a Class as being responsible for its
145 instances in the case of a blog, we implement class methods by
146 eql-specializing on the class, e.g.
147 
148 ```lisp
149 (defmethod foo ((doc-type (eql (find-class 'bar))))
150   ... )
151 ```
152 
153 - `discover`: Create instances for documents of the class and put them
154   in the in-memory database with `add-document`.
155 
156   For CONTENT, this means checking the blog repo for any files with a
157   matching extension and loading them from disk. If your class is a
158   subclass of CONTENT, it inherits a pleasant default method for this.
159 
160   For INDEXes, this means iterating over any relevant CONTENT in the
161   database, and creating INDEXes in the database that include that
162   content.
163 
164 - `publish`: Iterate over all instances of the class, rendering each
165   one to HTML and writing it out to the staging directory on disk.
166 
167 
168 **Instance Methods**:
169 
170 - `page-url`: Retrieve the relative path for the object on the site.
171   The implementation of `page-url` is not fully specified. For most
172   content types, we compute and store the path on the instance at
173   initialization time making `page-url` just a reader method.
174 
175 - `render`: A method that calls the appropriate template with `theme-fn`,
176   passing it any needed arguments and returning rendered HTML.
177 
178 **Invariants**:
179 
180 - Any Content Types (subclasses of CONTENT) are expected to be stored in
181   the site's git repo with the lowercased class-name as a file extension,
182   i.e. (".post" for POST files).
183 
184 **Protocol Helpers**:
185 
186 - `add-document`: Add the document to *coleslaw*'s in-memory
187   database. It will error if the `page-url` of the document is not
188   unique. Such a hash collision represents content on the site being
189   shadowed/overwritten. This should be used in your `discover` method.
190 
191 - `delete-document`: Remove a document from *coleslaw*'s in-memory
192   database. This is currently only used by the incremental compilation
193   plugin.
194 
195 - `write-document`: Write the document out to disk as HTML. It takes
196   an optional template name and render-args to pass to the template.
197   This should be used in your `publish` method.
198 
199 - `find-all`: Return a list of all documents of the requested class.
200   This is often used in the `publish` method to iterate over documents
201   of a given type.
202 
203 - `purge-all`: Remove all instances of the requested class from the DB.
204   This is primarily used at the REPL or for debugging but it is also
205   used in a `:before` method on `discover` to keep it idempotent.
206 
207 ### Current Content Types & Indexes
208 
209 There are 5 INDEX subclasses at present: TAG-INDEX, MONTH-INDEX,
210 NUMERIC-INDEX, FEED, and TAG-FEED. Respectively, they support
211 grouping content by tags, publishing date, and reverse chronological
212 order. Feeds exist to special case RSS and ATOM generation.
213 Currently, there is only 1 content type: POST, for blog entries.
214 PAGE, a content type for static page support, is available as a plugin.
215 
216 ## Areas for Improvement (i.e. The Roadmap)
217 
218 ### TODO for 0.9.7
219 
220 * Test suite improvements:
221   * `load-content`/`read-content`/parsing
222   * Content Discovery
223   * Theme Compilation
224   * Content Publishing
225   * Common Plugins including Injections
226 * Add proper errors to read-content/load-content? Not just ignoring bad data. Line info, etc.
227 * Improved template debugging? "${" instead of "{$", static checks for valid slots, etc.
228   At least a serious investigation into how such things might be provided.
229 * Some minor scripting conveniences with cl-launch? (Scaffold a post/page, Enable incremental, Build, etc).
230 
231 ### Assorted Cleanups
232 
233 * Try to get tag-index urls out of the tags. Post templates use them.
234 * Profile/memoize find-all calls in **INDEX** `render` method.
235 
236 ### Real Error Handling
237 
238 One reason Coleslaw's code base is so small is probably the
239 omission of any serious error handling. Trying to debug
240 coleslaw if there's a problem during a build is unpleasant
241 at best, especially for anyone not coming from the lisp world.
242 
243 We need to start handling errors and reporting errors in ways
244 that are useful to the user. Example errors users have encountered:
245 
246 1. Loading of Content. If `read-content` fails to parse a file, we
247    should tell the user what file failed and why. We also should
248    probably enforce more constraints about metadata. E.g. Empty
249    metadata is not allowed/meaningful. Trailing space after separator, etc.
250 2. Trying to load content from the bare repo instead of the clone.
251    i.e. Specifying the `:repo` in .coleslawrc as the bare repo.
252    The README should clarify this point and the need for posts to be
253    ".post" files.
254 3. Custom themes that try to access non-existent properties of content
255    do not currently error. They just wind up returning whitespace.
256    When the theme compiles, we should alert the user to any obvious
257    issues with it.
258 4. Dear Lord it was miserable even debugging a transposed character error
259    in one of the templates. "${foo}" instead of "{$foo}". But fuck supporting
260    multiple templating backends I have enough problems. What can we do?
261 
262 ### Scripting Conveniences
263 
264 It would be convenient to add command-line tools/scripts to run coleslaw,
265 set up the db for incremental builds, scaffold a new post, etc. for new users.
266 Fukamachi's Shelly, Xach's buildapp or Fare's cl-launch would be useful here. frog and hakyll are
267 reasonable points of inspiration for commands to offer.
268 
269 #### Commands
270 
271 This is a initial set of commands which will be used to implement the first coleslaw cli, feel free to contribute!
272 Imagine a executable `coleslaw`, the commands would be invoked like this: `coleslaw <commandname> <args>`
273 
274 * `build` generates the site. Takes:
275 	* `--repo-dir`: first defaults to `~/.coleslawrc`'s `repo-dir` then to `.` and otherwise fails
276 * `clean` removes the files from `output-dir` and `staging-dir`. Takes:
277 	* `--repo-dir`: See above
278 * `rebuild` is a shortcut for `clean` and then `build`. Takes:
279 	* `--repo-dir`: See above
280 * `post` creates a new empty `.post` file. Takes:
281 	* `--repo-dir`: See above
282 	* `--title`: title (also used for generating file name)
283 	* `--date`: same as the header-key. If not given, current time is used.
284 	* `--format`: same as the header-key (optional, defaults to `md`)
285 	* …
286 * `serve` starts a hunchentoot serving the blog locally
287 * maybe `page` which is `post` for static sites.
288 
289 Ideas for later:
290 
291 * Deployment. It would be nice to have but user needs vary greatly
292   and there are multiple deployment methods to support (heroku, s3,
293   gh-pages, git, etc). This will require some careful thought.
294 
295 ### Plugin Constraints
296 
297 There is no system for determining what plugins work together or
298 enforcing the requirements or constraints of any particular
299 plugin. That is to say, the plugins are not actually modular. They are
300 closer to controlled monkey-patching.
301 
302 While adding a [real module system to common lisp][asdf3] is probably
303 out of scope, we might be able to add some kind of [contract library][qpq]
304 for implementing this functionality. At the very least, a way to check
305 some assertions and error out at plugin load time if they fail should be
306 doable. I might not be able to [make illegal states unrepresentable][misu],
307 but I can sure as hell make them harder to construct than they are now.
308 
309 @PuercoPop has suggested looking into how [wookie does plugins][wookie].
310 It's much more heavyweight but might be worth looking into. If we go that
311 route, the plugin support code will be almost half the coleslaw core.
312 Weigh the tradeoffs carefully.
313 
314 ### New Content Type: Shouts!
315 
316 I've also toyed with the idea of a content type called a SHOUT, which
317 would be used primarily to reference or embed other content, sort of a
318 mix between a retweet and a del.icio.us bookmark. We encounter plenty
319 of great things on the web. Most of mine winds up forgotten in browser
320 tabs or stored on twitter's servers. It would be cool to see SHOUTs as
321 a plugin, probably with a dedicated SHOUT-INDEX, and some sort of
322 oEmbed/embed.ly/noembed support.
323 
324 ### Better Content Types
325 
326 Creating a new content type is both straightforward and doable as a
327 plugin. All that is really required is a subclass of CONTENT with
328 any needed slots, a template, a `render` method to call the template
329 with any needed options, a `page-url` method for layout, and a
330 `publish` method.
331 
332 Unfortunately, this does not solve:
333 
334 1. The issue of compiling the template at load-time and making sure it
335    was installed in the theme package. The plugin would need to do
336    this itself or the template would need to be included in 'core'.
337    Thankfully, this should be easy with *cl-closure-template*.
338 2. More seriously, there is no formal relationship between content
339    types and indexes. Consequentially, INDEXes include only POST
340    objects at the moment. Whether the INDEX should specify what
341    Content Types it includes or the CONTENT which indexes it appears
342    on is not yet clear.
343 
344 ### Contributing
345 
346 The preferred workflow is more or less:
347 
348     - fork and clone
349     - make a branch
350     - commit your changes
351     - push your branch to your github fork
352     - open a Pull Request
353 
354 #### Fork and clone
355 
356 You may clone the main github repository or your fork, whichever you cloned
357 will be known as origin in your git repository. You have to add the other git
358 repository to your remotes, so if you cloned from your fork execute:
359 
360 
361 ```bash
362 git remote add upstream git@github.com:redline6561/coleslaw.git
363 ```
364 
365 If you cloned from the main github repository execute:
366 
367 ```bash
368 git remote add fork git@github.com:<YourUsername>/coleslaw.git
369 ```
370 
371 For the rest of the steps we will assume you cloned from your fork and that the main github repository has the remote name of upstream.
372 
373 #### Make a branch
374 
375 ```bash
376 git checkout -b <branch_name>
377 ```
378 
379 It is important to work always on branch so one can track changes in upstream by simply executing ```git pull upstream master:master``` from the master branch. If one can't come up with a suitable branch name just name it patch-n.
380 2
381 #### Commit your changes
382 
383 Make the changes you want to coleslaw, add the files with that changes (```git add <path/to/file>```) and commit them (```git commit```). Your commit message should strive to sum up what has changes and why.
384 
385 #### Push your branch to your github fork
386 
387 ```bash
388 git push origin branch
389 ```
390 
391 #### Open a Pull Request
392 
393 After pushing the branch to your fork, on github you should see a button to open a pull request. In the PR message give the rationale for your changes.
394 
395 [closure_template]: https://github.com/archimag/cl-closure-template
396 [api_docs]: https://github.com/redline6561/coleslaw/blob/master/docs/plugin-api.md
397 [clmd]: https://github.com/gwkkwg/cl-markdown
398 [clrz]: https://github.com/redline6561/colorize
399 [pyg]: http://pygments.org/
400 [incf]: https://github.com/redline6561/coleslaw/blob/master/plugins/incremental.lisp
401 [asdf3]: https://github.com/fare/asdf3-2013
402 [qpq]: https://github.com/sellout/quid-pro-quo
403 [misu]: https://blogs.janestreet.com/effective-ml-revisited/
404 [wookie]: https://github.com/orthecreedence/wookie/blob/master/plugin.lisp#L181