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.