;;; root page

;; Root fs directory of document home, location of blog directory.
(define +root-dir+ (car (command-line-arguments)))
;; Directory (appended to root-dir, and used in blog URL) to use
;; as blog root.  Example, "/blog" -> "/path/to/doc/root/blog"
;;(define +blog-prefix+ "/blog")
(define +blog-prefix+ "/blog")

(require-library 3e8-html)
(use regex)
(use rfc3339)
(use fmt)
(use posix)
(use atom)
(require-library zmarkup4)
(import (prefix zmarkup4 zmarkup:))

(define (blog entries #!key teaser)
  `(div (@ (id "main"))
        (div (@ (class "blog"))
             ,@(map (lambda (e) (blog-entry->shtml e teaser: teaser))
                    entries))))

(define (blog-entry->shtml e #!key teaser)
  (define (pad0 x i)
    (fmt #f (pad-char #\0 (pad/left i x))))
  (let ((date (blog-entry-updated e)))
    `((h2 (@ (class "date")) ,(rfc3339->jim-date date))
      (div (@ (class "blogbody"))
           (h3 (@ (class "title"))
               (a (@ (href ,(blog-entry-permalink e)))
                  ,(blog-entry-title e)))
           (div (@ (class "entrybody"))
                ,(if (and teaser
                          (blog-entry-teaser-sxml e))
                     `(,(blog-entry-teaser-sxml e)
                       (a (@ (href ,(blog-entry-permalink e)))
                          "Read more after the jump."))
                     (blog-entry-sxml e)))
           (div (@ (class "footer"))
                (span (@ (class "metadata"))
                      ,(intersperse
                        `(
                          ,@(if (and (= 0 (rfc3339-hours date))
                                    (= 0 (rfc3339-minutes date))
                                    (= 0 (rfc3339-seconds date))
                                    (= 0 (rfc3339-fractions date)))
                               '() ;; Don't display if "fake" time.
                               `((,(pad0 (rfc3339-hours date) 2)
                                  ":" ,(pad0 (rfc3339-minutes date) 2)
                                  " ")))
                          (a (@ (href ,(blog-entry-permalink e))
                                 (title "Permalink to '" ,(text-content (blog-entry-title e)) "'"))
                              "Permalink"
                              ;; ,(& "#x2045") " : " ,(& "#x2046")
                              ))
                        " | "))
                )))))

              ;; ,(& "#8718")  ; PROOF
              ;; ,(& "#x25a1") ; white square
              ;; ,(& "#x25ab") ; white square
              ;; ,(& "#x27e0") ; lozenge
              ;; ,(& "#x25b7")
              ;; ,(& "#x25fc")
              ;; ,(& "#x2750")
              ;; ,(& "#x2752")              
              ;; ,(& "#x25a2")
              ;; ,(& "#x25a3")
              ;; ,(& "#x25b2")
              ;; ,(& "#x25b3")

(define-record blog-entry
  published updated ;rfc3339 date objects
  title     ;; may be sxml
  id        ;; Unique ID.  Should be stored in database, currently not
  sxml
  teaser-sxml)

(define (blog-entry-permalink e)
  (define (pad0 n x)
    (pad-char #\0 (pad/left n x)))
  (let ((dt (blog-entry-published e)))
    (fmt #f
         +blog-prefix+ "/"
         (pad0 4 (rfc3339-year dt)) "/"
         (pad0 2 (rfc3339-month dt)) "/"
         (pad0 2 (rfc3339-day dt)) "/"
         (tag-suitable-title (text-content (blog-entry-title e)))
         "/")))

(define jim-date->rfc3339  ; (jim-date->rfc3339 "July 7, 2010") => #<rfc3339 "2010-07-07T00:00:00-06:00">
  (let ((rx:date (regexp "([A-z]+) ([0-9]+), ([0-9]+)")))
    (lambda (jdate)
      (define (month->number m)
        (cond ((assoc m '(("January" . 1) ("February" . 2) ("March" . 3)
                          ("April" . 4) ("May" . 5) ("June" . 6)
                          ("July" . 7) ("August" . 8) ("September" . 9)
                          ("October" . 10) ("November" . 11) ("December" . 12)))
               => cdr)
              (else #f)))
      (match (string-match rx:date jdate)
             (#f #f)
             ((_ m d y)
              (make-rfc3339 (string->number y) (month->number m) (string->number d)
                            0 0 0 0 (* 3600 6))))))) ;ignore dst
(define (rfc3339->jim-date R)
  (define (number->month m)
    (cond ((assv m '((1 . "January") (2 . "February") (3 . "March")
                     (4 . "April") (5 . "May") (6 . "June")
                     (7 . "July") (8 . "August") (9 . "September")
                     (10 . "October") (11 . "November") (12 . "December")))
           => cdr)
          (else #f)))
  (string-append (or (number->month (rfc3339-month R))
                     (error 'rfc3339->jim-date "conversion failure" R))
                 " "
                 (->string (rfc3339-day R)) ", " (->string (rfc3339-year R))))

(define (blog-entry date title body #!key categories teaser)
  (let ((date (or (and (rfc3339? date) date)
                  (string->rfc3339 date)
                  (jim-date->rfc3339 date)
                  (error 'blog-entry "illegal date" date))))
    (make-blog-entry date date title
                     (string-append "tag:3e8.org,"
                                    (substring (rfc3339->string date) 0 10)
                                    ":"
                                    "/blog/"
                                    (tag-suitable-title (text-content title)))
                     body
                     teaser)))

;;; zmarkup entries
(define (zm:extract-metadata tag doc)
  ;; Can't use assv because bare strings are present, and we don't have a meta section.
  ;; (Maybe we should; it would make extracting the body easier.)
  (cond ((find (lambda (x) (and (list? x) (eqv? (car x) tag))) doc)
         => cadr)
        (else #f)))
;; This extra syntax is supported at toplevel:
;; (jump) -- delineates point at which article will be truncated for front page display
;; (date "") -- publish and (currently) updated date of article
;; (title "") -- Article title
;; (tags x y z) -- Blog tags/categories
(define (read-zmarkup-blog-entry file)
  (let ((zdoc (call-with-input-file (conc "./blog/" file)
                zmarkup:read-toplevel)))
    (let ((date  (zm:extract-metadata 'date zdoc))
          (title (zm:extract-metadata 'title zdoc))  ; text-content will probably not work yet, if inline zmarkup present
          (tags  (zm:extract-metadata 'tags zdoc)))
      (receive (pre post)
          (break (lambda (x) (and (pair? x) (eqv? (car x) 'jump)))
                 zdoc)
        (let ((teaser (if (null? post)
                          #f
                          (cdr (zm:block-item->shtml pre))))
              (sdoc (zm:block-item->shtml zdoc)))
          (blog-entry date title
                      (cdr sdoc)        ; skip *TOP*
                      categories: tags
                      teaser: teaser))))))
(define (zm:block-item->shtml item)
  (cond ((symbol? item)   ; Not certain this is necessary; simple-xml may fail to escape this if a symbol, though
         (zm:block-item->shtml (symbol->string item)))
        ((string? item)
         (zm:block-item->shtml (list ': item)))
        ((list? item)
         (case (car item)
           ((@) (cons (car item)
                      (map zm:attr-item->shtml (cdr item))))
           ;; Colons, by definition, always expect inline arguments.  We must preserve the colon
           ;; when wrapping in a 'p' tag so that it retains its textness (space-interspersing properties).
           ((:) (zm:block-item->shtml `(p ,item)))
           ((+) (error 'zm:block-item->shtml "concats are illegal as block items" item)) ; maybe not, but we don't need them
           ;; I think I have to check here what kind of args the elt expects---inline args, block args.
           ;; Block items may expect block argument or inline arguments.  Inline items always expect inline arguments.
           ;; Certain block items may allow (li) or require (td) omitting paragraph tags around a single block argument
           ;; when that argument is a bare string or colon item.  Browsers usually allow a combination of inline and block
           ;; contents, but if there are multiple blocks, the inline content should be promoted to block (usually, paragraph).
           ((p pre) (cons (car item) (map zm:inline-item->shtml (cdr item))))
           ;;  ((li))  ; special processing--inline+block dual content.  strictly, we should only allow inside ul/ol.
           ;;  ((td))  ; special processing--inline+block dual content.  strictly, we should only allow inside tr.
           ((jump title date tags) '())
           (else
            (cons (car item) (map zm:block-item->shtml (cdr item))))))
        (else
         (error 'zm:block-item->shtml "unhandled item" item))))

;; This is for 2 purposes: convert certain values that simple-xml cannot handle
;; (such as symbols) to strings; and handle any macros.
(define (zm:inline-item->shtml item)
  (cond ((symbol? item)
         (zm:inline-item->shtml (symbol->string item)))
        ((number? item)
         (zm:inline-item->shtml (number->string item)))
        ((string? item)
         item)
        ((list? item)
         (let ((tag (car item)))
           (case tag
             ;; insert inline macros here
             ((mdash) `(& "mdash"))
             ((@) (cons (car item)
                        (map zm:attr-item->shtml (cdr item))))
             (else (cons (car item)
                         (map zm:inline-item->shtml (cdr item)))))))
        (else
         (error 'zm:inline-item->shtml "unhandled item" item))))

;; attribute values are flattened into strings; shtml can't handle
;; non-text nodes.  NOTE: We could error if a value is a list.
(define (zm:attr-item->shtml item)
  (unless (pair? item)
    (error 'zm:attr-item->shtml "attribute arguments must be pairs" item))
  (cons (car item) (map ->string (cdr item))))

;;;

(define (tag-suitable-title t)  ;; WARNING: Non-ASCII characters will be omitted
  (string-downcase
   (string-translate
    (string-substitute (regexp "[^ A-z0-9-]") "" t)
    #\space #\-)))

;; Stolen from chicken-doc-html
(define (sxml-walk doc ss)
  (let ((default-handler (cond ((assq '*default* ss) => cdr)
                               (else
                                (lambda (t b s) (error 'sxml-walk
                                            "No default binding for" t)))))
        (text-handler (cond ((assq '*text* ss) => cdr)
                            (else #f))))
    (let loop ((doc doc))
      (cond ((null? doc) '())
            ((pair? doc)
             (let ((tag (car doc))
                   (body (cdr doc)))
               (if (symbol? tag)
                   (let ((handler-cell (assq tag ss)))
                     (if handler-cell
                         ((cdr handler-cell) tag body ss)
                         (default-handler tag body ss)))
                   (map loop doc))))
            (else
             (if text-handler
                 (text-handler '*text* doc ss)
                 doc))))))
(define (text-content doc)
  (if (string? doc)
      doc
      (tree->string
       (sxml-walk doc `((@ . ,(lambda (t b s) '()))
                        (*default* . ,(lambda (t b s) (sxml-walk b s)))
                        (*text* . ,(lambda (t b s) b)))))))
(define (hil lang . body)
  `(pre (@ (class "prettyprint lang-" ,lang))
        . ,body))

;;;

(define blog-entries
  (list
   (read-zmarkup-blog-entry "symbolics-concordia-in-a-lisp-machine.txt")
   (blog-entry "2012-11-28T14:27:00-06:00" "QEMU and sgabios"
               `((p "Continuing from my "
                    (a (@ (href "/blog/2012/11/28/qemu-linux-test-image-with-serial-console/")) "previous post")
                    ", another way to redirect output to a serial console is to use Google's "
                    (a (@ (href "http://code.google.com/p/sgabios"))
                       "sgabios")
                    " option ROM, which mirrors BIOS output from the screen to a serial port.  Just place this ROM alongside qemu's other roms (e.g. in " (tt "/usr/share/qemu") ") and start qemu with " (tt "-device sga") ".")
                 (p
                  "Since this works for LILO, we can get into the LILO boot prompt on the serial console, and start Linux with " (tt "linux console=ttyS0") ".  Once Linux boots up we can modify lilo.conf and run " (tt "lilo") ".  This avoids some of the rigamarole of the previous method, although guestfish is still useful to edit lilo.conf, due to the lack of an editor in the test image.")
                 (p "However, there's no prebuilt sgabios package for Ubuntu 12.04, and qemu 1.0 doesn't come with sgabios included.  So I built sgabios along with a .deb that drops it into qemu's ROMs directory.")
                 (p "I now present to you: ")
                 (ul (li (a (@ (href "https://gist.github.com/4163790"))
                          "a script which downloads, builds and packages sgabios")
                         ", ")
                     (li (a (@ (href "/pub/sgabios/qemu-sgabios_0+svn8_all.deb"))
                         "the qemu-sgabios .deb")
                          ", and ")
                     (li (a (@ (href "/pub/sgabios/sgabios.bin"))
                         "the ROM file itself")
                         ".")))
               categories: '(virtualization))

   (blog-entry "2012-11-28T00:45:00-06:00" "QEMU linux test image with serial console"
               `((p "As provided, "
                    (a (@ (href "http://wiki.qemu.org/download/linux-0.2.img.bz2"))
                       "QEMU's test linux image")
                    " expects to be on either a graphical or curses-based terminal. "
                    "Let's say you'd like to patch the image to use the serial console instead.  To make this more interesting, let's not resort to booting in graphical or curses mode to set up the serial console in the first place.")
                 (p "First, grab the test image.")
                 ,(hil 'sh "
$ mkdir linux-test && cd linux-test
$ curl http://wiki.qemu.org/download/linux-0.2.img.bz2 | bunzip2 > linux-0.2.img
")
                 (p "The image uses LILO; to get LILO to interact with the serial port, we have to add " (tt "serial=0,9600n8") " to lilo.conf.  We also need to get the kernel to use the serial line by appending " (tt "console=ttyS0") " to the kernel arguments in lilo.conf.")

                 (p "One problem: the image doesn't come with an editor (not even " (i "ed") ", man!).  Also, we have no interactive console yet either way.  So, in order to manipulate the disk image directly, we use the amazing " (a (@ (href "http://libguestfs.org")) "guestfish") ". ")
                 
                 (pre "
$ guestfish -i -a linux-0.2.img
><fs> " (b "edit /etc/lilo.conf"))

                 (p "Now we can edit lilo.conf to add the serial and console arguments.  Updated, it looks something like:")
                 (pre "
boot = /dev/hda
" (b "serial = 0,9600n8") "
root = /dev/hda
# [... 5 lines omitted ...]
append = \"" (b "console=ttyS0") " sb=0x220,5,1,5 ide2=noprobe ide3=noprobe ide4=noprobe ide5=noprobe\"
image = /boot/vmlinuz-2.6.20
  label = linux
")
                 (p "It might have been nice to automate this editing with guestfish's support for " (a (@ (href "http://augeas.net")) "augeas") ", but unfortunately augeas lacks a lens for lilo.conf.")
                 (p "Anyway, we still have to execute " (tt "lilo") " to update the boot sector.  We can try to do this via guestfish as well; the guestfish instance is running a mini-VM attached to your disk, and you can actually run code located on your disk within the mini-VM.")
                 (pre "><fs> command lilo" "\n"
                      "libguestfs: error: command: Fatal: open /dev/hda: No such file or directory")
                 (p "Ugh.  Our disk in the guestfish VM is located at " (tt "/dev/vda") ", not " (tt "hda") ".  Well, we can fake it with a symlink:")
                 (pre "><fs> command \"ln -s /dev/vda /dev/hda\"" "\n"
                      "><fs> command lilo" "\n"
                      "libguestfs: error: command: Fatal: Sorry, don't know how to handle device 0xfd00")
                 (p "By device 0xfd00 it means " (tt "/dev/vda") " (major 253, minor 0); apparently it doesn't like virtblk devices.  It might work by explicitly specifying the geometry, or upgrading LILO, but let's try another method.")
                 (p "What we'll do is use QEMU's support for directly loading the kernel and bypassing the boot loader.  We can copy the kernel out of the image, and launch our VM using that external kernel, pointing it to the serial console.  We can then run LILO to update the boot sector.")
                 (p "(Naturally, using this method, we could skip messing around with LILO at all, and just start up the VM this way all the time.  But we've come this far, so let's make the change permanent.)")
                 (pre "><fs> copy-out /boot/vmlinuz-2.6.20 ." "\n"
                      "><fs> quit" "\n"
                      "\n"
                      "$ ls -l vmlinuz-2.6.20" "\n"
                      "-rw-r----- 1 jim jim 2040204 Nov 27 22:16 vmlinuz-2.6.20" "\n"
                      "\n"
                      "$ kvm -nographic -hda linux-0.2.img -kernel vmlinuz-2.6.20 \\" "\n"
                      "      -append \"console=ttyS0 root=/dev/hda\"")
                 (p "The machine should boot up and print its output to the serial console (your terminal).  Run lilo when you get the login shell:")
                 (pre "sh-2.05b# lilo" "\n"
                      "Added linux *" "\n"
                      "sh-2.05b# exit")
                 (p "Now this image has been permanently converted to communicate with the serial console.  Boot it like so and you'll see both the LILO and kernel output on your terminal:")
                 (pre "$ kvm -nographic -hda linux-0.2.img")
                 )
               categories: '(virtualization))
   (blog-entry "2012-11-20T21:33:00-06:00" "shell templates"
               `((p "Via " (a (@ (href "https://github.com/rtomayko/shocco")) "Shocco")
                    ", a neat little technique for templating in shell.  The idea is to encapsulate a here document in a function, and use the function arguments (" (tt "$1 $2 ...") ") as the template variables.  The cool part is the use of " (tt "$(cat)") " in the template body, allowing you to pipe in data to the function and have it wrapped in the output.  This is extremely useful in the middle of a pipeline; much of shocco is, in fact, a gigantic pipeline.")
                 (p "Here's a simple example.  We assume the input is already properly escaped for HTML.")
                 ,(hil 'sh "
layout() {
  cat <<HTML
<html><head>
<title>$1</title>
<body><p>$(cat)</p></body>
</head></html>
HTML
}

echo 'My body is ready' | layout 'Status'
")
                 (p "Output:")
                 ,(hil 'html "
<html><head>
<title>Status</title>
<body><p>My body is ready</p></body>
</head></html>")
                 (p "Shocco is shoc-full (sorry) of interesting shell techniques and I'd recommend checking it out if you're at all into shell programming.")))
   (blog-entry "2011-12-13T19:00:00-06:00" "jQuery and HTML5 data attribute conversion"
               `((p "In jQuery 1.4.3 and later, you can use " (tt ".data()") " to access the value of "
                    (a (@ (href "http://www.w3.org/TR/html5/elements.html#embedding-custom-non-visible-data-with-the-data-attributes"))
                       "custom data attributes")
                    " on your HTML5 elements.  jQuery tries to figure out what type of data you're passing and do the appropriate result conversion, falling back to string if it can't figure it out. "
                    " This is convenient, but a problem can occur when you want to pass a string but your value looks like (for example) a boolean:"
                    (pre "<input id='abc' data-foo='true'>\n\n"
                         "$('#abc').data('foo')  /* true (boolean) */")
                    (p "It's not possible to quote the string value either; you'll just get a string with embedded quotes.  The official workaround is to use " (tt ".attr('data-foo')") ", which will return the raw string.  Of course, this means that to safely pass a string without interpretation, you can't accept any other object. "
                       (a (@ (href "http://bugs.jquery.com/ticket/7579")) "Some") " "
                       (a (@ (href "http://bugs.jquery.com/ticket/7231")) "people")
                       " are unhappy with this behavior, but... " (i "wontfix") ".")
                    (p "However, there's another trick you can use.  Pass a quoted string inside a 1-element array, then reference that element. This will ensure the JSON parser is engaged because the value starts with " (tt "[") ". It also lets you pass arbitrary objects you do want converted (in which case you couldn't use " (tt ".attr()") "). Example:")
                    (pre "
<input id=\"abc\" data-foo='[\"true\"]' data-bar='[true]'>

$('#abc').data('foo')[0] /* \"true\" (string) */
$('#abc').data('bar')[0] /* true   (boolean) */")
                    (p "It's a little clunky, but at least you'll get consistent results.  If you're passing several values, it's probably easier just to use a JSON hash."))))
   (blog-entry "2011-08-07T16:10:00-05:00" "ccze"
               `((p (a (@ (href "http://freshmeat.net/projects/ccze/")) "ccze") " is a nice way to colorize your logs.  The defaults are tuned more for following logs in a dedicated window, than for viewing them inline at a prompt.  I use an alias like this:")
                 ,(hil 'sh "alias cz='(ccze -m ansi | less -MnFRX)'")
                 (p "allowing usage like")
                 ,(hil 'sh "$ cz < /var/log/messages\n" "$ tail -n 20 /var/log/syslog | cz\n"
                       "$ dmesg | tail | cz")))
   (blog-entry "2011-08-02T12:34:56-05:00" "Take off every SIGTERM"
`((p "While working on the " (a (@ href "https://github.com/ursetto/zbguide") "zguide") " for the " (a (@ href "http://api.call-cc.org/doc/zmq")  "zmq") " egg in " (a (@ href "http://call-cc.org") "Chicken") ", I encountered a problem with
terminating a process in a timely manner when it has a SIGTERM handler installed.")
,(hil 'scm #<<EOF
(use posix)
(on-exit (lambda () (print "exiting")))
(set-signal-handler! signal/term
                     (lambda (s) (print "terminating") (exit)))
(read)
(print "finished")
EOF
)
(p "If you compile and run this program, and " (tt "kill -TERM") " it from
another window, nothing happens right away.  You have to hit " (tt "Enter") "
afterwards, and then it will print " (tt "terminating") " and " (tt "exiting") ".")

(p "The reason for this is that the posix unit installs signals with
" (tt "signal(3)") ", which sets the " (tt "sigaction") " flag " (tt "SA_RESTART") " under the hood.
\"Slow\" system calls (" (tt "read") ", " (tt "zmq_recv") ") which have received no input
yet will automatically restart when the signal handler returns.
Chicken has a single global signal handler that just sets a flag
indicating which signal was received, schedules the interrupt handler
to run in a moment, and returns immediately.  The syscall is then
restarted before the interrupt handler gets a chance to run.")

(p "To hack around this behavior you can add")

,(hil 'scm #<<EOF
(foreign-code "siginterrupt(SIGTERM, 1);")
EOF
)
(p "to the top of your file, so slow syscalls will immediately exit with
" (tt "EINTR") " even when no input is available, and your handler will be
invoked.  To wit:")

,(hil 'scm #<<EOF
(foreign-code "siginterrupt(SIGTERM, 1);")
(use posix)
(on-exit (lambda () (print "exiting...")))
(set-signal-handler! signal/term
                     (lambda (s) (print "terminating...") (exit)))
(read)
(print "finished")
EOF
)
(p "If you do not explicitly hook " (tt "SIGTERM") ", a " (tt "TERM") " signal will interrupt "
(tt "read") " immediately and terminate.  This is true on Linux and Mac OS X
at least, but it's not clear to me that this can be relied upon.")

(p "You can see this behavior as well with " (tt "SIGINT") ".  By default Ctrl-C
terminates a " (tt "read") " immediately.  However, when the posix egg is
loaded, it hooks Ctrl-C with " (tt "signal(3)") " and so Ctrl-C will not
immediately terminate a " (tt "read") ".")

,(hil 'scm #<<EOF
 (use posix)
 (read)
 ^C^C^C^C^C^C
EOF
)
(p "Instead, you have to press " (tt "Enter") " afterward (as line buffering is
active by default).  To terminate immediately you can do:")

,(hil 'scm #<<EOF
 (use posix)
 (foreign-code "siginterrupt(SIGINT, 1);")
 (read)
EOF
)

(p "This situation might need to be addressed with an egg that provides more
nuanced signal handling than the posix unit.")

(p "Final note: It's not possible to restore the default signal handler
(" (tt "SIG_DFL") ") with the posix unit, either.  Passing #f to " (tt "set-signal-handler!")
 " will ignore the signal (" (tt "SIG_IGN") ")."))
categories: '(chicken scheme zeromq robots))

   (blog-entry "2010-08-01T23:36:20-05:00" "Default namespaces in SXML"
               `((p "The stock SXML->XML serializer from " ,(link "http://www.modis.ispras.ru/Lizorkin/sxml-tutorial.html#htoc32" "sxml-tools") " has a couple aesthetic issues related to namespace output.") (ol (li "It doesn't support default namespaces at all, which makes the already-verbose XML positively prolix, and could cause some non-conformant XML processors to fail.") (li "It does not allow redeclarations of XML prefixes, so you may sometimes get an autogenerated prefix name even when you provided a mapping.  (This was a \"design goal,\" though.)") (li "It does not support declaring all prefixes in the root element, which in certain cases can elevate the natural redundancy of XML to dizzying heights.")) (p "I added support for 1. and 2. in version 0.2 of the " ,(link "http://3e8.org/chickadee/sxml-serializer" "sxml-serializer") " egg, released yesterday. No.3 unfortunately will take some time to think about, as the code is geared to declare prefixes as locally as possible.") (p "So I was going to write a blog post with a lot of examples and in-depth explanation, but instead, I just documented the egg!  See " ,(link "http://3e8.org/chickadee/sxml-serializer#sec:The_default_namespace" "The default namespace") " and " ,(link "http://3e8.org/chickadee/sxml-serializer#sec:Redeclaring_XML_prefixes" "Redeclaring XML prefixes") " for more details.") (p "Here's a preview, though.  The change introduces a new " (tt "*default*") " pseudo-namespace which we can use to map any number of URIs to the default namespace.  This works with nested elements and also handles the empty namespace correctly.  Below is a pretend Atom document that is rendered without any prefixes:") ,(hil 'scm "> (serialize-sxml\n    '(*TOP* (@ (*NAMESPACES*\n                (atom \"http://www.w3.org/2005/Atom\")\n                (xhtml \"http://www.w3.org/1999/xhtml\")))\n       (atom:feed (atom:entry\n                   (atom:content (@ (type \"xhtml\"))\n                    (xhtml:div (xhtml:p \"I'm invincible!\"))))))\n    ns-prefixes: '((*default* . \"http://www.w3.org/2005/Atom\")\n                   (*default* . \"http://www.w3.org/1999/xhtml\")))\n\n") ,(hil 'xml "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n  <entry>\n    <content type=\"xhtml\">\n      <div xmlns=\"http://www.w3.org/1999/xhtml\">\n        <p>I'm invincible!</p>\n      </div>\n    </content>\n  </entry>\n</feed>") (p "Instead, if we omit the " (tt "*default*") " mappings from " (tt "ns-prefixes") " -- or just use the stock serializer -- every element is prefixed:") ,(hil 'xml "<atom:feed xmlns:atom=\"http://www.w3.org/2005/Atom\">\n  <atom:entry>\n    <atom:content type=\"xhtml\">\n      <xhtml:div xmlns:xhtml=\"http://www.w3.org/1999/xhtml\">\n        <xhtml:p>I'm invincible!</xhtml:p>\n      </xhtml:div>\n    </atom:content>\n  </atom:entry>\n</atom:feed>") (p "I like to call this \"terse mode,\" because irony is the spice of life.")))
   (blog-entry "2010-07-31T13:40:00-05:00" "Namespaces in SXML, part 2"
               `((p ,(link "http://3e8.org/blog/2010/07/30/namespaces-in-sxml-part-1/" "Last time") ", we talked about the distinction between SXML shortcut names and XML prefixes, and particularly about the *NAMESPACES* node.  This time let's talk about the prefixes you pass to the parser and the serializer.") (p "The default SSAX parser in Chicken is " ,(link "http://3e8.org/chickadee/ssax#def:ssax:xml-.3esxml" "ssax:xml->sxml") ". It accepts an alist that maps user namespace shortcuts (symbols) to namespaces (URI strings). Let's see what happens if we pass it a null list:") (pre "> (ssax:xml->sxml\n   (open-input-string\n    \"<cars:part xmlns:cars=\\\"http://www.cars.com/xml\\\" />\")\n   '())") (pre "(*TOP* (http://www.cars.com/xml:part))") (p "Because we didn't give it an association between shortcuts and URIs, the parser returns fully-qualified names.  It completely discards the XML prefix in the original document, because the prefix is only meaningful for languages that cannot qualify element names with URIs (i.e. XML 1.0).  An important consequence is that you cannot recreate the original XML document prefixes without additional information provided by the user.") (p "Now let's associate the shortcut prefix " (tt "cs") " to the namespace URI for the XML prefix " (tt "cars") ", and parse the same document:") (pre "> (ssax:xml->sxml\n   (open-input-string\n    \"<cars:part xmlns:cars=\\\"http://www.cars.com/xml\\\" />\")\n   '((cs . \"http://www.cars.com/xml\")))") (pre "(*TOP* (@ (*NAMESPACES* (cs \"http://www.cars.com/xml\")))\n (cs:part))") (p "I used " (tt "cs") " here so that you could see that the prefix " (tt "cars") " is completely irrelevant to the output, except on the XML side in determining the universal name.  It is not even retained in the SXML document.  The fully qualified SXML name " (tt "http://www.cars.com/xml:part") " is now, via the user's association, changed to the short name " (tt "cs:part") ".  This association is also recorded in the *NAMESPACES* node so that the namespace URI can be reconstructed when transformed back to XML.  Notice that during parsing, the alist actually maps " (i "from") " URI strings " (i "to") " shortcut symbols, which is the opposite of what you'd expect.") (p "Let's now consider transforming the two documents above back to XML. In the previous post, we saw the erroneous case where we tried to use a shortcut namespace without defining that association, so the shortcut was treated as a full namespace URI:") (pre "> (->xml '(*TOP* (cars:part)))\n<prfx1:part xmlns:prfx1=\"cars\" />") (p "However, the first SXML document we produced above is valid, and will produce valid output:") (pre "> (->xml '(*TOP* (http://www.cars.com/xml:part)))\n<prfx1:part xmlns:prfx1=\"http://www.cars.com/xml\" />") (p "There's no assocation to an XML prefix, though, so " (tt "prfx1") " is generated for you.  This is a perfectly legal XML document, and is readable by any conformant parser, even though it looks strange.") (p "We can pass another association list, this time to the serializer, to map URI strings to XML prefixes.  Below, the namespace URI " (tt "http://www.cars.com/xml") " is mapped to the XML prefix " (tt "cars") ", instead of the auto-generated " (tt "prfx1") ".") (pre "> (->xml '(*TOP* (http://www.cars.com/xml:part))\n         ns-prefixes: '((cars . \"http://www.cars.com/xml\")))\n<cars:part xmlns:cars=\"http://www.cars.com/xml\" />") (p "It's important to remember that the alist you pass to the serializer in " (tt "ns-prefixes") " " (i "does not map shortcut prefixes to URIs") ".  That's the job of the alist passed to the parser--or more accurately, the job of the *NAMESPACES* node.  " (tt "ns-prefixes") " only maps URIs to XML prefixes.  Returning to the erroneous example above,") (pre "> (->xml '(*TOP* (cars:part))\n         ns-prefixes: '((cars . \"http://www.cars.com/xml\")))\n<prfx1:part xmlns:prfx1=\"cars\" />") (p "we can see " (tt "ns-prefixes") " has no effect.  There is no such namespace URI as " (tt "http://www.cars.com/xml") " in that document, nor does " (tt "ns-prefixes") " create a shortcut mapping to that URI.") (p "Turning to the second example which was generated by the SSAX parser,") (pre "> (->xml '(*TOP* (@ (*NAMESPACES* (cs \"http://www.cars.com/xml\")))\n           (cs:part)))\n<cs:part xmlns:cs=\"http://www.cars.com/xml\" />") (p "we can see there is a namespace association in *NAMESPACES* from the shortcut " (tt "cs") " to the URI " (tt "http://www.cars.com/xml") ".  Recall this association was created by the user when calling the parser, not by the XML document.  During serialization, this assocation causes the shortcut namespace " (tt "cs") " to be translated to the URI " (tt "http://www.cars.com/xml") ".  However, we still need to determine the resulting XML prefix.  By default, the serializer conveniently uses the shortcut name " (tt "cs") " as the XML prefix!") (p "That was, however, just the default.  If we actually provide a mapping from URI to XML prefix in " (tt "ns-prefixes") ", that will override the default:") (pre "> (->xml '(*TOP* (@ (*NAMESPACES* (cs \"http://www.cars.com/xml\")))\n           (cs:part))\n         ns-prefixes: '((cars . \"http://www.cars.com/xml\")))\n<cars:part xmlns:cars=\"http://www.cars.com/xml\" />") (p "So the shortcut " (tt "cs") " is translated to the namespace URI " (tt "http://www.cars.com/xml") " via *NAMESPACES*, and the URI is then mapped to the XML prefix " (tt "cars") " via " (tt "ns-prefixes") ". Again, notice how " (tt "ns-prefixes") " contains no reference to the shortcut " (tt "cs") ", because it never sees the shortcut names; it only sees the namespace after expansion into a URI.") (p "Confused yet?  Let me sum up.") (ul (li "When parsing XML to SXML, XML prefixed names are mapped to universal names (local names qualified with URIs), based on the xmlns attributes in the document.  Optionally, these URIs are mapped to shortcut names based on an alist passed by the user, and this mapping is also stored in the *NAMESPACES* node.") (li "When serializing SXML to XML, shortcut names are mapped back to URI namespace strings using the associations stored in the *NAMESPACES* node, and all URIs are then mapped to XML prefixes based on a user-provided alist.  Since universal names are illegal in XML 1.0, all URIs " (i "must") " be mapped to prefixes; therefore, automatic prefixes are created if you do not provide a mapping.")) (p "In the next installment, we'll take a look at a more complex example.")))
   (blog-entry "2010-07-30T17:35:00-05:00" "Namespaces in SXML, part 1"
               `((p "Namespaces in SXML are tricky.  The SSAX SXML parser takes a list of namespace prefix to URI assocations; the SXML document itself contains *NAMESPACE* nodes mapping prefixes to URIs; and the " ,(link "http://www.modis.ispras.ru/Lizorkin/sxml-tutorial.html#hevea:serializ" "SXML serializer") " takes a list mapping prefixes to URIs as well!  How does it all fit together?") (p "In the discussion below I will be using the " ,(link "http://3e8.org/chickadee/sxml-serializer" "sxml-serializer") " egg for " ,(link "http://call-cc.org" "Chicken") ".  I also define the following helper function which transforms SXML to XML and prints it to stdout:") (pre "(define (->xml doc . opts) (print (apply sxml-serializer doc opts)))") (p "In SXML, element names usually consist of a qualifying URI and a local name, separated by a colon.  This is similar to the universal names described in " ,(link "http://www.jclark.com/xml/xmlns.htm" "XML namespaces") ":") (pre "<{http://www.cars.com/xml}part />      <!-- XML universal name -->\n(http://www.cars.com/xml:part)         ;; SXML name") (p "XML 1.0 cannot handle such identifiers, because they contain illegal characters. So it does prefix mapping instead:") (pre "<cars:part xmlns:cars=\"http://www.cars.com/xml\" />") (p "However, SXML can handle these identifiers directly, as shown above, without any need for prefix mapping.  To find the local name, you take everything right of the rightmost colon, in this case " (tt "part") ".") (p "Now, the application developer might not like dealing with these long URIs when querying or typing in a document, so SXML provides a way to define a shortcut name for the URI.  These shortcuts are defined in the document inside *NAMESPACE* administrative nodes, usually at the top level.") (pre "> (->xml\n   '(*TOP* (@ (*NAMESPACES* (cars \"http://www.cars.com/xml\")))\n     (cars:part)))\n<cars:part xmlns:cars=\"http://www.cars.com/xml\" />") (p "Now you can use " (tt "cars") " to mean " (tt "http://www.cars.com/xml") " anywhere in the document.  Also, invididual elements may have their own local associations:") (pre "> (->xml\n   '(*TOP*\n     (cars:part (@ (@ (*NAMESPACES* (cars \"http://www.cars.com/xml\")))))\n     (cars:part)))\n<cars:part xmlns:cars=\"http://www.cars.com/xml\" />\n<prfx1:part xmlns:prfx1=\"cars\" />") (p "There, in the first " (tt "cars:part") " element and any children, " (tt "cars") " stands for " (tt "http://www.cars.com/xml") ".  Outside of it, the association does not exist.") (p "What's with the second " (tt "cars:part") " element, though?  It's been rendered as the XML element " (tt "prfx1:part") " with the namespace URI " (tt "cars") "!  Well, that's because we gave no association for the shortcut " (tt "cars") ", so the serializer treats " (tt "cars") " itself as the qualifying URI.  Remember, when SXML elements include a colon, the left side is the full qualifying URI unless you explicitly specify a shortcut association.  And when URIs don't have an associated XML prefix, one is generated for you, such as " (tt "prfx1") ".") (p "This brings us to our first insight, which is that " (i "XML prefixes are not SXML shortcut names") ".  They may look similar, and even overlap sometimes in naming, but the distinction is critical.") (p "Next time, more fun with namespaces."))
               categories: '(chicken sxml))
   (blog-entry "2010-07-27T22:55:04-05:00" "Atomized"
               `((p "The " (a (@ (href "http://3e8.org/chickadee/atom")) "atom egg") " v0.1 for Chicken has been released.  It can read and write Atom 1.0 feeds and I am using it to generate the " (a (@ href "http://3e8.org/blog/atom10.xml") "feed") " for this site."))
               categories: '(chicken scheme atom))
   (blog-entry "July 7, 2010" "Phoning it in"
              `((p "Recently I have optimized this site and " (a (@ (href "/chickadee")) "chickadee")
                   " for display on iPhone and other small displays, using CSS3 @media queries to alter the layout as described at " (a (@ (href "http://hicksdesign.co.uk/journal/finally-a-fluid-hicksdesign")) "hicksdesign") " and elsewhere.  This site has always sported a fluid layout so the conversion was not difficult.  However, I'd like to note a couple things that I didn't see explored sufficiently elsewhere.")
                (ol (li "Use only " (tt "initial-scale=1") " in specifying viewport."
                        (p "Most sites seem to recommend the use of ")
                        ,(hil 'html "<meta name=\"viewport\" content=\"width=device-width\" />")
                        (p "which will make iPhone treat the viewport as 320px across.  This is wrong for landscape mode, which is 480px, and will render larger instead of adding to available horizontal space.  Instead in a properly fluid layout you should omit an explicit width and use")
                        ,(hil 'html "<meta name=\"viewport\" content=\"initial-scale=1\" />")
                        (p "Webkit browsers will then set the width to " (tt "device-width") " in portrait mode, and " (tt "device-height") " in landscape."))
                    (li "Prefer " (tt "width") " instead of " (tt "device-width") " in media queries not explicitly targeted at iPhone."
                        (p "Since width may now be the device width " (em "or") " height, you need not use the " (tt "orientation") " property to distinguish between portrait and landscape, but rather just the width of the viewport.  For example with the two queries below you can handle both orientations on iPhone, and also handle desktop browsers with small windows:")
                        ,(hil 'css "@media screen and (max-width: 600px) {}\n@media screen and (max-width: 400px) {}")
                        (p "Of course you can still target iPhone and iPad directly with " (tt "max-device-width") ", for example to increase link size for the touchscreen.")
                        )
                    (li "Turn off Webkit's automatic font size adjustment."
                        (p "Mobile Safari will sometimes increase your font size without your permission, usually in landscape mode.  This can be useful for the average website not optimized for mobile use, but is counterproductive in our case.  You should turn this off for iPhone:")
                        ,(hil 'css "@media only screen and (max-device-width:480px) {\n"
                             "  html { -webkit-text-size-adjust: none; }      }")
                        (p "iPad already has this property set to \"none\".  Also, this directive will cause desktop Safari to " (em "ignore the user's text zoom setting") ", so make sure it is wrapped in the iPhone-only media query as above.")
                        (p "Edit: Setting the value to \"100%\" instead of \"none\" supposedly allows text zoom, avoiding the need for the iPhone media query, but I haven't tested it yet.")))))
         (blog-entry "May 10, 2010" "Hello, bird"
                     `((p "My " (a (@ (href "/chickadee")) "chickadee") " Chicken documentation server is now operational.  It features fast access to Chicken docs and incremental search, in a package that's Lisp-machine-chic.  It uses my " (a (@ (href "/chickadee/chicken-doc")) "chicken-doc") " backend, which also provides docs at the command-line.")))
         (blog-entry "February 28, 2010" "Selective service"
                     `((p "I like to change Emacs' selective display indicator" ,(& 'mdash) "the hidden-line ellipsis in outline and org mode" ,(& 'mdash) "so it stands out more.  But in emacs 23, my original recipe ("
                          (a (@ (href "http://groups.google.com/group/gnu.emacs.help/browse_thread/thread/e284ff4f77286ba4/095c51bf22c3368e?pli=1")) "derived from here")
                          ") failed because it relied on hardcoded bit-shifting.  Below is an improved version that uses glyphs and works in both emacs 22 and 23.  It changes the indicator from \"...\" to \" >>>\" in yellow text:")
                       ,(hil 'el "
(make-face 'invisible-text-ellipsis-face)  ;; arbitrary name
(set-face-foreground 'invisible-text-ellipsis-face \"yellow\")
(set-face-background 'invisible-text-ellipsis-face nil)
(let ((dot (make-glyph-code ?> 'invisible-text-ellipsis-face)))
    (set-display-table-slot standard-display-table 'selective-display
                            (vector ?\\  dot dot dot)))
")))
         (blog-entry "July 24, 2009" "Lilliput"
                     `((p "Until recently I used " (a (@ (href "http://www.quesera.com/reynhout/misc/rsync+hfsmode/")) "rsync+hfsmode") " to back up my Mac to a Linux box.  I stopped only because I needed the features of rsync 3, but that lost me the ability to back up extended attributes in AppleDouble format, which can reside on a plain, non-HFS+ filesystem.  Anyway, it turns out rsync+hfsmode had been creating corrupt AppleDouble files for three years" ,(& 'mdash) "ever since I moved from PPC to Intel.  So much for testing your backups.")
                       (p "Briefly, it's an endian issue.  " (i "Use htonl(), guys!") "  So, I wrote " (a (@ (href "/pub/adouble-fix.pl")) "a simple script, adouble-fix.pl")
 " to detect and repair broken AppleDouble files created by rsync+hfsmode.  It's limited to files which contain only Finder Info and resource forks, but this is exactly what the --hfs-mode=appledouble option produces.")
                       (pre "$ time find 4 -name \"._*\" -type f -print0 | xargs -0 adouble-fix.pl -n -q
Dry-run finished.  1878 files seen, 1852 fixed, 26 skipped, 0 errors.
Dry-run finished.  1895 files seen, 1641 fixed, 254 skipped, 0 errors.
Dry-run finished.  1511 files seen, 1301 fixed, 210 skipped, 0 errors.
real 0m23.586s  user 0m1.460s  sys 0m1.450s

$ time find 4 -name \"._*\" -type f -print0 | xargs -0 adouble-fix.pl -q
Finished.  1878 files seen, 1852 fixed, 26 skipped, 0 errors.
Finished.  1895 files seen, 1641 fixed, 254 skipped, 0 errors.
Finished.  1511 files seen, 1301 fixed, 210 skipped, 0 errors.
real 0m3.555s  user 0m2.240s  sys 0m0.980s

$ time find 4 -name \"._*\" -type f -print0 | xargs -0 adouble-fix.pl -q
Finished.  1878 files seen, 0 fixed, 1878 skipped, 0 errors.
Finished.  1895 files seen, 0 fixed, 1895 skipped, 0 errors.
Finished.  1511 files seen, 0 fixed, 1511 skipped, 0 errors.
real 0m1.069s  user 0m0.820s  sys 0m0.400s"
)
                        ))
         (blog-entry "July 17, 2009" "Zeos, RIP"
                     `((p "Computers are fast these days" ,(& 'mdash) "real fast.  One might venture to say " (i  "too fast") ", two times fast.  Back in my day, you could " (tt "`type a:autoexec.bat`") ", drink a sip of coffee, change your mind and Ctrl-C it before you cluttered up your screen with unwanted output.  I got nostalgic for those halcyon days of clean screens, and that's why I bought the 640GB Western Digital Caviar Green.")
                       (p "The WD6400AACS is an adequate performer, and runs nice and cool; exactly what I want in a RAID 1.  But it was " (i "this") " stellar feature which really caught my eye: after 8 seconds of inactivity, the drive parks its heads.  The first subsequent access incurs a half-second pause, which I like to call my \"me time\".")
                       (p "Eight.  Unconfigurable.  Seconds.  Configuration?  No thank you!  You'd lose the primary nostalgic benefit: frequent, jarring pauses during interactive use, which is why I bought the thing in the first place!")
                       (p "Let's have a look at an example session with the Caviar Green.")
                       (pre "$ ls /usr/share/doc       # what the heck was I looking for?\n"
                            "  [dramatic half-second pause before results appear]\n"
                            "  [you peruse the directory list for 10 seconds; clunk, heads park]\n"
                            "$ cd mutt-1.5; ls         # found it!\n"
                            "  [dramatic half-second pause; exeunt results]\n"
                            "  [you get distracted by an errant cat for a moment; parks heads, clunk]\n"
                            "$ cat README.Debian       # there's my bedtime story\n"
                            "  [dramatic-half-second pause music]\n"
                            "  [you skim the readme for 10 seconds; clunks park, head]\n"
                            "$ date                    # it feels like aeons have passed\n"
                            "  [ironically dramatic half-second pause]\n"
                            "  2300 AD                 # gato, is that you?\n")
                       (p "Meanwhile, while I pause to gather my thoughts, the drive is continuously falling asleep and being woken up again every 15 seconds anyway as Linux periodically squirts a bit of data at it, causing an absolutely adorable constant clacking sound.")
                       (p "Now, in an alternate universe where I don't actually enjoy using my command-line like it was the gas pedal on an old man's Cadillac, I might scour the internet for hours and come up empty except for an unsupported, DOS-based, placebo utility which only pretended to fix the problem, and eventually resort to a backgrounded while loop touching a file every 7 seconds.")
                       (p "But in this universe, I and Ferris Bueller rate the WD6400AACS a Strong Buy.")))
         (blog-entry "February 14, 2009" "Walentyn"
                     `((p ,(link "gopher://3e8.org" "Welcome back") " to "
                          ,(link "http://chicken.wiki.br/eggref/4/phricken" "1993") ".")))
         (blog-entry "January 22, 2009" (link "http://www.allthelyrics.com/lyrics/cabaret_soundtrack_200000/married-lyrics-1049229.html" "Heiraten")
                     `((p "Thousands of days ago, during my OS/2 phase, I used the mail client " ,(link "http://pmmail.os2voice.org/index.php?title=PMMail_for_OS/2" "PMMail") ".  Recently, I discovered a long-forgotten cache of 1996-era PMMail-format messages, hidden amongst a surplus of hyphens.  I thought there perhaps might be something worth reading in there" ,(& "mdash") "a poignant reminder of the human mind's infinite capacity for self-delusion" ,(& "mdash") "and in an effort to recover these messages, I wrote a trivial " ,(link "pub/pmmail-to-maildir.pl" "converter from PMMail to Maildir format") ".")
                       (p "Now, one may explore the dark days of college to one's heart's content.  Chiefly, one finds that one was once able to write real good English, which that I can't do anymore.")))
         (blog-entry "June 4, 2008" "Shift-Command-Z"
                     `((p "I redid the site " ,(link "src/" "in Scheme") ".  You won't notice any difference.")))
         (blog-entry "May 24, 2008" "The AA vote"
                     `((p "JEdict (4.5.3) does not let you change the font used in its WebKit web browser, nor does it let you specify a custom user stylesheet.  Kill two birds with ... well, two stones:\n")
                       (pre "$ defaults write -app JEDict WebKitUserStyleSheetLocationPreferenceKey -string \"~/css/jedict.css\"\n"
                            "$ defaults write -app JEDict WebKitUserStyleSheetEnabledPreferenceKey -boolean true")
                       (p "A minimal stylesheet for viewing something like 2ch ascii art on OS X might be:")
                       ,(hil 'css "body { font: 14pt \"MS-PGothic\" !important; }  /* 16.5pt works too -- but not 16 */\n" "table { font-size: 100%; }\n")))

         (blog-entry "March 28, 2008" "Jonesing"
                     `((p "I made the following changes to "
                          ,(link "http://annexia.org/forth" "Jonesforth") ", which are "
                          ,(link "/pub/jonesforth-modified.tar.gz" "available here") ":")
                       (p "Tail-call optimization; " (tt "DOVAR/DOCON") " -" ">" " simplified " (tt "CONSTANT") ", " (tt "VARIABLE") ", and " (tt "VALUE") ";"
                          " FIG-FORTH-style " (tt "CREATE ... DOES>") " support; case-insensitive " (tt "FIND") "; Pentium optimizations; "
                          (tt "SEE") " aborts on word not found; constant, variable and " (tt "DOES>")
                          " decompilation (and indicate primitives).  Also added optional space-saving "
                          (tt "DOES>") " support, which cannot be decompiled and is probably slower.")))

         (blog-entry "February 29, 2008" "Schaltjahr"
                     `((p "I use SQLite3 for my searches database and I was looking for a way to create a "
                          "frequency table of search terms--either updating the term count or inserting new terms"
                          "as needed.  Evidently, according to "
                          ,(link "http://blog.modp.com/2007/11/sqlite3-and-on-duplicate-key-update.html"
                                 "this blog post")
                          ", MySQL has an extension " (tt "INSERT ... ON DUPLICATE KEY UPDATE")
                          " which lets you insert or update as needed.  The solution in that post for SQLite3 is programmatic: "
                          "check if an INSERT fails, and do an UPDATE if so.")
                       (p "However, I thought this was not particularly elegant and it also would not work from a trigger. "
                          "Here is the autovivification solution I came up with:")
                       ,(hil 'sql "CREATE TABLE terms(count INTEGER, term TEXT PRIMARY KEY);\n"
                            "...\n"
                            "INSERT OR IGNORE INTO terms(count, term) VALUES (0, 'my term');\n"
                            "UPDATE terms SET count = count + 1 WHERE term = 'my term';\n")
                       (p "Execute these two statements whenever you want to bump a term's frequency. "
                          "The " (tt "ON CONFLICT IGNORE") " (aka " (tt "OR IGNORE") ") causes the INSERT to silently fail "
                          "on constraint violation; specifically, the uniqueness of the primary key.  The base "
                          "value of 0 ensures the count starts at 1.")))

         (blog-entry "February 25, 2008" "And your little spiffy, too"
                     `((p "Persistent connections exhibited the same issue on "
                          (a (@ (href "http://chicken.wiki.br/spiffy")) "spiffy") ", so I wrote a "
                          (a (@ (href "http://chicken.wiki.br/socket")) "socket egg") " which allows you to disable "
                          "Nagle's algorithm (among other socket options) on Chicken TCP connections:")
                       ,(hil 'scm "(http:listen-procedure \n"
                            " (lambda (port backlog host)\n"
                            "  (let ((L (tcp-listen port backlog host)))\n"
                            "    " `(i "(set! (tcp-no-delay (tcp-listener-fileno L)) #t)") " L)))")))

         (blog-entry "February 18, 2008" "lighttpd's naggling issue"
                     `((p "Benchmarking lighttpd on OS X with httperf, I found that persistent connections "
                          "that were fetching files of certain sizes (some big, some small) had their first "
                          "request satisfied in under 1ms, but experienced a 40ms delay on subsequent requests. "
                          "Apache did not have this problem. "
                          "Only " (a (@ (href "http://www.webmasterworld.com/apache/3320954.htm"))
                                     " one guy on the entire Internet")
                          " reported seeing something like this, and he got (surprise) no response.")
                       (p "Studying ktrace/strace and tcpdumps from both sides indicated it might be a Nagle "
                          "problem, and since httperf does disable Nagling, that left lighttpd.  Indeed, "
                          "I found that lighttpd does not (as of 1.4.18) set the TCP_NODELAY "
                          "flag on its sockets.  I guess it assumes the OS disables the Nagle optimization "
                          "globally, which is generally true on Linux, but not OS X.  Or evidently Solaris, "
                          "since " (a (@ (href "http://www.sun.com/servers/coolthreads/tnb/lighttpd.jsp")) "Sun said")
                          " they patched lighttpd themselves.\n")
                       (p "I rebuilt the lighttpd from MacPorts and added a small patch:\n")
                       (pre "port uninstall lighttpd\n" "port -d extract lighttpd    # -d to see extract path\n"
                            ,(link "/pub/lighttpd-1.4.18-tcpnodelay.diff"
                                   "(patch network.c with TCP_NODELAY option)")
                            "\n"
                            "port install lighttpd \n")
                       (p "and it solved the issue.")))

         (blog-entry "February 18, 2004" "Dreamcast hacking."
                     `((p "I've started coding for the Dreamcast again recently, and posted a few things in the Dreamcast section.  First, "
                          ,(link "/hacks/dc/#demos" "serpent")
                          ", a version of the KOS bubbles demo that runs about five times faster, due to an optimized assembly loop that transforms and sends vertices to the PVR via the store queues.  This sparked a discussion on DMA, and a DMA-based variant coded by Dan Potter was added to the examples tree.  Second, "
                          (a (@ (href "/hacks/dc/#demos")) "oceano")
                          ", which uses the specular highlight feature of the PVR to simulate sparkles on water.  It doesn't try to be mathematically correct--rather it's meant as a jumping off point for others.  Third, "
                          (a (@ (href "/hacks/dc/#demos")) "punch")
                          ", a test harness for benchmarking large polygon performance, originally written to test punch-through polygons.  There are also a few "
                          (a (@ (href "/hacks/dc/#patches")) "patches")
                          " I wrote along the way, some of which are in the main KOS tree now.")))

         (blog-entry "April 30, 2003" "Summary of additions."
                     `(p "Over the past year I've added several items: "
                         ,(link "/hacks/brkout" "brkout") ", a breakout clone for the Dreamcast; "
                         ,(link "/hacks/ultima6/" "u6edit") ", my first world editor for Ultima 6; and several "
                         ,(link "/music/" "musical pieces") ".  Today I added "
                         ,(link "/hacks/ultima6" "pu6e") ", which supersedes u6edit and offers many more features."))
))

;;; main page to stdout

(render-page
 "3e8.org"

 (navskip)
 (languages 'en)
 (call-resp (category)
            "In EBCDIC we trust.")
 (navbar 'home)
 
 `(div (@ (id "content"))
       ,(blog blog-entries teaser: #t)
       ,(sidebar)))

;;; generate entry permalinks to files

(for-each
 (lambda (e)
   (let ((dir (string-append +root-dir+ "/" (blog-entry-permalink e))))
     (let ((fn (string-append dir "/index.html")))
       (fprintf (current-error-port)  "Generating blog entry ~a...\n" fn)
       (create-directory dir #t)
       (with-output-to-file fn
         (lambda ()
           (render-page
            (string-append "3e8.org blog - " (text-content (blog-entry-title e)))
            (navskip)
            (languages 'en)
            (call-resp (category)
                       "In EBCDIC we trust.")
            (navbar 'home)

            `(div (@ (id "content"))
                  ,(blog (list e))
                  ,(sidebar))))))))
 blog-entries)

;;; generate atom feed

;; assume we want all entries in the feed right now

(with-output-to-file (string-append +root-dir+ "/" (blog-feed-uri))
  (lambda ()
    (write-atom-doc
     (make-atom-doc
      (make-feed
       authors: (list (make-author name: "jim" uri: "http://3e8.org"))
       id: "tag:3e8.org,2010-07-27:/blog/atom10" ;; unchanging feed ID
       title: (make-title "3e8.org")
       links:  (list (make-link uri: (string-append "http://3e8.org" (blog-feed-uri))
                                relation: "self"
                                type: 'atom)
                     (make-link uri: "http://3e8.org"  ;; or 3e8.org +blog-prefix+ ?
                                type: 'html))
       updated: (rfc3339->string (time->rfc3339 (seconds->utc-time))) ;; consider it updated NOW
       subtitle: (make-subtitle "In EBCDIC we trust.")
       entries:
       (map (lambda (e)
              (make-entry id: (blog-entry-id e)
                          title: (make-title (text-content (blog-entry-title e))) ;; consider HTML
                          updated: (rfc3339->string (blog-entry-updated e))
                          published: (rfc3339->string (blog-entry-published e))
                          links: (list (make-link uri: (string-append "http://3e8.org"
                                                                      (blog-entry-permalink e))))
                          content: (make-content (xml (blog-entry-sxml e))
                                                 type: 'html)
                          )
              )
            blog-entries)
       )))))

;;; old entries

;; There was a (br (@ (style "clear"))) here previously--don't know what it did.  However, found this comment on another site: <!-- This clearing element should immediately follow the #mainContent div in order to force the #container div to contain all child floats -->

;; (*COMMENT* "\n\n\n\t\t<h2 class=\"date\">\n\tApril 04, 2002\n\t</h2>\n\t\n\n\t<div class=\"blogbody\">\n\t\n\t<a name=\"000003\"></a>\n\t<h3 class=\"title\">Just missed the deadline.</h3>\n\n\t<div class=\"entrybody\">\n\t\n\t<p>My tardiness in updating this page has provoked outrage in my readership community of one, and so I feel the time for such is ripe. Of course, I have been updating Trivial Hacks all along, though I guess nobody noticed.</p>\n\n<p>But, tardiness or not, I do have an excuse. Hmm? Yes, I was getting to that. It's simply that I've been too busy setting up my company, Ursetto Consulting, Inc. That's right &mdash; Ursetto Consulting, Inc. Check it out, if you dare.</p>\n\n<p>Otherwise, not much has changed. The Esperanto page is still crusty. There's no Spanish page at all. The links... don't get me started. I haven't even changed the musical intro in years, by Crikey. But who cares? You love me anyway. So stay tuned to this space for more exciting updates, such as the new Music section and the rather explicit Searches page.</p>\n\t\n\t\n\t</div>\n\t\n\t</div>\n\t\n\t</div>\n\t\n\n\n\n\n\t\t<h2 class=\"date\">\n\tMay 30, 2000\n\t</h2>\n\t\n\n\t<div class=\"blogbody\">\n\t\n\t<a name=\"000005\"></a>\n\t<h3 class=\"title\">General text update.</h3>\n\n\t<div class=\"entrybody\">\n\t\n\t<p>The fates have once again conspired to have me update my home page.  Let's see... at this rate, the next update should arrive in January, 2002.  Unlike the reconstructive surgery of '98, I don't have too much to add this time around; but the comment about this page being rather crumbly around the edges was itself getting stale.  As I'm sure <em>that</em> comment will be in a year or so.<br />\n  <br />\nChange-wise, 24601.html now reflects the fact that I graduated, and this welcome page has obviously been rewritten.  Or is it obvious?  I'll have to check the weblogs to see if I got any hits in the last two years.</p>\n\n<p>The Esperanto version of this page is rather outdated.  This is because I haven't studied the language for a while, so I can't update it without introducing gibberish.  On the flip side, this site might sport a brand new Spanish page soon.  A boy can dream... </p>\n\t\n\t\n\n\t</div>\n\t\n\t<div class=\"posted\">Posted by jim3e8 at <a href=\"/archives/000005.html\">12:00 AM</a>\n\t\t| <a href=\"http://3e8.org/cgi-bin/mt/mt-comments.cgi?entry_id=5\" onclick=\"OpenComments(this.href); return false\">Comments (0)</a>\n\t\n\t\n\t</div>\n\t\n\t</div>\n\t\n\n\n\n\n\t\t<h2 class=\"date\">\n\tOctober 03, 1998\n\t</h2>\n\t\n\n\t<div class=\"blogbody\">\n\t\n\t<a name=\"000004\"></a>\n\t<h3 class=\"title\">Complete site redesign.</h3>\n\n\t<div class=\"entrybody\">\n\t\n\t<p>I'd been neglecting my page for quite a while and so it'd become rather crumbly around the edges, much like a <font color=ffff55>lemon</font> cookie.  So I decided that, instead of writing a paper, I'd do some autumn cleaning.  The result is not quite done but I've got all the previous \"content\" back on line, in a much more palatable format.<br />\n    <br />\nI've finally produced Esperanto versions of this page and the navigation bar; eventually I plan to esperantize the entire site.  I used the Latin-3 (iso-8859-3) encoding, so you'll need the proper fonts to view it.  In Netscape, I use the TrueType font <a href=\"http://esperanto.agoranet.be/times_se.exe\">Times SudEuro</a>; you can also get a whole bunch of Latin-3 TrueType fonts in one package from <a href=\"ftp://ftp.stack.nl/pub/esperanto/fonts.dir/lat3pttf.zip\">ftp.stack.nl</a>.  There's more information about Esperanto on the web (and elsewhere) at <a href=\"http://www.esperanto-usa.org\">www.esperanto-usa.org</a>. <br />\n    <br />\nBy the way, the Esperanto version of this page isn't a straight translation; you'll have to learn the language to see what it says :) Don't worry, it only takes a few weeks to understand a reasonable amount, and less time if you're willing to use a dictionary constantly.  In my case, my Esperanto totally cremates my Japanese, and I've studied Japanese for much longer.  The proof of this is that there's no way I could write a non-trivial page in Japanese at this point. QED.<br />\n    <br />\nI always appreciate e-mail, even if the entire message body is only \"<a href=\"/supportmail.html\">your story sucks</a>\".  If you stop by, do drop me a line at <a href=\"mailto: ursetto@uiuc.edu\">ursetto@uiuc.edu</a>.   <br />\n</p>\n\t\n\t\n\n\t</div>\n\t\n\t<div class=\"posted\">Posted by jim3e8 at <a href=\"/archives/000004.html\">12:00 AM</a>\n\t\t| <a href=\"http://3e8.org/cgi-bin/mt/mt-comments.cgi?entry_id=4\" onclick=\"OpenComments(this.href); return false\">Comments (0)</a>\n\t\n\t\n\t</div>\n\t\n\t</div>\n\t\n")
;; (*COMMENT* "\n\n<div class=\"sidetitle\">\nRecent Entries\n</div>\n\n<div class=\"side\">\n<a href=\"/archives/000007.html\">Dreamcast hacking.</a><br />\n<a href=\"/archives/000006.html\">Summary of additions.</a><br />\n<a href=\"/archives/000003.html\">Just missed the deadline.</a><br />\n<a href=\"/archives/000005.html\">General text update.</a><br />\n<a href=\"/archives/000004.html\">Complete site redesign.</a><br />\n\n</div>\n\n<div class=\"sidetitle\">\nFrom the Archives\n</div>\n\n<div class=\"side\">\n<a href=\"/archives/2004_02.html\">February 2004</a><br />\n<a href=\"/archives/2003_04.html\">April 2003</a><br />\n<a href=\"/archives/2002_04.html\">April 2002</a><br />\n<a href=\"/archives/2000_05.html\">May 2000</a><br />\n<a href=\"/archives/1998_10.html\">October 1998</a><br />\n\n</div>\n\n<div class=\"sidetitle\">\nArchive Search\n</div>\n \n<div class=\"side\">\n<form method=\"get\" action=\"http://3e8.org/cgi-bin/mt/mt-search.cgi\">\n<input type=\"hidden\" name=\"IncludeBlogs\" value=\"1\" />\n<input id=\"search\" name=\"search\" size=\"20\" /><br />\n<input id=\"searchbtn\" type=\"submit\" value=\"Search &raquo;\" />\n</form>\n</div>\n\n")
;; (*COMMENT* "\n<div class=\"sidetitle\">\nLinks\n</div>\n\n<div class=\"side\">\n<a href=\"\">Add Your Links Here</a><br />\n</div>\n\n<div class=\"syndicate\">\n<a href=\"/index.rdf\">Syndicate this site (XML)</a>\n</div>\n\n<div class=\"powered\">\nPowered by<br /><a href=\"http://www.movabletype.org\">Movable Type 2.661</a><br />    \n</div>\n")
