No commit activity in last 3 years
No release in over 3 years
tributary: a tiny, toto-inspired blogging engine



 Project Readme


A tiny, heavily toto-inspired and (no less heavily) Sinatra-powered blogging engine.


tributary aggregates a set of Items, representing individual website contents (blog posts, photolog entries, standalone pages, etc.) stored in YAML-enhanced Markdown files, and allows accessing them with a simple 1:1 URL-to-filename mapping. All Items with a publication date are combined into a Stream, which can be interated over (to, e.g., display the seven most recent Items, or all photos) or navigated from inside (to get the Item that’s previous/subsequent to the current one).


An Item is represented in the filesystem in the form of text file with a Markdown body and a YAML header:

date: 2010-07-15
title: welcome to tributary

tributary _welcome_ article

The above example, if stored as articles/, will be seen in tributary as an Item object with Item#body returning the kramdown-generated <p>tributary <em>welcome</em> article</p>\n, Item#title and Item#date returning YAML-parsed welcome to tributary and 2010-07-15 00:00:00 +0200, while Item#type and Item#path will be inherited from the filesystem location and return :articles and welcome, respectively.

Item types

Every Item has a type, inherited from the Item’s position in the filesystem and returned by Item#type. When a given Item is requested over HTTP (via its Item#path) the view associated with its type is rendered; this allows rendering different HTML/CSS layouts for different content types (‘static’ pages vs blog posts vs photolog entries, for example).


As mentioned above, every Item has an associated type, and this type defines the HTML view displayed when the given Item is requested; this allows for different rendering of e.g. static pages vs photolog entries. The views are written in Haml and are located in views/*.haml files, with the file’s basename equal to the Item’s type.

Additionally, for every type the URL with that type’s name can be accessed (for example http://…/pages, assuming at least in one case Item#type returns :pages). In this case, the relevant views/*.index.haml Haml view is rendered (so views/pages.index.haml in the aforementioned example). These views can be used to create custom category-like pages – e.g., a page indexing all of your photographs that renders their thumbnails.


The Stream object contains all tributary Items ordered by their publication date (if present) and can be queried to return a given number of the most recent Items or an Item previous of (or subsequent to) a given Item (the returned Item(s) take into account the current locale and lang_limit settings). Every such query can additionally filter the Stream and for example request the subsequent Item that is also a photolog entry.

Multilingual support

There are two session variables governing the user’s language preferences: locale and lang_limit. The first sets the user’s preferred language (and, for example, allows for a localised user interface), while the second limits the contents returned from the Stream.

Every Item can have multiple language versions (stored in <type>/<path>.*.md files, where * maps to the relevant locale) and/or a language-agnostic version (stored in the <type>/<path>.md file). When a given Item is requested (via the http://…/<path> URL) tributary chooses the language version most suitable for the request, based on either an explicit locale cookie sent along with the request or the Accept-Language HTTP header. If the preferred language version is not available, tributary falls back to the language-agnostic version or (if there’s no such version) to a language version that’s not explicitely filtered out by the lang_limit setting.

The site’s interface can be multilingualised using R18n (via R18n for Sinatra). The t object (available in views) can be sent messages like t.recent_items, which are translated to the relevant localised strings based on YAML entries in i18n/*.yml files (where * maps to the current user’s locale). The example i18n/en.yml file (in the spec/site directory) contains

recent_items: recent items

while the example i18n/pl.yml file contains

recent_items: najnowsze

– and so the views/index.haml view used by the spec site can call t.recent_items to get a properly localised string (note that Tributary::App already registers Sinatra::R18n and exposes locale to the session, while also setting the locale to either the user’s preference, their browser configuration’s default or English, so there’s nothing more that needs to be done).


Application-level configuration is stored right on the App object itself; see the example, which happens to serve the site used by specs:

Tributary::App.configure do |config|
  config.set :author,   'Ary Tribut'
  config.set :root,     'spec/site'
  config.set :sitename, 'a tributary site'

User-level configuration is also stored on the App object and can be operated on by visiting the /set?option=value URLs – for example, setting the locale to English and lang_limit to English and Polish can be done by visiting the /set?locale=en&lang_limit=en+pl URL.

The user_prefs config option contains a list of settings that can be altered by visiting /set (and defaults to [:lang_limit, :locale]) – changing this option (by setting it as above in, for example) allows the users to alter other (e.g., nonexistent by default) settings and see the changes reflected on the App object.

The settings altered by the user are kept in the given user’s session and so persist between requests and visits.


Plugins (put in the App.plugins Array) are modules which get to extend Items. See the Mnml plugin for an example implementation filtering the output of the body and title methods.

© MMX-MMXI Piotr Szotkowski, licensed under AGPL 3 (see LICENCE)