is a bunch of scripts used for powering my blog, you could also call it a blog engine or blog compiler. Utterson currently supports creating paged listings of posts with an atom feed, separate pages for posts, handling of tags with their own atom feeds and sitemap support.
The goals were
- to make it as simple as possible: A blogauthor simply creates the raw html post content with his prefered editor (authors recommendation: emacs’ muse mode.),
- use only basic unix tools (there are similar tools like blosxom, written in various scripting languages). With Utterson the html files are assembled using the m4 macro processor, which is driven by a bunch of shell scripts, which in turn are controlled by a self-generating Makefile.
- minimizing security risks by creating only static files
- avoid server-side processing.
the easiest way to start
git clone git://github.com/stef/utterson.git cd utterson mkdir blog posts cp -n cfg/utterson.cfg.example cfg/utterson.cfg cp -n cfg/make.cfg.example cfg/make.cfg echo "<p>Hello World</p>" >posts/first_post.html make -f Makefile.in liveposts Makefile make ls blog
Now open up some of the files in blog/ with a webbrowser. easy, eh? :)
- cfg/utterson.cfg – the configuration file
- cfg/make.cfg – some path config for the make file. The upload destination to your live site is set here.
- cfg/liveposts – list of all published posts in descending order of appearance, can be remade using
- cfg/tags/ – lists all posts in the order to be rendered in the listings and atom feed – newest first.
- posts/ – the directory where you store all your raw posts in html format (must be created on setup!) – it’s recommended to handle this via a separate git repo.
- layout/ – templates for the xhtml, sitemap and atom targets
- blog/ – the directory where the results will be stored
- Makefile – generate this with
make -f Makefile.in Makefile
- Makefile.in – the all knowing makefile template.
- bin/ – the directory where the helper scripts are stored
- COPYING – the AGPLv3 license
The configuration of the blog can be found in cfg/utterson.cfg. The scripts search the current directory, ~/.config/utterson, /etc/utterson and the ../cfg directory relative to the location of the utterson shell scripts. Use this to adapt to your needs. For longer html snippets it’s easier to put them into separate files under cfg/, and then set them in utterson.cfg like this
The Makefile needs some path settings, these can be set in cfg/make.cfg. Except for the upload destination for the live site, the defaults should work out of the box.
cfg/liveposts – this file is important, it controls what will be rendered. It contains a list of all posts that should be published (included in the archive, sitemap, atomfeed). This file can be set to all html files in posts/ using
make liveposts, for example if you want to add everything in posts/.
Tags are stored under cfg/tags/ each tag lists all posts in order – that means usually new posts go to the top of this file. The posts should also contain the ‘post/’ directory prefixed to the filename.
All input goes in posts/. You create here html files containing only the body of the posts. The publishing date will be taken from the files timestamp, while the post’ title will be taken from the filename. Easy, yes?
After putting your post in posts/, you can run
make drafts, which will automatically detect the new file and renders it into your destination directory under blog/drafts/.
Youtube and vimeo embedds are supported via shortcuts in utterson.lib/filter():
- youtube: [youtube=http://www.youtube.com/watch?v=]
- vimeo: [vimeo=http://vimeo.com/]
- images with captions: [caption align=“alignright|alignleft” caption=“”][/caption]
When you have a proper makefile and you invoke the all target, the blog will be generated into blog/. Since we’re using make, only those files will be regenerated that have changed. E.g. when adding a new post, the atom, sitemap, index, some tags pages and the archive pages will be regenarated to reflect this change. Unchanged posts are not regenerated, only the new post will be used to generate the permalink endpoint of the post under blog/posts. The all target takes care of the complete blog, so afterwards it should be safe to invoke the install target as well, set the destination in cfg/make.cfg.
If you want to publish your draft you need to prepend the name of the file to the cfg/liveposts file. If you do not have other drafts, then
make liveposts automatically adds all HTML files in posts/ to cfg/liveposts.
You might want to attach your new post to any tags under cfg/tags. After the update to your tags and liveposts you should run
make all, which should regenerate all the changed files.
If you only have one draft, then publishing it consists of only three steps:
- add the new post to any tags
make livepost allto generate a preview
make installto set the changes live.
At step (2) if you have multiple drafts, but do not want to publish all of them, you must add them manually to cfg/liveposts instead of using make.
If you want to change to look of the results, check the templates. These can be found in layout/. Here you can find the templates for the headers, footers, single page rendering and list rendering of posts for xhtml, sitemap and atom targets. Anything contained in the layout/static directory will be directly copied to the results, this way you can distribute your css and image files for example.
Utterson is basically controlled by the makefile, if you don’t have a makefile you should run
make -f Makefile.in Makefile. You have common targets like _clean, make, all, posts, atom, sitemap, archive, tags, static. The install target rsyncs your blog/ directory via ssh to the live site. You should edit the config variables on top of the Makefile.in to set the destination of the install target and regenerate your Makefile to set up your own site.
Sometimes it’s useful to force a remake of the Makefile, easy:
make make should do the job.
The helper scripts doing the main work are in the bin/ directory.
- atom: outputs an atomfeed for all posts fed over stdin.
- post: outputs a page containing one post, pointing to the prev+next posts, all three given as arguments.
- page: outputs an index page, params are current page name, next, prev pagename, stdin supplies the posts to be included on this particular page.
- sitemap: generates a gzipped sitemap of all posts and the archive, tags are not yet supported.
- static: simply filters the contents of layout/static through m4 to the results directory.
- makemake: Helper script used by the Makefile target. Generates portions of the makefile taking the contents of cfg/liveposts into account. The makefile must be regenerated if you add a new post or tag.
- utterson.lib: the core of utterson, this handles the templating, configuration and various helper functions.
- wpmigrate: experimental script to convert wordpress exports to utterson-style single post files. Attention: wp does not really contain xhtml, the results will be ugly. – careful: needs xmlstarlet as a dependency!
- wptouch: experimental script for updating the timestamps of posts according to a wp export xml file. This is handy, if you manually clean up the posts after a wpmigrate invocation. – careful: needs xmlstarlet as a dependency!
- mailhandler: (experimental) converts an emailmsg to a file in posts/ (use with the supplied procmailrc for email support). see also next item. This script currently does not invoke the makefile to generate the new state of the blog. The workflow here is not quite clear yet.
- procmailrc: (experimental) use this if utterson is installed on the live site to support mailing in blogposts.
Only blogposts are accepted, if they are signed using pgp by anyone known in the public keyring (which of course you control). Easy and secure authentication of content. :) To sign a post is currently only supported using mutt, here’s the macro from my .muttrc:
macro compose \CP "Fgpg --clearsign --default-key <insert your own key>\ny"
- emacs support: super-comfortable automatic publishing from muse-mode and advanced make support is due to some basic settings in init.el:
;; muse blog settings (defun my-muse-mode-hook () (setq auto-fill-mode t) (flyspell-mode 1) ) (add-hook 'muse-mode-hook 'my-muse-mode-hook) (defvar muse-my-xhtml-markup-strings (nconc '((image-with-desc . "<img class=\"alignright\" title=\"%3%\" src=\"%1%.%2%\" alt=\"%3%\" width=\"300\" />") muse-xhtml1.0-markup-regexps))) (muse-derive-style "just-body" "xhtml1.0" :header "" :strings 'muse-my-xhtml-markup-strings :footer "") ;; credit: http://danamlund.dk/emacs/make-runner.html (defun my-get-makefile-targets (f) (with-temp-buffer (insert-file f) (end-of-buffer) (let ((v (lambda (v) (if (re-search-backward "^\\([^:\n#[:space:]]+?\\):" (not 'bound) 'noerror) (cons (match-string 1) (funcall v v)) '())))) (funcall v v)))) (defvar my-make-last-target "" "holds the last target, and what is used if no input is given when doing my-make") (defun my-make () "If no target is specified the last target will be used. To force running make without a target use the fake target \".\"." (interactive) (let* ((targets (my-get-makefile-targets "../Makefile")) (prompt (if (string= "" my-make-last-target) "Target: " (concat "Target (" my-make-last-target "): "))) (target (completing-read prompt targets nil nil)) (target (cond ((string= "" target) my-make-last-target) ((string= "." target) "") (t target)))) (setq my-make-last-target target) (compile (concat "cd ..; make " target)))) (defun my-muse-publish () (interactive) (progn (muse-publish-file (buffer-file-name) "just-body" (expand-file-name (concat (file-name-directory buffer-file-name) "../posts/")) ) (my-make) )) ;; working on drafts (define-key muse-mode-map [f7] 'my-muse-publish) ;; working with the blog (define-key muse-mode-map [f8] 'my-make)