{"id":15395455,"url":"https://github.com/stef/utterson","last_synced_at":"2025-04-16T00:09:28.763Z","repository":{"id":624135,"uuid":"264115","full_name":"stef/utterson","owner":"stef","description":"a minimal static blog generator written using old-school unix tools (make, ksh, m4, awk, procmail and a pinch of elisp)","archived":false,"fork":false,"pushed_at":"2019-03-27T13:18:39.000Z","size":98,"stargazers_count":37,"open_issues_count":0,"forks_count":5,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-04-16T00:09:17.462Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"outbrain/orchestrator","license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stef.png","metadata":{"files":{"readme":"README.textile","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2009-07-29T23:16:45.000Z","updated_at":"2025-03-08T01:10:50.000Z","dependencies_parsed_at":"2022-07-07T15:40:59.326Z","dependency_job_id":null,"html_url":"https://github.com/stef/utterson","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stef%2Futterson","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stef%2Futterson/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stef%2Futterson/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stef%2Futterson/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stef","download_url":"https://codeload.github.com/stef/utterson/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249173084,"owners_count":21224483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T15:28:25.011Z","updated_at":"2025-04-16T00:09:28.736Z","avatar_url":"https://github.com/stef.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"h1. Utterson\n\nis 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.\n\nThe goals were\n* 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.),\n* 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.\n* *minimizing security risks* by creating only static files\n* *avoid server-side processing*.\n\nh2. the easiest way to start\n\nbc. git clone git://github.com/stef/utterson.git\ncd utterson\nmkdir blog posts\ncp -n cfg/utterson.cfg.example cfg/utterson.cfg\ncp -n cfg/make.cfg.example cfg/make.cfg\necho \"\u003cp\u003eHello World\u003c/p\u003e\" \u003eposts/first_post.html\nmake -f Makefile.in liveposts Makefile\nmake\nls blog\n\nNow open up some of the files in _blog_/ with a webbrowser. easy, eh? :)\n\nh2. Details\n\n* _cfg/utterson.cfg_ - the configuration file\n* _cfg/make.cfg_ - some path config for the make file. The upload destination to your live site is set here.\n* _cfg/liveposts_ - list of all published posts in descending order of appearance, can be remade using @make liveposts@\n* _cfg/tags/\u003ctag\u003e_ - lists all posts in the order to be rendered in the listings and atom feed - newest first.\n* _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.\n* _layout_/ - templates for the xhtml, sitemap and atom targets\n* _blog_/ - the directory where the results will be stored\n* _Makefile_ - generate this with @make -f Makefile.in Makefile@\n* _Makefile.in_ - the all knowing makefile template.\n* _bin_/ - the directory where the helper scripts are stored\n* _COPYING_ - the AGPLv3 license\n\nh3. Config\n\nThe 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 @TVARS[TRACKERCODE]=\"$(cat ${CFGDIR}/tracker.html)\"@\n\nThe _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.\n\n_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_/.\n\n*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.\n\nh3. Writing posts\n\nAll 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?\n\nAfter 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_/.\n\nYoutube and vimeo embedds are supported via shortcuts in utterson.lib/filter():\n* youtube: [youtube=http://www.youtube.com/watch?v=\u003cid\u003e]\n* vimeo: [vimeo=http://vimeo.com/\u003cid\u003e]\n* images with captions: [caption align=\"alignright|alignleft\" caption=\"\u003ccaption\u003e\"]\u003cimage\u003e[/caption]\n\nh3. Make blog\n\nWhen 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_.\n\nIf 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_.\n\nYou 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.\n\nIf you only have one draft, then publishing it consists of only three steps:\n# add the new post to any tags\n# run @make livepost all@ to generate a preview\n# run @make install@ to set the changes live.\n\nAt 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.\n\nh3. Templates\n\nIf 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.\n\nh3. Makefile\n\nUtterson 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.\n\nSometimes it's useful to force a remake of the Makefile, easy: @make make@ should do the job.\n\nh3. Scripts\n\nThe helper scripts doing the main work are in the _bin_/ directory.\n\n* *atom*: outputs an atomfeed for all posts fed over stdin.\n* *post*: outputs a page containing one post, pointing to the prev+next posts, all three given as arguments.\n* *page*: outputs an index page, params are current page name, next, prev pagename, stdin supplies the posts to be included on this particular page.\n* *sitemap*: generates a gzipped sitemap of all posts and the archive, tags are not yet supported.\n* *static*: simply filters the contents of layout/static through m4 to the results directory.\n* *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.\n* *utterson.lib*: the core of utterson, this handles the templating, configuration and various helper functions.\n* *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!\n* *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!\n* *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.\n* *procmailrc*: (*experimental*) use this if utterson is installed on the live site to support mailing in blogposts.\nOnly 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 \u003cinsert your own key\u003e\\ny\"@\n* emacs support: super-comfortable automatic publishing from muse-mode and advanced make support is due to some basic settings in init.el:\n\nbc. ;; muse blog settings\n(defun my-muse-mode-hook ()\n  (setq auto-fill-mode t)\n  (flyspell-mode 1)\n  )\n(add-hook 'muse-mode-hook 'my-muse-mode-hook)\n(defvar muse-my-xhtml-markup-strings\n  (nconc\n   '((image-with-desc . \"\u003cimg class=\\\"alignright\\\" title=\\\"%3%\\\" src=\\\"%1%.%2%\\\" alt=\\\"%3%\\\" width=\\\"300\\\" /\u003e\")\n     muse-xhtml1.0-markup-regexps)))\n(muse-derive-style \"just-body\" \"xhtml1.0\"\n                   :header \"\"\n                   :strings 'muse-my-xhtml-markup-strings\n                   :footer \"\")\n;; credit: http://danamlund.dk/emacs/make-runner.html\n(defun my-get-makefile-targets (f)\n  (with-temp-buffer\n    (insert-file f)\n    (end-of-buffer)\n    (let ((v (lambda (v)\n               (if (re-search-backward \"^\\\\([^:\\n#[:space:]]+?\\\\):\"\n                                       (not 'bound) 'noerror)\n                   (cons (match-string 1) (funcall v v))\n                 '()))))\n      (funcall v v))))\n(defvar my-make-last-target \"\"\n  \"holds the last target, and what is used if no input is given\n  when doing my-make\")\n(defun my-make ()\n  \"If no target is specified the last target will be used.\n   To force running make without a target use the fake target \\\".\\\".\"\n  (interactive)\n    (let* ((targets (my-get-makefile-targets \"../Makefile\"))\n           (prompt (if (string= \"\" my-make-last-target)\n                       \"Target: \"\n                     (concat \"Target (\" my-make-last-target \"): \")))\n           (target (completing-read prompt targets nil nil))\n           (target (cond ((string= \"\" target) my-make-last-target)\n                         ((string= \".\" target) \"\")\n                         (t target))))\n      (setq my-make-last-target target)\n      (compile (concat \"cd ..; make \" target))))\n(defun my-muse-publish ()\n  (interactive)\n  (progn\n    (muse-publish-file\n     (buffer-file-name)\n     \"just-body\"\n     (expand-file-name\n      (concat\n       (file-name-directory buffer-file-name)\n       \"../posts/\"))\n     )\n    (my-make)\n    ))\n;; working on drafts\n(define-key muse-mode-map [f7] 'my-muse-publish)\n;; working with the blog\n(define-key muse-mode-map [f8] 'my-make)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstef%2Futterson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstef%2Futterson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstef%2Futterson/lists"}