Generating RSS feeds via BeepBeep

September 20, 2009. Filed under beepbeep

One of the joys of working with a small micro-framework like BeepBeep is that you can spend a couple of days working with the code and then have a pretty firm grasp of the entire project. One of the downfalls is that you'll often need to reinvent a number of components that you'd get for free with more mature frameworks like Django. Today's example of this was generating an RSS feed.

Fortunately, it turns out that RSS feeds are rather simple. They are simply XML documents in this format:

<rss version="2.0">
    <title>Test 1</title>
    <description>a channel</description>
    <lastBuildDate>Sun, 20 Sep 2009 22:00:34 GMT</lastBuildDate>
        <title>Item 1</title>
        <description>an item</description>
        <pubDate>Sun, 20 Sep 2009 22:00:34 GMT</pubDate>
        <title>Item 2</title>
        <description>an item</description>
        <pubDate>Sun, 20 Sep 2009 22:00:34 GMT</pubDate>

I put together a BeepBeep controller template (click here for the full code for generating RSS feeds in BeepBeep) which does just that. I won't duplicate the implementation here, but I will take a quick look at usage. The crux of generating the RSS feeds is this implementation of handle_request/2:

handle_request("index",[]) ->
    Channels = [{[{title, "Test 1"}],
        [[{title, "Item 1"}], [{title, "Item 2"}]]}],
    {text, rss(Channels), "application/rss+xml"}.

Here Channels is a list of 2-tuples where the first element is a property list describing the channel, and the second element is a list of property lists which describe the items in the channel. A more complex usecase would look like:

-define(RSS_ITEM_DEFAULTS, [{title, "my item"}, {link, ""}, {description, "an item"}, {pub_date, ""}, {guid, 0}]). 18. -define(RSS_CHANNEL_DEFAULTS, [{title, "my channel"}, {link, ""}, {description, "a channel"}, {language, "en-us"}]).

entries() ->
    Entry = [{title, "RSS and BeepBeep"},
                   {link, ""},
                   {description, "An entry on..."},
                   {pub_date, "Sun, 20 Sep 2009 25:00:00 GMT"},
                   {guid, ""}],
entry_channel() ->
   Channel = [{title, "Entries"}, {link, ""}, {description, "Entries are fun!"}],
    % Also supports {language, "en-us"}
   {Channel, entries()}.

tags() ->
    Tag = [{title, "Erlang"},
                {link, ""},
                {description, "Entries on Erlang"},
                {pub_date, "Sun, 20 Sep 2009 25:00:00 GMT"},
                {guid, ""}],
tag_channel() ->
   Channel = [{title, "Tags"}, {link, ""}, {description, "Tags are useful?"}],
    {Channel, tags()}.

%% Served at /rss/, assuming controller is named rss_controller.erl
handle_request("index",[]) ->
        {text, rss([tag_channel(), entry_channel()]), "application/rss+xml"};
%% Served at /rss/channel/tag/ and /rss/channel/entry/,
%% assuming the controller is named rss_controller.erl
handle_request("channel",[Channel]) ->
    case Channel of
        "tag" ->
            {text, rss([tag_channel()]), "application/rss+xml"};
        "entry" ->
            {text, rss([entry_channel()]), "application/rss+xml"}

As per the above example, usage is simple--a bit verbose--but hopefully enough to get the job done. In some ways, I find it more flexible and intuitive than Django's syndication framework, but perhaps that is to be expected as the syndication framework is vastly more featureful. I'll readily admit it isn't a fair comparison.

The general topic of how to develop reusable libraries for BeepBeep is something I've been pondering a bit lately. Thus far my approach has been to be backend agnostic where reasonable, and to otherwise standardize on CouchDB for persistence. I'll probably refine my approach here (storage abstraction layer?) as I keep using BeepBeep as part of my platform for developing web apps.