1 {-# LANGUAGE ExistentialQuantification #-}
2 {-# LANGUAGE FlexibleContexts #-}
3 {-# LANGUAGE RankNTypes #-}
5 -----------------------------------------------------------------------------
8 -- Module : Distribution.Simple.Command
9 -- Copyright : Duncan Coutts 2007
12 -- Maintainer : cabal-devel@haskell.org
13 -- Portability : non-portable (ExistentialQuantification)
15 -- This is to do with command line handling. The Cabal command line is
16 -- organised into a number of named sub-commands (much like darcs). The
17 -- 'CommandUI' abstraction represents one of these sub-commands, with a name,
18 -- description, a set of flags. Commands can be associated with actions and
19 -- run. It handles some common stuff automatically, like the @--help@ and
20 -- command line completion flags. It is designed to allow other tools make
21 -- derived commands. This feature is used heavily in @cabal-install@.
22 module Distribution
.Simple
.Command
23 ( -- * Command interface
28 , getNormalCommandDescriptions
31 -- ** Constructing commands
32 , ShowOrParseArgs
(..)
38 -- ** Associating actions with commands
43 -- ** Building lists of commands
48 -- ** Running commands
55 -- ** Constructing Option Fields
59 -- ** Liftings & Projections
63 -- * Option Descriptions
71 -- ** OptDescr 'smart' constructors
85 import Distribution
.Compat
.Prelude
hiding (get
)
88 import qualified Data
.Array as Array
89 import qualified Data
.List
as List
90 import Distribution
.Compat
.Lens
(ALens
', (#~
), (^
#))
91 import qualified Distribution
.GetOpt
as GetOpt
92 import Distribution
.ReadE
93 import Distribution
.Simple
.Utils
95 data CommandUI flags
= CommandUI
96 { commandName
:: String
97 -- ^ The name of the command as it would be entered on the command line.
98 -- For example @\"build\"@.
99 , commandSynopsis
:: String
100 -- ^ A short, one line description of the command to use in help texts.
101 , commandUsage
:: String -> String
102 -- ^ A function that maps a program name to a usage summary for this
104 , commandDescription
:: Maybe (String -> String)
105 -- ^ Additional explanation of the command to use in help texts.
106 , commandNotes
:: Maybe (String -> String)
107 -- ^ Post-Usage notes and examples in help texts
108 , commandDefaultFlags
:: flags
109 -- ^ Initial \/ empty flags
110 , commandOptions
:: ShowOrParseArgs
-> [OptionField flags
]
111 -- ^ All the Option fields for this command
114 data ShowOrParseArgs
= ShowArgs | ParseArgs
116 type Description
= String
118 -- | We usually have a data type for storing configuration values, where
119 -- every field stores a configuration option, and the user sets
120 -- the value either via command line flags or a configuration file.
121 -- An individual OptionField models such a field, and we usually
122 -- build a list of options associated to a configuration data type.
123 data OptionField a
= OptionField
125 , optionDescr
:: [OptDescr a
]
128 -- | An OptionField takes one or more OptDescrs, describing the command line
129 -- interface for the field.
143 (a
-> [Maybe String])
144 | ChoiceOpt
[(Description
, OptFlags
, a
-> a
, a
-> Bool)]
152 -- | Short command line option strings
155 -- | Long command line option strings
156 type LFlags
= [String]
158 type OptFlags
= (SFlags
, LFlags
)
159 type ArgPlaceHolder
= String
161 -- | Create an option taking a single OptDescr.
162 -- No explicit Name is given for the Option, the name is the first LFlag given.
164 -- Example: @'option' sf lf d get set@
165 -- * @sf@: Short option name, for example: @[\'d\']@. No hyphen permitted.
166 -- * @lf@: Long option name, for example: @["debug"]@. No hyphens permitted.
167 -- * @d@: Description of the option, shown to the user in help messages.
168 -- * @get@: Get the current value of the flag.
169 -- * @set@: Set the value of the flag. Gets the current value of the flag as a
177 -> MkOptDescr get set a
179 option sf lf
@(n
: _
) d get set arg
= OptionField n
[arg sf lf d get set
]
182 "Distribution.command.option: "
183 ++ "An OptionField must have at least one LFlag"
185 -- | Create an option taking several OptDescrs.
186 -- You will have to give the flags and description individually to the
187 -- OptDescr constructor.
192 -> [get
-> set
-> OptDescr a
]
193 -- ^ MkOptDescr constructors partially
194 -- applied to flags and description.
196 multiOption n get set args
= OptionField n
[arg get set | arg
<- args
]
198 type MkOptDescr get set a
=
206 -- | Create a string-valued command line interface.
207 -- Usually called in the context of 'option' or 'multiOption'.
209 -- Example: @'reqArg' ad mkflag showflag@
211 -- * @ad@: Placeholder shown to the user, e.g. @"FILES"@ if files are expected
213 -- * @mkflag@: How to parse the argument into the option.
214 -- * @showflag@: If parsing goes wrong, display a useful error message to
221 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
222 reqArg ad mkflag showflag sf lf d get set
=
227 (fmap (\a b
-> set
(get b `mappend` a
) b
) mkflag
)
230 -- | Create a string-valued command line interface with a default value.
236 -> (b
-> [Maybe String])
237 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
238 optArg ad mkflag
(dv
, mkDef
) showflag sf lf d get set
=
243 (fmap (\a b
-> set
(get b `mappend` a
) b
) mkflag
)
244 (dv
, \b -> set
(get b `mappend` mkDef
) b
)
247 -- | (String -> a) variant of "reqArg"
253 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
254 reqArg
' ad mkflag showflag
=
255 reqArg ad
(succeedReadE mkflag
) showflag
257 -- | (String -> a) variant of "optArg"
261 -> (Maybe String -> b
)
262 -> (b
-> [Maybe String])
263 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
264 optArg
' ad mkflag showflag
=
265 optArg ad
(succeedReadE
(mkflag
. Just
)) ("", mkflag Nothing
) showflag
270 -> (String, Maybe String -> b
)
271 -> (b
-> [Maybe String])
272 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
273 optArgDef
' ad
(dv
, mkflag
) showflag
=
274 optArg ad
(succeedReadE
(mkflag
. Just
)) (dv
, mkflag Nothing
) showflag
276 noArg
:: Eq b
=> b
-> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
277 noArg flag sf lf d
= choiceOpt
[(flag
, (sf
, lf
), d
)] sf lf d
284 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
285 boolOpt g s sfT sfF _sf _lf
@(n
: _
) d get set
=
286 BoolOpt d
(sfT
, ["enable-" ++ n
]) (sfF
, ["disable-" ++ n
]) (set
. s
) (g
. get
)
287 boolOpt _ _ _ _ _ _ _ _ _
=
289 "Distribution.Simple.Setup.boolOpt: unreachable"
296 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
297 boolOpt
' g s ffT ffF _sf _lf d get set
= BoolOpt d ffT ffF
(set
. s
) (g
. get
)
299 -- | create a Choice option
302 => [(b
, OptFlags
, Description
)]
303 -> MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
304 choiceOpt aa_ff _sf _lf _d get set
= ChoiceOpt alts
306 alts
= [(d
, flags
, set alt
, (== alt
) . get
) |
(alt
, flags
, d
) <- aa_ff
]
308 -- | create a Choice option out of an enumeration type.
309 -- As long flags, the Show output is used. As short flags, the first character
310 -- which does not conflict with a previous one is used.
312 :: (Bounded b
, Enum b
, Show b
, Eq b
)
313 => MkOptDescr
(a
-> b
) (b
-> a
-> a
) a
314 choiceOptFromEnum _sf _lf d get
=
316 [ (x
, (sf
, [map toLower $ show x
]), d
')
318 , let d
' = d
++ show x
325 sflags
' = foldl f
[] [firstOne
..]
327 let prevflags
= concatMap snd prev
334 , toLower sf `
notElem` prevflags
336 firstOne
= minBound `
asTypeOf` get
undefined
341 -> [GetOpt
.OptDescr
(flags
-> flags
)]
342 commandGetOpts showOrParse command
=
343 concatMap viewAsGetOpt
(commandOptions command showOrParse
)
345 viewAsGetOpt
:: OptionField a
-> [GetOpt
.OptDescr
(a
-> a
)]
346 viewAsGetOpt
(OptionField _n aa
) = concatMap optDescrToGetOpt aa
348 optDescrToGetOpt
(ReqArg d
(cs
, ss
) arg_desc set _
) =
349 [GetOpt
.Option cs ss
(GetOpt
.ReqArg
(runReadE set
) arg_desc
) d
]
350 optDescrToGetOpt
(OptArg d
(cs
, ss
) arg_desc set
(dv
, def
) _
) =
351 [GetOpt
.Option cs ss
(GetOpt
.OptArg dv set
' arg_desc
) d
]
353 set
' Nothing
= Right def
354 set
' (Just txt
) = runReadE set txt
355 optDescrToGetOpt
(ChoiceOpt alts
) =
356 [GetOpt
.Option sf lf
(GetOpt
.NoArg set
) d |
(d
, (sf
, lf
), set
, _
) <- alts
]
357 optDescrToGetOpt
(BoolOpt d
(sfT
, lfT
) ([], []) set _
) =
358 [GetOpt
.Option sfT lfT
(GetOpt
.NoArg
(set
True)) d
]
359 optDescrToGetOpt
(BoolOpt d
([], []) (sfF
, lfF
) set _
) =
360 [GetOpt
.Option sfF lfF
(GetOpt
.NoArg
(set
False)) d
]
361 optDescrToGetOpt
(BoolOpt d
(sfT
, lfT
) (sfF
, lfF
) set _
) =
362 [ GetOpt
.Option sfT lfT
(GetOpt
.NoArg
(set
True)) ("Enable " ++ d
)
363 , GetOpt
.Option sfF lfF
(GetOpt
.NoArg
(set
False)) ("Disable " ++ d
)
366 getCurrentChoice
:: OptDescr a
-> a
-> [String]
367 getCurrentChoice
(ChoiceOpt alts
) a
=
368 [lf |
(_
, (_sf
, lf
: _
), _
, currentChoice
) <- alts
, currentChoice a
]
369 getCurrentChoice _ _
= error "Command.getChoice: expected a Choice OptDescr"
371 liftOption
:: (b
-> a
) -> (a
-> (b
-> b
)) -> OptionField a
-> OptionField b
372 liftOption get
' set
' opt
=
373 opt
{optionDescr
= liftOptDescr get
' set
' `
map` optionDescr opt
}
376 liftOptionL
:: ALens
' b a
-> OptionField a
-> OptionField b
377 liftOptionL l
= liftOption
(^
# l
) (l
#~
)
379 liftOptDescr
:: (b
-> a
) -> (a
-> (b
-> b
)) -> OptDescr a
-> OptDescr b
380 liftOptDescr get
' set
' (ChoiceOpt opts
) =
382 [ (d
, ff
, liftSet get
' set
' set
, (get
. get
'))
383 |
(d
, ff
, set
, get
) <- opts
385 liftOptDescr get
' set
' (OptArg d ff ad set
(dv
, mkDef
) get
) =
390 (liftSet get
' set
' `
fmap` set
)
391 (dv
, liftSet get
' set
' mkDef
)
393 liftOptDescr get
' set
' (ReqArg d ff ad set get
) =
394 ReqArg d ff ad
(liftSet get
' set
' `
fmap` set
) (get
. get
')
395 liftOptDescr get
' set
' (BoolOpt d ffT ffF set get
) =
396 BoolOpt d ffT ffF
(liftSet get
' set
' . set
) (get
. get
')
398 liftSet
:: (b
-> a
) -> (a
-> (b
-> b
)) -> (a
-> a
) -> b
-> b
399 liftSet get
' set
' set x
= set
' (set
$ get
' x
) x
401 -- | Show flags in the standard long option command line format
402 commandShowOptions
:: CommandUI flags
-> flags
-> [String]
403 commandShowOptions command v
=
405 [ showOptDescr v od | o
<- commandOptions command ParseArgs
, od
<- optionDescr o
409 maybePrefix
(lOpt
: _
) = ["--" ++ lOpt
]
411 showOptDescr
:: a
-> OptDescr a
-> [String]
412 showOptDescr x
(BoolOpt _
(_
, lfTs
) (_
, lfFs
) _ enabled
) =
415 Just
True -> maybePrefix lfTs
416 Just
False -> maybePrefix lfFs
417 showOptDescr x c
@ChoiceOpt
{} =
418 ["--" ++ val | val
<- getCurrentChoice c x
]
419 showOptDescr x
(ReqArg _
(_ssff
, lf
: _
) _ _ showflag
) =
420 [ "--" ++ lf
++ "=" ++ flag
423 showOptDescr x
(OptArg _
(_ssff
, lf
: _
) _ _ _ showflag
) =
425 Just s
-> "--" ++ lf
++ "=" ++ s
426 Nothing
-> "--" ++ lf
430 error "Distribution.Simple.Command.showOptDescr: unreachable"
432 commandListOptions
:: CommandUI flags
-> [String]
433 commandListOptions command
=
434 concatMap listOption
$
435 addCommonFlags ShowArgs
$ -- This is a slight hack, we don't want
436 -- "--list-options" showing up in the
437 -- list options output, so use ShowArgs
438 commandGetOpts ShowArgs command
440 listOption
(GetOpt
.Option shortNames longNames _ _
) =
441 ["-" ++ [name
] | name
<- shortNames
]
442 ++ ["--" ++ name | name
<- longNames
]
444 -- | The help text for this command with descriptions of all the options.
445 commandHelp
:: CommandUI flags
-> String -> String
446 commandHelp command pname
=
447 commandSynopsis command
449 ++ commandUsage command pname
450 ++ ( case commandDescription command
of
452 Just desc
-> '\n' : desc pname
457 else "Flags for " ++ cname
++ ":"
459 ++ ( GetOpt
.usageInfo
""
460 . addCommonFlags ShowArgs
461 $ commandGetOpts ShowArgs command
463 ++ ( case commandNotes command
of
465 Just notes
-> '\n' : notes pname
468 cname
= commandName command
470 -- | Default "usage" documentation text for commands.
471 usageDefault
:: String -> String -> String
472 usageDefault name pname
=
482 -- | Create "usage" documentation from a list of parameter
484 usageAlternatives
:: String -> [String] -> String -> String
485 usageAlternatives name strs pname
=
487 [ start
++ pname
++ " " ++ name
++ " " ++ s
488 |
let starts
= "Usage: " : repeat " or: "
489 , (start
, s
) <- zip starts strs
492 -- | Make a Command from standard 'GetOpt' options.
499 -- ^ usage alternatives
501 -- ^ initial\/empty flags
502 -> (ShowOrParseArgs
-> [OptionField flags
])
505 mkCommandUI name synopsis usages flags options
=
508 , commandSynopsis
= synopsis
509 , commandDescription
= Nothing
510 , commandNotes
= Nothing
511 , commandUsage
= usageAlternatives name usages
512 , commandDefaultFlags
= flags
513 , commandOptions
= options
516 -- | Common flags that apply to every command
517 data CommonFlag
= HelpFlag | ListOptionsFlag
519 commonFlags
:: ShowOrParseArgs
-> [GetOpt
.OptDescr CommonFlag
]
520 commonFlags showOrParseArgs
= case showOrParseArgs
of
522 ParseArgs
-> [help
, list]
528 (GetOpt
.NoArg HelpFlag
)
529 "Show this help text"
530 helpShortFlags
= case showOrParseArgs
of
532 ParseArgs
-> ['h
', '?
']
537 (GetOpt
.NoArg ListOptionsFlag
)
538 "Print a list of command line flags"
542 -> [GetOpt
.OptDescr a
]
543 -> [GetOpt
.OptDescr
(Either CommonFlag a
)]
544 addCommonFlags showOrParseArgs options
=
545 map (fmap Left
) (commonFlags showOrParseArgs
)
546 ++ map (fmap Right
) options
548 -- | Parse a bunch of command line arguments
552 -- ^ Is the command a global or subcommand?
554 -> CommandParse
(flags
-> flags
, [String])
555 commandParseArgs command global args
=
557 addCommonFlags ParseArgs
$
558 commandGetOpts ParseArgs command
560 | global
= GetOpt
.RequireOrder
561 |
otherwise = GetOpt
.Permute
562 in case GetOpt
.getOpt
' order options args
of
564 |
any listFlag flags
-> CommandList
(commandListOptions command
)
565 |
any helpFlag flags
-> CommandHelp
(commandHelp command
)
567 listFlag
(Left ListOptionsFlag
) = True; listFlag _
= False
568 helpFlag
(Left HelpFlag
) = True; helpFlag _
= False
569 (flags
, opts
, opts
', [])
570 | global ||
null opts
' -> CommandReadyToGo
(accum flags
, mix opts opts
')
571 |
otherwise -> CommandErrors
(unrecognised opts
')
572 (_
, _
, _
, errs
) -> CommandErrors errs
574 -- Note: It is crucial to use reverse function composition here or to
575 -- reverse the flags here as we want to process the flags left to right
576 -- but data flow in function composition is right to left.
577 accum flags
= foldr (flip (.)) id [f | Right f
<- flags
]
581 ++ (commandName command
)
588 -- For unrecognised global flags we put them in the position just after
589 -- the command, if there is one. This gives us a chance to parse them
590 -- as sub-command rather than global flags.
592 mix
(x
: xs
) ys
= x
: ys
++ xs
594 data CommandParse flags
595 = CommandHelp
(String -> String)
596 | CommandList
[String]
597 | CommandErrors
[String]
598 | CommandReadyToGo flags
599 instance Functor CommandParse
where
600 fmap _
(CommandHelp help
) = CommandHelp help
601 fmap _
(CommandList opts
) = CommandList opts
602 fmap _
(CommandErrors errs
) = CommandErrors errs
603 fmap f
(CommandReadyToGo flags
) = CommandReadyToGo
(f flags
)
605 data CommandType
= NormalCommand | HiddenCommand
607 = Command
String String ([String] -> CommandParse action
) CommandType
609 -- | Mark command as hidden. Hidden commands don't show up in the 'progname
610 -- help' or 'progname --help' output.
611 hiddenCommand
:: Command action
-> Command action
612 hiddenCommand
(Command name synopsys f _cmdType
) =
613 Command name synopsys f HiddenCommand
617 -> (flags
-> [String] -> action
)
619 commandAddAction command action
=
621 (commandName command
)
622 (commandSynopsis command
)
623 (fmap (uncurry applyDefaultArgs
) . commandParseArgs command
False)
626 applyDefaultArgs mkflags args
=
627 let flags
= mkflags
(commandDefaultFlags command
)
634 -> CommandParse
(a
, CommandParse action
)
635 commandsRun globalCommand commands args
=
636 case commandParseArgs globalCommand
True args
of
637 CommandHelp help
-> CommandHelp help
638 CommandList opts
-> CommandList
(opts
++ commandNames
)
639 CommandErrors errs
-> CommandErrors errs
640 CommandReadyToGo
(mkflags
, args
') -> case args
' of
641 ("help" : cmdArgs
) -> handleHelpCommand cmdArgs
642 (name
: cmdArgs
) -> case lookupCommand name
of
643 [Command _ _ action _
] ->
644 CommandReadyToGo
(flags
, action cmdArgs
)
645 _
-> CommandReadyToGo
(flags
, badCommand name
)
646 [] -> CommandReadyToGo
(flags
, noCommand
)
648 flags
= mkflags
(commandDefaultFlags globalCommand
)
650 lookupCommand cname
=
651 [ cmd | cmd
@(Command cname
' _ _ _
) <- commands
', cname
' == cname
653 noCommand
= CommandErrors
["no command given (try --help)\n"]
655 -- Print suggested command if edit distance is < 5
656 badCommand
:: String -> CommandParse a
659 [] -> CommandErrors
[unErr
]
663 , "Maybe you meant `" ++ s
++ "`?\n"
667 map fst . List
.sortBy (comparing
snd) $
669 |
(Command cname
' _ _ _
) <- commands
'
670 , let dist
= editDistance cname
' cname
673 unErr
= "unrecognised command: " ++ cname
++ " (try --help)"
675 commands
' = commands
++ [commandAddAction helpCommandUI
undefined]
676 commandNames
= [name |
(Command name _ _ NormalCommand
) <- commands
']
678 -- A bit of a hack: support "prog help" as a synonym of "prog --help"
679 -- furthermore, support "prog help command" as "prog command --help"
680 handleHelpCommand cmdArgs
=
681 case commandParseArgs helpCommandUI
True cmdArgs
of
682 CommandHelp help
-> CommandHelp help
683 CommandList
list -> CommandList
(list ++ commandNames
)
684 CommandErrors _
-> CommandHelp globalHelp
685 CommandReadyToGo
(_
, []) -> CommandHelp globalHelp
686 CommandReadyToGo
(_
, (name
: cmdArgs
')) ->
687 case lookupCommand name
of
688 [Command _ _ action _
] ->
689 case action
("--help" : cmdArgs
') of
690 CommandHelp help
-> CommandHelp help
691 CommandList _
-> CommandList
[]
692 _
-> CommandHelp globalHelp
695 globalHelp
= commandHelp globalCommand
697 -- Levenshtein distance, from https://wiki.haskell.org/Edit_distance
698 -- (Author: JeanPhilippeBernardy, Simple Permissive Licence)
699 editDistance
:: Eq a
=> [a
] -> [a
] -> Int
700 editDistance xs ys
= table
Array.! (m
, n
)
702 (m
, n
) = (length xs
, length ys
)
703 x
= Array.array (1, m
) (zip [1 ..] xs
)
704 y
= Array.array (1, n
) (zip [1 ..] ys
)
706 table
:: Array.Array (Int, Int) Int
707 table
= Array.array bnds
[(ij
, dist ij
) | ij
<- Array.range bnds
]
708 bnds
= ((0, 0), (m
, n
))
714 [ table
Array.! (i
- 1, j
) + 1
715 , table
Array.! (i
, j
- 1) + 1
716 , if x
Array.! i
== y
Array.! j
717 then table
Array.! (i
- 1, j
- 1)
718 else 1 + table
Array.! (i
- 1, j
- 1)
721 -- | Utility function, many commands do not accept additional flags. This
722 -- action fails with a helpful error message if the user supplies any extra.
723 noExtraFlags
:: [String] -> IO ()
724 noExtraFlags
[] = return ()
725 noExtraFlags extraFlags
=
726 dieNoVerbosity
$ "Unrecognised flags: " ++ intercalate
", " extraFlags
728 -- TODO: eliminate this function and turn it into a variant on commandAddAction
729 -- instead like commandAddActionNoArgs that doesn't supply the [String]
731 -- | Helper function for creating globalCommand description
732 getNormalCommandDescriptions
:: [Command action
] -> [(String, String)]
733 getNormalCommandDescriptions cmds
=
734 [ (name
, description
)
735 | Command name description _ NormalCommand
<- cmds
738 helpCommandUI
:: CommandUI
()
742 "Help about commands."
743 ["[FLAGS]", "COMMAND [FLAGS]"]
747 { commandNotes
= Just
$ \pname
->
752 ++ " Oh, apparently you already know this.\n"
755 -- | wraps a @CommandUI@ together with a function that turns it into a @Command@.
756 -- By hiding the type of flags for the UI allows construction of a list of all UIs at the
757 -- top level of the program. That list can then be used for generation of manual page
758 -- as well as for executing the selected command.
759 data CommandSpec action
760 = forall flags
. CommandSpec
(CommandUI flags
) (CommandUI flags
-> Command action
) CommandType
762 commandFromSpec
:: CommandSpec a
-> Command a
763 commandFromSpec
(CommandSpec ui action _
) = action ui