Wednesday, April 26, 2000
Woo hoo! Bug free the first time through …
It's always nice when code I write works the first time.
mod_blog
advancing yet some more. I was going to embed the HTML
within the module, but when I wrote some sample code (to make the archives
on the current site) it just got real messy real quick and I didn't even
bother finishing it up.
Instead, I quickly wrote code to process template files. This side works
pretty much like I think I'm going to end up doing it. I set up a
subdirectory containing the templates, each file containing just a portion
of a much larger page. In effect, each file is a chunk of HTML
code that is processed. During the processing, anything between double hash
signs is taken as the name of a callback function.
So for example, in the HTML
code I have:
<html> <head> <title>##title## - The Boston Diaries - Captain Napalm<title> </head>
And the callbacks are currently defined in C as such:
static void archive_callback(FILE *fpout,void *data) { struct tm *ptm = data; char date[BUFSIZ]; strftime(date,BUFSIZ,"%B %Y",ptm); fprintf(fpout,"%s",date); } /***************************************************/ void do_archive( FILE *fpout, int year, int month, int stday, int endday) { static struct chunk_callback cb = { "title" , archive_callback }; struct tm thisday; /* code to set thisday properly snipped */ ChunkProcess(fpout,"archivehead",&cb,1,&thisday); ChunkProcess(fpout,"bostontitle",NULL,0,NULL); ChunkProcess(fpout,"bartitle",&cb,1,&thisday); /* code to generate links for each day */ ChunkProcess(fpout,"end",NULL,0,NULL); }
The call to ChunkProcess()
takes an output file, the name of the
chunk to display, a structure declaring the callbacks, the size of that
array, and an extra pointer that is passed to the callback, in this case, to
a struct tm *
to the date we're processing.
The only thing I may change is the way callbacks are registered, but the mechanics certainly work.
Walkthrough
Now, how did I go about writing 145 lines of bug-free C code to implement
the chunk mechanism? Easy. I broke it down into simpler steps. The main
routine, ChunkProcess()
takes five parameters, an output file, the
name of the chunk, the callbacks, number of callbacks, and an arbitrary
pointer to data passed back to the callbacks.
So, basically, we have:
int (ChunkProcess)( FILE *fpout, char *name, struct chunk_callback *pcc, size_t scc, void *data ) { char fname[FILENAME_LEN]; FILE *fpin; int c; assert(fpout != NULL); assert(name != NULL); assert(pcc != NULL); assert(scc > 0); sprintf(fname,"chunks/%s",name); fpin = fopen(fname,"r"); if (fpin == NULL) return(CHUNKERR_OPEN); while(1) { c = fgetc(fpin); if (c == '#') { c = fgetc(fpin); if (c == '#') { chunk_handle(fpin,fpout,pcc,scc,data); continue; } fputc('#',fpout); } if (c == EOF) break; fputc(c,fpout); } fclose(fpin); return(ERR_OKAY); }
I just basically look for two consecutive hash marks, and if I find them, I
call chunk_handle()
to do the work for me (I should note my
convention I'm using here—StudlyCaps
has external linkage, visible to
other modules. lower_case
is local to this module). So we now
have:
static void chunk_handle( FILE *fpin, FILE *fpout, struct chunk_callback *pcc, size_t scc, void *data ) { char cmdbuf[BUFSIZ]; char *cmd; char *p; assert(fpin != NULL); assert(fpout != NULL); assert(pcc != NULL); assert(scc > 0); chunk_readcallback(fpin,cmdbuf,BUFSIZ); for ( p = cmdbuf ; (cmd = strtok(p," \t\v\r\n")) != NULL ; p = NULL ) { chunk_docallback(fpout,cmd,pcc,scc,data); } }
chunk_readcallback()
reads the text just past the double hash mark
to the following double hash mark. Then using strtok()
(easy since
it's there, I know how to use it and I'm not worried about threading issues
yet) I break it up. This allows us to specify multiple callbacks within a
single entry and for each callback, we find it and call the function.
static void chunk_docallback( FILE *fpout, char *cmd, struct chunk_callback *pcc, size_t scc, void *data ) { int i; assert(fpout != NULL); assert(cmd != NULL); assert(pcc != NULL); assert(scc > 0); for (i = 0 ; i < SCC ; i++) { if (strcmp(cmd,pcc[i].name) == 0) { (*pcc->callback)(fpout,data); return; } } fprintf(fpout,"##processing error - can't find [%s] ##",cmd); }
Again, since I'm just playing around and want something that works, the linear scan doesn't scale, but since I'm not planning on having a dozen or more callbacks, it doesn't hurt. It can be changed easily though since we do pass in the size of the array and as long as it's noted that the array should be sorted alphabetically we can later change to a binary search.
I'm not sure if a hash table is the way to go at this point—that might
require a different way of passing in or registering the callbacks, and as
it stands right now, I can use the same templates and have different code
for the callbacks. The chunk “bartitle” which I defined is used all over
the place, and the title itself may not be a date, so the ability to change
what ##title##
does depending upon what I'm displaying is
crucial—I just pass in a different callback array.
The fprintf()
is there for diagnostics—I can leave it out with
the effect of undefined callbacks don't generate any output at all, but
there is no notification of the undefined callback either. I put it in but
another way of handling it might be to print out the callback as found in
the text, between double hashmarks.
And that's it. The code for chunk_readcallback()
is easy enough to
leave it as an exercise for the reader, as well as the definition of
struct callback
.
The trick is just breaking it up into simple pieces.