Parsing differences between Guile's SRFI-37 and getopt_long
For a long time now I was getting irritated every time I tried to bind
some directory into a Guix's shell container. The usual --expose
~/foo
does not work. You have to do --expose=$HOME/foo
due to
a peculiarity of args-fold
(from SRFI-37) distributed
with GNU Guile. Before I write my own implementation, I want to first analyze
the differences.
In this short blurb for my implementation (all that is left is to write
the documentation), I focus on the compliance to the SRFI-37 wording and on
differences in parsing to GNU C library's getopt_long
.
Short refresher on SRFI-37
The procedure for parsing the command line is args-fold
and
it has the signature reproduced below.
procedure: (args-fold ARGS OPTIONS UNRECOGNIZED-OPTION-PROC OPERAND-PROC SEEDS ...)
The OPTIONS
is a list with members created by
the option
procedure with the signature again reproduced below. In
the examples in this post I will often pass #f
as
the OPTION-PROC
, since it will be irrelevant.
procedure: (option NAMES REQUIRED-ARG? OPTIONAL-ARG? OPTION-PROC)
Short example putting this all together follows.
(use-modules (srfi srfi-37))
(define (option-processor opt name arg seed)
(cons name seed))
(args-fold '("-a" "-b")
(list (option '(#\a #\b) #f #f option-processor))
#f #f '())
;;; => (#\b #\a)
Differences
Signaling an error on missing or extraneous arguments
Missing or extraneous argument for an option causes an error to be signaled.
(args-fold '("--a")
(list (option '("a") #t #f #f))
#f #f '())
;;; error--> In procedure args-fold: Missing required argument after `--a'
(args-fold '("--a=b")
(list (option '("a") #f #f #f))
#f #f '())
;;; error--> In procedure args-fold: Extraneous argument after `--a'
While this is often useful, I do not believe it actually complies to the specification. The specification is not very detailed, so we can only guess what the intended behavior is. Based on this part:
ARG will be a string, or #f if args-fold didn't encounter an option-argument.Anthony Carrico, SRFI-37
I think that the correct behavior is to call the option processor and
pass it #f
as the argument value. The processor should inspect
both the option and the argument, and signal an error if deemed appropriate.
I mean, both behaviors are fine. ¯\_(ツ)_/¯ But given this is supposed to be a fundamental, low-level option processing, it is better to err on the side of giving more power to the caller, not less.
An optional argument can be in a separate argument
When an option takes an optional argument, args-fold
allows
for it to be in a separate argument, while getopt_long
does not.
In other words, given (option '(#\a) #f #t #f)
and command
line -a b
, args-fold
parses it as an option "a" with
argument value of "b". However getopt_long
sees it as option
"a" and operand "b".
Since optional arguments are somewhat rare, this is not a huge problem, but it prevents me from writing a program complying with section 12.1 of POSIX. And I like POSIX.
Long options do not accept value in a separate argument
This one is annoying and the main reason I even started looking into
this. Passing an argument for long option in a separate argument,
like --foo bar
, does not work. It needs to be --foo=bar
. This means that tilde (e.g. in --foo=~/Download
) is not expanded.
No support for abbreviations
Did you know that you can abbreviate long options? No? Well me neither.
But apparently, as long as the prefix is unique, you can shorten the options.
So ls --versio
is the same as ls --version
.
Is this a good thing? Probably not. Should it be possible to turn this off? Yes. Is it possible to turn this off? No. Nevertheless, this is a parsing difference.
No support for POSIXLY_CORRECT
This is an environment variable that can be defined to
make getopt_long
stop parsing options on first operand. Sometimes
it is useful, and I think it is a shame not to support it, especially since it
is fairly easy.
Conclusion?
I will write my own args-fold
. With blackjack. And
hookers.