Multi-file Project in Chicken Scheme
I have a little program, currently called 'eml', that I wrote in Perl and
that I decided to rewrite in Chicken Scheme. It is supposed to be a
multitool for managing directory structures of email in maildir format,
but so far, it has only one function, wn
, which stands for "whats-new".
It reports which maildirs have new or unread messages, highlighting those
that are new or unread within the hour, or whatever timespan you specify.
The call form of eml is subcommand style, similar to git or other version
control programs:
$ eml wn
Although I much prefer to program in scheme over Perl, the general framework of the Perl version is quite satisfactory, so there are certain aspects of it that I would like to retain for the scheme version. Thus in setting out, my design goals are as follows:
- It should be runable either interpreted or compiled.
- It should work even when called via a symlink.
- Each subcommand (like 'wn') should be in its own scheme file.
- Subcommands should be encapsulated in modules (Chicken modules or otherwise).
- Documentation for each subcommand (for the
--help
display) should be stored in that command's module. - The main application (eml) should not need overmuch internal information about the subcommand modules. If each subcommand module provides a predetermined interface to its functionality, it should be pluggable into the main application with minimal changes to the main application.
These design goals were largely inspired by how the Perl version of the program worked. See the Perl module App::Cmd for details. It may be necessary to alter and adjust these goals for the different programming platform, but you've got to start somewhere.
I have at least a preliminary implementation of goals 1 through 4, so that is what I am blogging about today. I say preliminary because I am still exploring other options, like the system egg, so I make no claim that my method is the best. I haven't found a ready-made template for the kind of application I would like to write, so I am feeling my way around in the dark with this.
The main challenge to overcome is in abstracting away the difference
between running a Chicken program interpreted versus compiled. The main
scheme file needs to include
the subcommand scheme files, but if you say
(include "wn")
, that will only work for the compiled version of the
program, or when you run the program interpreted from its own directory.
It breaks when you run the program interpreted from elsewhere, or via a
symlink. Chicken's include
is a special form which takes a string as
its argument, so you can't compute the path dynamically. For that, you
need load
.
So the idea is to use include
when compiling, but load
when running
the program interpreted. Zbigniew on the Chicken IRC channel helped me
solve this quandary: Chicken has a special form called
cond-expand
for doing just this sort of thing. I just wrote it up and wrapped it in a
define-syntax
form. Then if it proves useful, it could be put in a
library.
(define-syntax inclub
(syntax-rules ()
((inclub string)
(cond-expand
(compiling
(include string))
(else
(let ((install-path
(filepath:drop-file-name
(if (symbolic-link? (program-name))
(read-symbolic-link (program-name) #t)
(program-name)))))
(load (filepath:join-path (list install-path string)))))))))
To use it:
(inclub "wn")
You might be wondering about the name. That was Zbigniew's idea — "inclub: the brute force 'include'". Hardy-har-har.
If the inclubbed file provides a module, you can import it as normal:
(import wn)
So that is where I am at with this little project. Goals 5 and 6 may prove not to be a natural fit for Chicken, but whether they are or not, I will be blogging a follow-up of my findings and my eventual solution.
Follow-up here: Multi-file Project in Chicken Scheme, 2