[getopt-sig] Comparing option libraries

A.T. Hofkamp a.t.hofkamp@tue.nl
Tue, 26 Feb 2002 11:58:59 +0100 (CET)


On Sun, 24 Feb 2002, Greg Ward wrote:

> Hi all --

Hi Greg,

I think you are doing a terrific job of answering all issues raised in this
SIG, many thanks for that.


Now, with respect to the re-implementation of the command-line interface of
ripoff, using argtools:
(details are on http://www.python.org/sigs/getopt-sig/compare.html).

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).

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.

>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.


Now on to your questions:
- How to implement the --file and --pipe options ?
  From your comment I understand that these options are mutually exclusive,
  i.e. the user cannot specify both.
  In Optik, both options set the same value (one does something like
  'opions.output='file', and the other does 'options.output='pipe').

  It is true that it is not easy to implement in argtools in the sense that you
  can do:
      self.file = self.AddOption(VoidOption('file'))
      self.pipe = self.AddOption(VoidOption('pipe'))
  because these are 2 seperate option objects.
  Due to the above assumption, this means in argtools that you have a concept
  'output to file y/n', as well as a concept 'output to pipe y/n'.

  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.

  If the implementor of a program really wants this behaviour, then at least he
  should make that decision rather than the option processing module making
  that decision for him.
  That is, he should specify somewhere what to do when the user specifies both
  --pipe and --file.
  I think argtools behaves properly by making the implementor at least realize
  that the ambiguity exists, and that he should deal with it.


  So, can --pipe and --file be implemented in argtools ?
  Yes.
  Since we are talking about a single option concept, there should be a single
  option object for the option parser.
  You would derive an option class from BaseValueOption, which recognizes
  'file' and 'pipe', and sets the value of the option to e.g. 'file'/'pipe'.
  Very rough, untested implementation:

  class RipoffOutputOption(BaseValueOption):
     def _init__(self,defaultval='pipe'):
        BaseValueOption.__init__(self,'not used',defaultval)
     def Match(self,opt,arg):
        if opt=='file' or opt=='pipe':
	   self.SetUsed(1)
	   self.SetSet(1)
	   self.SetValue(opt)
	   return _OPTIONMATCH
        return _NOMATH

  Now you have a single option object that understands both --pipe and --file,
  and handles them, as in the ripoff program.

  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.

  Having to create a derived class for this case is good. It is a sufficient
  hurdle for people to reconsider their option interface before implementing
  something confusing as --file and --pipe.

- A similar story holds for the verbosity of the output, except that the
  complexity of the realtions between the various option settings is even more
  complex.

- A counting option (i.e. an option object that counts how many times an option
  like -v has been specified) is currently not implemented in argtools (though
  it should be!!).
  An implementation:

  class CountingOption(BaseValueOption):
      def __init__(self,optnames,defaultval=0):
         BaseValueOption.__init__(self,optnames,defaultval)

      def Match(self,opt,arg):
         if self.Parse(opt):
             self.SetUsed(1)
             self.SetSet(1)
             self.SetValue( self.Value()+1 )
             return argtools._OPTIONMATCH
         return argtools._NOMATCH

  Then, -v can be specified as "self.v = self.AddOption(CountingOption('v'))".

  Note that in my opinion, the above code should be considered part of
  argtools, not of the ripoff program (and thus not being counted as 'lines of
  code' for the ripoff implementation).

- 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.

- 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.

  While this seems to work for most option values, it does give some special
  care when handling the value of an option.
  (in a dynamic language such as Python, the problem is much less severe, but
  for example in C++, how would you differentiate between nothing, --bool,
  --bool=false, and --bool=true ?)

  I think the seperation between usage and value is good. It gives less
  surprises to the programmer of what to check.


So the questions are:

- Is the idea of 'an option object represents a single concept' good ?

and

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

(for me, both questions should be answered 'yes').

Albert
PS With your comparison page, you suggest some form of competition. I do not
   have that intention. Optik is much more world-ready than argtools. On the other
   hand, Optik does make some assumptions that may not always be correct/wanted.
   Argtools seems to make it more difficult for the programmer in those cases.

   By introducing (some of the) concepts from argtools in the final package of
   this SIG, everybody wins.
-- 
Constructing a computer program is like writing a painting