Code:

Arena v1.0

Rambling Preamble

arena can probably be called a content-management system, in that it manages content for a website, but that title sounds grandiose given that the overall goal for arena was to be a simple and elegant intermediary between the content of a website and the layout of that website. That likely sounds no less grandiose, but it does mean that arena could be called a blogging tool, or a tool to create and manage forums, or a way to display information from a database, or nearly anything else.

As with any good content-management system, arena separates the content of your site from its layout. You can change the text of a given web page without rewriting the HTML that makes up that page; in fact, you do not need to know HTML or CSS or JavaScript at all in order to create content for your site (some familiarity with Markdown is helpful, however). You can likewise change the layout of your site without the need to re-flow or manipulate the content to fit into the new design. arena will generate the appropriate code.

The overall design goals of arena are always simplicity and elegance. Simplicity means that the code is tight and efficient; speed trumps size. The code does no more than it needs to do, but it is flexible enough to leave room for expansion. Absolutely nothing is hard-coded.

Elegance means the code is easy to read, and so is the solution created by that code. The Markdown syntax for posts was chosen because it fits both criteria, and so integrates nicely with overall tone of arena.

It is for the above reasons that arena is coded in Python, even though it existed in PHP in a previous incarnation. I disagree with some of Python's design quirks, but overall the scheme provides for code that is both simple and easy to read, whereas PHP tends to get messy like C++ gets messy -- unless you're very careful. Additionally, because the result of compiling every script is saved, Python generally performs faster than PHP (except where tools such as the Zend Optimizer are incorporated -- but the last host I used did not have such a feature enabled, and it is built into Python).

Disavowal and Disclaimer

It is worth noting that, although I am documenting arena and making it available to the general public, I will not provide support for the code! You use it at your own risk. arena is a means to an end for me, and while I'm happy if it helps other people, I have other goals to achieve, of which arena was only a first step. I expect that, if you intend to use the code, you will have more than a passing familiarity with Python or you will be willing to learn in a trial by fire if such a thing becomes necessary. If you are thoughtful and careful, however, you should be able to get arena to do what you want it to do; the code is flexible enough to allow for additional features to be added without breaking everything.

Don't get me wrong, though: I would like to hear about your experiences with arena, and may consider your really good idea for an additional feature, but unless it's a major bug that threatens a catastrophe of dinosaur proportions (either an extinction event or a Jurassic Park event -- take your pick), I expect that you will figure out the problem without me.

Of Channels and Views

Some definitions might be in order before we continue:

  • Content means any information that can be retrieved from a data source, including (but not limited to) a database
  • Layout means the way in which the content is displayed

arena addresses these concepts by means of the channel and the view.

A channel can be thought of as the answer to the questions, "What do you want to display here and from where are you getting the information?" I use channels on this site as categories of information: home, coding, search, etc. -- but they are also used to fetch a specific set of information for display, such as determining which channels are shown in the navigation sidebar. A channel may help to define a particular query that is executed against a MySQL database, or it can link to a routine that obtains content from some other medium. The data returned is then fed to a view so that it can be displayed. arena is flexible enough, however, that a database need not be used; the information can come from almost anywhere.

If a channel is the answer to one set of questions, then a view is the answer to another -- namely, "How do you want to display the information you have?" I use views on this site to control how, where, and how much of the information returned by each channel is displayed. These views are controlled by Jinja2 templates, but arena is flexible enough to allow another display scheme to be used. Python itself could be directly used to render views, but you will need to build the Python view manager yourself. I consider it good practice to keep the back-end processing for the site as far away from the presentation of the site as possible, which is why AnArenaView goes through so much trouble to calculate and establish a large number of variables for each jinja2 template that is displayed.

Now that you have been armed with knowledge of channels and views, you are ready for a general overview of how arena works.

How it Works

This is a general overview of how arena works:

  1. An HTTP request is routed to viewpoint.py by the web server. This request may contain query parameters, such as channel and view, or it might contain a file or other data from a form that was presented to a user. Whatever the content of the request, viewpoint.py is simply the entry point; it does little more than create an instance of AViewpoint, which it then executes.
  2. As a subclass of AnArenaMediator, AViewpoint first constructs a log file, where status and debugging messages will be placed. It then attempts to connect to the data source specified in the local configuration file. Depending on the data source, additional information may be required in order to make this connection, such as a user name, password, etc. If the connection fails, an error message will be both logged and displayed.
  3. After successfully connecting to the data source, AViewpoint next attempts to fetch content for display. The fetchContent() method calls openChannel(), which will query the data source for a channel that defines how to access the content to be displayed. If channel was one of the query parameters specified by the client, that value is used; otherwise, the default channel specified in the local configuration file is used. The channel record is encapsulated in an instance of AChannel.
  4. The channel record specifies a content provider: the name of a module and a class within that module. (Here you can see arena's plug-in system in action. Any Python module can be used to produce content, provided that it resides in the Plugins folder and that it returns an iterable Python object.) The specified module is loaded, and an instance of the specified class created.
    1. For the majority of channels displayed by this site, the content provider is AnArenaParsedContentProvider. The content provider may refer to the metadata stored in the channel record for information on what data to retrieve. AnArenaParsedContentProvider is designed to look for a meta-field named onFetchContent; this is a fragment of Python code that, when executed, will help to build the MySQL query that will be used to retrieve content from the data source.
  5. The content provider retrieves content. AnArenaParsedContentProvider wraps each row returned by the MySQL in a class that will parse the content before it is displayed. The class that is used is specified by the value of another meta-field associated with the channel record: contentHandler. These are instances or descendants of AnArenaPost.
    • To provide syntax highlighting in Markdown code blocks, I created a plugin called AnArenaPostWithHighlighting and simply changed the value of the contentHandler meta-field. Voila! Posts now include syntax highlighting.
    • AnArenaParsedContentProvider sets itself up as a Python iterable object and returns itself; this behavior allows each row returned by the data source to be wrapped in an instance of AnArenaPost.
  6. With content retrieved, AViewpoint now attempts to render the view that will present that content to the user. The channel record contains a meta-field -- viewProvider -- that specifies which view provider to use. As with the content provider above, this is simply a plugin that will receive the iterable content and return a string. Any Python module can be used to render content, provided that it resides in the Plugins folder, will accept an iterable Python object, and will return a string. The view provider is loaded and invoked, and the content is passed to it for rendering.
    • For all of the channels displayed by this site, the view provider used is AnArenaView.
  7. AnArenaView is a subclass of AJinja2View and so is designed to work with jinja2 templates. The channel record may contain a meta-field named viewParameters which can be used to specify the view to display; alternatively, this view can be specified as part of the HTTP request sent by the client. In either case, this "view" is actually the name of a folder that exists within the views subdirectory; inside of this folder are the templates that will be used to format the content for display. If no template is specified as part of the HTTP request from the client, and template is specified as part of the viewParameters meta-field, then AnArenaView will look for templates within the view folder that are named index.tpl, index.tpl.html, index.tpl.htm, or index.xml. If none of these can be found, an error is generated.
  8. When a template is found within the specified view, it is parsed and executed by the jinja2 system. A large number of variables are provided for the templates to use; the most important of which is Content; this represents the iterable content provided by the content provider. If the items in Content are instances of AnArenaPost, then the template may choose to invoke the parsedContent() method on each of them; this will parse the content in the post. This is how posts that contain Markdown-formatted content are processed for display.
  9. The view provider returns a string which is outputted to the buffer used for client output. This buffer is then sent to the client and displayed by the web browser.

Whew! What a long post! Nevertheless, that's how it all works, and I will address some of the specifics -- such as the Markdown parser -- in future posts. You are welcome to browse the documentation for more detail, or you can download the source and see it firsthand.

Thanks for reading!