Frontend Projects
Pimprenelle
This is a little website I wrote some 20 years ago, for playfully exploring a picture catalog. It has a somewhat interesting architecture; there are only two files to it:
pimprenelle.cgi
pimprenelle.db
The .db file is an SQLite database.
It contains images (SQLite blobs), localised string resources and page templates, based on a parent-child hierarchy in the tables. So for example, the page template can query all the images and French captions for a particular page, and will know which page to open when the user clicks on an image that âopensâ a set, or when a back navigation button is pressed.
The CGI script is written in Perl and looks like the code below. All the rest of the app is actually Perl code stored in SQLite text columns. I was writing a website generator called âRecapâ based on this DB centered code, and this website is the only example.
What I also still like about Recap is that it handles sessions using query parameters, not cookies. It also did its own session management and client logging. The latest backup of the site I found when preparing this post still had a full 6 year history of client requests, although the server logs where long gone.
#!/run/current-system/sw/bin/perl
use strict;
use warnings;
use CGI qw(:all);
use CGI::Carp qw(fatalsToBrowser set_message);
use DBI;
use HTML::Entities;
use Digest::MD5 qw(md5_hex);
use GD;
my $db_debug;
my $dbh = open_dbi();
my (%globalCode, %globalScalar);
BEGIN {
sub handle_errors {
my $msg = shift;
print "<h1>Carp</h1>";
print "$msg";
print "<h2>globalCode Array</h2>";
foreach (sort keys %globalCode) {
print $_ . ':' . $globalCode{$_} . "<br>\n";
}
}
set_message(\&handle_errors);
}
#
# Pull in subroutine definitions from the database.
#
my $sth = sql_query_handle(
qq{
select string_id, literal
from strings
where stringtype_id = 'SUBROUTINE'
order by string_id
}
);
while (my @row = $sth->fetchrow_array()) {
$globalCode{$row[0]} = eval $row[1];
if ($@) {
if (exists $globalCode{'aardvark'}
&& ref($globalCode{'aardvark'}) eq 'CODE') {
$globalCode{'aardvark'}->($row[0], $@);
}
else {
die "Error loading subroutine '$row[0]': $@";
}
}
}
$sth->finish;
#
# Run the application's main subroutine.
#
if (exists $globalCode{'main'}
&& ref($globalCode{'main'}) eq 'CODE') {
$globalCode{'main'}->();
}
else {
die "Pimprenelle database does not contain a valid 'main' subroutine";
}
$dbh->disconnect
or die "Cannot disconnect from the database";
#
# Open the Pimprenelle SQLite database.
#
sub open_dbi {
my $dbh = DBI->connect(
"dbi:SQLite:dbname=/srv/pimprenelle/pimprenelle.db",
"",
"",
{
RaiseError => 1,
AutoCommit => 1,
}
);
return $dbh;
}
#
# Prepare and execute an SQL query.
#
sub sql_query_handle {
my ($sql, @params) = @_;
my $sth = $dbh->prepare($sql);
if (defined $sth) {
my $n = 1;
foreach my $param (@params) {
$sth->bind_param($n++, $param);
}
$db_debug .= "\n$sql\n";
$sth->execute()
or die "Couldn't execute statement: "
. $sth->errstr
. $db_debug;
}
return $sth;
}