The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Blog::Blosxom - A module version of the apparently inactive blosxom.cgi

VERSION

Version 0.01

SYNOPSIS

    use Blog::Blosxom;

    ...

    my $blog = Blog::Blosxom->new(%params);
    $blog->run($path, $flavour);

A path comes in from somewhere - usually an HTTP request. This is applied to the blog directory. A configurable file extension is added to the path and if it matches a file that file is served as the matched entry. If the path matches a directory, all entries in that directory and in subdirectories are served. If the path looks like a date, all posts that match that date are served. A string is returned, which is usually printed back to CGI. The string is the matched entries, served in the specified output format, or flavour. The flavour is determined by the file extension on the incoming path, or a GET parameter, or a configured default.

DESCRIPTION

Blosxom is a blog engine. It is a rewrite of a CGI script found at www.blosxom.com. Blosxom uses the filesystem as the database for blog entries. Blosxom's run() method takes two parameters: the path and the flavour.

The CGI script that ships with the module is an example of how to use this module to reproduce the behaviour of the original Blosxom script, but the idea here is that it is up to you how to get this data.

Every file that ends in a configurable file extension and is placed somewhere within the blog root directory is, in the default situation, served up on a big page, in date order, having been processed and turned into blog posts. The set of files chosen for display is pared down by specifying a path to limit the set to only entries under that path, the narrowest possible filter of course being when the path actually matches a single blog entry.

Alternatively, the path may be a date in the format YYYY[/MM[/DD]], with the brackets denoting an optional section. This will be used to filter the posts by date instead of by location. All posts under the blog root are candidates for being returned. A TODO is to concatenate a date-style filter and a directory-style filter.

The module is designed to be extensible. That is, there are several methods in it that are designed to be overridden by subclasses to extend the functionality of Blog::Blosxom. You can see the PLUGINS section below for details on these, and examples.

TERMS

entry

Entry is used to mean both the individual article (its title, content and any metadata) and the file that contains that data. The entry is a filename with a customisable file extension. The file name and file extension have two differnent purposes.

The file extension is used to decide what is a blog post and what isn't. Files that have the defined file extension will be found by the blog engine and displayed, so long as they are within the filter.

The entry's filename is used to find a single entry. The path you provide to run() is given the file extension defined for blog entries and then applied to the root directory. If this is a file, that is served. If not, it is tested without the file extension to be a directory. If it is a directory, all files ending with this extension and within that directory are served.

story

The story is the formatted version of an entry. The file story.$flavour is used to insert the various parts of the blog entry into a template, which is then concatenated to a string which is itself returned from the run method. See below for what I mean by $flavour.

flavour

The flavour of the blog is simply the format in which the blog entry is served as a story. The flavour is determined by you. The CGI script takes the file extension from the request URI, or the flav GET parameter.

If neither is provided, the default flavour is used. This is passed as a parameter to new and defaults to html.

component

A component is one of the five (currently) sections of the page: head, foot, story, date and content-type. The story and date components appear zero-to-many times on each page and the other three appear exactly once.

template

A template is a flavoured component. It is defined as a file in the blog root whose filename is the component and whose extension is the flavour. E.g. head.html is the HTML-flavoured head component's template.

METHODS

new(%params)

Create a new blog. Parameters are provided in a hash and are:

blog_title

This will be available in your templates as $blog_title, and by default appears in the page title and at the top of the page body.

This parameter is required.

blog_description

This will be available in your templates as $blog_description. This does not appear in the default templates.

blog_language

This is used for the RSS feed, as well as any other flavours you specify that have a language parameter.

datadir

This is where blosxom will look for the blog's entries. A relative path will be relative to the script that is using this module.

This parameter is required.

url

This will override the base URL for the blog, which is automatic if you do not provide.

depth

This is how far down subdirectories of datadir to look for more blog entries. 0 is the default, which means to look down indefinitely. 1, therefore, means to look only in the datadir itself, up to n, which will look n-1 subdirectories down.

num_entries

This is the maximum number of stories to display when multiple are found in the filter.

file_extension

By default, Blosxom will treat .txt files as blog entries. Change this to use a different file extension. Do not provide the dot that separates the filename and the file extension.

default_flavour

The flavour simply determines which set of templates to use to draw the blog. This defines which flavour to use by default. Vanilla blosxom has HTML and RSS flavours, of which RSS sucks really hard so really only HTML is available by default.

show_future_entries

This is a bit of a strange one, since by default, having a future date on an entry is a filesystem error, but if you want to override how the date of a template is defined, this will be helpful to you.

plugin_dir

Tells blosxom where to look for plugins. This is empty by default, which means it won't look for plugins. Relative paths will be taken relative to the script that uses this module.

plugin_state_dir

Some plugins wish to store state. This is where the state data will be stored. It will need to be writable. Defaults to plugin_dir/state if you specify a plugin_dir.

static_dir

Blosxom can publish your files statically, which means you run the script and it creates HTML files (for example) for each of your entries, instead of loading them dynamically. This defines where those files should go. I haven't actually implemented this because I don't really want to.

static_password

You have to provide a password if you want to use static rendering, as a security measure or something.

static_flavours

An arrayref of the flavours that Blosxom should generate statically. By default this is html and rss.

static_entries

Set this to a true value to turn on static generation of individual entries. Generally there is no point because your entries are static files already, but you may be using a plugin to alter them before rendering.

run ($path, $flavour)

It is now the responsibility of the user to provide the correct path and flavour. That is because there are several ways that you can gain this information, and it is not up to this engine to decide what they are. That is, this information comes from the request URL and, possibly, the parameter string, POST, cookies, what-have-you.

Therefore:

  • The path is the entire path up to the filename. The filename shall not include a file extension. The filename is optional, and if omitted, the directory given will be searched for all entries and an index page generated.

  • The flavour can be gathered in any manner you desire. The original Blosxom script would use either a parameter string, ?flav=html, or simply by using the flavour as the file extension for the requested path.

No flavour provided will result in the default being used, obviously. No path being provided will result in the root path being used, since these are equivalent.

template($path, $component, $flavour)

Returns a chunk of markup for the requested component in the requested flavour for the requested path. The path will be the one given to run.

By default the template file chosen is the file $component.$flavour within the $path provided, and if not found, upwards from there to the blog root.

The templates used are content_type, head, story, date and foot, so the HTML template for the head would be head.html.

entries_for_path

Given a path, find the entries that should be returned. This may be overridden by a plugin defining the function "entries", or this "entries_for_path" function. They are synonymous. See PLUGINS for information on overriding this method.

The path will not include datadir.

It implements two behaviours. If the path requested is a real path then it is searched for all blog entries, honouring the depth parameter that limits how far below the datadir we should look for blog entries.

If it is not then it is expected to be a date, being in 1, 2 or 3 parts, in one true date ISO format. This version will return all entries filtered by this date specification. See also date_of_post, which determines the date on which the post was made and can be overridden in plugins.

date_of_post ($fn)

Return a unix timestamp defining the date of the post. The filename provided to the method is an absolute filename.

filter (@entries)

This function returns only the desired entries from the array passed in. By default it just returns the array back, so is just a place to check for plugins.

This can be overridden by plugins in order to alter the way the module filters the files. See PLUGINS for more details.

sort (@entries)

Sort @entries and return the new list.

Default behaviour is to sort by date.

static_mode($password, $on)

Sets static mode. Pass in the password to turn it on. Turns it off if it is already on.

interpolate($template, $extra_data)

Each template is interpolated, which means that variables are swapped out if they exist. Each template may have template-specific variables; e.g. the story template has a title and a body. Those are provided in the extra data, which is a hashref with the variable name to be replaced (without the $) as the key, and the corresponding value as the value.

By default, a different set of variables are available to each template:

All templates

These are defined by you when you provide them to new() or run()

blog_title
blog_description
blog_language
url
path_info
flavour

Story (entry) template

These are defined by the entry.

title

Post title

body

The body of the post

yr
mo
mo_num
da
dw
hr
min

Timestamp of entry. mo = month name; dw = day name

path

The folder in which the post lives, relative to the blog's base URL.

fn

The filename of the post, sans extension.

Head template

title

This method can be overridden by a plugin.

entry_data ($entry)

Provided with the entry data, which is an arrayref with the entry filename, relative to datadir, in the first slot and a hashref in the second. The hashref will have at least a date entry, being a UNIX timestamp for the entry. See the section on plugin entries.

Returns a hashref containing the following keys:

title

Post title

body

The body of the post

yr
mo
mo_num
da
dw
hr
min

Timestamp of entry. mo = month name; dw = day name

path

The folder in which the post lives, relative to the blog's base URL.

fn

The filename of the post, sans extension.

These should be returned such that it is true that

  $path . "/" . $fn . "." . $flavour eq $request_url

i.e. these components together are what was originally asked for. (Note that flavour is a variable available to templates but not returned by this method.)

head_data ()

Return the data you want to be available to the head template. The head is attached to the top of the output after the entries have been run through, so you have the data for all the entry data available to you in the arrayref $self->{entries}.

Example:

    my $self = shift;
    my $data = {};
    if(@{$self->{entries}} == 1) {
        $data->{title} = $self->{entries}->[0]->{title}
    }
    return $data;

foot_data ()

Return the data you want to be available in your foot template. This is attached to the output after everything else, as you'd expect.

USAGE

Quick start

To quick start, first you need to create a Blosxom object. You have to provide three parameters to the new method:

    datadir
    blog_title
    blog_description

The latter is likely to be dropped as a requirement soon.

Then you need to find some way of collecting a path and a flavour from the user. The original script used the URL provided by the web server. You provide these to the run method.

    use Blog::Blosxom;
    use CGI qw(standard);

    my $blog = Blog::Blosxom->new(
        datadir => '/var/www/blosxom/entries',
        blog_title => 'Descriptive blog title.',
        blog_description => 'Descriptive blog description.',
    );

    my $path = path_info() || param('path');
    my ($flavour) = $path =~ s/(\.\w+)$// || param('flav');

    print header,
          $blog->run($path, $flavour);

The above is a complete CGI script that will run your blog. Note that header is a CGI function. Don't print that if you're not using CGI!

Now that we know how to run Blosxom we can look at how to make entries.

Entries

To create an entry, create a plaintext file in your datadir with the .txt extension. The first line of this file is the title of the post and the rest is the body.

This post will be displayed if it is somewhere under the $path you provided to run, unless it is the 41st such file, because by default only 40 are displayed at once.

The txt part of all this is configurable in new.

Flavour

The flavour that you provide determines which set of templates are used to compose the output blog.

You may be wondering about the fact that the blog entry ends with .txt, but in the CGI script we have used the extension to determine the flavour. The file extension is ignored when mapping the path to the file system, so your path could feasibly match a single entry, which will of course be served on its own.

The template to be chosen is a file whose file extension is the current flavour and whose file name is the template in question. Have a look at the docs for the template function.

Writing these templates is the primary way you make your blog entries show up as decent stories. The other way is when you override the entry_data method and have the content of your entries moulded into some slightly better markup.

More information

The best source of information on this is the documentation for the methods themselves. Therefore, we provide the execution order so you can see exactly what is going on and figure stuff out that way.

new

Blosxom is an object-oriented thing now. This is so that you can subclass it to override any or all of the default functionality, which is kind of the point of Blosxom in the first place.

run

The original script found the path and flavour for you but this one lets you decide where they should come from, so you can integrate them into other applications if you wish.

entries_for_path

The next thing that happens is the path is searched for all entries, and this function simply returns them all. This function returns an array of each entry's filename and a bit of extra data alongside.

date_of_post

entries_for_path calls date_of_post to get that little bit of extra data.

filter

Then the entries list is filtered. This function is empty by default, intended to be overridden by plugins or subclasses.

sort

The remaining list is sorted. This is done by date by default, the date being ascertained during entries_for_path.

entry_data

This is one of the more powerful functions to reimplement. It returns as much data about the provided entry as is necessary for your use of Blosxom. The required return data are defined in the docs for this function; see also the PLUGINS section.

This is called for each entry that remains after filter is done and sort has ordered them.

template

This is called to get the data to give to interpolate. It simply returns the contents of the template file.

interpolate

This is the second of the more powerful functions. In this function, the raw templates have their variables replaced with their values. How that works is documented in the method's own documentation!

Each entry's data is given to its template through this function; occasionally while this is happening the date template is also given its data too.

head_data

This gets data to give to interpolate for the head template.

foot_data

This gets data to give to interpolate for the foot template.

The interpolated templates are aggregated into an array, onto which the head and foot templates are added at the end, allowing info about the whole page to be available to both the top and bottom of the page.

PLUGINS

Writing plugins for this new version of Blosxom is easy. If you know exactly what you want from your plugin, you can simply subclass this one and override the methods below. Alternatively, you can create files in some directory, and then configure your Blosxom object to use that directory as your plugins directory.

In order to use a plugin directory, the package name in the plugin file must be identical to the filename itself. That is because the blosxom engine uses the filename to know which package to give to require. The only thing that deviates from this rule is that you can prepend the filename with any number of digits, and these will be used to load the plugins in order. The order is that returned by the sort function, so it is recommended all your numbers have the same number of digits.

In order to disable a plugin, simply alter its start subroutine to return a false value instead of a true one.

Starting a plugin

As mentioned, it is necessary that your plugin's filename is the same as the package defined in the plugin. Please also include a bit of a comment about what your plugin does, plus author information. The community would appreciate it if you would use an open licence for your copyrighting, since the community is built on this attitude. However, the licence you use is, of course, up to you.

The smallest possible plugin (comments aside) is shown below, and its filename would be myplugin.

  ## Blosxom plugin myplugin
  ## Makes blosxom not suck
  ## Author: Altreus
  ## Licence: X11/MIT

  package myplugin;

  sub start{1}

  1;

Hooks

Every single hook in the plugin will be passed the Blosxom object as the first argument, effectively running the function as a method on the object itself. This is shown as the $self argument.

In all cases, the first plugin that defines a hook is the one that gets to do it. For this reason you may find that you want to use the method above to decide the order in which the plugins are loaded.

Also in all cases, except where a true/false value is expected, simply not returning anything is the way to go about deciding you don't want to alter the default behaviour. For example, if you wanted to take the date of a post from the filename, then in the cases where the filename does not define a date, you can simply return; and processing will continue as if it had not defined this functionality in the first place.

Also also in all cases, you can get the return value of the default method by simply calling $self->method. This is helpful if you want to slightly but not wildly alter the output, such as adding extra information to the same set of data. Obviously this is not true of start and skip, since these are not methods on the class in the first place.

start ($self)

The start subroutine is required in your module and will return either a true or a false value to decide whether the plugin should be used.

You can use the values on the Blosxom object if you need to make a decision.

  sub start {
      return shift->{static_mode}; # Only active in static mode
  }

template ($self, $path, $comp, $flavour)

    $path:    path of request, filename removed
    $comp:    component e.g. head, story
    $flavour: quite obviously the flavour of the request

This returns the template to use in the given path for the given component for the given flavour. The requested filename will not be part of the path, if the requested path matched an individual entry.

The default template procedure is to check the given path and all parent directories of that path, up to the blog root, for the file $comp.$flavour, and to use the first one found.

Since it is pretty easy to find this file based on just the filename, you'd think this method has something a bit more difficult to do. In fact this function returns the content of that file, ready for interpolation.

This function implements the functionality of both the template hook in the original blosxom script, as well as the hooks for the individual templates themselves. That means that if your original plugin defined a new template for, e.g., the date.html in certain situations, this is where you should now return that magic HTML.

entries_for_path ($self, $path)

    $path: The path as provided to run()

This returns an array of items. Each item is itself an arrayref, whose first entry is the filename and whose second entry is a hashref. The hashref is required to contain the 'date' key, which specifies the date of the file.

That's pretty complicated. Here's some more info. When Blosxom is converting entries into stories it needs to know what entries exist under a given path, and the date of each entry. It therefore needs an array of arrayrefs, each arrayref representing one of the entries found under the given path, in the format [$filename, $date].

However, since it is envisioned you might want to add more metadata about the entry, the date part is a hashref, so you can add more stuff to it if you want to. So now the format is [$filename, { date = $date }]>.

The filename returned is the path and filename of the entry file, relative the datadir. The input $path is not necessarily relative to anything, because it will be the path the user requested. Thus, please note, it may contain the year, month and day of the requested post(s) and not a path to any real file or directory at all.

It is worth noting that you can override how Blosxom decides the date of the file by implementing the date_of_post method instead of this one.

date_of_post ($self, $post)

    $post: path and filename relative to blog root

You should return an arrayref where [0] is the 4-digit year, [1] is the 2-digit month, and [2] is the 2-digit day. This is not checked for validity but will probably cause something to blow up somewhere if it is not a real date.

filter ($self, @entries)

    @entries: an array of all entries, exactly as returned by entries_for_path

This function does nothing by default and is a hook by which you can scrupulously filter out posts one way or another. You are given the output of the entries_for_path method above, and you should return an array in exactly the same format, except having removed any entries you do not want to show up on the generated page.

sort ($self, @entries)

    @entries: an array of all entries, exactly as returned by entries_for_path

You can override the default sorting mechanism, which is by date by default, by implementing this method. It is advisable to empty your date template if you do this, because the date template is inserted every time the date changes as processing goes down the list.

A future release may see the date template replaced by a divider template, which would be configurable and merely default to the date.

skip

The skip function is a feature from the original blosxom. Setting it to return a true value will cause the Blosxom object to stop just before anything is actually output. That is, it will find all the entries and pull in the templates but not do anything with them if any active plugin makes this function return true. This is useful if, e.g., your plugin issues a redirect header.

interpolate ($self, $template, $extra_data)

This is where you can override the default way in which the variables are interpolated into the template.

The extra data will be a hashref of var => val, var being the variable name to interpolate, without its $.

If you don't call the parent function for this, be aware that there is an option called $self->{require_namespace}, which means that only fully-qualified variables will be interpolated. You should honour this if you intend anyone else to use your plugin.

The three functions entry_data, head_data and foot_data return simple hashrefs that are interpolated in this function.

You should also be aware that there are several "global" variables, available to all templates, that are not returned by any of those functions. They are hard-coded in the default implementation of interpolate, so it is probably for the best if you defer to this method.

The section on usage will probably help here.

entry_data ($entry)

This is provided with an arrayref as returned by entries_for_path, and should return a hashref with the keys as described above, in the method's own documentation. Briefly, they are

 title body yr mo mo_name da dw hr min path fn

You may also provide any extra keys that your own templates may want. It is recommended that you use next::method to get the default hashref, and add more things to it.

See the section on usage for how all this works and thus to get a better idea of what you should or should not be overriding.

head_data

This function is called to get the data required for the head template. By default, only the global variables are available in the head template.

You may override any global variable by returning it as a key in this hashref, or you can add to the set of available variables instead. See the section on usage for how all this works.

foot_data

This function is called to provide data to the foot template. By default it returns an empty hashref.

You may override any global variable by returning it as a key in this hashref, or you can add to the set of available variables instead.

See the section on usage for how all this works.

AUTHOR

Altreus, <altreus at perl.org>

TODO

Most existing plugins won't work because I've changed the way it works to the extent that the same functions don't necessarily exist. However, existing plugins should be fairly easy to tailor to the new plugin system. I didn't think this was too important a feature because a good number of the plugins at the blosxom plugin repository are 404s anyway.

The plugin system is like the original blosxom's, where the first plugin to define a function is the boss of that function. Some functions may benefit from the combined effort of all plugins, such as filtering. That is something to think about in the future.

Static rendering is not yet implemented. Frankly I don't think I can be bothered.

A comment system is common on many blogs and I think I will write a separate plugin to make this easy.

BUGS

Bug reports on github, please! http://github.com/Altreus/Blog-Blosxom/issues

You can also get the latest version from here.

SUPPORT

You are reading the only documentation for this module.

You should check out the examples folder. If you don't know where that is, either check $HOME/.cpan/build or browse/clone the Git repository http://github.com/Altreus/Blog-Blosxom

If you're brave you can see if I'm around in #perl on irc.freenode.com. Your use case will help me refine the module, since in its initial state it was merely a rewrite of the original script for my own sanity.

ACKNOWLEDGEMENTS

Thanks to the original author of blosxom.cgi for writing it and giving me code to do much of the stuff it did.

http://blosxom.com

Thanks to f00li5h on that irc.freenode.com (and many others!) for being the first person I mean cat other than me to use it and therefore have lots of things to say about it.

LICENSE AND COPYRIGHT

This module is released under the X11/MIT licence, which is the one where you use it as you wish and don't blame me for it. I hope the author of the original script does not take this badly; if the author sees this and wishes me to change the licence I am happy to do so.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 688:

You forgot a '=back' before '=head2'