« Previous | Next »

Metaprogramming Pascal with Mustache in Smalltalk

06 Feb 2016

A while back I wrote about command line scripting of Pharo to backup an SQLite database, specifically, this server's iptables log. Eventually I wrote a program in Pascal to archive and rollover the log database. Reason being, using the excellent Free Pascal compiler, I link SQLite statically into the final executable, meaning I only deploy a single binary executable, which is a tad more convenient than deploying the Pharo VM, image, sources, and script files.

Well, not quite just one file.

My program, iptlb, uses the SQLite online backup API to back up the running log database to an archive database file, then "backs up" an empty template database to the running log database file, effectively overwriting it. Because I want the flexibility to change the database schema during each backup run, iptlb creates a new template database each time it is invoked. Because I also want just one Pascal source file, I want to store the database schema in the source file itself, so that the schema gets version-controlled along with the Pascal code.

However, Pascal does not support multi-line string literals:

(* This Pascal code is invalid. *)
  dbSchema = '
    create table x (xk int, xv varchar);
    create table y (yk int, yv int);

This means that I cannot embed the database schema into iptlb's Pascal source directly as a multi-line string. In production, I have to also deploy the database schema SQL as a separate file which is read by iptlb to create its template database.

There is a workaround in Pascal for lack of multi-line strings, as follows:

  sl: TStringList;
  dbSchema: String;
  sl := TStringList.create;
  sl.add('create table x (xk int, xv varchar);');
  sl.add('create table y (yk int, yv int);');
  dbSchema := sl.text; // dbSchema is now the multiline string.

This looks like a templating thing. And that calls for Mustache!

After fiddling around, I created a simple class PMMultiLineStringTemplate which wraps the work with Mustache. Here's the motivating use case:

| tmpls mst |

"The Mustache template of the Pascal code."
tmpls := '
function {{functionName}}: String;
  sl: TStringList;
  sl := TStringList.create;{{#stringList}}
  {{functionName}} := sl.text;

mst := PMMultilineStringTemplate new.
mst atAttribute: 'functionName' putValue: 'dbSchema'.
mst template: tmpls.

FileSystem disk workingDirectory / 'ulog-schema.sql' readStreamDo: [ :rs |
    rs ascii.
    [ rs atEnd ] whileFalse: [
        | x |
        x := rs nextLine.
        (x size > 0) ifTrue: [
            mst atAttribute: 'stringList' withSubKey: 'sline' putValue: x ]]].

FileSystem disk workingDirectory / 'dbschema.pas' writeStreamDo: [ :ws |
    ws ascii;
        lineEndConvention: #lf; 
        nextPutAll: mst value ]

Copying the output of dbschema.pas (generated with a proper schema as above) into iptlb.pas, I now have one source file iptlb.pas embedding the SQL schema, and I deploy one binary executable iptlb only.

Tags: metaprogramming, Pascal