[getopt-sig] Comparing option libraries

Greg Ward gward@python.net
Wed, 27 Feb 2002 19:53:22 -0500


On 26 February 2002, A.T. Hofkamp said:
> In the argtools-version of the source, you raise a number of questions
> of 'how to do this in argtools' ?  The answer to those questions is
> both low tech (i.e. 'this is how'), and conceptual (i.e. 'this is why
> it is not easily possible').
> 
> Unfortunately, I have little time this week for the first part, so I will
> concentrate on the second part (which is also more interesting here imho).

Yes, the concepts are definitely more important than the mechanics.
Feel free to supply a reworked version of ripoff_argtools.py when you
get the chance, though.  ;-)

> Argtools is built on the following assumption:
> 
> An option object (i.e. an instance of an option class) represents a single
> command-line concept.
> Example, the 'verbosity of the program' (i.e. what level of output should the
> program produce) is a single concept, which should be implemented by a single
> option object.

Got it.  Let me paraphrase, and explain Optik's philosophy in the same
terms:

  * Optik 1.x: an Option represents a particular action performed on
    a particular destination variable

  * Argtools: an Option represents everything you might do to a
    particular destination variable

Eg. in Optik, you might have one Option to increment the 'verbose'
variable ("-vvv"), another Option to set it to a specific value
("--verbose=3"), and a third to zero it ("-q" or "--quiet", which are
synonyms wrapped up in the same Option).  Of course, you're free to
arrange your verbosity options any way you please, but that's how I have
done it so far.

With Argtools, every command-line option that could affect the 'verbose'
variable is bundled into a single Option object.

> From this assumption follows that options are independant from each other
> (since they are related to different concepts), and that the order of options
> is not relevant (also because they control different concepts).
> 
> I think the above are sound design principles. It should be non-trivial to
> deviate from them.

It took a day to sink in, but I very definitely see your point.  There
might very well be an Optik 2.0 after all.  ;-)

>   The current ripoff program implements the policy that 'the last
>   setting is valid', i.e. if a user specifies 'ripoff --file --pipe',
>   it is considered to be equivalent to 'ripoff --pipe'.  I disagree
>   with this equivalence notion. If a user specified both options, it
>   is likely that he wants both options.  By ignoring some of his
>   options, you create unexpected behaviour of the program.  You should
>   at least warn the user that you ignore some part of the commandline,
>   I personally would not even execute the command until the user makes
>   an unambigious choice.

I strongly disagree here; "-qvq" should be the same as "-q", and
"--use-files --use-pipes" should be the same as "--use-pipes".  I do
*not* see those options as mutually exclusive, I see them as writing to
the same variable.  Perhaps "interfering options" would be a better term
for them; Optik is perfectly happy to let you have interfering options,
but the concept is nonsensical with Argtools because its concept of
"option" is somewhat larger.

I'm not sure I can entirely explain my rationale, especially since Tony
Ibbs already hit the main points.  It's based on gut instinct and years
of experience more than anything else.  It boils down to this:
command-line options are processed in left-to-right order, and rightmost
wins.  Order only matters for interfering options, but interfering
options are (IMHO) perfectly OK, because the user typing the command is
not necessarily solely in charge of all the words in that command-line:
witness aliases, or shell scripts that end like

   exec something_else --this --that $*

(Yes, I know that $* is the wrong thing to use in a shell script, but I
can't remember the right spelling.  That's why I don't write shell
scripts.)

>   I think it is better to throw the --file and --pipe options out, and
>   define --output=file and --output=pipe. This is implemented in the
>   EnumOption of argtools. No ambiguities, no user confusion.

I'm lazy.  Why should I have to type that many characters when I can get
away with fewer?  Having 20 command-line options instead of 17 isn't
going to break the bank, and "--file" is just plain easier to type than
"--output=file".  (And "-f", which is what I really use, is easier
still.)

> - Your line count is not entirely fair due to some interfacing problems.
>   If you would develop ripoff using argtools, then you would _NOT_
>   make copies from the parser object to the options object, you would
>   use the parser object itself for querying options.  Alternatively,
>   you would add the option objects into 'options' rather than in the
>   parser object.  In both cases, the lines of code for copying option
>   values should be excluded from the line-count.

But my preferred mode of operation is to pass around an options object
that I can use as follows:

    if options.use_files:
        # ... do things one way ...
    else:
        # ... do them another way

or

    if not options.rip_only:
        # ... encode the track we just ripped ...

I find that the clearest and most obvious way to work, and I'd like to
see more programs work that way.  I most explicitly do *not* want do
have to do this:

    if parser.use_files.Seen():
        # ...

because that takes an implementation detail from one part of the program
-- how exactly the value of the 'use_files' flag is set -- and imposes
it all over the rest of the program.  What if I take your advice and
switch to "--output={file,pipe}" -- do I then have to go throughout my
code and change the above to

    if parser.output.Value() == "file":
        # ...

?  Yuck.  Also, having to call a method just to get an option value is
annoying and clunky.  Direct attribute access is simple, cheap,
painless, and hard to screw up.  (If you get it wrong, Python calls you
on it with AttributeError.)

> - For options with arguments, how to see whether the option is used, etc ?
>   argtools has 3 methods for querying an option object with a value:
>   - Used(): returns 1 if the option is specified, 0 otherwise
>   - Set(): returns 1 if the option on the command line has an argument
>   - Value(): the value of the argument of the option object.
> 
>   Thus:
>   if not option.Used():
>      # option is not specified
>   elif option.Used() and not option.Set():
>      # option without argument is specified, for example --log
>   elif option.Used() and option.Set():
>      # option with argument is specified, for example --log=$HOME/mylog
>      # value of the argument in option.Value()
> 
>   Obviously, if --log is not allowed, then the second case never happens, and
>   Set() and Used() are always the same.
> 
>   Notice the strict seperation of usage and value. In many other option
>   toolkits including Optik, these notions are mixed together into special
>   values of the argument.

Thank you, that clarifies something important.  The lack of "was option
X seen?" bothered me for a while, so I implemented subclasses of
OptionParser and Option to see how hard it was.  (See
examples/required_2.py in the Optik CVS.)  It was trivial.  But I
couldn't see why this was really necessary, because usually all I do is
test a value against None to see if its corresponding option was
supplied.

But you're absolutely right: this is not the right way to see if an
option was supplied; it's only the right way to see if an option value
is None.  Explicit is better than implicit.  Just added it to my to-do
list.

> So the questions are:
> 
> - Is the idea of 'an option object represents a single concept' good ?

Sounds promising, but it will require a careful rethink.  If it happens,
it'll be called Optik 2.0.

> - Should we have a seperation between usage and value of an option ?

Yep, and trivial to add.  Will be in Optik 1.3.

> PS With your comparison page, you suggest some form of competition. I do not
>    have that intention.

I was careful to use the word "comparison" instead of "competition", and
I tried to stay as even-handed as I could.  I'm certainly not aiming for
a competition, I'm trying to get all the good ideas out on the table.
Seems to be working so far.

        Greg
-- 
Greg Ward - geek-at-large                               gward@python.net
http://starship.python.net/~gward/
War is Peace; Freedom is Slavery; Ignorance is Knowledge