Multi-file Project in Chicken Scheme 2

In part 1 I covered the inclub syntax form that provides an abstract interface for including/loading in a multi-file project, such that the program works whether compiled or interpreted or called via a symlink. Now we'll take the next step and implement pluggable subcommands.

Our goal is to have a uniform interface for subcommands such that the main program does not need to know any internal details about particular commands, and only needs to be minimally modified when we want to add a new subcommand. We achieve this by writing a module to manage the subcommands, and have this module imported by the main app as well as by all subcommands. Each subcommand will register itself with the subcommand module, and the main app can call into this module to get information about subcommands and to call them. Here is the source code for the subcommand module:

(module subcommand
    (define-subcommand
     subcommand-call
     subcommand-exists?
     subcommand-ref
     subcommand-validate-args)

(import chicken scheme extras)

(use
 srfi-69)

(define-record subcommand
  name execute validate)

(define subcommands (make-hash-table))

(define (define-subcommand name execute validate-args)
  (hash-table-set! subcommands
                   name
                   (make-subcommand name execute validate-args)))

(define (subcommand-call name args)
  ((subcommand-execute (subcommand-ref name)) args))

(define (subcommand-exists? name)
  (hash-table-exists? subcommands name))

(define (subcommand-ref name)
  (hash-table-ref/default subcommands name #f))

(define (subcommand-validate-args name args)
  ((subcommand-validate (subcommand-ref name)) args))

)

Now each subcommand will register itself by calling define-subcommand. Here are the relevant portions of wn.scm:

(module wn
    ()

(import chicken scheme extras)

(import subcommand)

;; implementation elided

(define-subcommand "wn" execute validate-args)

)

Now here is the main app, included in its entirety, since it is short and sweet.

#! /bin/sh
#| -*- scheme -*-
exec csi -s $0 "$@"
|#

(import chicken scheme extras)

(use
 inclub)

(inclub "subcommand")
(import subcommand)

(inclub "wn")

(define main
  (case-lambda
   (() (print-help))
   ((command . args)
    (cond
     ((subcommand-exists? command)
      (subcommand-validate-args command args)
      (subcommand-call command args))
     (else (error "unknown command"))))))

(apply main (command-line-arguments))

So what do you think, Chickeners? Good? Bad? Naïve? Hacky? (Hmm, I better get to work on writing a comment form for my blog!) Well, to me, this is the first draft of a work in progress, and if a better way to write the multi-file program that meets the criteria laid out in part 1 comes to light, then you can surely expect another followup.