1{-# OPTIONS_GHC -Wall #-}
3module CommandLine.Args.Internal
5 , Flags, noFlags, flags, (|--)
7 , Parser, int, file, anything, custom
8 , Args, noArgs, required, optional, zeroOrMore, oneOrMore, oneOf
9 , require0, require1, require2, require3, require4, require5
10 , RequiredArgs, args, exactly, (!), (?), (...)
15import qualified Data.Char as Char
16import qualified Data.List as List
17import GHC.IO.Handle (hIsTerminalDevice)
18import System.Environment (getArgs)
19import System.Exit (exitFailure)
20import System.IO (hPutStrLn, stderr)
21import Text.PrettyPrint.ANSI.Leijen
22 ( Doc, (<>), comma, displayIO, fillSep, green
23 , plain, red, renderPretty, text, vcat
26import Elm.Utils (nearbyNames)
34 Route :: (args -> flags -> a) -> Args args -> Flags flags -> Route a
43 FMore :: Flags (a -> b) -> Flag a -> Flags b
56(|--) :: Flags (a -> b) -> Flag a -> Flags b
66 Flag :: String -> Parser a -> String -> Flag (Maybe a)
67 OnOff :: String -> String -> Flag Bool
70flag :: String -> Parser a -> String -> Flag (Maybe a)
75onOff :: String -> String -> Flag Bool
85 Anything :: String -> Parser String
86 File :: Parser FilePath
87 Int :: String -> Parser Int
88 Custom :: String -> (String -> Maybe a) -> Parser a
91file :: Parser FilePath
96int :: String -> Parser Int
101anything :: String -> Parser String
106custom :: String -> (String -> Maybe a) -> Parser a
117getParserInfo :: Parser a -> ParserInfo
118getParserInfo parser =
133runParser :: Parser a -> String -> Either ParserError a
134runParser parser str =
143 if all Char.isDigit str then
144 Right (read str :: Int)
146 Left (Expecting tipe)
148 Custom tipe toMaybe ->
151 Left (Expecting tipe)
157data ParserError = Expecting String
169required :: Parser a -> Args a
174optional :: Parser a -> Args (Maybe a)
180zeroOrMore :: Parser a -> Args [a]
186oneOrMore :: Parser a -> Args (a, [a])
193require0 :: args -> Args args
198require1 :: (a -> args) -> Parser a -> Args args
206require2 :: (a -> b -> args) -> Parser a -> Parser b -> Args args
215require3 :: (a -> b -> c -> args) -> Parser a -> Parser b -> Parser c -> Args args
225require4 :: (a -> b -> c -> d -> args) -> Parser a -> Parser b -> Parser c -> Parser d -> Args args
226require4 func a b c d =
236require5 :: (a -> b -> c -> d -> e -> args) -> Parser a -> Parser b -> Parser c -> Parser d -> Parser e -> Args args
237require5 func a b c d e =
248oneOf :: [Args a] -> Args a
257data RequiredArgs a where
258 Done :: a -> RequiredArgs a
259 Required :: RequiredArgs (a -> b) -> Parser a -> RequiredArgs b
263 Exactly :: RequiredArgs a -> Args a
264 Optional :: RequiredArgs (Maybe a -> b) -> Parser a -> Args b
265 Multiple :: RequiredArgs ([a] -> b) -> Parser a -> Args b
266 OneOf :: [Args a] -> Args a
269args :: a -> RequiredArgs a
274exactly :: RequiredArgs a -> Args a
279(!) :: RequiredArgs (a -> b) -> Parser a -> RequiredArgs b
284(?) :: RequiredArgs (Maybe a -> b) -> Parser a -> Args b
289(...) :: RequiredArgs ([a] -> b) -> Parser a -> Args b
298get :: Route a -> [(String, Route a)] -> IO a
299get route namedRoutes =
301 case parse route namedRoutes chunks of
302 Right flagsAndArgs ->
314 = AfterCommand String RouteError
315 | BadCommand String [String] String
316 | NoCommand RouteError
319parse :: Route a -> [(String, Route a)] -> [String] -> Either Error a
320parse route namedRoutes chunks =
321 case parseHelp namedRoutes chunks [] of
326 case parseRoute route chunks of
332 (name, specificRouteError) : _ ->
333 Left (AfterCommand name specificRouteError)
337 first : _ | not (List.isPrefixOf "-" first) ->
338 case nearbyNames id first (map fst namedRoutes) of
340 Left (NoCommand routeError)
343 Left (BadCommand first (init suggestions) (last suggestions))
346 Left (NoCommand routeError)
349parseHelp :: [(String, Route a)] -> [String] -> RouteErrors -> Either RouteErrors a
350parseHelp namedRoutes chunks errors =
355 (name, route) : rest ->
356 case parseNamedRoute name route chunks of
358 parseHelp rest chunks (errs ++ errors)
368type RouteErrors = [(String, RouteError)]
371parseNamedRoute :: String -> Route a -> [String] -> Either RouteErrors a
372parseNamedRoute name route rawChunks =
374 first : chunks | name == first ->
375 case parseRoute route chunks of
390data RouteError = BadFlags FlagError | BadArgs ArgError
393parseRoute :: Route a -> [String] -> Either RouteError a
394parseRoute (Route func args_ flags_) chunks =
395 case parseFlags flags_ chunks of
397 Left (BadFlags flagError)
399 Right (flagData, remainingChunks) ->
400 case parseArgs args_ remainingChunks of
402 Left (BadArgs argError)
405 Right (func argData flagData)
414 | MissingArg ParserInfo
415 | BadArg String ParserError
419parseArgs :: Args a -> [String] -> Either ArgError a
420parseArgs args_ chunks =
423 parseOneOf argsList chunks []
425 Exactly requiredArgs ->
426 do (value, extras) <- parseRequiredArgs requiredArgs chunks
432 Left (ExtraArgs extras)
434 Optional requiredArgs optionalParser ->
435 do (func, subChunks) <- parseRequiredArgs requiredArgs chunks
441 do value <- runArgParser optionalParser chunk
442 Right (func (Just value))
445 Left (ExtraArgs extras)
447 Multiple requiredArgs multiParser ->
448 do (func, extras) <- parseRequiredArgs requiredArgs chunks
449 values <- traverse (runArgParser multiParser) extras
453parseOneOf :: [Args a] -> [String] -> [ArgError] -> Either ArgError a
454parseOneOf argsList chunks errors =
457 Left (NoneOf (reverse errors))
460 case parseArgs args_ chunks of
462 parseOneOf rest chunks (err:errors)
468parseRequiredArgs :: RequiredArgs a -> [String] -> Either ArgError (a, [String])
469parseRequiredArgs args_ chunks =
472 Right (value, chunks)
474 Required funcArgs parser ->
475 do (func, subChunks) <- parseRequiredArgs funcArgs chunks
478 Left (MissingArg (getParserInfo parser))
481 do value <- runArgParser parser chunk
482 Right ( func value, rest )
485runArgParser :: Parser a -> String -> Either ArgError a
486runArgParser parser chunk =
487 case runParser parser chunk of
489 Left (BadArg chunk parserError)
500 Either FlagError (a, [String])
504 = BadOnOff String String
505 | BadFlag String ParserInfo (Maybe (String, ParserError))
506 | Unrecognized String [(String, String)]
509parseFlags :: Flags a -> [String] -> Result a
510parseFlags flags_ chunks =
511 do (a, leftoverChunks) <- parseFlagsHelp flags_ chunks
512 case List.find (List.isPrefixOf "-") leftoverChunks of
514 Right (a, leftoverChunks)
517 Left (Unrecognized badArg (collectFlagInfo flags_))
520parseFlagsHelp :: Flags a -> [String] -> Result a
521parseFlagsHelp flags_ chunks =
524 Right (value, chunks)
526 FMore makeFunc flag_ ->
527 do (func, remainingChunks) <- parseFlagsHelp makeFunc chunks
528 (arg, leftoverChunks) <- parseFlag flag_ remainingChunks
529 Right (func arg, leftoverChunks)
532parseFlag :: Flag a -> [String] -> Result a
533parseFlag flag_ chunks =
535 Flag name parser _summary ->
536 parseFlagFlag name parser chunks
538 OnOff name _summary ->
539 parseFlagOnOff name chunks
542parseFlagFlag :: String -> Parser a -> [String] -> Result (Maybe a)
543parseFlagFlag name parser chunks =
544 case findDoubleDash [] ("--" ++ name) chunks of
546 Right (Nothing, chunks)
548 Just (ChunkZipper before value after) ->
551 parseFlagFlagHelp name parser before str after
554 parseFlagFlagHelp name parser before str after
557 Left (BadFlag name (getParserInfo parser) Nothing)
560parseFlagFlagHelp :: String -> Parser a -> [String] -> String -> [String] -> Result (Maybe a)
561parseFlagFlagHelp name parser before str after =
562 case runParser parser str of
564 Right (Just value, before ++ after)
567 Left $ BadFlag name (getParserInfo parser) $
568 Just (str, parserError)
571parseFlagOnOff :: String -> [String] -> Result Bool
572parseFlagOnOff name chunks =
573 case findDoubleDash [] ("--" ++ name) chunks of
575 Right (False, chunks)
577 Just (ChunkZipper before value after) ->
580 Right (True, before ++ after)
583 Right (True, before ++ str : after)
586 Left (BadOnOff name str)
595 { _before :: [String]
607findDoubleDash :: [String] -> String -> [String] -> Maybe ChunkZipper
608findDoubleDash revPrev prefix chunks =
610 flag_ : val : rest | flag_ == prefix ->
611 if List.isPrefixOf "-" val then
612 Just $ ChunkZipper (reverse revPrev) DefNope (val : rest)
614 Just $ ChunkZipper (reverse revPrev) (Possibly val) rest
616 [flag_] | flag_ == prefix ->
617 Just $ ChunkZipper (reverse revPrev) DefNope []
619 flag_ : rest | List.isPrefixOf (prefix ++ "=") flag_ ->
622 drop (length prefix + 1) flag_
624 Just $ ChunkZipper (reverse revPrev) (Definitely value) rest
627 findDoubleDash (chunk : revPrev) prefix rest
637collectFlagInfo :: Flags a -> [(String, String)]
638collectFlagInfo flags_ =
639 collectFlagInfoHelp flags_ []
642collectFlagInfoHelp :: Flags a -> [(String, String)] -> [(String, String)]
643collectFlagInfoHelp flags_ info =
648 FMore otherFlags flag_ ->
649 collectFlagInfoHelp otherFlags (getFlagInfo flag_ : info)
652getFlagInfo :: Flag a -> (String, String)
655 OnOff name summary ->
658 Flag name _ summary ->
666failure :: Error -> IO a
668 do let doc = toDoc err
669 isTerminal <- hIsTerminalDevice stderr
670 displayIO stderr $ renderPretty 1 80 $
671 if isTerminal then doc else plain doc
679 BadCommand badName suggestions lastSuggestion ->
684 , text "Did you mean"
686 ++ oxfordify (map code suggestions) "or" (code lastSuggestion <> text "?")
688 NoCommand (BadArgs argError) ->
689 argErrorToDoc Nothing argError
691 NoCommand (BadFlags flagError) ->
692 flagErrorToDoc Nothing flagError
694 AfterCommand name (BadArgs argError) ->
695 argErrorToDoc (Just name) argError
697 AfterCommand name (BadFlags flagError) ->
698 flagErrorToDoc (Just name) flagError
705argErrorToDoc :: Maybe String -> ArgError -> Doc
706argErrorToDoc maybeCommand argError =
709 text $ "TODO - " ++ show (map (argErrorToDoc maybeCommand) argErrors)
711 MissingArg (ParserInfo tipe) ->
712 text $ "TODO - " ++ tipe
714 BadArg chunk _parserError ->
715 text $ "TODO - " ++ chunk
717 ExtraArgs extraChunks ->
718 text $ "TODO - " ++ show extraChunks
725flagErrorToDoc :: Maybe String -> FlagError -> Doc
726flagErrorToDoc maybeCommand flagError =
728 BadOnOff name value ->
731 "--" ++ name ++ "=" ++ value
734 flagErrorToDocHelp "This on/off flag has a trailing equals sign:" chunk
736 [ text "Just get rid of the equals sign, like this:"
737 , green (text ("--" ++ name))
742 flagErrorToDocHelp "This on/off flag was given a value:" chunk
743 [ text "An on/off flag either exists or not, no need for an equals sign and value."
744 , text "Maybe you want this instead?"
746 , text " " <> green (text ("--" ++ name))
750 BadFlag name (ParserInfo tipe) Nothing ->
752 "This flag cannot appear without a value:"
754 [ text $ "It needs a " ++ tipe ++ " value."
757 BadFlag name (ParserInfo tipe) (Just (value, parserError)) ->
759 "This flag was given a bad value:"
760 ("--" ++ name ++ "=" ++ value)
761 [ fillSep $ map text $ words $
762 "I need a " ++ tipe ++ " value, but "
765 Expecting expectedType ->
766 value ++ " is not a valid " ++ expectedType ++ "."
769 Unrecognized chunk [] ->
774 "I am not expecting any flags."
777 "The `" ++ command ++ "` command does not expect any flags."
779 flagErrorToDocHelp "Get rid of this flag:" chunk $
780 [ fillSep (map text (words explanation))
783 Unrecognized chunk info ->
784 case takeWhile ('=' /=) (dropWhile ('-' ==) chunk) of
786 flagErrorToDocHelp "This is not a valid flag:" chunk
787 [ text "Valid flags look like --output=FILE"
793 nearbyNames id name (map fst info)
795 flagErrorToDocHelp "I do not recognize this flag:" chunk $
796 unrecognizedExplanation $
797 if null suggestions then map fst info else suggestions
800flagErrorToDocHelp :: String -> String -> [Doc] -> Doc
801flagErrorToDocHelp summary original explanation =
805 , text " " <> red (text original)
811unrecognizedExplanation :: [String] -> [Doc]
812unrecognizedExplanation suggestions =
815 text " " <> green (text ("--" ++ name))
822 [ text "Looks like a typo. Maybe you want:"
824 , toBullet suggestion
829 [ text "Maybe you want one of these?"
832 ++ map toBullet suggestions
841oxfordify :: [Doc] -> String -> Doc -> [Doc]
842oxfordify chunks conjuction lastChunk =
848 [ chunk, text conjuction, lastChunk ]
851 map (<> comma) chunks ++ [ text conjuction, lastChunk ]
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