This is the project website for Ode (pronounced oh-dee), a personal publishing engine for the web. Ode is unique in that it is designed to be simple – not necessarily easy.
Simple means understandable (at least it does here).
Just a small update to the release. There is in fact no change to Ode itself or bundled theme(s), and addin(s). (The version and build numbers are unchanged.)
There is no need to download this release if you have r1.
What is different?
!--jump--!
1 - I've added .html formatted versions of both the abbreviated and detailed the installation instructions.
The previous release included only plain text instructions which were inconvenient for some users because of issues related to text wrapping and line ending inconsistencies across platforms.
I am keeping the plain text versions of the instructions in along with the new html formatted versions for those users who prefer to use a plain text editor.
For anyone who is using the plain text installation instructions be aware:
You may need to convert line endings for your platform.
These instructions were prepared on Mac OS X using the default line endings for that platform. They should work for Mac OS X and other Unix-like operating systems but will need to be converted for Windows, Mac OS Classic (OS 9 and earlier), and possibly others.
The installation files are not hard wrapped.
You will need to wrap the lines at some reasonable width or turn on soft wrapping in your editor.
Again, you can avoid these issues by using the html formatted version of the installation instructions.
2 - I've added a changelog.
New to Ode? You may want walk through this short (29 slide) intro presentation.
The presentation includes just 29 slides and attempts to answer the first questions you might have about Ode (from the basics to design goals).
Note: The presentation is running through Ode by taking advantage of the S5 standard, which is based entirely on XHTML, CSS, Javascript. Not only is it a nice introduction to what Ode is all about, but it's also a good demonstration of just how flexible Ode's simple themes are. The entire presentation consists of a post and a theme (just like this one).
With the 1st release out, let's get to know Ode a little better: Introduction
Now that the first release is out I want to make sure you know what Ode can do. Though all of Ode's features are discussed in the annotated source, it's also true that they're not apparent otherwise. I want to be clear that I do not expect you to go digging through the source every time you have a question.
Ode's motto is 'simple means you know how it works', so let's get to know Ode a little better.
Ode is extensible. That means the app allows developers (that means you) to redefine how the script works and extend what Ode is capable of doing.
Ode's extensions are called 'addins'. You may be familiar with other projects where this same sort of thing is referred to as an extension, module, addon, plugin, etc.
The first release ships with a single addin which provides support for the Markdown text formatting syntax.
More about Markdown from the official website:
The overriding design goal for Markdown's formatting syntax is to make it as readable as possible. The idea is that a Markdown-formatted document should be publishable as-is, as plain text, without looking like it's been marked up with tags or formatting instructions.
Because Ode's posts are discrete text files intended to be created and modified using a plain text editor, Markdown is a nice complement.
This extensibility is an integral aspect of Ode's design. However, the ability to 'add in' features raises the question,
"What should Ode do itself, in absence of any installed addins?"
There is no easy answer. Too far in one direction the app might be lean (small and fast) but lacking even the most basic features, and so utterly useless. Small and fast sounds good, but not if it only compels nearly all users to install a set of addins to provide needed functionality that should have been built-in. Alternatively, the core script might be feature rich but relatively large. If we go too far this way, Ode will include features that aren't beneficial to some users. It might be possible to ignore those aspects of the script, but it's possible, even likely, that everyone will pay a performance penalty for their inclusion. Furthermore, it could be necessary to install addins just to get rid of unwanted features, further impacting performance. A big app is also hard to understand, and more difficult to support and maintain.
The goal then is to find just the right compromise. We want Ode to be legitimately useful but not excessive.
The basics really are pretty simple.
Ode is an app designed to dynamically generate a website from individual posts, which are collectively the content of the site. The presentation of this content is determined by one or more themes, which dictate the overall look and layout.
That brief description introduces two important elements:
In addition to these topics I want to introduce two others:
Wondering where to go next? Let's take a closer look at posts
Posts are plain text files. OK, but let's get to know posts a little better.
Posts are discrete text files that can be created in any plain text editor.
A post consists of the following 3 elements:
The first line of the post (i.e. everything before the first line ending) is considered the title of the post.
Tip: Keep post titles plain. These are often used in page titles, and links to posts. They may be included in search results, or used to reference content on your site at some 3rd party web service. Keeping titles uncomplicated will avoid compatibility issues.
For example, you will probably want to avoid including links in page titles.
The title is followed by one or more tags.
Tags are bits of text inserted directly into post files either by Ode itself or one or more installed addins. They can be used to store information and instructions relevant to the post.
Individual tags are a single line of text and have the following general form:
tag prefix literal : tag source/addin name : tag name : tag value
!--jump--!
tag prefix literal - All tags start with the string 'tag'.
tag source/addin name - All tags should include the name of the source addin so that it's easy to match each tag with the associated addin.
tag name - Addins may support more than one tag. The name identifies a specific tag. Though more than one addin may use the same tag name(s), the combination of addin name : tag name should be unique among all addins. This correctly implies that no two addins should have the same name. (For the time being we'll resolve naming conflicts on a case by case basis.)
tag value - the corresponding value of the named tag.
Example
The Markdown addin that ships with Ode supports a single tag.
> tag: markdown : on_off : on|off
Markdown's 'on_off' tag can be used to disable markdown conversion for an individual post.
There may be as few as zero (0) tags, in which case the title is immediately followed by the body of the post, and there is no limit on the number of tags that can be attached to any single post.
Everything following the tag block is considered the body of the post.
The body of the post can include anything you like, including valid HTML, Markdown formatted text (assuming the markdown addin is installed and active), even javascript, flash objects, and anything else you might want to put in a post.
There are no inherent limitations. This is a big advantage of using something like Ode, as opposed to a hosted service, Facebook, or Twitter.
Just keep in mind that posts are always part of the larger page structure, which is provided by the theme. So, you never want to include structural elements in your posts that would conflict with the theme.
Did you know that Ode let's you maintain a live history of your themes as you make changes and then revisit any prior version on a per request basis. (It's your own local Wayback Machine.) Let's learn a little more about themes.
Did you know you can maintain a live history of your themes as you make updates? Let's take a closer look at themes.
Themes completely specify the look of the site and are (that is, they really should be) standards-compliant (X)HTML and CSS. (Yes, I'm talking to you. :)
Each theme has a minimum of four components (along with any other misc resources you may want to include, e.g. CSS files, maybe an image directory, etc).
These theme components are:
!--jump--!
Example: content_type.html
This file must contain one of a small number of valid content_type strings (appropriate for the page being returned).
This is used in the HTTP header and communicates crucial information to the web server.
What are these values?
They are standard strings which are used to identify the type of the associated document.
These 'type definitions' are published and maintained as recommended standards by the World Wide Web Consortium (http://www.w3.org/) and are used in combination with Content-Type entity-header field, which is part of the HTTP protocol.
Refer to the W3C's website for more information.
(Fair warning: The w3.org site is confusing, as are the RFCs and other documents describing these protocols.)
You do not need to understand how these media types work to use them. But, you do need to make sure you are using the proper media type for the documents you are serving.
Unless you know what you're doing, do not modify the content_type._ file included with any of the bundled themes.
If you are writing your own themes, start by deciding which language you want to support (e.g. HTML 4, XHTML 1) and then search for the proper media type to use.
'text/html' is appropriate for standard HTML 4.x and XHTML 1.0 (including strict) but not XHTML 1.1, which expects 'application/xhtml+xml'.
You should consider the W3C recommendations authoritative.
2 - date._ theme file
Example: date.html
The file contains a format string which is inserted between posts whenever a specified date range is exceeded, where the range is determined by the format string itself. (Keep reading.)
A simple date._ file may look like:
START OF FILE
<h3>$wkday_name, $month_day $month_name $year</h3>
END OF FILE
('START OF FILE' and 'END OF FILE' are used here to set off the content of a fictitious date._ theme file and are not part of the file itself.)
So, in this case the date._ file consists of the single line:
<h3>$wkday_name, $month_day $month_name $year</h3>
$wkday_name
The value of $wkday_name is a 3 character string representing a day the week (Mon, Tue, ..., Sun).
$month_day,
The value of $month_day is a 2 digit string representing a day of the month (01, 02, ..., 31)
$month_name
The value of $month_name is a 3 character string representing a month name (Jan, Feb, ..., Dec)
$year
The value of $year is a 4 digit string representing a year (e.g. 2009)
Whenever the modification date of a post differs, in any way, from the date of the previous post, a new date string will be inserted between the two.
Notice that the smallest measure of time included in this format string is:
either $wkday_name or $month_day.
Two posts generated on the same day will _not_ have a date string between them, because both of those posts would share the same day of the week ($wkday_name), day of the month ($month_day), month ($month_name), and year ($year).
However, if the date string looked something like
START OF FILE
<h3>$wkday_name, $month_day $month_name and $sec seconds</h3>
END OF FILE
a date would be inserted between any two posts with modification times that differed by as little as a second. Practically speaking, a date-string would be inserted between every two consecutive posts.
This is a pretty unlikely format string, which would result in something like the following string being inserted between every two consecutive posts:
Thu, 12 Jan and 43 seconds
3 - page._
Example: page.html
page._ is a single, fairly typical, (X)HTML page with a few important exceptions (none of which should bother your HTML editor or development applications).
The page._ file must be divided into three sections:
IMPORTANT: This is accomplished by inserting special HTML comments at two points in the page file to mark the boundaries between sections:
These comments are defined in the config file using:
$page_delimiter_head_posts
$page_delimiter_posts_foot
The posts section should include all of the literal text and uninterpolated variables necessary to generate the actual content of the site.
This section is repeated once for each post displayed.
4) page_no_posts._
Example: page_no_posts.html
Everything said for the page._ file also applies to page_no_posts._.
For every request, the routine chooses one of page._ or page_no_posts._, and pulls the head, posts, and foot theme components from that file.
page_no_posts._ is used whenever the number of posts matching the current request (i.e. the value of $num_posts_s, which is passed into the routine from the caller) is zero (0).
page._ is used in every other case.
page_no_posts._ should be a drop in substitute for page._, so like page._, it too must be divided into three sections:
Read the description for page._ above for more info.
Though the basic structure of the page must be the same, theme designers are free to design the look and layout of a no_posts page however they see fit. The file might be exactly the same, or it might be entirely distinct.
The script supports variable interpolation in themes, so variables such as $config::site_title, in theme files are replaced by the corresponding values.
This simple variable interpolation is fundamentally important to Ode. This is what allows the script to build a page from a collection of discrete post files. (Keep reading.)
Think of themes as templates.
Themes are stored alongside posts under the site's document root.
In addition to the document root itself, any subdirectory under the site's document root may contain a 'themes' directory.
Each of these directories may contain one or more subfolders, each dedicated to an individual theme.
So, the picture is this:
There may be any number of 'themes' directories scattered throughout the site's content directories. (As many as one 'themes' folder per directory.)
Each of these 'themes' folders may contain any number of folders defining individual themes.
There is no limit on the number of individual themes per 'themes' directory, except that no two directory entries at the same path can share the same name. (You will probably recognize that this is not a limitation of the script, but a fundamental characteristic of the structure of the underlying filesystem).
The general idea is that themes which are closer to the request will always override themes with the same name higher in the content hierarchy, (i.e. further from the request).
This simple design allows us to define different themes for different segments of the site and then apply them in a way that is consistent with the natural organization and categorization scheme for the content of an Ode site.
For example let's say that the following is the partial structure of an Ode site.
/themes/html
/entertainment/themes/html
/entertainment/movies/
/entertainment/tv/themes/html
/entertainment/video_games/
/operating_systems/themes/html
/operating_systems/apple/themes/html
/operating_systems/apple/macosx/
/operating_systems/linux/themes/
/operating_systems/linux/redhat/fedora/themes/
/operating_systems/linux/ubuntu/
/operating_systems/microsoft/themes/html
/operating_systems/microsoft/windows7/themes/html
/operating_systems/microsoft/xp/themes/html
/travel/
/travel/usa/
/travel/western_europe/
/travel/western_europe/france/
The root, as well as many, but not all, of the children, grandchildren, and later descendants of the root contain a 'themes/html/' directory.
When a client submits a request for some page on the site, the script starts by looking for the requested theme closest to the request. If the named theme cannot be found, the routine works backward, up toward the root until it finds the theme it's looking for, or runs out of places to look.
Let's look at a couple of example requests:
Example 1:
.../operating_systems/microsoft/xp/some_post.html
Given this request, the routine starts by looking for an html theme at:
/operating_systems/microsoft/xp/
There is a 'themes/html/' directory at: '/operating_systems/microsoft/xp/', and this is the theme used in response to the request.
Example 2
.../entertainment/tv/index.html
The routine starts by looking for an html theme at:
/entertainment/tv/
You can see that there is a 'themes/html/' directory at that path, and this is the theme that will be used.
Note that it is different than the theme used in the first example.
These two themes are completely independent of each other, which gives us a lot of flexibility when it comes to designing an Ode site. We are not restricted to a single look. Rather, we can create as many themes as we might want, tied to various subdirectories, (i.e. content categories) to tailor the site however we see fit.
Example 3
.../entertainment/movies/some_post.html
Again the routine starts looking for an html theme closest to the request at:
/entertainment/movies/
This time there it will not find the theme it's looking for (in fact it will not find any themes directory at all). So the routine will try again one level closer to the root at:
/entertainment/
As you can see, there is a themes/html/ directory at /entertainment/ and this is the theme that will be used.
Notice that this is a different theme then the one used for example 1 and 2.
It is the same theme that would be used for a request like:
.../entertainment/some_post.html
and
.../entertainment/video_games/some_post.html
This example demonstrates that because the two directories:
.../entertainment/movies/
.../entertainment/video_games/
do not have an 'html/' theme, they share the theme at their parent directory: 'entertainment/'
Example 4
.../travel/western_europe/some_post.html
Given this request, the routine will start at the directory closest to the root. Failing to find an html/ theme there, it will search in the next directory closest to the root, and so on.
The routine will fail to find a theme at:
Before eventually finding a 'themes/html/' directory at the site root.
This is the same theme that would be used for a request for the site's homepage, and also for every other branch of the hierarchy, at any level that does not contain its own more specific html theme.
This setup allows us to create custom themes for any portion of the content hierarchy we wish, simply by creating the necessary 'themes/html/' folder and defining a theme which is appropriate to that category.
For example we may want a different look for the following two segments of the site:
Alternatively we can maintain a consistent look across the entire site by defining a single html theme at the site root.
Because themes completely dictate the look and layout of an Ode site, this arrangement allows for the freedom to create what appear to be entirely distinct sites.
But wait, that's not all. There's more.
What I've already described allows us to apply themes across the filesystem space, but I wanted to be able to apply themes across time as well.
The theme of a site consists of everything that is not the content of the site. As such the theme is incredibly important. To my way of thinking it is fully one half of the whole that is a site.
Moreover, it is strictly correct to say that it is everything that is not content, because there may in fact be quite a bit of content contained in the themes.
Picture a fairly typical 3 column site design:
_ _ _ _ _ _ _
| _ _ _ _ _ _ |
| | | |
| | content | |
| | | |
| | | |
| | _ _ _ _| |
| _ _ _ _ _ _ |
_ _ _ _ _ _ _
| _ _ _ _ _ _ |
| | | |
| content | | |
| | | |
| | | |
| _ _ _ _ _ _ |
| _ _ _ _ _ _ |
Everything except the portion of the diagram labeled content is dictated by the themes. This includes the header, footer and sidebars. Those portions of the page will probably include: images, announcements and other notices, important links to relevant content elsewhere on the web, ads, and other content.
Though the theme may change very little from day to day, there may be subtle changes introduced daily or weekly, and occasionally more substantial modifications.
I wanted the script to support navigating themes in time as well as space.
This means that it must be possible to archive themes in such a way that they are available to the script. Of course, there must also be a way to request archived themes.
To allow for this, the script recognizes a 'site_look_date' parameter, which works in combination with dated theme archives. These work together to allow clients to specify a target date, in addition to a theme name.
As described above, individual themes are defined by directories found in container directories titled 'themes', and these containers exist alongside content under the site's document root.
For example
/technology/apple/macosx/themes/html/
The 'html/' directory in 'themes/' defines a specific theme that can be applied to requests for posts in the '/technology/apple/macosx/' directory, and subdirectories of .../macosx/.
Dated theme archives work much the same way, except that the names of the individual theme directories include a '-' followed by an eight (8) digit date suffix, in the format YYYYMMDD, specifying the date when the instance of the theme was created.
For example
/technology/apple/macosx/themes/html-20090131/
In this example we see that there is an 'html' theme at
/technology/apple/macosx/
which was created on Jan 31, 2009.
Note: The '-' is a delimiter, separating the date portion of the directory name from the rest of the name. This is intended to make it less confusing (for both the user and the script) when the non-date portion of the theme name includes digits.
For example
'rss2-20090131'
As already mentioned, dated themes work in combination with the 'site_look_date' parameter
The value of the parameter is a 4, 6, or 8 character string of digits, conforming to the format YYYY[MM[DD]].
All of the following describe valid site_look_date formats:
YYYY (2009 )
YYYYMM (200901 )
YYYYMMDD (20090128)
Underscores are allowed between components of the site_look_date string to improve readability.
The following are all valid site_look_date strings:
2009_0128
200901_28
2009_01_28
2009___01_28
The value of the site_look_date parameter specifies a target date.
It is a request for the version of the theme (whatever theme is specified in the request) that was in use (i.e. current) on the specified day.
For example
http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/some_file.html?
site_look_date=2009_0201
This address is a request for the post at:
'/technology/apple/macosx/some_file'.
It also indicates that the response should use an 'html' theme, and more specifically the 'html' theme that would have been the current theme on February 1, 2009.
There is one other factor we need to consider, namely the configuration option, 'use_site_look_date'.
The option is interpreted as a Boolean.
When true the script recognizes (i.e. acts on) the site_look_date parameter and uses dated themes.
When false, the site_look_date parameter is ignored, and the script recognizes only non-dated themes.
So let's say that the /technology/apple/macosx/ directory includes a themes folder containing the following group of html themes:
.../themes/html-20090131/ , 2009_0131
.../themes/html-20090202/ , 2009_0202
.../themes/html/ , No date
Which of these themes will be used to build the page returned to the client in response to the following request?:
http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/some_file.html?
site_look_date=2009_0201
To answer that question, we need to consider the value of $config::use_site_look_date.
If the value is false the dated themes are ignored and '.../themes/html' is used. All of the other theme directories are ignored. On the other hand, if the value is true, then the non-dated theme is ignored, and the script chooses from among only the dated themes.
That leaves
.../themes/html-20090131/ , 2009_0131
.../themes/html-20090202/ , 2009_0202
It's important to understand that the date associated with a theme is the date when the theme was created (the date when the theme was first used).
In this case, 'html-20090131/' is the theme that would have been in use on the date specified by the site_look_date parameter in the example above, and so this is the theme that should be used in answer to the request.
Apparently 'html-20090131/' was replaced as the current theme two days later.
Assuming there are no other html themes with later dates, /html-20090202/ would be used for all requests including site_look_date values on or after 2009_0202.
There are 3 cases we need to consider
Case 1: The site_look_date mechanism is disabled
All dated themes are ignored and the script navigates the non-dated themes only to determine the appropriate theme to use.
Case 2: The site_look_date mechanism is enabled but the site_look_date parameter is not included in the request.
The non-dated themes are ignored and the script must negotiate the dated themes to determine the appropriate theme to use.
The absence of the site_look_date parameter is interpreted as an implicit request for the current version of the theme (i.e the theme with the latest date).
Case 3: The site_look_date mechanism is enabled and the parameter is included as part of the request.
As with the second case, non-dated themes are ignored.
Unlike case 2, the parameter specifies a target date. The goal of the script in this case is not to determine the current theme, but the theme that would have been used to answer the request on the specified date.
This final case is a little more complicated than the other two.
As with the previous case, we're looking for dated theme directories, e.g.
themes/
html-2009_0101
html-2007_02_15
html-20090612
Remember that in the previous case we were looking for the current version of the named theme closest to the request, i.e. the theme folder with the latest date nearest to the requested path.
What does that mean?
We started looking for a 'themes/' directory containing a dated version of the requested theme in the path closest to the request.
If we could not find such a theme, the search continued one level closer to the site's document root. (If an appropriate theme was not found after working all the way back to the root, the routine returned a baked-in error theme.)
On finding a themes directory containing at least one dated theme appropriate to the request, the script sorted all of the matching themes in that directory, then selected and used the theme with the latest date (because the theme with the latest date is the current version of the theme).
So continuing with the example above:
themes/
html-2009_0101
html-2007_02_15
html-20090612
are sorted in ascending order as follows:
html-2007_02_15
html-2009_0101
html-20090612
'html20090612' is selected because it is the theme with the latest date. (It is the current version of the theme.)
This brings us to the difference in the behavior of the script if a valid site_look_date value is included. In this case, because the client has specified a target date, we're no longer looking for the current version.
Continuing with the example from above:
themes/
html-2009_0101
html-2007_02_15
html-20090612
If the request included the parameter:
site_look_date=2009_01_20
The theme the visitor will expect to see is:
html-2009_0101
Why?
html-20090612 is the current version, but did not exist on 2009_0120.
html-2007_02_15 did exist at the time of the request, but it had already been superseded by html-2009_0101.
So we know that we're looking for the dated theme with the latest date substring that is also before the value of the site_look_date parameter.
But this only describes part of the behavior of the script for this case.
In the previous case (which applies whenever the site_look_date mechanism is active but the parameter is not included as part of the request), we stopped looking for themes as soon as we found one that was appropriate to the request. This allowed us to prioritize themes closest to the request.
For example consider the following partial directory structure:
.../technology/apple/
.../technology/apple/themes/
.../technology/apple/themes/html-20090612/
.../technology/apple/macosx/
.../technology/apple/macosx/themes/
.../technology/apple/macosx/themes/html-2009_0101
.../technology/apple/macosx/themes/html-2007_02_15
Given the request:
http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/index.html
The theme used in response will be:
.../technology/apple/macosx/themes/html-2009_0101
Because it is the theme with the latest date substring in the themes directory closest to the request.
This is true even though technology/apple/themes/html-20090612 is more recent.
Again, this is because closeness of the theme to the request is the primary consideration.
This agrees with the behavior of the script when the site_look_date mechanism is disabled altogether. In both cases the idea is that themes can be customized on a per category basis by always preferring themes which are closer to the requested path.
Things are a little different when a valid site_look_date parameter is included with the request. We're no longer looking for the current version of the theme. Instead, we want to make sure that the script returns the theme that would have been used in answer to the same request on the specified date. This requires a bit more work.
When we specify a date using the parameter, we're telling the script that the provided date is the current date for the purposes of picking a theme. Any theme with a date later than the value of site_look_date has to be ignored because it doesn't exist yet from the perspective of the script. Those themes won't be written until some time in the future.
Here's an example to illustrate this behavior:
.../technology/apple/
.../technology/apple/themes/
.../technology/apple/themes/html-20090101/
.../technology/apple/themes/html-20090201/
.../technology/apple/themes/html-20090501/
.../technology/apple/macosx/
.../technology/apple/macosx/themes/
.../technology/apple/macosx/themes/html
.../technology/apple/macosx/themes/html-2009_02_16
Consider the request:
http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/
?site_look_date=2009_02_15
If this were case 2, we would be looking for the current version of the named theme closest to the request, which would be:
'.../technology/apple/macosx/themes/html-2009_02_16'
The script would find this version of the theme and stop looking. But, we're not looking for the current version. We're looking for the version of the theme that would have been used to answer the request on 2009_02_15. We can't use html-2009_02_16 because it won't exist until the next day. So the script has to continue its search one level up the directory hierarchy (i.e. one level closer to the root).
That brings us to:
'.../technology/apple/themes/'
which contains three dated themes that match the name specified with the request:
html-20090101
html-20090201
html-20090501
html-20090501 doesn't work because the date is after the requested site_look_date, so it essentially hasn't happened yet and the script ignores it. That leaves two potential matches, html-20090201 and html-20090101. Of these two, html-20090201 is the best fit because it has the latest date that's still earlier than the requested site_look_date. This is the theme that's used in answer to the request.
What if there is no 'best fit' theme?
(This will be true whenever there is no appropriately named theme with a date substring before the date specified by the site_look_date parameter.)
In this case, the script simply uses the current version of the theme.
To accomplish this, without starting over again, ode identifies the current dated theme closest to the request during the process of attempting to find a theme that fits site_look_date. The path to this theme is squirreled away.
If the script fails to find any suitable 'best fit' theme it resorts to using the saved current version (or the baked-in error theme if it did not encounter a single dated theme matching the request).
Dated themes work in combination with the 'use_site_look_date' option set in the configuration file.
The option $config::use_site_look_date is either true or false.
If false, the script ignores dated themes and picks the named theme closest to the request. If true, the script looks for the parameter site_look_date.
For example,
?site_look_date=2009_1101
If the parameter isn't included with the request, the script looks for the current version, which is the dated version of the named theme with the latest date. If the parameter is included, it determines which dated version of the theme was in use on the date specified by the parameter.
What does this allow you to do?
Well let's say I'm going to update one of my themes. I can make a copy of the current theme and change the date suffix on the new copy to today's date (leaving the old one as it is). If a visitor doesn't request a specific date (with the parameter), they'll get the updated theme. But if they request a site_look_date earlier than today, they'll get the site as it looked on the specified date.
Of course, visitors probably won't know about the site_look_date parameter, so this is really a feature for site owners. For example they could use site_look_date to construct links that recreate the site as it looked at any point in time, or make an interface that will allow visitors to roll back the look of the site.
It's like a live local version of 'The Internet Archive Wayback Machine', and thanks to dated themes requires no additional work on the part of the site maintainer.
That's the gist of themes.
I've said that you should think of themes as templates. The template includes variables which are placeholders for values generated by the script (the title ($post_title) and body ($post_body) of your posts for example). When the script runs it replaces the placeholders with the values they represent to generate a complete page in response to a visitor request.
I haven't yet discussed what those placeholders are. I will provide a list of all of the variables suitable for use in theme files in a separate post.
There is more to Ode than just posts and themes. Let's talk about some of the subtler (i.e. less obvious) aspects.
Ode allows you to target posts by date. You can for example construct a request for every post from New Year's day for the past 10 years. Learn more about date restrictions.
Navigate posts by category OR date. Do you want to see what you post last Mar 29, or every Mar 29th (or the 29th of any month for the past 3 years)? You can do that.
Ode recognizes two forms of date restriction:
1 - path-based date restrictions
Note: This feature can be disabled in the config file by setting the value of the $use_path_date_restrictions to false.
The path-based restrictions allow the visitor to specify a target date as part of the path. Posts are compared against the date in the path and only entries with matching dates are returned.
Example 1
http://sample.net/cgi-bin/ode.cgi/technology/apple/macosx/2009/01/01/
is a request for all posts in the /technology/apple/macosx/ category with a post date of January 1st, 2009.
Example 2
http://sample.net/cgi-bin/ode.cgi/technology/2007/
Is a request for all posts in the /technology/ category with date in the year 2007 (i.e. Jan 1st, 2007 - Dec 31st, 2007).
2 - Parameter based date ranges
Note: Read about Ode's recognized query string parameters for more information about these date range parameters. (put link here)
The script also recognizes three date related parameters:
!--jump--!
start_date is used to define a lower boundary (i.e. an earliest date) for posts returned in response to the request. Selected posts must be dated on or after the specified start_date.
The end_date parameter specifies an upper boundary (i.e. a latest date) for selected posts. A qualifying post must be dated on or before the end_date value.
date_pattern adds some necessary flexibility to the script's date matching capabilities by introducing a very simple wild card mechanism.
The date_pattern will be in the format YYYYMMDD:
Each of YYYY, MM, and DD are composed of digits 0 - 9 except that any of these may be wild.
Wild portions of the date_pattern match any value.
There is a single wild character '-', which always replaces a single digit. IMPORTANT: Entire portions must be wild for the value to be considered valid.
Let's look at some examples of valid date_pattern values:
2009_0101, All posts returned must be dated 2009\_0101
----_0101, Posts from Jan 1st of any year
2009_--01, Posts from the 1st of any month in the year 2009
2009_01--, Posts from any day in the the month of Jan 2009
----_01--, All posts from Jan regardless of year or day of the month
(Note that underscore characters are allowed between year, month, and day to improve readability.)
The following are not valid date_pattern parameter values:
--08_0101, Year must be wild or literal, not a little of each.
08_0101 , The pattern must conform to the format YYYYMMDD
1 - The date range parameters can be used in combination.
That is, any or all of start_date, end_date, and date_pattern may be included with any request.
2 - The path-based restrictions and date range parameters are mutually exclusive, with the parameters taking precedence over the path-based dates. This means that if a path-based date restriction is used along with any of the parameters, the path restriction is completely ignored and only the parameter is enforced.
Path date restrictions function as something like dynamic date-based quasi-categories. They limit the entries returned in response to the request to only those items with a post date matching the specified year, month, day, ...
year,
year, month,
year, month, day
year, month, day, hour
year, month, day, hour, min,
year, month, day, hour, min, sec
This is essentially what happens with path-based categories as well (i.e. posts returned must match the requested path).
Example 1
http://sample.net/cgi-bin/ode.cgi/technology/apple/2009/03/01/
'/2009/03/01/' is a path-based date restriction, which instructs the script to limit content returned to entries with a post date of March 3rd, 2009.
Considering the rest of the path, the request targets posts matching the date 2009/0301 which are also found in the category 'technology > apple' (including subdirectories of technology/apple/).
Example 2
http://sample.net/cgi-bin/ode.cgi/2007/
is a request for entries with a post date in the year 2007.
Example 3
http://sample.net/cgi-bin/ode.cgi/2009/12/28/23/59/59/
This very specific request targets entries with a post date of: Dec 28 2009, 11:59:59p.
Of course we might expect that at most a single post would satisfy this request. The script supports these very specific date restrictions (down to the sec) in order to allow for very precise date-based permalinks.
Ode naturally supports category-based permalinks. So why use date-based permalinks?
Category-based permalinks work just fine and because Ode depends on the file system as its content database we know that category-based permalinks are always unique. (We cannot accidentally create two posts with the same name in the same category because the operating system won't allow it.) Because of this, any reference which includes both a filename and path is a suitable permalink.
The trouble is that these permalinks will break if the posts are moved.
It's reasonable to assume that the site maintainer will, at some point, need to relocate at least a single post.
Yes, we can handle those changes through some sort of redirection, but
a. setting up the redirection can be a pain,
b. because it's a pain, some people won't bother, and we should strive to avoid breaking the web (even just our own very small corner of it) whenever possible.
A post date is intended to be the date when the post was first added to the site. Because a post can never be first added to the site a second time, this date should not change.
Date-based permalinks should prove to be robust under Ode because of steps taken with the script to ensure that post dates do not change unexpectedly (even if a post is copied, moved, etc.).
Unfortunately, date-based permalinks are not guaranteed to be unique the way that category permalinks are.
Though we can't force the OS to allow us to create two identically named files at the same path even if we wanted to, it is technically possible to create two posts with the same name and post date.
Note that in practice this should never happen unintentionally. Post dates are computed automatically, and it seems highly unlikely that an author (or even multiple authors working on the same site) will create two identically named posts at exactly the same time (to the second).
If this is a concern for you, the possibility of conflicts can be eliminated though the use of an informal naming convention which guarantees uniqueness.
A couple of final notes about path based date restrictions:
1 - Filesystem path components must precede date restrictions.
2 - The names of actual path components (the names of directories under $config::document_root) must not consist of only digits.
Any request for such an address is likely to cause problems for the path-based date restriction mechanism. What's worse, it would be treated as a date restriction, which is certainly not what you want.
Let's repeat that, so there is no confusion:
1 - Real path components (categories) must precede path-based date restrictions whenever both are included as part of the requested address.
This means that the only valid elements of the requested address that can appear after the date restriction are:
*filename (some_post) *suffix (html) *the query string (?first_param=first_value&second_param=second_value)
2 - Path components (i.e. directories under the site's document root) must not consist of only digits.
Category names may contain digits, but must also include at least one non-digit character. (Where a digit is any character meeting Perl's definition of a digit, i.e. matching \d.)
There is no such restriction on filenames.
Finally, remember that the path-based date restriction mechanism can be disabled.
This is accomplished by setting the value of the appropriate setting in the config file ($use_path_date_restrictions) to false. When the value is false, all of the behaviors related to date-based path restrictions are essentially ignored (skipped), with the result that components of the path which appear to be dates are treated like any other element of the path.
For example
http://sample.net/cgi-bin/ode.cgi/dir1/dir2/dir3/2009/01/
If $use_path_date_restrictions is false, '2009/01/' is treated like any other element of the path.
In this case, the script interprets the URL as a request for all posts in the '01' subcategory, of '2009', itself contained in 'dir3' and so on.
As you know, you communicate with Ode by issuing requests in the form of a URL. The query string is an important part of the URL which allows you to pass information and instructions to the server. You don't know ode if you don't know about its query string parameters
You don't know Ode until you know about Ode's query string parameters. Learn more.
Let's take a look at the pattern of a normal request to Ode:
scheme://base_url/path/year/month_num/month_day/filename.theme \ ?query string
http://sample.net/cgi-bin/ode.cgi \ /dir1/dir2/dir3/2009/01/15/ \
some_filename.some_theme? \
first_param=first_value&second_param=second_value \
&third_param=third_value
|----------------------------- req_string -----------------------|
|http://sample.net/cgi-bin/ode.cgi| |/dir1/dir2/dir3/| \
|--------- base_url --------------| | ----- path ----|
|2009| / | 01| / | 15| / \
|year| |month_num| |month_day| /
|some_filename| . |some_theme| ? \
|- filename --| . |- theme --| ?
|first_param=first_value&second_param=second_value& \
third_param=third_value|
|----------------------------- query string ---------|
There may be any number of parameter name/value pairs included as part of a query string. We can't know what all of them might be. For example, some addin, which does not yet exist, may operate by reading the value of some as-of-yet unknown parameter.
However, the script itself recognizes a small set of parameters. Knowing what these are and how they are used is important to getting the most out of using Ode.
Parameters recognized by ode.cgi (in the absence of any addins) include:
It's probably best to think of these as 3 groups, such that parameters within each group are related and can work in combination.
Group 1: Date restriction parameters
1 - start_date
2 - end_date
3 - date_pattern
Group 2: Navigation parameters
4 - num_posts
5 - first_post
Group 3: Theme related parameters
6 - site_look_date
Let's take a look at each of these groups
!--jump--!
You may already know that a client may include one or more date restriction components as part of the path. These path-based date restrictions function as something like dynamic date-based quasi-categories.
They limit the entries returned in response to the request to only those items with a post date matching the specified year, month, day, ..., second
For example, given the request
http://sample.net/cgi-bin/ode.cgi/technology/apple/2009/03/01/
'2009/03/01/' is a path-based date restriction, which instructs the script to limit content returned to entries with a post date of March 1st, 2009.
These path-based date restrictions are only one of two mechanisms the script employs that allow for date related requests.
There is also a parameter-based date range feature.
The relevant parameters are:
These can be used individually or in combination.
When used in combination they should be flexible enough to specify just about any date range you might be want.
Parameter: start_date
?start_date=20090128
If only the start_date parameter is used, the response includes posts from (and including) the specified start_date going forward.
Partial dates match the beginning of the period. e.g.,
start_date=2009
is equivalent to start_date=20090101
The value of the start_date parameter is a 4, 6, or 8 character string of digits in the form:
YYYY[MM[DD]]
YYYYMMDD - 4 digit year, 2 digit month, and 2 digit day
20090128
YYYYMM - 4 digit year and 2 digit month
200901
YYYY - 4 digit year only
2009
Underscores are allowed between components of the start_date string to improve readability. So the following are all valid start_date values:
'2009_0128'
'200901_28'
'2009_01_28'
Parameter: end_date
?end_date=20090128
If only the end_date parameter is used, the response includes posts to (and including) the specified end_date and before.
Partial dates match the end of the period. e.g.,
end_date=2009
is equivalent to end_date=20091231
The value of the end_date parameter is a 4, 6, or 8 character string of digits in the form:
YYYY[MM[DD]]
YYYYMMDD - 4 digit year, 2 digit month, and 2 digit day
20090128
YYYYMM - 4 digit year and 2 digit month
200901
YYYY - 4 digit year only
2009
Underscores are allowed between components of the end_date string to improve readability. So the following are all valid end_date strings:
'2009_0128'
'200901_28'
'2009_01_28'
The start_date and end_date parameters can be combined. The result is a range of dates bracketed at the beginning by the start_date value and at the end by the value of end_date.
Example 1:
?start_date=20070420&end_date=2009_0420
Matches entries with post dates starting on April 20th, 2007 and ending on April 20, 2009. All other posts are excluded.
Example 2
?start_date=2009_0101&end_date=2009_1231
Matches all entries in 2009 (i.e with post dates from 2009_0101 to 2009_1231 inclusive.)
Example 3
?start_date=2009&end_date=2009
Note that the following values match exactly the same set of posts as the previous example.
This is not a special case. It follows from the rules we have a already discussed. Remember that partial start_date values match the beginning of the given period and partial end_date values match the end.
Question: What if the end_date is earlier than the start_date?
Answer: The end_date parameter is ignored.
Example 4
?start_date=20091231&end_date=20090101
Because the value of end_date is earlier than start_date, the end_date parameter is ignored entirely and the parameters match all entries from Dec 31, 2009 to the current date and time.
Parameter: date_pattern
?date_pattern=2008_0214
As the name suggests, date_pattern is a pattern match mechanism (albeit a very simple one) which greatly improves the flexibility with which we can specify dates.
date_pattern is an 8 character string of digits and a single wild card, '-'.
Digits are literal characters in the string, while the wild card will match any single digit.
Important: Unlike start_date and end_date, date_pattern does not allow date strings of fewer than 8 characters.
This 8 character string consists of three components:
Any of these components can be replaced by wild cards '-', but the component must be replaced in its entirety.
The following are all valid date_patterns:
----1231
2008--31
200812--
----12--
(Of course, there are many more valid date patterns.)
On the other hand, the examples below are invalid:
--081231, Year is --08
The year component must consist of either 4 digits or 4 wild card characters (not a mix of both).
200-1231, Year is 200-
The year component must consist of either 4 digits or 4 wild card characters (not some of each).
20081-31, Month is 1-
The month component must consist of either 2 digits or 2 wild card characters (not a mix of both). Again, individual components (year, month, day) must consist entirely of digits or wild card characters. How are these patterns interpreted? Let's look at a few examples:
date_pattern=----0214, All posts from Feb 14th in any year.
date_pattern=------14, All posts made on the 14th of any month and year.
date_pattern=2008----, All posts from 2008 regardless of month or day
date_pattern=----03--, All posts in March regardless of year or day.
The following rules apply to start_date, end_date, and date_pattern (except that in the case of date_pattern, the rule applies only for those components which are not wild).
Remember that the format for all of these date parameters is: YYYYMMDD.
YYYY
The value of YYYY must be greater than or equal to EARLIEST_YEAR (1969 by default).
MM
The value of MM must be one of: '01', '02', ..., '12'.
There are only twelve months of the year, and MM must specify one of them.
DD
The value of DD must be one of: '01', '02', ..., '31'.
There are at most 31 days in any month, and DD must specify one of them.
Note that the script does not require that MM and DD together specify a valid date. ( e.g. it will not complain about 2009_0231).
Why?
For two reasons:
This isn't intended to be a robust date checking mechanism, just a simple test to avoid the sort of confusion that might result from inadvertent errors.
The script behaves sensibly even when these date range parameters are invalid.
For example: '2008_0231'
If this were the start_date value, the script would return entries with post dates after 0231, beginning with 0301.
This is the same range that would be returned for the valid date 0228.
Likewise, if this were the end_date value, the script would return entries with post dates before and including 0231,including all entries with post dates through 0228, but not 0301.
Again, this is exactly how the script would behave with the valid date.
Of course, an invalid date_pattern, for example:
----0231
would most likely return no posts, because there should be no entries dated Feb 31st.
Even in this case, it can be argued that the response is valid, because we are accurately providing the client all of the matching posts (in the same way that we would for any valid date_pattern).
How do these work together?
IMPORTANT: Parameter date ranges override path based date restrictions.
Given a request like:
/Technology/Software/2009/04/19/index.boku?start_date=2008
the path date restriction is silently ignored.
!Make sure you understand this point!
start_date and end_date can be used together to define a range of post dates starting at start_date and ending with end_date (as described above).
date_pattern can also be used with start_date and end_date.
This is also straightforward.
Candidate posts are compared to date_pattern first, and then if they fit the pattern, are checked against start_date and end_date parameters.
To be included in the response, a post must satisfy all of the conditions.
Group 2: Navigation parameters
Parameter: num_posts
The num_posts parameter overrides $config::num_posts and dictates the total number of posts returned in response to the request.
More accurately, there will be no more than num_posts entries. (Of course, we can't return more posts then the total number that satisfy the request, regardless of the value of num_posts.)
The value must be a positive integer (i.e. a string of digits 0 - 9) or 'all'. (Note that this is a case-insensitive match, so all of the following are recognized: 'all', 'All', 'ALL', 'AlL', ...).
An integer is a request for a specific number of posts. The word 'all' specifies that every post matching the request should be returned.
So for example, given a request like:
http://sample.net/cgi-bin/ode.cgi/?num_posts=all
The script will return a single page containing every post on the site.
Parameter: first_post
The first_post parameter specifies the first post included on the page returned to the browser.
All posts before first_post (in sorted order) are skipped.
The value of first_post must be a positive integer (i.e. a string of digits 0 - 9).
For example, given the request:
http://sample.net/cgi-bin/ode.cgi/?first_post=6
Posts 6 - 15 are returned with the page (assuming $config::num_posts is 10, the default value).
Together, these two parameters can be used to create links allowing a browser to navigate forward and backward through successive pages of posts.
Let's say we have a site with 100 posts and we want 10 posts displayed per page.
At the end of the first page of posts we might want to include a 'next' link to the next page of 10 posts (11 - 20). That link would look something like:
<a href="http://sample.net/cgi-bin/ode.cgi/ \
?first_post=11&num_posts=10">next 10 posts</a>
From the second page, we might want to navigate forward to the next page (21 - 30) and back to the first page (1 - 10). These links would look like
<a href="http://sample.net/cgi-bin/ode.cgi/ \
?first_post=21&num_posts=10">next 10 posts</a>
<a href="http://sample.net/cgi-bin/ode.cgi/ \
?first_post=1&num_posts=10">previous 10 posts</a>
(Tip: Ode provides these next and previous posts links automatically.)
Group 3: Theme related parameters
Parameter: site_look_date
The site_look_date parameter hooks into the script's theme mechanism allowing clients to request a previous version of a named theme.
The value of the site_look_date is the same sort of 4, 6, or 8 character string of digits as start_date and end_date:
YYYY[MM[DD]]
YYYYMMDD - 4 digit year, 2 digit month, and 2 digit day
20080128
YYYYMM - 4 digit year and 2 digit month
200801
YYYY - 4 digit year only
2008
Underscores are allowed between components of the site_look_date string to improve readability.
The following are all valid sitelookdate strings:
2008_0128
200801_28
2008_01_28
The theme used to build the page will be the version of the named theme that was in use on the provided site_look_date, (or the current version of the theme if there is no more appropriate version).
Read about themes for more info.
That completes our little tour which included the following stops:
Hopefully you feel like you understand Ode a little bit better. There is more to discuss. We're just getting started. Check back soon.
Just a quick post to officially announce that Ode release 1.0 is available (and also).
You'll find a download link for the release in the sidebar at ode-is-simple.com/home.
You'll find several new posts related to installation and addin development on the blog.
Why the new site? I wanted a place to post regular updates about the project and other related news and info without disturbing the introductory info at ode-is-simple.com/home. (Please bear with me as I straighten all of this out.)
!--jump--!
I will be working on this consistently in the foreseeable future.
Next, I will be focusing primarily on documentation describing Ode's features (most of which are not obvious). Expect an overview by early next week.
Also look for:
More themes, addins, documentation, a community site, and more.
Stay tuned.
Just a quick note to say that there are general installation instructions included with each release of Ode. The info you find here should be considered supplemental.
A short video (at just over 6 minutes) walking through the entire installation procedure.
I've already posted a quick start guide for setting up an Ode site with DreamHost, as well as a discussion about why I'm recommending DreamHost for anyone who wants to give Ode a try and doesn't already have a preferred hosting provider.
Here's a short video (at just over 6 minutes) walking through the entire installation procedure.
!--jump--!
Simple.