[darcs-devel] Darcs Argument Handling

Ben Franksen ben.franksen at online.de
Sun May 4 00:02:24 UTC 2014


Let me continue this thread by taking a slightly broader perspective.

Why is the darcs UI code such a mess?
=====================================

One reason is the DarcsFlag type.

One the one hand, DarcsFlag is designed to closely correspond to the actual 
words given on the command line or in a defaults file. (The correspondence 
is almost 1:1, except for aliases.) This is not yet a problem per se.

But on the other hand, [DarcsFlag] permeates throughout all the command 
implementations, and this is indeed a problem. Because as a result the 
command internal logic is fraught with and mixed up with details of *how* 
the user specified options, instead of concentrating on *what* has been 
specified.

Just to illustrate my point, look at the functions hasEditMetadata and 
getAuthor in Darcs.UI.Commands.Amend: they do low-level list handling to 
extract information about what to do from the raw list of flags.

The mix-up of the "how" and "what" of command options has one additional 
disadvantage: there is no single place where we can find what a command does 
by default. It's all finely dispersed throughout the command 
implementations:

> grep '`elem` opts' src/Darcs/UI/Commands/*.hs | wc -l
110

Consider a simple option of the yes/no type, such as EditDescription / 
NoEditDescription. A command implementation could take decisions, based on 
the outcome of

- EditDescription `elem` opts
- NoEditDescription  `elem` opts

There is no way to predict from the outside which of them the implementation 
choses and therefore no way to predict what happens in case neither option 
is in the list, or both of them are.

What can we do about it?
========================

The design space is large, but let me present a strawman proposal so you 
have something to pick apart.

We replace the use of [DarcsFlag] in command implementations with one common 
large record type that contains an entry for each possible option. A common 
name for such a thing is Config, and it would look like this:

data Config = Config {
  help :: Bool,
  listOptions :: Bool,
  test :: Bool,
  changesToAllFiles :: Bool,
  leaveTestDir :: Bool,
  timings :: Bool,
  verbosity :: Verbosity
  ...
  author :: Maybe String
  ...
  editDescription :: Bool,
  ...
  diffAlgorithm :: DiffAlgorithm
  ...
}

with some helper data types e.g.

data DiffAlgorithm = Myers | Patience

-- are these in fact meant to be mutually exclusive?
data Verbosity = Quiet | NormalVerbosity | Verbose
               | Debug  | DebugHTTP | DebugVerbose

Stuff like prompting for an author or selecting it from a list (if not given 
on the command line) should be done in helper functions outside of command 
implementations, if possible.


It may be difficult to come to a conclusion how to represent each option, 
considering that some of the options may be interpreted differently by some 
commands. But I don't regard that as a deficiency of the proposal. Quite the 
opposite, in fact: it encourages us to think about, review, and then define 
option semantics once and for all in a systematic way. For instance, we will 
have to decide how to handle conflicting options given at the same level of 
precedence. This is currently more or less undefined, see example [1] below.


The next step is to define one(!) function

defaultConfig :: Command -> Config

that returns the hard-coded defaults for a given command (most of the 
returned values will just be references to a common default, with one or two 
exceptions, where commands have differing defaults for the same option).

Command line arguments and defaults from the two configuration files are 
each independently converted to a [DarcsFlag]:

cmdlineArgs :: IO (Command, [DarcsFlag], ExtraArgs)
userDefaults, repoDefaults :: Command -> IO [DarcsFlag]

and the resulting three lists of DarcsFlag are combined using a single 
function

applyFlags :: Command -> [DarcsFlag] -> Config -> Config


I am willing to engage in an effort to design the Config record and replace 
[DarcsFlags] with Config in the command implementations, but I would like to 
hear what you guys think before I start with this in earnest. It is also 
necessary to clarify resp. reach consensus about which of the many multiple-
choice option groups (see Darcs.UI.Arguments) are in fact meant to be 
mutually exclusive (I guess most are), and which are really meant to be 
multiple-choice. And how to represent the latter ones in the Config type.

Cheers
Ben

[1] Currently we have

darcs send --sign
= darcs send --sign --no-sign
= darcs send --no-sign --sign

so in this case --sign wins over --no-sign. This is an artefact of the 
implementation, which uses code like

  if Sign `elem` flags then ...

If it did instead

  if NoSign `elem` flags ...

it would be the other way around.
-- 
"Make it so they have to reboot after every typo." -- Scott Adams




More information about the darcs-devel mailing list