Cabal-3.16.1.0: src/Distribution/Simple/Command.hs
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Simple.Command
-- Copyright : Duncan Coutts 2007
-- License : BSD3
--
-- Maintainer : cabal-devel@haskell.org
-- Portability : non-portable (ExistentialQuantification)
--
-- This is to do with command line handling. The Cabal command line is
-- organised into a number of named sub-commands (much like darcs). The
-- 'CommandUI' abstraction represents one of these sub-commands, with a name,
-- description, a set of flags. Commands can be associated with actions and
-- run. It handles some common stuff automatically, like the @--help@ and
-- command line completion flags. It is designed to allow other tools make
-- derived commands. This feature is used heavily in @cabal-install@.
module Distribution.Simple.Command
( -- * Command interface
CommandUI (..)
, commandShowOptions
, CommandParse (..)
, commandParseArgs
, getNormalCommandDescriptions
, helpCommandUI
-- ** Constructing commands
, ShowOrParseArgs (..)
, usageDefault
, usageAlternatives
, mkCommandUI
, hiddenCommand
-- ** Associating actions with commands
, Command
, commandAddAction
, noExtraFlags
-- ** Building lists of commands
, CommandType (..)
, CommandSpec (..)
, commandFromSpec
-- ** Running commands
, commandsRun
, commandsRunWithFallback
, defaultCommandFallback
-- * Option Fields
, OptionField (..)
, Name
-- ** Constructing Option Fields
, option
, multiOption
-- ** Liftings & Projections
, liftOption
, liftOptionL
-- * Option Descriptions
, OptDescr (..)
, fmapOptDescr
, Description
, SFlags
, LFlags
, OptFlags
, ArgPlaceHolder
-- ** OptDescr 'smart' constructors
, MkOptDescr
, reqArg
, reqArg'
, optArg
, optArg'
, optArgDef'
, noArg
, boolOpt
, boolOpt'
, choiceOpt
, choiceOptFromEnum
) where
import Distribution.Compat.Prelude hiding (get)
import Prelude ()
import qualified Data.Array as Array
import qualified Data.List as List
import Distribution.Compat.Lens (ALens', (#~), (^#))
import qualified Distribution.GetOpt as GetOpt
import Distribution.ReadE
import Distribution.Simple.Utils
data CommandUI flags = CommandUI
{ commandName :: String
-- ^ The name of the command as it would be entered on the command line.
-- For example @\"build\"@.
, commandSynopsis :: String
-- ^ A short, one line description of the command to use in help texts.
, commandUsage :: String -> String
-- ^ A function that maps a program name to a usage summary for this
-- command.
, commandDescription :: Maybe (String -> String)
-- ^ Additional explanation of the command to use in help texts.
, commandNotes :: Maybe (String -> String)
-- ^ Post-Usage notes and examples in help texts
, commandDefaultFlags :: flags
-- ^ Initial \/ empty flags
, commandOptions :: ShowOrParseArgs -> [OptionField flags]
-- ^ All the Option fields for this command
}
data ShowOrParseArgs = ShowArgs | ParseArgs
type Name = String
type Description = String
-- | We usually have a data type for storing configuration values, where
-- every field stores a configuration option, and the user sets
-- the value either via command line flags or a configuration file.
-- An individual OptionField models such a field, and we usually
-- build a list of options associated to a configuration data type.
data OptionField a = OptionField
{ optionName :: Name
, optionDescr :: [OptDescr a]
}
-- | An OptionField takes one or more OptDescrs, describing the command line
-- interface for the field.
data OptDescr a
= ReqArg
Description
OptFlags
ArgPlaceHolder
(ReadE (a -> a))
(a -> [String])
| OptArg
Description
OptFlags
ArgPlaceHolder
(ReadE (a -> a))
(String, a -> a)
(a -> [Maybe String])
| ChoiceOpt [(Description, OptFlags, a -> a, a -> Bool)]
| BoolOpt
Description
OptFlags {-True-}
OptFlags {-False-}
(Bool -> a -> a)
(a -> Maybe Bool)
fmapOptDescr :: forall a b. (b -> a) -> (a -> (b -> b)) -> OptDescr a -> OptDescr b
fmapOptDescr x u = \case
ReqArg d o p upd get -> ReqArg d o p (fmap m upd) (get . x)
OptArg d o p upd (str, g) get -> OptArg d o p (fmap m upd) (str, m g) (get . x)
ChoiceOpt opts -> ChoiceOpt $ fmap (\(d, o, upd, get) -> (d, o, m upd, get . x)) opts
BoolOpt d true false upd get -> BoolOpt d true false (\b -> m $ upd b) (get . x)
where
m :: (a -> a) -> (b -> b)
m upd_a b = u (upd_a $ x b) b
-- | Short command line option strings
type SFlags = [Char]
-- | Long command line option strings
type LFlags = [String]
type OptFlags = (SFlags, LFlags)
type ArgPlaceHolder = String
-- | Create an option taking a single OptDescr.
-- No explicit Name is given for the Option, the name is the first LFlag given.
--
-- Example: @'option' sf lf d get set@
-- * @sf@: Short option name, for example: @[\'d\']@. No hyphen permitted.
-- * @lf@: Long option name, for example: @["debug"]@. No hyphens permitted.
-- * @d@: Description of the option, shown to the user in help messages.
-- * @get@: Get the current value of the flag.
-- * @set@: Set the value of the flag. Gets the current value of the flag as a
-- parameter.
option
:: SFlags
-> LFlags
-> Description
-> get
-> set
-> MkOptDescr get set a
-> OptionField a
option sf lf@(n : _) d get set arg = OptionField n [arg sf lf d get set]
option _ _ _ _ _ _ =
error $
"Distribution.command.option: "
++ "An OptionField must have at least one LFlag"
-- | Create an option taking several OptDescrs.
-- You will have to give the flags and description individually to the
-- OptDescr constructor.
multiOption
:: Name
-> get
-> set
-> [get -> set -> OptDescr a]
-- ^ MkOptDescr constructors partially
-- applied to flags and description.
-> OptionField a
multiOption n get set args = OptionField n [arg get set | arg <- args]
type MkOptDescr get set a =
SFlags
-> LFlags
-> Description
-> get
-> set
-> OptDescr a
-- | Create a string-valued command line interface.
-- Usually called in the context of 'option' or 'multiOption'.
--
-- Example: @'reqArg' ad mkflag showflag@
--
-- * @ad@: Placeholder shown to the user, e.g. @"FILES"@ if files are expected
-- parameters.
-- * @mkflag@: How to parse the argument into the option.
-- * @showflag@: If parsing goes wrong, display a useful error message to
-- the user.
reqArg
:: Monoid b
=> ArgPlaceHolder
-> ReadE b
-> (b -> [String])
-> MkOptDescr (a -> b) (b -> a -> a) a
reqArg ad mkflag showflag sf lf d get set =
ReqArg
d
(sf, lf)
ad
(fmap (\a b -> set (get b `mappend` a) b) mkflag)
(showflag . get)
-- | Create a string-valued command line interface with a default value.
optArg
:: Monoid b
=> ArgPlaceHolder
-> ReadE b
-> (String, b)
-> (b -> [Maybe String])
-> MkOptDescr (a -> b) (b -> a -> a) a
optArg ad mkflag (dv, mkDef) showflag sf lf d get set =
OptArg
d
(sf, lf)
ad
(fmap (\a b -> set (get b `mappend` a) b) mkflag)
(dv, \b -> set (get b `mappend` mkDef) b)
(showflag . get)
-- | (String -> a) variant of "reqArg"
reqArg'
:: Monoid b
=> ArgPlaceHolder
-> (String -> b)
-> (b -> [String])
-> MkOptDescr (a -> b) (b -> a -> a) a
reqArg' ad mkflag showflag =
reqArg ad (succeedReadE mkflag) showflag
-- | (String -> a) variant of "optArg"
optArg'
:: Monoid b
=> ArgPlaceHolder
-> (Maybe String -> b)
-> (b -> [Maybe String])
-> MkOptDescr (a -> b) (b -> a -> a) a
optArg' ad mkflag showflag =
optArg ad (succeedReadE (mkflag . Just)) ("", mkflag Nothing) showflag
optArgDef'
:: Monoid b
=> ArgPlaceHolder
-> (String, Maybe String -> b)
-> (b -> [Maybe String])
-> MkOptDescr (a -> b) (b -> a -> a) a
optArgDef' ad (dv, mkflag) showflag =
optArg ad (succeedReadE (mkflag . Just)) (dv, mkflag Nothing) showflag
noArg :: Eq b => b -> MkOptDescr (a -> b) (b -> a -> a) a
noArg flag sf lf d = choiceOpt [(flag, (sf, lf), d)] sf lf d
boolOpt
:: (b -> Maybe Bool)
-> (Bool -> b)
-> SFlags
-> SFlags
-> MkOptDescr (a -> b) (b -> a -> a) a
boolOpt g s sfT sfF _sf _lf@(n : _) d get set =
BoolOpt d (sfT, ["enable-" ++ n]) (sfF, ["disable-" ++ n]) (set . s) (g . get)
boolOpt _ _ _ _ _ _ _ _ _ =
error
"Distribution.Simple.Setup.boolOpt: unreachable"
boolOpt'
:: (b -> Maybe Bool)
-> (Bool -> b)
-> OptFlags
-> OptFlags
-> MkOptDescr (a -> b) (b -> a -> a) a
boolOpt' g s ffT ffF _sf _lf d get set = BoolOpt d ffT ffF (set . s) (g . get)
-- | create a Choice option
choiceOpt
:: Eq b
=> [(b, OptFlags, Description)]
-> MkOptDescr (a -> b) (b -> a -> a) a
choiceOpt aa_ff _sf _lf _d get set = ChoiceOpt alts
where
alts = [(d, flags, set alt, (== alt) . get) | (alt, flags, d) <- aa_ff]
-- | create a Choice option out of an enumeration type.
-- As long flags, the Show output is used. As short flags, the first character
-- which does not conflict with a previous one is used.
choiceOptFromEnum
:: (Bounded b, Enum b, Show b, Eq b)
=> MkOptDescr (a -> b) (b -> a -> a) a
choiceOptFromEnum _sf _lf d get =
choiceOpt
[ (x, (sf, [map toLower $ show x]), d')
| (x, sf) <- sflags'
, let d' = d ++ show x
]
_sf
_lf
d
get
where
sflags' = foldl f [] [firstOne ..]
f prev x =
let prevflags = concatMap snd prev
in prev
++ take
1
[ (x, [toLower sf])
| sf <- show x
, isAlpha sf
, toLower sf `notElem` prevflags
]
firstOne = minBound `asTypeOf` get undefined
commandGetOpts
:: ShowOrParseArgs
-> CommandUI flags
-> [GetOpt.OptDescr (flags -> flags)]
commandGetOpts showOrParse command =
concatMap viewAsGetOpt (commandOptions command showOrParse)
viewAsGetOpt :: OptionField a -> [GetOpt.OptDescr (a -> a)]
viewAsGetOpt (OptionField _n aa) = concatMap optDescrToGetOpt aa
where
optDescrToGetOpt (ReqArg d (cs, ss) arg_desc set _) =
[GetOpt.Option cs ss (GetOpt.ReqArg (runReadE set) arg_desc) d]
optDescrToGetOpt (OptArg d (cs, ss) arg_desc set (dv, def) _) =
[GetOpt.Option cs ss (GetOpt.OptArg dv set' arg_desc) d]
where
set' Nothing = Right def
set' (Just txt) = runReadE set txt
optDescrToGetOpt (ChoiceOpt alts) =
[GetOpt.Option sf lf (GetOpt.NoArg set) d | (d, (sf, lf), set, _) <- alts]
optDescrToGetOpt (BoolOpt d (sfT, lfT) ([], []) set _) =
[GetOpt.Option sfT lfT (GetOpt.NoArg (set True)) d]
optDescrToGetOpt (BoolOpt d ([], []) (sfF, lfF) set _) =
[GetOpt.Option sfF lfF (GetOpt.NoArg (set False)) d]
optDescrToGetOpt (BoolOpt d (sfT, lfT) (sfF, lfF) set _) =
[ GetOpt.Option sfT lfT (GetOpt.NoArg (set True)) ("Enable " ++ d)
, GetOpt.Option sfF lfF (GetOpt.NoArg (set False)) ("Disable " ++ d)
]
getCurrentChoice :: OptDescr a -> a -> [String]
getCurrentChoice (ChoiceOpt alts) a =
[lf | (_, (_sf, lf : _), _, currentChoice) <- alts, currentChoice a]
getCurrentChoice _ _ = error "Command.getChoice: expected a Choice OptDescr"
liftOption :: (b -> a) -> (a -> (b -> b)) -> OptionField a -> OptionField b
liftOption get' set' opt =
opt{optionDescr = liftOptDescr get' set' `map` optionDescr opt}
-- | @since 3.4.0.0
liftOptionL :: ALens' b a -> OptionField a -> OptionField b
liftOptionL l = liftOption (^# l) (l #~)
liftOptDescr :: (b -> a) -> (a -> (b -> b)) -> OptDescr a -> OptDescr b
liftOptDescr get' set' (ChoiceOpt opts) =
ChoiceOpt
[ (d, ff, liftSet get' set' set, (get . get'))
| (d, ff, set, get) <- opts
]
liftOptDescr get' set' (OptArg d ff ad set (dv, mkDef) get) =
OptArg
d
ff
ad
(liftSet get' set' `fmap` set)
(dv, liftSet get' set' mkDef)
(get . get')
liftOptDescr get' set' (ReqArg d ff ad set get) =
ReqArg d ff ad (liftSet get' set' `fmap` set) (get . get')
liftOptDescr get' set' (BoolOpt d ffT ffF set get) =
BoolOpt d ffT ffF (liftSet get' set' . set) (get . get')
liftSet :: (b -> a) -> (a -> (b -> b)) -> (a -> a) -> b -> b
liftSet get' set' set x = set' (set $ get' x) x
-- | Show flags in the standard long option command line format
commandShowOptions :: CommandUI flags -> flags -> [String]
commandShowOptions command v =
concat
[ showOptDescr v od | o <- commandOptions command ParseArgs, od <- optionDescr o
]
where
maybePrefix [] = []
maybePrefix (lOpt : _) = ["--" ++ lOpt]
showOptDescr :: a -> OptDescr a -> [String]
showOptDescr x (BoolOpt _ (_, lfTs) (_, lfFs) _ enabled) =
case enabled x of
Nothing -> []
Just True -> maybePrefix lfTs
Just False -> maybePrefix lfFs
showOptDescr x c@ChoiceOpt{} =
["--" ++ val | val <- getCurrentChoice c x]
showOptDescr x (ReqArg _ (_ssff, lf : _) _ _ showflag) =
[ "--" ++ lf ++ "=" ++ flag
| flag <- showflag x
]
showOptDescr x (OptArg _ (_ssff, lf : _) _ _ _ showflag) =
[ case flag of
Just s -> "--" ++ lf ++ "=" ++ s
Nothing -> "--" ++ lf
| flag <- showflag x
]
showOptDescr _ _ =
error "Distribution.Simple.Command.showOptDescr: unreachable"
commandListOptions :: CommandUI flags -> [String]
commandListOptions command =
concatMap listOption $
addCommonFlags ShowArgs $ -- This is a slight hack, we don't want
-- "--list-options" showing up in the
-- list options output, so use ShowArgs
commandGetOpts ShowArgs command
where
listOption (GetOpt.Option shortNames longNames _ _) =
["-" ++ [name] | name <- shortNames]
++ ["--" ++ name | name <- longNames]
-- | The help text for this command with descriptions of all the options.
commandHelp :: CommandUI flags -> String -> String
commandHelp command pname =
commandSynopsis command
++ "\n\n"
++ commandUsage command pname
++ ( case commandDescription command of
Nothing -> ""
Just desc -> '\n' : desc pname
)
++ "\n"
++ ( if cname == ""
then "Global flags:"
else "Flags for " ++ cname ++ ":"
)
++ ( GetOpt.usageInfo ""
. addCommonFlags ShowArgs
$ commandGetOpts ShowArgs command
)
++ ( case commandNotes command of
Nothing -> ""
Just notes -> '\n' : notes pname
)
where
cname = commandName command
-- | Default "usage" documentation text for commands.
usageDefault :: String -> String -> String
usageDefault name pname =
"Usage: "
++ pname
++ " "
++ name
++ " [FLAGS]\n\n"
++ "Flags for "
++ name
++ ":"
-- | Create "usage" documentation from a list of parameter
-- configurations.
usageAlternatives :: String -> [String] -> String -> String
usageAlternatives name strs pname =
unlines
[ start ++ pname ++ " " ++ name ++ " " ++ s
| let starts = "Usage: " : repeat " or: "
, (start, s) <- zip starts strs
]
-- | Make a Command from standard 'GetOpt' options.
mkCommandUI
:: String
-- ^ name
-> String
-- ^ synopsis
-> [String]
-- ^ usage alternatives
-> flags
-- ^ initial\/empty flags
-> (ShowOrParseArgs -> [OptionField flags])
-- ^ options
-> CommandUI flags
mkCommandUI name synopsis usages flags options =
CommandUI
{ commandName = name
, commandSynopsis = synopsis
, commandDescription = Nothing
, commandNotes = Nothing
, commandUsage = usageAlternatives name usages
, commandDefaultFlags = flags
, commandOptions = options
}
-- | Common flags that apply to every command
data CommonFlag = HelpFlag | ListOptionsFlag
commonFlags :: ShowOrParseArgs -> [GetOpt.OptDescr CommonFlag]
commonFlags showOrParseArgs = case showOrParseArgs of
ShowArgs -> [help]
ParseArgs -> [help, list]
where
help =
GetOpt.Option
helpShortFlags
["help"]
(GetOpt.NoArg HelpFlag)
"Show this help text"
helpShortFlags = case showOrParseArgs of
ShowArgs -> ['h']
ParseArgs -> ['h', '?']
list =
GetOpt.Option
[]
["list-options"]
(GetOpt.NoArg ListOptionsFlag)
"Print a list of command line flags"
addCommonFlags
:: ShowOrParseArgs
-> [GetOpt.OptDescr a]
-> [GetOpt.OptDescr (Either CommonFlag a)]
addCommonFlags showOrParseArgs options =
map (fmap Left) (commonFlags showOrParseArgs)
++ map (fmap Right) options
-- | Parse a bunch of command line arguments
commandParseArgs
:: CommandUI flags
-> Bool
-- ^ Is the command a global or subcommand?
-> [String]
-> CommandParse (flags -> flags, [String])
commandParseArgs command global args =
let options =
addCommonFlags ParseArgs $
commandGetOpts ParseArgs command
order
| global = GetOpt.RequireOrder
| otherwise = GetOpt.Permute
in case GetOpt.getOpt' order options args of
(flags, _, _, _)
| any listFlag flags -> CommandList (commandListOptions command)
| any helpFlag flags -> CommandHelp (commandHelp command)
where
listFlag (Left ListOptionsFlag) = True; listFlag _ = False
helpFlag (Left HelpFlag) = True; helpFlag _ = False
(flags, opts, opts', [])
| global || null opts' -> CommandReadyToGo (accum flags, mix opts opts')
| otherwise -> CommandErrors (unrecognised opts')
(_, _, _, errs) -> CommandErrors errs
where
-- Note: It is crucial to use reverse function composition here or to
-- reverse the flags here as we want to process the flags left to right
-- but data flow in function composition is right to left.
accum flags = foldr (flip (.)) id [f | Right f <- flags]
unrecognised opts =
[ "unrecognized "
++ "'"
++ (commandName command)
++ "'"
++ " option `"
++ opt
++ "'\n"
| opt <- opts
]
-- For unrecognised global flags we put them in the position just after
-- the command, if there is one. This gives us a chance to parse them
-- as sub-command rather than global flags.
mix [] ys = ys
mix (x : xs) ys = x : ys ++ xs
data CommandParse flags
= CommandHelp (String -> String)
| CommandList [String]
| CommandErrors [String]
| CommandReadyToGo flags
instance Functor CommandParse where
fmap _ (CommandHelp help) = CommandHelp help
fmap _ (CommandList opts) = CommandList opts
fmap _ (CommandErrors errs) = CommandErrors errs
fmap f (CommandReadyToGo flags) = CommandReadyToGo (f flags)
data CommandType = NormalCommand | HiddenCommand
data Command action
= Command String String ([String] -> CommandParse action) CommandType
-- | Mark command as hidden. Hidden commands don't show up in the 'progname
-- help' or 'progname --help' output.
hiddenCommand :: Command action -> Command action
hiddenCommand (Command name synopsis f _cmdType) =
Command name synopsis f HiddenCommand
commandAddAction
:: CommandUI flags
-> (flags -> [String] -> action)
-> Command action
commandAddAction command action =
Command
(commandName command)
(commandSynopsis command)
(fmap (uncurry applyDefaultArgs) . commandParseArgs command False)
NormalCommand
where
applyDefaultArgs mkflags args =
let flags = mkflags (commandDefaultFlags command)
in action flags args
-- Print suggested command if edit distance is < 5
badCommand :: [Command action] -> String -> CommandParse a
badCommand commands' cname =
case eDists of
[] -> CommandErrors [unErr]
(s : _) ->
CommandErrors
[ unErr
, "Maybe you meant `" ++ s ++ "`?\n"
]
where
eDists =
map fst . List.sortBy (comparing snd) $
[ (cname', dist)
| -- Note that this is not commandNames, so close suggestions will show
-- hidden commands
(Command cname' _ _ _) <- commands'
, let dist = editDistance cname' cname
, dist < 5
]
unErr = "unrecognised command: " ++ cname ++ " (try --help)"
commandsRun
:: CommandUI a
-> [Command action]
-> [String]
-> IO (CommandParse (a, CommandParse action))
commandsRun globalCommand commands args =
commandsRunWithFallback globalCommand commands defaultCommandFallback args
defaultCommandFallback
:: [Command action]
-> String
-> [String]
-> IO (CommandParse action)
defaultCommandFallback commands' name _cmdArgs = pure $ badCommand commands' name
commandsRunWithFallback
:: CommandUI a
-> [Command action]
-> ([Command action] -> String -> [String] -> IO (CommandParse action))
-> [String]
-> IO (CommandParse (a, CommandParse action))
commandsRunWithFallback globalCommand commands defaultCommand args =
case commandParseArgs globalCommand True args of
CommandHelp help -> pure $ CommandHelp help
CommandList opts -> pure $ CommandList (opts ++ commandNames)
CommandErrors errs -> pure $ CommandErrors errs
CommandReadyToGo (mkflags, args') -> case args' of
("help" : cmdArgs) -> handleHelpCommand flags cmdArgs
(name : cmdArgs) -> case lookupCommand name of
[Command _ _ action _] ->
pure $ CommandReadyToGo (flags, action cmdArgs)
_ -> do
final_cmd <- defaultCommand commands' name cmdArgs
return $ CommandReadyToGo (flags, final_cmd)
[] -> pure $ CommandReadyToGo (flags, noCommand)
where
flags = mkflags (commandDefaultFlags globalCommand)
where
lookupCommand cname =
[ cmd | cmd@(Command cname' _ _ _) <- commands', cname' == cname
]
noCommand = CommandErrors ["no command given (try --help)\n"]
commands' = commands ++ [commandAddAction helpCommandUI undefined]
commandNames = [name | (Command name _ _ NormalCommand) <- commands']
-- A bit of a hack: support "prog help" as a synonym of "prog --help"
-- furthermore, support "prog help command" as "prog command --help"
handleHelpCommand flags cmdArgs =
case commandParseArgs helpCommandUI True cmdArgs of
CommandHelp help -> pure $ CommandHelp help
CommandList list -> pure $ CommandList (list ++ commandNames)
CommandErrors _ -> pure $ CommandHelp globalHelp
CommandReadyToGo (_, []) -> pure $ CommandHelp globalHelp
CommandReadyToGo (_, (name : cmdArgs')) ->
case lookupCommand name of
[Command _ _ action _] ->
case action ("--help" : cmdArgs') of
CommandHelp help -> pure $ CommandHelp help
CommandList _ -> pure $ CommandList []
_ -> pure $ CommandHelp globalHelp
_ -> do
fall_back <- defaultCommand commands' name ("--help" : cmdArgs')
return $ CommandReadyToGo (flags, fall_back)
where
globalHelp = commandHelp globalCommand
-- Levenshtein distance, from https://wiki.haskell.org/Edit_distance
-- (Author: JeanPhilippeBernardy, Simple Permissive Licence)
editDistance :: Eq a => [a] -> [a] -> Int
editDistance xs ys = table Array.! (m, n)
where
(m, n) = (length xs, length ys)
x = Array.array (1, m) (zip [1 ..] xs)
y = Array.array (1, n) (zip [1 ..] ys)
table :: Array.Array (Int, Int) Int
table = Array.array bnds [(ij, dist ij) | ij <- Array.range bnds]
bnds = ((0, 0), (m, n))
dist (0, j) = j
dist (i, 0) = i
dist (i, j) =
minimum
[ table Array.! (i - 1, j) + 1
, table Array.! (i, j - 1) + 1
, if x Array.! i == y Array.! j
then table Array.! (i - 1, j - 1)
else 1 + table Array.! (i - 1, j - 1)
]
-- | Utility function, many commands do not accept additional flags. This
-- action fails with a helpful error message if the user supplies any extra.
noExtraFlags :: [String] -> IO ()
noExtraFlags [] = return ()
noExtraFlags extraFlags =
dieNoVerbosity $ "Unrecognised flags: " ++ intercalate ", " extraFlags
-- TODO: eliminate this function and turn it into a variant on commandAddAction
-- instead like commandAddActionNoArgs that doesn't supply the [String]
-- | Helper function for creating globalCommand description
getNormalCommandDescriptions :: [Command action] -> [(String, String)]
getNormalCommandDescriptions cmds =
[ (name, description)
| Command name description _ NormalCommand <- cmds
]
helpCommandUI :: CommandUI ()
helpCommandUI =
( mkCommandUI
"help"
"Help about commands."
["[FLAGS]", "COMMAND [FLAGS]"]
()
(const [])
)
{ commandNotes = Just $ \pname ->
"Examples:\n"
++ " "
++ pname
++ " help help\n"
++ " Oh, apparently you already know this.\n"
}
-- | wraps a @CommandUI@ together with a function that turns it into a @Command@.
-- By hiding the type of flags for the UI allows construction of a list of all UIs at the
-- top level of the program. That list can then be used for generation of manual page
-- as well as for executing the selected command.
data CommandSpec action
= forall flags. CommandSpec (CommandUI flags) (CommandUI flags -> Command action) CommandType
commandFromSpec :: CommandSpec a -> Command a
commandFromSpec (CommandSpec ui action _) = action ui