How I'm writing pages out

Previous: Keyword/permission management ] [ Top: To-do manager ] [ Next: Datasheet interface ]

The todomgr_pageout function is pretty much the same as all the template output functions I use in my daily work. It looks for tags of the form [##tag##] in a base HTML file, and replaces them with hash lookups in a tags variable in its caller (in Perl I do the same by passing a hash reference in, but in Tcl I can just look up into the stack frame of my caller, which is so incredibly arcane it gives me a little frisson every time I do it.)

I've been using this ungainly hack for a long time, in several different languages now. And by golly I'm never going to stop!

Note that the HTTP return status is passed in as an optional parameter. The default is, of course, 200, but a useful alternative is 401 for user authentication, for instance.
proc todomgr_pageout {conn file {status 200}} {
   upvar tags tags
   global todomgr_home
   set fn $todomgr_home/$file
   if {![file exists $fn]} {
      See Handling 404 returns

   set fil [open $fn]
   while {[gets $fil line] >= 0} {
      set hit [regexp -nocase {\[##([-a-z_ 0-9!/?]*)##\]} $line match tag]
      while {$hit} {
         regsub -all -nocase \\\[##$tag##\\\] $line [escape_ampersand [todomgr_pageout_tag_value $tag]] line
         set hit [regexp -nocase {\[##([-a-z_ 0-9!/?]*)##\]} $line match tag]
      append pg $line "\n"
   close $fil

   ns_return $conn $status text/html $pg

If you look away from the truly horrible things we have to do to get regsub to work with those square brackets, the whole thing is pretty obvious: you open the file, read in a line at a time, and find tags of the form [##tag##]. Then you pass the text from the tag into todomgr_pageout_tag_value, which returns the value. There's one little irritating bit about that, though. Since regexp has one oh-so-helpful "feature" that I would remove given the chance: it replaces all occurences of '&' with the match string. So the result of todomgr_pageout_tag_value has to be processed in order to escape ampersands. But since both regsub and Tcl itself have to be escaped, we end up with a triple-escape. It's just so lovely. Here's escape_ampersand:
proc escape_ampersand {str} {
   regsub -all "&" $str \\\\\\& retval
   return $retval
And of course actual retrieval of values from the tags array is simple. A tag is just an arbitrary string, so this is a dandy place to define "special" tags or even functional tags. I'm defining two special tags here; flagopen and flagclose. These are functional tags, so that [##flagopen flag##] resolves to <!-- if flag is equal to an empty string or zero, and resolves to an empty string otherwise. The corresponding [##flagclose flag##] resolves to -->, of course. This means that we can display parts of a page conditionally depending on whether a particular flag is true or not; it allows much greater flexibility in page design. (And allows us to push much more of the page design into the template so that code changes aren't necessary.)

A more complex implementation of todomgr_pageout could simply omit that portion of the template enclosed in the flagopen/flagclose pair, but I'm not going to go that deep right now.
proc todomgr_pageout_tag_value {tag} {
   upvar tags tags
   if [string match "flagopen *" $tag] {
      set tag [string range $tag 9 end]
      if [info exists tags($tag)] {
         if {$tags($tag) != 0 && $tags($tag) != ""} { return "" }
      return "<!--"
   if [string match "flagclose *" $tag] {
      set tag [string range $tag 10 end]
      if [info exists tags($tag)] {
         if {$tags($tag) != 0 && $tags($tag) != ""} { return "" }
      return "-->"
   if [info exists tags($tag)] { return $tags($tag) }
   return ""

The only thing left to do is to define how we handle non-existent pages.

Handling 404 returns
The simplest way to do this is simply to return a standard apology and be done with it. And since I'm in a hurry, that's what I'll do:
   return [ns_return $conn 404 text/html "
  <i>You step in the stream<br>
     the water has moved on<br>
     page not found

  Sorry, <code>[ns_conn url $conn]</code> can't be found.

Sorry for the cheesy haiku but I just love that one. If you've ever encountered a missing link on the rest of my site, you'll see I use it there, too. And that's all the apology you're going to get.

Previous: Keyword/permission management ] [ Top: To-do manager ] [ Next: Datasheet interface ]

This code and documentation are released under the terms of the GNU license. They are additionally copyright (c) 2000, Vivtek. All rights reserved except those explicitly granted under the terms of the GNU license.