1{-# LANGUAGE GADTs, OverloadedStrings #-}
15import Data.Monoid ((<>))
16import qualified Data.List as List
17import qualified Data.Maybe as Maybe
18import GHC.IO.Handle (hIsTerminalDevice)
19import qualified System.Environment as Env
20import qualified System.Exit as Exit
21import qualified System.FilePath as FP
22import System.IO (hPutStrLn, stderr)
23import qualified Text.PrettyPrint.ANSI.Leijen as P
25import Reporting.Suggest as Suggest
26import Terminal.Internal
34 BadArgs :: [(CompleteArgs a, ArgError)] -> Error
35 BadFlag :: FlagError -> Error
39 = ArgMissing Expectation
40 | ArgBad String Expectation
45 FlagWithValue :: String -> String -> FlagError
46 FlagWithBadValue :: String -> String -> Expectation -> FlagError
47 FlagWithNoValue :: String -> Expectation -> FlagError
48 FlagUnknown :: String -> Flags a -> FlagError
54 , _examples :: IO [String]
62exitSuccess :: [P.Doc] -> IO a
64 exitWith Exit.ExitSuccess
67exitFailure :: [P.Doc] -> IO a
69 exitWith (Exit.ExitFailure 1)
72exitWith :: Exit.ExitCode -> [P.Doc] -> IO a
74 do isTerminal <- hIsTerminalDevice stderr
75 let adjust = if isTerminal then id else P.plain
76 P.displayIO stderr $ P.renderPretty 1 80 $
77 adjust $ P.vcat $ concatMap (\d -> [d,""]) docs
82getExeName :: IO String
84 FP.takeFileName <$> Env.getProgName
87stack :: [P.Doc] -> P.Doc
89 P.vcat $ List.intersperse "" docs
92reflow :: String -> P.Doc
94 P.fillSep $ map P.text $ words string
101exitWithHelp :: Maybe String -> String -> P.Doc -> Args args -> Flags flags -> IO a
102exitWithHelp maybeCommand details example (Args args) flags =
103 do command <- toCommand maybeCommand
106 , P.indent 4 $ P.cyan $ P.vcat $ map (argsToDoc command) args
110 case flagsToDocs flags [] of
115 [ "You can customize this command with the following flags:"
116 , P.indent 4 $ stack docs
120toCommand :: Maybe String -> IO String
121toCommand maybeCommand =
122 do exeName <- getExeName
129 exeName ++ " " ++ command
132argsToDoc :: String -> CompleteArgs a -> P.Doc
133argsToDoc command args =
136 argsToDocHelp command required []
138 Multiple required (Parser _ plural _ _ _) ->
139 argsToDocHelp command required ["zero or more " ++ plural]
141 Optional required (Parser singular _ _ _ _) ->
142 argsToDocHelp command required ["optional " ++ singular]
145argsToDocHelp :: String -> RequiredArgs a -> [String] -> P.Doc
146argsToDocHelp command args names =
149 P.hang 4 $ P.hsep $ map P.text $
150 command : map toToken names
152 Required others (Parser singular _ _ _ _) ->
153 argsToDocHelp command others (singular : names)
156toToken :: String -> String
158 "<" ++ map (\c -> if c == ' ' then '-' else c) string ++ ">"
161flagsToDocs :: Flags flags -> [P.Doc] -> [P.Doc]
162flagsToDocs flags docs =
172 Flag name (Parser singular _ _ _ _) description ->
173 [ P.dullcyan $ P.text $ "--" ++ name ++ "=" ++ toToken singular
174 , P.indent 4 $ reflow description
177 OnOff name description ->
178 [ P.dullcyan $ P.text $ "--" ++ name
179 , P.indent 4 $ reflow description
182 flagsToDocs more (flagDoc:docs)
189exitWithOverview :: P.Doc -> P.Doc -> [Command] -> IO a
190exitWithOverview intro outro commands =
191 do exeName <- getExeName
194 , "The most common commands are:"
195 , P.indent 4 $ stack $ Maybe.mapMaybe (toSummary exeName) commands
196 , "There are a bunch of other commands as well though. Here is a full list:"
197 , P.indent 4 $ P.dullcyan $ toCommandList exeName commands
198 , "Adding the --help flag gives a bunch of additional details about each one."
203toSummary :: String -> Command -> Maybe P.Doc
204toSummary exeName (Command name summary _ _ (Args args) _ _) =
209 Common summaryString ->
212 [ P.cyan $ argsToDoc (exeName ++ " " ++ name) (head args)
213 , P.indent 4 $ reflow summaryString
217toCommandList :: String -> [Command] -> P.Doc
218toCommandList exeName commands =
220 names = map toName commands
221 width = maximum (map length names)
224 P.text $ exeName ++ " " ++ name ++ replicate (width - length name) ' ' ++ " --help"
226 P.vcat (map toExample names)
233exitWithUnknown :: String -> [String] -> IO a
234exitWithUnknown unknown knowns =
237 takeWhile (\(r,_) -> r <= 3) (Suggest.rank unknown id knowns)
240 case map toGreen (map snd nearbyKnowns) of
245 ["Try",nearby,"instead?"]
248 ["Try",a,"or",b,"instead?"]
251 ["Try"] ++ map (<> ",") (init abcs) ++ ["or",last abcs,"instead?"]
253 do exeName <- getExeName
255 [ P.fillSep $ ["There","is","no",toRed unknown,"command."] ++ suggestions
256 , reflow $ "Run `" ++ exeName ++ "` with no arguments to get more hints."
264exitWithError :: Error -> IO a
269 flagErrorToDocs flagError
275 [ reflow $ "I was not expecting any arguments for this command."
276 , reflow $ "Try removing them?"
279 [(_args, argError)] ->
280 argErrorToDocs argError
283 argErrorToDocs $ head $ List.sortOn toArgErrorRank (map snd argErrors)
286toArgErrorRank :: ArgError -> Int -- lower is better
294toGreen :: String -> P.Doc
299toYellow :: String -> P.Doc
301 P.yellow (P.text str)
304toRed :: String -> P.Doc
313argErrorToDocs :: ArgError -> IO [P.Doc]
314argErrorToDocs argError =
316 ArgMissing (Expectation tipe makeExamples) ->
317 do examples <- makeExamples
320 ["The","arguments","you","have","are","fine,","but","in","addition,","I","was"
321 ,"expecting","a",toYellow (toToken tipe),"value.","For","example:"
323 , P.indent 4 $ P.green $ P.vcat $ map P.text examples
326 ArgBad string (Expectation tipe makeExamples) ->
327 do examples <- makeExamples
329 [ "I am having trouble with this argument:"
330 , P.indent 4 $ toRed string
332 ["It","is","supposed","to","be","a"
333 ,toYellow (toToken tipe),"value,","like"
334 ] ++ if length examples == 1 then ["this:"] else ["one","of","these:"]
335 , P.indent 4 $ P.green $ P.vcat $ map P.text examples
342 [_] -> ("this argument", "it")
343 _ -> ("these arguments", "them")
346 [ reflow $ "I was not expecting " ++ these ++ ":"
347 , P.indent 4 $ P.red $ P.vcat $ map P.text extras
348 , reflow $ "Try removing " ++ them ++ "?"
356flagErrorHelp :: String -> String -> [P.Doc] -> IO [P.Doc]
357flagErrorHelp summary original explanation =
360 , P.indent 4 (toRed original)
365flagErrorToDocs :: FlagError -> IO [P.Doc]
366flagErrorToDocs flagError =
368 FlagWithValue flagName value ->
370 "This on/off flag was given a value:"
371 ("--" ++ flagName ++ "=" ++ value)
372 [ "An on/off flag either exists or not. It cannot have an equals sign and value.\n\
373 \Maybe you want this instead?"
374 , P.indent 4 $ toGreen $ "--" ++ flagName
377 FlagWithNoValue flagName (Expectation tipe makeExamples) ->
378 do examples <- makeExamples
380 "This flag needs more information:"
382 [ P.fillSep ["It","needs","a",toYellow (toToken tipe),"like","this:"]
383 , P.indent 4 $ P.vcat $ map toGreen $
384 case take 4 examples of
386 ["--" ++ flagName ++ "=" ++ toToken tipe]
389 map (\example -> "--" ++ flagName ++ "=" ++ example) examples
392 FlagWithBadValue flagName badValue (Expectation tipe makeExamples) ->
393 do examples <- makeExamples
395 "This flag was given a bad value:"
396 ("--" ++ flagName ++ "=" ++ badValue)
398 ["I","need","a","valid",toYellow (toToken tipe),"value.","For","example:"
400 , P.indent 4 $ P.vcat $ map toGreen $
401 case take 4 examples of
403 ["--" ++ flagName ++ "=" ++ toToken tipe]
406 map (\example -> "--" ++ flagName ++ "=" ++ example) examples
409 FlagUnknown unknown flags ->
411 "I do not recognize this flag:"
414 let unknownName = takeWhile ('=' /=) (dropWhile ('-' ==) unknown) in
415 case getNearbyFlags unknownName flags [] of
420 [ P.fillSep ["Maybe","you","want",P.green thisOne,"instead?"]
424 [ P.fillSep ["Maybe","you","want","one","of","these","instead?"]
425 , P.indent 4 $ P.green $ P.vcat suggestions
430getNearbyFlags :: String -> Flags a -> [(Int, String)] -> [P.Doc]
431getNearbyFlags unknown flags unsortedFlags =
434 getNearbyFlags unknown more (getNearbyFlagsHelp unknown flag : unsortedFlags)
437 map P.text $ map snd $ List.sortOn fst $
438 case filter (\(d,_) -> d < 3) unsortedFlags of
442 nearbyUnsortedFlags ->
446getNearbyFlagsHelp :: String -> Flag a -> (Int, String)
447getNearbyFlagsHelp unknown flag =
450 ( Suggest.distance unknown flagName
454 Flag flagName (Parser singular _ _ _ _) _ ->
455 ( Suggest.distance unknown flagName
456 , "--" ++ flagName ++ "=" ++ toToken singular
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list