November 26, 2014 View Source on GitHub

A light, extensible Perl CMS by Dave Pagurek



Trestle config

Here is an example index.fcgi:

use Trestle;
use Trestle::Theme::Pahgawks;
use Trestle::Plugin::CodePrettify;
use Trestle::Plugin::GoogleAnalytics;
use Trestle::Plugin::YouTube;
use Trestle::Plugin::DisqusComments;
use Trestle::Plugin::ImageCaption;

my $site = Trestle->new({
    dev => 1,
    root => "http://localhost/Trestle",
    theme => Trestle::Theme::Pahgawks->new(),
    plugins => [
    cacheLife => 0


If dev is enabled, CGI::Carp will send warnings and fatal errors to the browser.

cacheLife is the number of hours before a cached page is set to be rerendered.

Apache config

In .htaccess, uncomment the respective line based on your system setup:

#Use this if your Apache setup supports mod_fcgid
#AddHandler fcgid-script .fcgi

#Use this if you want to run Trestle as normal cgi
AddHandler cgi-script .fcgi


Here is an example page to demonstrate page structure.


    "title": "Hooked on a Feeling",
    "thumbnail": "%root%/content/images/hasselhoff-thumbnail.jpg",
    "date": "2014-08-27",
    "youtube": "PJQVlVHsFF8"

I made this song to represent the juxtaposition of societal norms and the microcosm of cultural phenomena exhibited through the greenscreen. It is very meaningful and artistic.

The boat part is actually for real though.

Metadata is written as a JSON object in an HTML comment at the top of the page.

%root% will be replaced by the root directory as defined in index.fcgi

Inline YouTube video urls will be replaced with an embedded player thanks to Trestle::Plugin::YouTube

Images in the form <img src="img" full="img-full" caption="Caption"> will be replaced by a captioned image thanks to Trestle::Plugin::YouTube


Here is the structure of an example plugin:

package Trestle::Plugin::Example;

sub new {
	my $class = shift;
	my $self = {};

	$self->{pages} = 1;
	$self->{categories} = 1;
	$self->{archives} = 1;
	$self->{index} = 1;
	$self->{error} = 1;

	bless $self, $class;
	return $self;

sub content {
	my ($self, $content, $page) = @_;

    $content =~ s/foo/bar/ig;

	return $content;


Set $self->{pagetype} in sub new to register the plugin for that type of content.

In the content sub, you can do what you want with the $content variable and then return the new page content.


Here is the basic structure of a theme:

package Trestle::Theme::Example;

use strict;

use lib "../..";
use Trestle::Theme;

sub new {
    my $class = shift;
    my $self = {
        dir => "Trestle/Theme/Example",
        theme => Trestle::Theme->new()

    bless $self, $class;
    return $self;

sub content {
    my ($self, $page) = @_;

    return $self->{theme}->render("$self->{dir}/template/content.tmpl", {
        themeDir => $self->{dir},
        title => $page->meta("title"),
        category => $page->meta("category"),
        content => $page->content

sub dir {
    my ($self, $category) = @_;

    return $self->{theme}->render("$self->{dir}/template/dir.tmpl", {
        themeDir => $self->{dir},
        title => $category->info("name"),
        name => $category->info("name"),
        pages => $category->info("pages")

sub archives {
    my ($self, $root, @cats) = @_;
    my $source = "";

    return $self->{theme}->render("$self->{dir}/template/archives.tmpl", {
        themeDir => $self->{dir},
        title => "Portfolio",
        categories => \@cats


sub error {
    my ($self, $error, $root) = @_;

    return $self->{theme}->render("$self->{dir}/template/error.tmpl", {
        themeDir => $self->{dir},
        title => "Page Not Found",
        error => $error

sub main {
    my ($self, $page) = @_;

    return $self->{theme}->render("$self->{dir}/template/content.tmpl", {
        themeDir => $self->{dir},
        title => $page->meta("title"),
        content => $page->content


A theme uses HTML::Template-style templates for .tmpl pages. Read the documentation to see how to write templates.

Themes have subs for each of the types of pages that potentially need to be rendered.