Incremental development

As a parallel to my "interactive development" essay, I thought I'd write one on the joys of incremental development. As briefly mentioned in the other essay, incremental and interactive development complement each other quite well.

To explore this, I thought I'd put down the development of a new, hopefully slicker, web interface for the essays (as-is, it's all done with static HTML and a text editor, it works fine for the volume of updates that I have, but I thought, maybe, that I could automate some things and maybe add feedback on the site and such-like).

Stage one, where we decide to go forth

First, generally decide on "what's in, what's out and what's maybes".

What's in is "the Publish concept". The structure I use for the essays file tree is "one essay per subdirectory", within each subdirectory, there's one file per version of the essay, named dirname-YYYYmmdd.html and (once published) the latest version also has a sym-link from index.html. This means we can programatically enumerate what has and hasn't been published. We'll not use that, however, and rather opt for having a file listing what has and hasn't been published. We'll also make a shell-scrtipt to manage publication. The main reason for using a file is because it's much simpler (read a file as compared to traverse a directory structure).

We also need to have at least one test-essay listed as published, so we run the publication script (with some debug output listed):

$ ./publish pinc
Make /home/ingvar/public_html/essays/PUBLISHED
Trying to publish pinc
Published pinc

Next problem, we don't want to re-load the publication file every time we need to check against "has been published" (while, again, it probably won't be a problem with expected current loads, it's always good to have at least a modicum of scalability in view). Having written a fair bit of code for checking file dates in C, I know of stat(2) and a bit of introspection shows we have a similar function in the SB-POSIX package:

CL-USER> (apropos "STAT" :sb-posix)

...
SB-POSIX:FSTAT (fbound)
SB-POSIX:STAT (fbound)

A quick function to re-load the "published essays" state, on an as-needed basis:

(defun freshen-publish ()
  (let ((stat (sb-posix:stat *publish-file*)))
    (when (> (sb-posix:stat-mtime stat) *publish-date*)
      (setf *published-essays* nil)
      (setf *publish-date* (sb-posix:stat-mtime stat))
      (with-open-file (publish *publish-file*)
	(loop for line = (read-line publish nil publish)
	      until (eql line publish)
	      do (push (string-trim '(#\Newline) line)
		       *published-essays*))))))
and testing reveals that, indeed, it seems to do the job.

We also need a way to display essays (we'll have this as a separate function, then wrap other layers on top; we'll need a dispatch handler for Hunchentoot and later on, we'll tart the essay-display up with some standard links and maybe comment page(s)). Easily done, start with a function to determine if teh essay has been published, then construct a pathname to the index.html for the essay, loop through that file and blast it to *standard-output* (later on, we COULD capture the data, cache it and use file-modification times to display the latest, but for now this is good-enough).

Getting pages displayed

Being eager to get to actually test things, I have carefully omitted reading any documentation and instead relied on half-finished examples and idle web-surfing. In this case, I had a bit of a stumbling block. Something, somewhere, was not co-operating. After a bit of help, I was pointed in the right direction, disabled the built-in error handling and went forth and upgraded one package, after which things worked.

Anyway, with that sorted, it was time to start playing around. It was quite simple to write a dispatcher. The basic dispatcher looks something like:

(defun essay-dispatcher (request)
  (setf *last-request* request)
  (let ((uri (hunchentoot:request-uri request)))
    (cond ((string= uri "/") 'essay-lister)
	  ((= (count #\/ uri :test #'char=) 2) 'essay-handler)
	  (t nil))))

That's enough to give us the top-level listing and a basic display of an essay. Next up, complicate things. The current essay site uses a definition list to list the essays, with the definition term being the essay name and the actual definition data being a short description of the essay. Of course we want to keep that. So, let's complicate things!

More state! More state!

At the moment, our *published-essays* list contains a list of names, no more, no less. Each name is a string, naming the directory the essay lives in. This needs to change, so let's go classful on the essay site.

We'll need some convention to keep a stable storage of what's currently in the top-level index.html. A file is probably a good choice. Let's call it DESCRIPTION, a nice, obvious name. It should have a nice, simple format. Let's use the first line as a title and the rest of the file as the description. Nice and simple.

Next up, we need to go through everything else that manipulates essays and fix them up, too. Of primary interest are the two functions essay-published-p that checks for the publishedness of a given essay and essay-index, a function tha returns the pathname to a given essay. Of secondary interest is the function that generates the URL (well, OK, URL fragment) for a given essay.

That's quickly fixed and we're ready to test. Things almost work (initially, I didn't get any title or link to the essay, a missing (str ...) in the anchor generation, "ooops").

More script mods

Now that we have more state for published essays, let's make sure that our publication script knows this. We add a (very simple) check:
checkdescr ()
{
  if [ ! -r $1/DESCRIPTION ]
  then
    echo -n "Title: "
    read title
    echo "Enter a description, finish with ^D"
    echo $title > $1/DESCRIPTION
    cat >> $1/DESCRIPTION
  fi
}

We also make sure that the script executes with the essay base directory as working directory. Then, to test everything, we try publishing a new essay.

head$ ./publish copyright
Title: Copyright and linking
Enter a description, finish with ^D
A short essay on copyright and linking. Needs fleshing out but does,
  at the moment, at elast present arguments for and against "linking is
  derivation" and there are good arguments both for and against.
Published copyright

Then, off to the web interface. Disaster, it's not showing up. That'd be because the essay-listing handler isn't doing its job. Quick fix, then more testing. Looking at "more than one listed", it's obvious that the current list order is "reverse order of listing in the PUBLISHDE file". It's certainly ONE order, but not necessarily the best one ever. A better order would be either "oldest on top" or "newest on top". As luck has it, it's not that hard to accomplish this, we can simply check the modification time of the index.html symlink and use tha as an approximate "last modification", then make sure we sort the project list.

At this point, it is time to factor out "get mtime from a pathname" (prior to this, it was handled in-line inside the "refresh the list of puiblished essays" function) and force a reload of the essay list (simply decrement the last modification time stamp). With that in place, we're set to rock, again.

More mods

So, we want a standard footer on each page. As-is, they're stored as full HTML pages, so what we'll do is find (and isolate) the <body> container and store only that in the essay class object, then it's trivial to reconstruct a custom page-foot.

From here

Next up, decide if I want to go the mod_lisp route, run hunchentoot on the loopback and use (sufficiently locked-down) proxying in Apache or, possibly, run hunchentoot on a separate IP and simply change DNS.

Next up, comments. We'll continue using gently structured files for our stable storage. It's not as neat as using a database, but in a prototype stage it's certainly faster to implement and faff around with. Couple of new classes (one for each comment, one for a comment-container), couple of new functions (basically to read and, eventually, write comments). Once that's in place, it's time to start working on "add comments" code.

First place of the comment handling is in place, we now handle "read comments from the file system" and there's also code to write a comment to the file system. Refreshing the comment list also seems to work. So, comment-adding. First step is to design a form for adding comments. Fairly easy, as these things go.

Next step is proper handling of "let's post a comment". Took a bit longer than expected, but comments can now be posted, end up stored and there's a minimum of checks on things.

Next, we need to fix things up so that we don't accidentally inject any stray HTML. That would be Bad. Of course there's a slight problem, in that our previously preciously-generated newlines disappeared, as we escaped HTML. Not to worry, terpri comes to the rescue!

This is one of Ingvar's essays

All fields below are mandatory, your email address will not be displayed by the site. All comments are sent to a moderation queue, so do not be surprised that it doesn't show up immediately.

Name:
Email (will not be displayed):
Comment: