[darcs-users] [issue1550] general purpose command line parsing library (CmdArgs)

Neil Mitchell ndmitchell at gmail.com
Fri Sep 18 15:16:29 UTC 2009


> data Darcs
>     = Help
>         {helpCommands :: [String]}
>     | Add
>       ...
>     | Remove

| So right now, we have this commands infrastructure, where each command
| is a member of DarcsCommand (so for example, add would be 'DarcsCommand
| "add" ...'; pull would be 'DarcsCommand "pull"...').  Would these two
| types trip over each other in any way?  I guess not.  I suppose we'd
| just be swapping out one field for another, eg.
| command_basic_options :: [DarcsOption] for commandArgs :: Darcs

The underlying structure is that each command (mode in CmdArgs
terminology) takes
a list of arguments drawn from a larger set of arguments. You've optimised for
parsing these options by keeping them as an unstructured list. CmdArgs optimises
for using these arguments by having a real structure that can be
properly queried.
I would hope that you replace your current options code with one using a proper
structure - since it will make your lives far easier.

To take an example - let's look at the --compress and --no-compress flags. The
default flag is --compress, but if you run --no-compress --compress
then in almost
all commands you'll end up with no compression. Apart from optimize
where that would
give you compression, but so would --compress --no-compress. The
reason for all this
effort is that you've flattened your flags, which makes it harder to
work with them,
and guarantees you have subtle bugs all over the place. (I'm not 100%
sure about the
effects I stated - they're only based on looking at the code)

If instead you had a compress :: Bool floating around then the
defaults vs business
logic gets separated, the whole NoCompress thing is eliminated
entirely, and it's
much simpler and easier to get right.

My first attempt at writing CmdArgs involved a list of options - it
turned out I was
horribly wrong :-)

| The matchers are a good case study because different commands need (a)
| different sets of matchers (b) a lot of commands have a lot of matchers
| in common and (c) sometimes matchers mean subtly different things for
| different commands [these subtleties in implementation are needed for an
| intuitive feel; I forget the details]

>     ,mode $ Unrecord
>         {from_match = [] &= typ "PATTERN" & text "select changes starting with a patch matching PATTERN"
>         ,from_patch = [] &= typ "REGEXP" & text "select changes starting with a patch matching REGEXP"
>         ,from_tag = [] &= typ "REGEXP" & text "select changes starting with a tag matching REGEXP"
>         ,last_ = Nothing &= typ "NUMBER" & text "select the last NUMBER patches"
>         ,matches = [] &= typ "PATTERN" & text "select patches matching PATTERN"
>         ,patches = [] &= flag "p" & typ "REGEXP" & text "select patches matching REGEXP"
>         ,tags = [] &= flag "t" & typ "REGEXP" & text "select tags matching REGEXP"
>         ,no_deps = False &= text "don't automatically fulfill dependencies"
>         ,prompt_for_dependencies = True &= text "prompt about patches that are depended on by matched patches" &
>                                    negative (flag "dont-prompt-for-dependencies" & text "don't ask about patches that are depended on by matched patches (with --match or --patch)"} &
>         &= helpSuffix textUnrecord & text "Remove recorded patches without changing the working copy."

| Again with the matchers.  Am I wanting the wrong thing in hoping to have
| groups of flags which can be shared by different commands?

It's important to point out that the bits after mode aren't reduced
implementations - they're completely finished. So for example,
Amend_Record and Show_Contents both contain match :: Maybe String, but
only one of their mode implementations needs to describe the flag
structure, the others inherit it. Unrecord has defined last_, so
everyone else can share. Currently everything that has match_one is
going to need to write:

      {match :: Maybe String, patch :: Maybe String, tag :: Maybe
String, index :: Maybe Int ...}

That's a little unfortunate, but not the end of the world - since they
don't have to describe those flags. It would be possible to do better,
but not massively better as Haskell records are pretty poor. We could
do:

data MatchOne = MatchOne {match :: ..., patch :: ..., index :: ..., tag :: ...}

Then everyone using MatchOne could do {matchOne :: MatchOne, ...},
plus the single user of match_one_nontag could do "hide tag" in their
attributes. We could also factor out several and range - the
several_or_last and several_or_range are only used once or twice so
can just include two items in their record.

The underlying trade off is that CmdArgs chooses to give more
structure to arguments by using types, but Haskell doesn't
abstract/compose types in the same way it can do with values. CmdArgs
might make common patterns in multiple flags slightly harder to
express, but I really hope the added safety/correctness will be more
than worth it.

Thanks

Neil


More information about the darcs-users mailing list