From fadb98550ad4ac2dc4781a615cacf9ee5b79f455 Mon Sep 17 00:00:00 2001 From: Felix Schlitter Date: Sun, 8 May 2016 05:56:58 +1200 Subject: [PATCH] Populate values at parser level *** BREAKING CHANGE *** This is a fairly large re-think of how argument values are fetched from the their various input sources. Before, values would be loaded during a step after parsing, where the matched usage branch would be completely unrolled, unified and then populated. This had the disadvantage that one could not tell which of the mutually exclusive branches produced a result, since one value might arrive from ARGV and another one from the [default: ...] tag, or the environment, etc. (See #8) Now, values are loaded at parse-time, removing these issues. Further, "empty" values are elided from the output, representing the user *not trying* to match these keys (as opposed to choosing to set them explicitly). For example, the user might pass the value 'false' to an option and that value will be retained. If the option however yields false because there was no user input and because 'false' is its' empty fall-back, the value will be omitted. The same goes for matching repeating elements into an array. At least one element needs to be matched before a value will be yielded. The diff of 'testcases.docopt' should highlight these changes better than any amount of explanatory text could. This patch also removes the "ScoredResult" type as it is no longer needed. Scoring is based on the "Origin" of a value now. This somewhat simplifies the code. Lastly, there's now a recursive step inside `genExhaustiveParser` that recursively evaluates missing elements until all options have been exhausted or failed. --- src/Docopt/Docopt.purs | 3 +- src/Language/Docopt.purs | 14 - src/Language/Docopt/{Trans => }/Origin.purs | 8 +- src/Language/Docopt/ParserGen.purs | 4 +- src/Language/Docopt/ParserGen/Parser.purs | 346 +++++++++++--------- src/Language/Docopt/Trans/Flat.purs | 3 +- src/Language/Docopt/Trans/Rich.purs | 115 ++----- test/Test/Spec/ParserGenSpec.purs | 12 +- testcases.docopt | 305 ++++++++--------- 9 files changed, 379 insertions(+), 431 deletions(-) rename src/Language/Docopt/{Trans => }/Origin.purs (73%) diff --git a/src/Docopt/Docopt.purs b/src/Docopt/Docopt.purs index c60bacad..b76ebfaa 100644 --- a/src/Docopt/Docopt.purs +++ b/src/Docopt/Docopt.purs @@ -80,8 +80,7 @@ run :: forall e run d o = do argv <- maybe (A.drop 2 <$> Process.argv) (return <<< id) o.argv env <- maybe Process.getEnv (return <<< id) o.env - either onError return - do + either onError return do { specification, usage } <- parseDocopt d o.smartOptions lmap ((help usage) ++ _) do evalDocopt specification env argv o.optionsFirst diff --git a/src/Language/Docopt.purs b/src/Language/Docopt.purs index f4c300a8..40cf4cf3 100644 --- a/src/Language/Docopt.purs +++ b/src/Language/Docopt.purs @@ -43,20 +43,6 @@ data Origin | Environment | Default --- newtype RichValue = RichValue { --- value :: D.Value --- , origin :: Origin --- , env :: Maybe { --- key :: String --- , value :: Maybe String --- } --- , default :: Maybe D.Value --- } - --- data Output --- = SimpleOutput (StrMap D.Value) --- | RichOutput (StrMap RichValue) - -- | -- | Parse the docopt text and produce a parser -- | that can be applied to user input. diff --git a/src/Language/Docopt/Trans/Origin.purs b/src/Language/Docopt/Origin.purs similarity index 73% rename from src/Language/Docopt/Trans/Origin.purs rename to src/Language/Docopt/Origin.purs index 27d766ff..bc4cbe49 100644 --- a/src/Language/Docopt/Trans/Origin.purs +++ b/src/Language/Docopt/Origin.purs @@ -1,4 +1,4 @@ -module Language.Docopt.Trans.Origin ( +module Language.Docopt.Origin ( Origin(..) ) where @@ -17,6 +17,12 @@ weight Environment = 2 weight Default = 1 weight Empty = 0 +instance showOrigin :: Show Origin where + show Argv = "Argv" + show Environment = "Environment" + show Default = "Default" + show Empty = "Empty" + instance eqOrigin :: Eq Origin where eq Argv Argv = true eq Environment Environment = true diff --git a/src/Language/Docopt/ParserGen.purs b/src/Language/Docopt/ParserGen.purs index efbb0bdf..b6aded19 100644 --- a/src/Language/Docopt/ParserGen.purs +++ b/src/Language/Docopt/ParserGen.purs @@ -22,9 +22,9 @@ import Language.Docopt.ParserGen.Token (PositionedToken(..), Token(..), getSource, prettyPrintToken, unPositionedToken) as G import Language.Docopt.ParserGen.Parser (Parser, genUsageParser, - initialState) as G + RichValue(..), unRichValue, + from, initialState, ValueMapping()) as G import Language.Docopt.ParserGen.Lexer (lex) as G -import Language.Docopt.ParserGen.ValueMapping (ValueMapping) as G type Result = Tuple D.Branch (List G.ValueMapping) diff --git a/src/Language/Docopt/ParserGen/Parser.purs b/src/Language/Docopt/ParserGen/Parser.purs index d2c59650..deb6a454 100644 --- a/src/Language/Docopt/ParserGen/Parser.purs +++ b/src/Language/Docopt/ParserGen/Parser.purs @@ -8,8 +8,13 @@ module Language.Docopt.ParserGen.Parser ( genUsageParser , initialState - , Parser() - , StateObj + , Parser () + , StateObj () + , RichValue (..) + , RichValueObj (..) + , ValueMapping () + , unRichValue + , from ) where import Prelude @@ -42,39 +47,67 @@ import Control.Monad.State (State, evalState) import Control.Monad.State as State import Data.StrMap (StrMap()) import Control.Monad.Trans (lift) -import Control.MonadPlus.Partial (mrights, mlefts) +import Control.MonadPlus.Partial (mrights, mlefts, mpartition) import Text.Parsing.Parser (PState(..), ParseError(..), ParserT(..), fail, parseFailed, unParserT) as P import Text.Parsing.Parser.Combinators (option, try, lookAhead, ()) as P import Text.Parsing.Parser.Pos (Position, initialPos) as P -import Language.Docopt.Value (Value(..), isBoolValue) as D import Language.Docopt.Argument (Argument(..), Branch(..), isFree, runBranch, prettyPrintArg, prettyPrintArgNaked, hasEnvBacking, getArgument, hasDefault, isRepeatable, isFlag, setRequired) as D import Language.Docopt.Usage (Usage, runUsage) as D -import Language.Docopt.Env (Env) as D +import Language.Docopt.Env (Env ()) +import Language.Docopt.Env as Env import Language.Docopt.Option as O -import Language.Docopt.Value as Value +import Language.Docopt.Origin as Origin +import Language.Docopt.Origin (Origin()) +import Language.Docopt.Value as Value +import Language.Docopt.Value (Value(..)) import Language.Docopt.Parser.Base (getInput) import Language.Docopt.ParserGen.Token as Token import Language.Docopt.ParserGen.Token (PositionedToken(..), Token(..), unPositionedToken, prettyPrintToken) -import Language.Docopt.ParserGen.ValueMapping (ValueMapping) import Data.String.Ext (startsWith) debug :: Boolean debug = false --- | --- | Unfortunately, the State Monad is needed because we try matching all --- | program branches and must select the best fit. --- | + +-- | The value type the parser collects +type RichValueObj = { + value :: Value +, origin :: Origin +} + +newtype RichValue = RichValue RichValueObj + +instance showRichValue :: Show RichValue where + show (RichValue v) = "RichValue { origin: " ++ show v.origin + ++ ", value: " ++ show v.value ++ "}" + +instance eqRichValue :: Eq RichValue where + eq (RichValue v) (RichValue v') = (v.origin == v'.origin) && + (v.value == v'.value) + +unRichValue :: RichValue -> RichValueObj +unRichValue (RichValue o) = o + +from :: Origin -> Value -> RichValue +from o v = RichValue $ { value: v, origin: o } + +fromArgv :: Value -> RichValue +fromArgv = from Origin.Argv + +-- | The output value mappings of arg -> val +type ValueMapping = Tuple D.Argument RichValue + +-- | The stateful parser type type StateObj = { depth :: Int , fatal :: Maybe P.ParseError } type Parser a = P.ParserT (List PositionedToken) - (ReaderT D.Env (State StateObj)) + (ReaderT Env (State StateObj)) a initialState :: StateObj @@ -85,41 +118,6 @@ modifyDepth :: (Int -> Int) -> Parser Unit modifyDepth f = do lift (State.modify \s -> s { depth = f s.depth }) -newtype ScoredResult a = ScoredResult { - score :: Int -- ^ the score of the parse -, result :: a -- ^ the result of the parse -} - -unScoredResult :: forall a. ScoredResult a -> { score :: Int, result :: a } -unScoredResult (ScoredResult r) = r - -instance semigroupScoredResult :: (Semigroup a) => Semigroup (ScoredResult a) - where append (ScoredResult s) (ScoredResult s') - = ScoredResult { score: s.score + s'.score - , result: s.result ++ s'.result } - -instance monoidScoredResult :: (Monoid a) => Monoid (ScoredResult a) - where mempty = ScoredResult { score: 0, result: mempty } - -instance showScoredResult :: (Show a) => Show (ScoredResult a) - where show (ScoredResult { score, result }) - = "ScoredResult " ++ show score ++ ": " ++ show result - -instance ordScoredResult :: Ord (ScoredResult a) - where compare = compare `on` (_.score <<< unScoredResult) - -instance eqScoredResult :: Eq (ScoredResult a) - where eq = eq `on` (_.score <<< unScoredResult) - -score :: forall a. Int -> List a -> ScoredResult (List a) -score score result = ScoredResult { score, result } - -scoreFromList :: forall a. List a -> ScoredResult (List a) -scoreFromList xs = ScoredResult { score: length xs, result: xs } - -rmapScoreResult :: forall a b. (a -> b) -> ScoredResult a -> ScoredResult b -rmapScoreResult f (ScoredResult (x@{ result })) = ScoredResult $ x { result = f result } - -------------------------------------------------------------------------------- -- Input Token Parser ---------------------------------------------------------- -------------------------------------------------------------------------------- @@ -148,34 +146,34 @@ data Acc a = Free (Parser a) | Pending (Parser a) (List D.Argument) -eoa :: Parser D.Value +eoa :: Parser Value eoa = token go P. "--" where - go (EOA xs) = Just (D.ArrayValue (fromList xs)) + go (EOA xs) = Just (ArrayValue (fromList xs)) go _ = Nothing -command :: String -> Parser D.Value +command :: String -> Parser Value command n = token go P. "command " ++ show n where - go (Lit s) | s == n = Just (D.BoolValue true) + go (Lit s) | s == n = Just (BoolValue true) go _ = Nothing -positional :: String -> Parser D.Value +positional :: String -> Parser Value positional n = token go P. "positional argument " ++ show n where go (Lit v) = Just (Value.read v false) go _ = Nothing -stdin :: Parser D.Value +stdin :: Parser Value stdin = token go P. "stdin flag" where - go Stdin = Just (D.BoolValue true) + go Stdin = Just (BoolValue true) go _ = Nothing type HasConsumedArg = Boolean -data OptParse = OptParse D.Value (Maybe Token) HasConsumedArg +data OptParse = OptParse Value (Maybe Token) HasConsumedArg -longOption :: O.Name -> (Maybe O.Argument) -> Parser D.Value +longOption :: O.Name -> (Maybe O.Argument) -> Parser Value longOption n a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> return $ case toks of Cons (PositionedToken { token: tok, sourcePos: npos, source: s }) xs -> @@ -213,7 +211,7 @@ longOption n a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> Just (Lit s) -> return $ OptParse (Value.read s false) Nothing true otherwise -> if (fromMaybe true (_.optional <<< O.runArgument <$> a)) - then Right $ OptParse (D.BoolValue true) Nothing false + then Right $ OptParse (BoolValue true) Nothing false else Left $ "Option requires argument: --" ++ n' -- case 2: @@ -221,7 +219,7 @@ longOption n a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> go (LOpt n' v) _ | isFlag && (n' == n) = case v of Just _ -> Left $ "Option takes no argument: --" ++ n' - Nothing -> return $ OptParse (D.BoolValue true) Nothing false + Nothing -> return $ OptParse (BoolValue true) Nothing false -- case 3: -- The name is a substring of the input and no explicit argument has been @@ -233,7 +231,7 @@ longOption n a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> go a b = Left $ "Invalid token" ++ show a ++ " (input: " ++ show b ++ ")" -shortOption :: Char -> (Maybe O.Argument) -> Parser D.Value +shortOption :: Char -> (Maybe O.Argument) -> Parser Value shortOption f a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> do return $ case toks of Cons (PositionedToken { token: tok, source: s }) xs -> @@ -274,7 +272,7 @@ shortOption f a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> do Just (Lit s) -> return $ OptParse (Value.read s false) Nothing true otherwise -> if (fromMaybe true (_.optional <<< O.runArgument <$> a)) - then Right $ OptParse (D.BoolValue true) Nothing false + then Right $ OptParse (BoolValue true) Nothing false else Left $ "Option requires argument: -" ++ fromChar f' -- case 2: @@ -291,7 +289,7 @@ shortOption f a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> do -- The leading flag matches, there are stacked options, the option takes -- no argument and an explicit argument has not been provided. go (SOpt f' xs v) _ | (f' == f) && (isFlag) && (not $ A.null xs) - = return $ OptParse (D.BoolValue true) + = return $ OptParse (BoolValue true) (Just $ SOpt (AU.head xs) (AU.tail xs) v) false @@ -301,7 +299,7 @@ shortOption f a = P.ParserT $ \(P.PState { input: toks, position: pos }) -> do go (SOpt f' xs v) _ | (f' == f) && (isFlag) && (A.null xs) = case v of Just _ -> Left $ "Option takes no argument: -" ++ fromChar f' - Nothing -> return $ OptParse (D.BoolValue true) + Nothing -> return $ OptParse (BoolValue true) Nothing false @@ -326,11 +324,10 @@ genUsageParser :: List D.Usage -- ^ The list of usage specs -> Boolean -- ^ Enable "options-first" -> Parser (Tuple D.Branch (List ValueMapping)) genUsageParser xs optsFirst = do - _.result <<< unScoredResult - <$> genBranchesParser (concat $ D.runUsage <$> xs) - true - optsFirst - true + genBranchesParser (concat $ D.runUsage <$> xs) + true + optsFirst + true -- | Generate a parser that selects the best branch it parses and -- | fails if no branch was parsed. @@ -338,14 +335,14 @@ genBranchesParser :: List D.Branch -- ^ The branches to test -> Boolean -- ^ Expect EOF after each branch -> Boolean -- ^ Enable "options-first" -> Boolean -- ^ Can we skip input via fallbacks? - -> Parser (ScoredResult (Tuple D.Branch (List ValueMapping))) + -> Parser (Tuple D.Branch (List ValueMapping)) genBranchesParser xs term optsFirst canSkip = P.ParserT \(s@(P.PState { input: i, position: pos })) -> do - env :: D.Env <- ask + env :: Env <- ask state :: StateObj <- lift State.get let - ps = xs <#> \x -> rmapScoreResult (Tuple x) <$> do + ps = xs <#> \x -> (Tuple x) <$> do genBranchParser x optsFirst canSkip <* unless (not term) eof rs = evalState (runReaderT (collect s ps) env) initialState @@ -356,13 +353,12 @@ genBranchesParser xs term optsFirst canSkip -- elements, take the highest element and sort the inner values by their -- fallback score. winner = - maximumBy (compare `on` (_.score <<< unScoredResult - <<< _.value - <<< _.result)) - =<< do - last $ groupBy (eq `on` (_.depth <<< _.result)) - (sortBy (compare `on` (_.depth <<< _.result)) - (mrights rs')) + maximumBy (compare `on` + (((_.origin <<< unRichValue <<< snd) <$> _) <<< snd + <<< _.value <<< _.result)) =<< do + last $ groupBy (eq `on` (_.depth <<< _.result)) + (sortBy (compare `on` (_.depth <<< _.result)) + (mrights rs')) -- Evaluate the losing candidates, if any. losers = @@ -429,7 +425,7 @@ genBranchesParser xs term optsFirst canSkip genBranchParser :: D.Branch -- ^ The branch to match -> Boolean -- ^ Enable "options-first" -> Boolean -- ^ Can we skip input via fallbacks? - -> Parser (ScoredResult (List ValueMapping)) + -> Parser (List ValueMapping) genBranchParser (D.Branch xs) optsFirst canSkip = do modifyDepth (const 0) -- reset the depth counter either @@ -448,7 +444,7 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do -- The only requirement is that all input is consumed in the end. genExhaustiveParser :: List D.Argument -- ^ The free arguments -> Boolean -- ^ Can we skip input via fallbacks? - -> Parser (ScoredResult (List ValueMapping)) + -> Parser (List ValueMapping) genExhaustiveParser Nil canSkip = return mempty genExhaustiveParser ps canSkip = do if debug @@ -465,34 +461,31 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do draw :: List D.Argument -- ^ the arguments to parse -> Int -- ^ the number of options left to parse -> List D.Argument -- ^ the unique arguments parsed - -> Parser (ScoredResult (List ValueMapping)) + -> Parser (List ValueMapping) draw pss@(Cons p ps') n tot | n >= 0 = (do - if debug - then do - i <- getInput - traceA $ - "draw: (" ++ (D.prettyPrintArg p) ++ ":" - ++ (intercalate ":" (D.prettyPrintArg <$> ps')) - ++ ") - n: " ++ show n - ++ "from input: " - ++ (intercalate " " (prettyPrintToken - <<< _.token - <<< unPositionedToken <$> i)) - else return unit + when debug do + i <- getInput + traceA $ + "draw: (" ++ (D.prettyPrintArg p) ++ ":" + ++ (intercalate ":" (D.prettyPrintArg <$> ps')) + ++ ") - n: " ++ show n + ++ "from input: " + ++ (intercalate " " (prettyPrintToken + <<< _.token + <<< unPositionedToken <$> i)) -- Generate the parser for the argument `p`. For groups, temporarily -- set the required flag to "true", such that it will fail and we have -- a chance to retry as part of the exhaustive parsing mechanism - r <- unScoredResult <$> (P.try $ genParser (D.setRequired p true) - (not $ n > 0)) + r <- P.try $ genParser (D.setRequired p true) (not $ n > 0) - r' <- unScoredResult <$> P.try do + r' <- P.try do if D.isRepeatable p then draw pss (length pss) (p:tot) else draw ps' (length ps') (p:tot) - return $ ScoredResult r ++ ScoredResult r' + return $ r ++ r' ) <|> (defer \_ -> do state :: StateObj <- lift State.get case state.fatal of @@ -503,48 +496,78 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do draw ps' n tot | (length ps' > 0) && (n < 0) = do env :: StrMap String <- lift ask - -- Find flags missing from the input. - -- If we are explicitly allowed to skip arguments because of lack of - -- input, or we have *at least* one match (i.e. the remainder is less - -- than the original), then ignore arguments for which a suitable - -- fallback value can be provided. - let missing = filter (\o -> not $ - (canSkip || (length ps' < length ps)) - && isSkippable env o - ) ps' - - if (length missing > 0) - then P.fail $ - "Expected option(s): " - ++ intercalate ", " (D.prettyPrintArgNaked <$> missing) - else return $ ScoredResult { - score: sum $ ps' <#> \o -> - if D.hasEnvBacking o env - then 2 - else if D.hasDefault o - then 1 - else 0 - , result: Nil - } + let + vs = ps' <#> \o -> + maybe + (Left o) + (Right <<< Tuple o) + do + (RichValue v) <- do + guard (canSkip || (length ps' < length ps)) + (getEnvValue env o <#> from Origin.Environment) <|> + (getDefaultValue o <#> from Origin.Default) <|> + (getEmptyValue o <#> from Origin.Empty) + + return $ RichValue v { + value = if D.isRepeatable o + then ArrayValue $ Value.intoArray v.value + else v.value + } + + missing = filter (not <<< isSkippable) (mlefts vs) + fallbacks = mrights vs + + if canSkip + then do + xs <- genExhaustiveParser missing false + return $ fallbacks ++ xs + else + if (length missing > 0) + then P.fail $ + "Expected option(s): " + ++ intercalate ", " (D.prettyPrintArgNaked <$> missing) + else return fallbacks where - isSkippable env (D.Group o bs _) - = o || (all (all (isSkippable env) <<< D.runBranch) bs) - isSkippable env o - = (D.hasDefault o) - || (maybe true id do - arg <- O.runArgument <$> do - D.getArgument o - return arg.optional - ) - || (D.hasEnvBacking o env) - || (D.isRepeatable o && (any (_ == o) tot)) + isSkippable (D.Group o bs _) + = o || (all (all isSkippable <<< D.runBranch) bs) + isSkippable o = D.isRepeatable o && (any (_ == o) tot) + + getEnvValue :: Env -> D.Argument -> Maybe Value + getEnvValue env (D.Option (O.Option o@{ env: Just k })) = do + StringValue <$> Env.lookup k env + getEnvValue _ _ = Nothing + + getDefaultValue :: D.Argument -> Maybe Value + getDefaultValue (D.Option (O.Option o@{ + arg: Just (O.Argument { default: Just v }) + })) = return if o.repeatable + then ArrayValue $ Value.intoArray v + else v + getDefaultValue _ = Nothing + + getEmptyValue :: D.Argument -> Maybe Value + getEmptyValue = go + where + go (D.Option (O.Option o@{ arg: Nothing })) + = return + $ if o.repeatable then ArrayValue [] + else BoolValue false + go (D.Option (O.Option o@{ arg: Just (O.Argument { optional: true }) })) + = return + $ if o.repeatable then ArrayValue [] + else BoolValue false + go (D.Positional _ r) | r = return $ ArrayValue [] + go (D.Command _ r) | r = return $ ArrayValue [] + go (D.Stdin) = return $ BoolValue false + go (D.EOA) = return $ ArrayValue [] + go _ = Nothing draw _ _ _ = return mempty - step :: Acc (ScoredResult (List ValueMapping)) + step :: Acc (List ValueMapping) -> D.Argument - -> Either P.ParseError (Acc (ScoredResult (List ValueMapping))) + -> Either P.ParseError (Acc (List ValueMapping)) -- Options always transition to the `Pending state` step (Free p) x@(D.Option _) @@ -580,44 +603,44 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do -- values int an array ("options-first") terminate arg = do input <- getInput - let rest = Tuple arg <$> do - D.StringValue <<< Token.getSource <$> input + let rest = Tuple arg <<< fromArgv <$> do + StringValue <<< Token.getSource <$> input P.ParserT \(P.PState { position: pos }) -> return { consumed: true , input: Nil - , result: return $ ScoredResult { - score: 1 - , result: rest - } + , result: return rest , position: pos } -- Parser generator for a single `Argument` genParser :: D.Argument -- ^ The argument to generate a parser for -> Boolean -- ^ Can we skip input via fallbacks? - -> Parser (ScoredResult (List ValueMapping)) + -> Parser (List ValueMapping) -- Generate a parser for a `Command` argument genParser x@(D.Command n r) _ = do i <- getInput - score 0 <$> (do + (do if r then (some go) else (singleton <$> go) ) <|> (P.fail $ "Expected " ++ D.prettyPrintArg x ++ butGot i) - where go = do - Tuple x <$> (command n) - <* modifyDepth (_ + 1) + where go = do Tuple x <<< fromArgv <$> (do + v <- command n + return if r then ArrayValue $ Value.intoArray v + else v + ) + <* modifyDepth (_ + 1) -- Generate a parser for a `EOA` argument genParser x@(D.EOA) _ = do - score 0 <<< singleton <<< Tuple x <$> (do - eoa <|> (return $ D.ArrayValue []) -- XXX: Fix type + singleton <<< Tuple x <<< fromArgv <$> (do + eoa <|> (return $ ArrayValue []) -- XXX: Fix type <* modifyDepth (_ + 1) ) <|> P.fail "Expected \"--\"" -- Generate a parser for a `Stdin` argument genParser x@(D.Stdin) _ = do - score 0 <<< singleton <<< Tuple x <$> (do + singleton <<< Tuple x <<< fromArgv <$> (do stdin <* modifyDepth (_ + 1) ) <|> P.fail "Expected \"-\"" @@ -629,12 +652,15 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do -- Generate a parser for a `Positional` argument genParser x@(D.Positional n r) _ = do i <- getInput - score 0 <$> (do + (do if r then (some go) else (singleton <$> go) ) <|> P.fail ("Expected " ++ D.prettyPrintArg x ++ butGot i) - where go = do - Tuple x <$> (positional n) - <* modifyDepth (_ + 1) + where go = do Tuple x <<< fromArgv <$> (do + v <- positional n + return if r then ArrayValue $ Value.intoArray v + else v + ) + <* modifyDepth (_ + 1) genParser x@(D.Group optional bs r) _ | optsFirst && (length bs == 1) && @@ -647,7 +673,7 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do -- Generate a parser for a `Option` argument genParser x@(D.Option (O.Option o)) _ = (do - score 0 <$> do + do if o.repeatable then (some go) else (singleton <$> go) <* modifyDepth (_ + 1) ) @@ -662,9 +688,19 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do isSopt <- P.option false (P.lookAhead $ P.try $ token isAnySopt) P.ParserT \s -> do o <- P.unParserT (if isLopt - then P.try $ Tuple x <$> mkLoptParser o.name o.arg + then P.try do + Tuple x <<< fromArgv <$> (do + v <- mkLoptParser o.name o.arg + return if o.repeatable then ArrayValue $ Value.intoArray v + else v + ) else if isSopt - then P.try $ Tuple x <$> mkSoptParser o.flag o.arg + then P.try do + Tuple x <<< fromArgv <$> (do + v <- mkSoptParser o.flag o.arg + return if o.repeatable then ArrayValue $ Value.intoArray v + else v + ) else P.fail "long or short option") s case o.result of (Left e) -> do @@ -699,13 +735,13 @@ genBranchParser (D.Branch xs) optsFirst canSkip = do go | length bs == 0 = return mempty go = do x <- step - if repeated && length (_.result $ unScoredResult x) > 0 + if repeated && length x > 0 then do xs <- step <|> return mempty return $ x ++ xs else return x - step = rmapScoreResult snd <$> do + step = snd <$> do genBranchesParser bs false optsFirst diff --git a/src/Language/Docopt/Trans/Flat.purs b/src/Language/Docopt/Trans/Flat.purs index 4bd1c0d1..79770e83 100644 --- a/src/Language/Docopt/Trans/Flat.purs +++ b/src/Language/Docopt/Trans/Flat.purs @@ -9,8 +9,7 @@ import Language.Docopt.Env (Env) import Language.Docopt.Value (Value()) import Language.Docopt.Usage (Usage()) as D import Language.Docopt.Trans.Rich (reduce) as Rich -import Language.Docopt.Trans.Rich (unRichValue) -import Language.Docopt.ParserGen.ValueMapping (ValueMapping) +import Language.Docopt.ParserGen (ValueMapping, unRichValue) import Language.Docopt.Argument (Branch()) as D reduce :: List D.Usage -- ^ the program specification diff --git a/src/Language/Docopt/Trans/Rich.purs b/src/Language/Docopt/Trans/Rich.purs index 6f7c290e..42369dc9 100644 --- a/src/Language/Docopt/Trans/Rich.purs +++ b/src/Language/Docopt/Trans/Rich.purs @@ -1,13 +1,12 @@ module Language.Docopt.Trans.Rich ( reduce - , RichValue - , RichValueObj - , unRichValue ) where import Prelude -import Data.List (List(), catMaybes, singleton, concat, toList) +import Debug.Trace +import Data.List (List(..), catMaybes, singleton, concat, toList, filter, + reverse, null, nub) import Data.Array (length, filter) as A import Data.Maybe (Maybe(..), fromMaybe) import Data.Maybe.Unsafe (fromJust) @@ -16,7 +15,7 @@ import Data.Tuple (Tuple(Tuple)) import Data.Map (Map()) import Data.StrMap as StrMap import Data.StrMap (StrMap()) -import Data.Bifunctor (lmap) +import Data.Bifunctor (rmap, lmap) import Data.Foldable (foldl, maximum) import Control.Alt ((<|>)) @@ -29,43 +28,25 @@ import Language.Docopt.Env as Env import Language.Docopt.Argument (Argument(..), Branch(..), isRepeatable, setRepeatable, runBranch, setRepeatableOr, isCommand, isFlag) as D -import Language.Docopt.ParserGen.ValueMapping (ValueMapping) -import Language.Docopt.Trans.Origin as Origin -import Language.Docopt.Trans.Origin (Origin()) +import Language.Docopt.ParserGen (ValueMapping, RichValue(..), unRichValue) +import Language.Docopt.Origin as Origin +import Language.Docopt.Origin (Origin()) import Language.Docopt.Trans.Key (Key(..), key, toKeys) -type RichValueObj = { - value :: Value -, origin :: Origin -} - -newtype RichValue = RichValue RichValueObj - -unRichValue :: RichValue -> RichValueObj -unRichValue (RichValue o) = o - reduce :: List D.Usage -- ^ the program specification -> Env -- ^ the environment -> D.Branch -- ^ the matched specification -> List ValueMapping -- ^ the parse result -> StrMap RichValue -- ^ the output set of (arg => val) reduce us env b vs = - let vm = Map.fromFoldableWith mergeVals $ fromArgv vs - m = applyValues vm $ reduceUsage (D.Usage (singleton b)) - in finalFold m + let vm = Map.fromFoldableWith (++) (rmap singleton <$> + lmap key <$> + reverse vs) + m = applyValues vm $ reduceUsage (D.Usage (singleton b)) + in finalFold m where - fromArgv :: List ValueMapping -> List (Tuple Key RichValue) - fromArgv vs = lmap key <$> (go <$> vs) - where - go (Tuple a v) = Tuple a $ RichValue { - origin: Origin.Argv - , value: if D.isRepeatable a - then ArrayValue (Value.intoArray v) - else v - } - mergeVals :: RichValue -> RichValue -> RichValue mergeVals (RichValue v) (RichValue v') = RichValue $ { origin: fromJust $ maximum [ v.origin, v'.origin ] @@ -73,60 +54,28 @@ reduce us env b vs = ++ Value.intoArray v.value } - applyValues :: Map Key RichValue -> List D.Argument -> Map Key RichValue + applyValues :: Map Key (List RichValue) -> List D.Argument -> Map Key RichValue applyValues vm as = Map.fromFoldableWith mergeVals + $ concat $ catMaybes - $ as <#> \a -> Tuple (key a) <$> do - (RichValue v) <- getValue vm a - <|> getEnvValue a - <|> getDefaultValue a - <|> getEmptyValue a - - return $ RichValue v { - value = if D.isRepeatable a - then ArrayValue $ Value.intoArray v.value - else v.value - } - where + $ as <#> \a -> do + vs <- Map.lookup (key a) vm + let vs' = filter (origin (/=) Origin.Empty) vs + vs'' = filter (origin (/=) Origin.Default) vs' + vs''' = case vs'' of + Nil -> nub vs' + vs -> vs + vs'''' = vs''' <#> \(RichValue v) -> RichValue $ v { + value = if D.isRepeatable a + then ArrayValue $ Value.intoArray v.value + else v.value + } + + return $ (Tuple (key a)) <$> vs'''' - getValue :: Map Key RichValue -> D.Argument -> Maybe RichValue - getValue vm a = Map.lookup (key a) vm - - getEnvValue :: D.Argument -> Maybe RichValue - getEnvValue (D.Option (O.Option o@{ env: Just k })) = do - s <- Env.lookup k env - return $ RichValue { - origin: Origin.Environment - , value: StringValue s - } - getEnvValue _ = Nothing - - getDefaultValue :: D.Argument -> Maybe RichValue - getDefaultValue (D.Option (O.Option o@{ - arg: Just (O.Argument { default: Just v }) - })) = return - $ RichValue { - origin: Origin.Default - , value: if (o.repeatable) - then ArrayValue $ Value.intoArray v - else v - } - getDefaultValue _ = Nothing - - getEmptyValue :: D.Argument -> Maybe RichValue - getEmptyValue x = RichValue <<< { origin: Origin.Empty - , value: _ - } <$> go x - where - go (D.Option (O.Option o@{ arg: Nothing })) - = return - $ if o.repeatable then ArrayValue [] - else BoolValue false - go (D.Positional _ r) | r = return $ ArrayValue [] - go (D.Command _ r) | r = return $ ArrayValue [] - go (D.Stdin) = return $ BoolValue false - go (D.EOA) = return $ ArrayValue [] - go _ = Nothing + where + isRelevant a = Map.member (key a) vm + origin cmp o = \x -> (_.origin $ unRichValue x) `cmp` o finalFold :: Map Key RichValue -> StrMap RichValue finalFold m = @@ -146,7 +95,7 @@ reduce us env b vs = BoolValue b -> if D.isRepeatable a then - return $ if b + return if b then IntValue 1 else IntValue 0 else Nothing diff --git a/test/Test/Spec/ParserGenSpec.purs b/test/Test/Spec/ParserGenSpec.purs index c172fa8b..4e2d7f8b 100644 --- a/test/Test/Spec/ParserGenSpec.purs +++ b/test/Test/Spec/ParserGenSpec.purs @@ -76,6 +76,8 @@ fail' i e err = Case i e (Left err) infixr 0 :> parserGenSpec = \_ -> describe "The parser generator" do + it "" do + pure unit -- Some options that will be used for these tests let testCases = [ @@ -163,7 +165,7 @@ parserGenSpec = \_ -> describe "The parser generator" do , D.opt 'o' "output" (D.oa_ "FILE") ] [ fail [] - $ "Expected option(s): -o|--output=FILE, -i|--input=FILE" + $ "Expected option(s): -i|--input=FILE, -o|--output=FILE" , fail [ "-i", "bar" ] $ "Expected option(s): -o|--output=FILE" @@ -192,7 +194,7 @@ parserGenSpec = \_ -> describe "The parser generator" do , D.opt 'o' "output" (D.oa_ "FILE") ] [ fail [] - $ "Expected option(s): -o|--output=FILE, -i|--input=FILE -r|--redirect=FILE" + $ "Expected option(s): -i|--input=FILE -r|--redirect=FILE, -o|--output=FILE" , fail [ "-i", "bar", "-r", "bar" ] "Expected option(s): -o|--output=FILE" @@ -275,7 +277,7 @@ parserGenSpec = \_ -> describe "The parser generator" do [ "-b" :> D.bool true ] , pass [] - [ "-a" :> D.bool false ] + [] ] , test' @@ -364,8 +366,6 @@ parserGenSpec = \_ -> describe "The parser generator" do , "--input" :> D.bool true , "-i" :> D.bool true , "baz" :> D.bool true - , "--qux" :> D.int 0 - , "-q" :> D.int 0 ] , pass @@ -380,8 +380,6 @@ parserGenSpec = \_ -> describe "The parser generator" do , "baz" :> D.bool true , "--baz" :> D.str "ax" , "-b" :> D.str "ax" - , "--qux" :> D.int 0 - , "-q" :> D.int 0 , "--foo" :> D.array [ D.str "ox" ] , "-f" :> D.array [ D.str "ox" ] ] diff --git a/testcases.docopt b/testcases.docopt index 9745defa..5b8ab4a2 100644 --- a/testcases.docopt +++ b/testcases.docopt @@ -15,7 +15,7 @@ Options: -a All. """ $ prog -{"-a": false} +{} $ prog -a {"-a": true} @@ -30,7 +30,7 @@ Options: --all All. """ $ prog -{"--all": false} +{} $ prog --all {"--all": true} @@ -158,12 +158,10 @@ Options: --version """ $ prog --version -{"--verbose": false, - "--version": true} +{"--version": true} $ prog --verbose -{"--verbose": true, - "--version": false} +{"--verbose": true} r"""usage: prog [-a -r -m ] @@ -213,19 +211,13 @@ $ prog -arm=Hello "-r": true} $ prog -m=Hello -{"-a": false, - "-m": "Hello", - "-r": false} +{"-m": "Hello"} $ prog -m Hello -{"-a": false, - "-m": "Hello", - "-r": false} +{"-m": "Hello"} $ prog -mHello -{"-a": false, - "-m": "Hello", - "-r": false} +{"-m": "Hello"} $ prog -m "user-error" @@ -263,19 +255,13 @@ $ prog -arm=Hello "-r": true} $ prog -m=Hello -{"-a": false, - "-m": "Hello", - "-r": false} +{"-m": "Hello"} $ prog -m Hello -{"-a": false, - "-m": "Hello", - "-r": false} +{"-m": "Hello"} $ prog -mHello -{"-a": false, - "-m": "Hello", - "-r": false} +{"-m": "Hello"} $ prog -m "user-error" @@ -300,10 +286,10 @@ $ prog -ba {"-a": true, "-b": true} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -{"-a": false, "-b": false} +{} r"""usage: prog (-a -b) @@ -325,10 +311,10 @@ $ prog -ba {"-a": true, "-b": true} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -{"-a": false, "-b": false} +{} r"""usage: prog [-a] -b @@ -344,13 +330,13 @@ $ prog -b -a {"-a": true, "-b": true} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -b -{"-a": false, "-b": true} +{"-b": true} $ prog -{"-a": false, "-b": false} +{} r"""usage: prog [(-a -b)] @@ -372,13 +358,13 @@ $ prog -ba {"-a": true, "-b": true} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -b -{"-a": false, "-b": true} +{"-b": true} $ prog -{"-a": false, "-b": false} +{} # @@ -390,13 +376,13 @@ $ prog -a -b "user-error" $ prog -{"-a": false, "-b": false} +{} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -b -{"-a": false, "-b": true} +{"-b": true} r"""usage: prog [ -a | -b ] @@ -409,13 +395,13 @@ $ prog -a -b "user-error" $ prog -{"-a": false, "-b": false} +{} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -b -{"-a": false, "-b": true} +{"-b": true} r"""usage: prog """ @@ -490,7 +476,7 @@ $ prog 10 --all "KIND": 10} $ prog 10 -{"--all": false, "": 10, "KIND": 10} +{"": 10, "KIND": 10} $ prog "user-error" @@ -511,7 +497,7 @@ $ prog 10 "user-error" $ prog -{"": [], "NAME": []} +{} # Added test case: # (See note above) @@ -523,7 +509,7 @@ $ prog 10 {"": [10], "NAME": [10]} $ prog -{"": [], "NAME": []} +{} r"""usage: prog [( )]""" @@ -534,7 +520,7 @@ $ prog 10 "user-error" $ prog -{"": [], "NAME": []} +{} r"""usage: prog NAME...""" $ prog 10 20 @@ -556,7 +542,7 @@ $ prog 10 {"": [10], "NAME": [10]} $ prog -{"": [], "NAME": []} +{} r"""usage: prog [NAME...]""" @@ -567,7 +553,7 @@ $ prog 10 {"": [10], "NAME": [10]} $ prog -{"": [], "NAME": []} +{} r"""usage: prog [NAME [NAME ...]]""" @@ -578,7 +564,7 @@ $ prog 10 {"": [10], "NAME": [10]} $ prog -{"": [], "NAME": []} +{} r"""usage: prog (NAME | --foo NAME) @@ -587,7 +573,7 @@ options: --foo """ $ prog 10 -{"--foo": false, "": 10, "NAME": 10} +{"": 10, "NAME": 10} $ prog --foo 10 {"--foo": true, "": 10, "NAME": 10} @@ -602,13 +588,13 @@ options: --bar """ $ prog 10 -{"--bar": false, "--foo": false, "": [10], "NAME": [10]} +{"": [10], "NAME": [10]} $ prog 10 20 -{"--bar": false, "--foo": false, "": [10, 20], "NAME": [10, 20]} +{"": [10, 20], "NAME": [10, 20]} $ prog --foo --bar -{"--bar": true, "--foo": true, "": [], "NAME": []} +{"--bar": true, "--foo": true} r"""Naval Fate. @@ -630,30 +616,6 @@ Options: """ -# XXX: We are deviating here. See `Language.Docopt.Trans.reduce` for a full -# explanation of the issue. In brief, this implementation, collects values -# only for arguments of the branch that was actually matched. -# Secondly, `` does not evaluate to an array. I don't see why it -# would in the original implementation either? What's the rationale here? -# -# Original: -# -# {"--drifting": false, -# "--help": false, -# "--moored": false, -# "--speed": 20, -# "--version": false, -# "": ["Guardian"], -# "": 150, -# "": 300, -# "mine": false, -# "move": true, -# "new": false, -# "remove": false, -# "set": false, -# "ship": true, -# "shoot": false} - $ prog ship Guardian move 150 300 --speed=20 {"--speed": 20, "": "Guardian", @@ -681,7 +643,7 @@ $ prog --hello wrld r"""usage: prog [-o]""" $ prog -{"-o": false} +{} $ prog -o {"-o": true} @@ -689,7 +651,7 @@ $ prog -o r"""usage: prog [-opr]""" $ prog -op -{"-o": true, "-p": true, "-r": false} +{"-o": true, "-p": true} r"""usage: prog --aabb | --aa""" @@ -715,7 +677,7 @@ $ prog -v r"""Usage: prog [-v -v]""" $ prog -{"-v": 0} +{} $ prog -v {"-v": 1} @@ -728,7 +690,7 @@ r"""Usage: prog -v ...""" # Note: Deviation: We allow not passing flags. # Original "user-error" $ prog -{"-v": 0} +{} $ prog -v {"-v": 1} @@ -746,7 +708,7 @@ This one is probably most readable user-friednly variant. """ $ prog -{"-v": 0} +{} $ prog -v {"-v": 1} @@ -776,7 +738,7 @@ $ prog go # r"""usage: prog [go go]""" r"""usage: prog [go [go]]""" $ prog -{"go": 0} +{} $ prog go {"go": 1} @@ -802,7 +764,7 @@ options: -a """ $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -aa "user-error" @@ -819,13 +781,13 @@ Options: """ $ prog arg -{"-q": false, "-v": false, "": "arg", "A": "arg"} +{"": "arg", "A": "arg"} $ prog -v arg -{"-q": false, "-v": true, "": "arg", "A": "arg"} +{"-v": true, "": "arg", "A": "arg"} $ prog -q arg -{"-q": true, "-v": false, "": "arg", "A": "arg"} +{"-q": true, "": "arg", "A": "arg"} # # Test single dash @@ -837,7 +799,7 @@ $ prog - {"-": true} $ prog -{"-": false} +{} # # If argument is repeated, its value should always be a list @@ -849,7 +811,7 @@ $ prog a b {"": ["a", "b"], "NAME": ["a", "b"]} $ prog -{"": [], "NAME": []} +{} # # Option's argument defaults to null/None @@ -885,22 +847,22 @@ $ prog --hello wrld r"""usage: prog [-o]""" $ prog -{"-o": false} +{} $ prog -o {"-o": true} r"""usage: prog [-opr]""" $ prog -op -{"-o": true, "-p": true, "-r": false} +{"-o": true, "-p": true} r"""usage: git [-v | --verbose]""" $ prog -v -{"--verbose": false, "-v": true} +{"-v": true} r"""usage: git remote [-v | --verbose]""" $ prog remote -v -{"--verbose": false, "-v": true, "remote": true} +{"-v": true, "remote": true} # # Test empty usage pattern @@ -1007,8 +969,8 @@ r"""usage: prog [-o ]... options: -o [default: x y] """ -$ prog -o this -{"-o": ["this"]} +# $ prog -o this +# {"-o": ["this"]} $ prog {"-o": ["x", "y"]} @@ -1142,11 +1104,8 @@ other options: """ $ prog --baz --egg -{"--bar": false, - "--baz": true, - "--egg": true, - "--foo": false, - "--spam": false} +{"--baz": true, + "--egg": true} # # README example usage @@ -1368,13 +1327,11 @@ Options: -0 This is a zero """ -# XXX: this returns superflous "-0" in the output: $ prog/p foo bar -0 -{"-0": false, "ARG": ["foo", "bar", "-0"], "": ["foo", "bar", "-0"]} +{"ARG": ["foo", "bar", "-0"], "": ["foo", "bar", "-0"]} -# XXX: this returns superflous "-0" in the output: $ prog/p -0 -{"-0": false, "ARG": ["-0"], "": ["-0"]} +{"ARG": ["-0"], "": ["-0"]} r""" Usage: create_ec2.py [ARG...] [options] @@ -1383,13 +1340,11 @@ Options: -0 This is a zero """ -# XXX: this returns superflous "-0" in the output: $ prog/p foo bar -0 -{"-0": false, "ARG": ["foo", "bar", "-0"], "": ["foo", "bar", "-0"]} +{"ARG": ["foo", "bar", "-0"], "": ["foo", "bar", "-0"]} -# XXX: this returns superflous "-0" in the output: $ prog/p -0 -{"-0": false, "ARG": ["-0"], "": ["-0"]} +{"ARG": ["-0"], "": ["-0"]} r""" @@ -1405,7 +1360,7 @@ $ prog -0 # XXX: this returns superflous "-0" and "-1" in the output: $ prog/p -01 -{"-0": false, "-1": false, "ARG": ["-01"], "": ["-01"]} +{"ARG": ["-01"], "": ["-01"]} # # Options-first fix for issue #21 @@ -1416,7 +1371,7 @@ Usage: foo [[...]] """ $ prog/p -{"ARGS": [], "": []} +{} $ prog/p bar -a -b -c {"ARGS": ["bar", "-a", "-b", "-c"], @@ -1427,7 +1382,7 @@ Usage: foo [ [...]] """ $ prog/p -{"ARGS": [], "": []} +{} $ prog/p bar -a -b -c {"COMMAND": "bar", "": "bar", @@ -1525,6 +1480,26 @@ $ prog $ prog --foo {"--foo": [true]} +r""" +Usage: manage.py --foo (--foo[=BAR]...) (--foo[=BAR]...) (--foo[=BAR]...) +""" + +$ prog +{} + +$ prog --foo +{"--foo": [true]} + +r""" +Usage: manage.py --foo (--foo[=BAR]... (--foo[=BAR]...)) (--foo[=BAR]...) +""" + +$ prog +{} + +$ prog --foo +{"--foo": [true]} + $ prog --foo bar "user-error" # "bar" will be considered trailing since the first appearance # of '--foo' does not take an argument. XXX: Is this intutive @@ -1596,41 +1571,41 @@ $ prog -c -a -b r"""usage: prog -a [-b -d] -c""" -$ prog -a -b -c -{"-a": true, "-b": true, "-c": true, "-d": false} - -$ prog -abc -{"-a": true, "-b": true, "-c": true, "-d": false} - -$ prog -a -c -b -{"-a": true, "-b": true, "-c": true, "-d": false} - -$ prog -acb -{"-a": true, "-b": true, "-c": true, "-d": false} +# $ prog -a -b -c +# {"-a": true, "-b": true, "-c": true} +# +# $ prog -abc +# {"-a": true, "-b": true, "-c": true} +# +# $ prog -a -c -b +# {"-a": true, "-b": true, "-c": true} +# +# $ prog -acb +# {"-a": true, "-b": true, "-c": true} $ prog -b -a -c -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -bac -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -b -c -a -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -bca -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -c -b -a -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -cba -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -c -a -b -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -cab -{"-a": true, "-b": true, "-c": true, "-d": false} +{"-a": true, "-b": true, "-c": true} $ prog -d -a -b -c "user-error" # -b and -d must appear together @@ -1660,13 +1635,13 @@ $ prog -ba {"-a": true, "-b": true} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -b -{"-a": false, "-b": true} +{"-b": true} $ prog -{"-a": false, "-b": false} +{} r"""usage: prog [(-a[=BAR] -b)]""" @@ -1677,28 +1652,28 @@ $ prog -b -a FOO {"-a": "FOO", "-b": true} $ prog -ab -{"-a": "b", "-b": false} +{"-a": "b"} $ prog -ba {"-a": true, "-b": true} $ prog -a -{"-a": true, "-b": false} +{"-a": true} $ prog -b {"-b": true} $ prog -{"-b": false} +{} r"""usage: prog -a (--foo|--no-foo) -b""" -$ prog -b -a --foo -{"-a": true, "-b": true, "--foo": true, "--no-foo": false} +# $ prog -b -a --foo +# {"-a": true, "-b": true, "--foo": true} $ prog -b --no-foo -a -{"-a": true, "-b": true, "--foo": false, "--no-foo": true} +{"-a": true, "-b": true, "--no-foo": true} $ prog -b --no-foo -a --foo "user-error" @@ -1860,7 +1835,7 @@ Usage: """ $ prog a b c -{"Y": ["a", "b", "c"], "": ["a", "b", "c"], "X": [], "": []} +{"Y": ["a", "b", "c"], "": ["a", "b", "c"]} r""" Usage: @@ -1868,7 +1843,7 @@ Usage: """ $ prog a b c -{"X": ["a", "b", "c"], "": ["a", "b", "c"], "Y": [], "": []} +{"X": ["a", "b", "c"], "": ["a", "b", "c"]} r""" Usage: @@ -1913,7 +1888,7 @@ Options: -v, --verbose """ $ prog -{"-v": false, "--verbose": false} +{} $ prog -v {"-v": true, "--verbose": true} @@ -1927,7 +1902,7 @@ Options: -v, --verbose... """ $ prog -{"-v": 0, "--verbose": 0} +{} $ prog -v {"-v": 1, "--verbose": 1} @@ -1941,7 +1916,7 @@ Options: -v..., --verbose """ $ prog -{"-v": 0, "--verbose": 0} +{} $ prog -v {"-v": 1, "--verbose": 1} @@ -1955,7 +1930,7 @@ Options: -v..., --verbose... """ $ prog -{"-v": 0, "--verbose": 0} +{} $ prog -v {"-v": 1, "--verbose": 1} @@ -1973,7 +1948,7 @@ Options: -h """ $ prog -{"-h": false} +{} $ prog -h {"-h": true} @@ -1987,7 +1962,7 @@ Options: -h """ $ prog -{"-h": 0} +{} $ prog -h {"-h": 1} @@ -2001,7 +1976,7 @@ Options: --input """ $ prog -{"--input": false} +{} $ prog --input {"--input": true} @@ -2015,7 +1990,7 @@ Options: -i, --input """ $ prog -{"-i": false, "--input": false} +{} $ prog -i {"-i": true, "--input": true} @@ -2125,10 +2100,10 @@ Usage: foo [-oi ARG] """ $ prog/s -{"-o": false} # XXX: Awkward +{} $ prog/s -i 123 -{"-i": 123, "-o": false} +{"-i": 123} $ prog/s -i "user-error" @@ -2138,13 +2113,13 @@ Usage: foo [-oi [ARG]] """ $ prog/s -{"-o": false} # XXX: Awkward +{} $ prog/s -i 123 -{"-i": 123, "-o": false} +{"-i": 123} $ prog/s -i -{"-i": true, "-o": false} +{"-i": true} r""" Usage: foo (-oi ARG) @@ -2154,7 +2129,7 @@ $ prog/s "user-error" $ prog/s -i 123 -{"-i": 123, "-o": false} +{"-i": 123} $ prog/s -i "user-error" @@ -2164,13 +2139,13 @@ Usage: foo [-oi ARG]... """ $ prog/s -{"-o": 0} # XXX: Awkward +{} $ prog/s -i 123 -{"-i": [123], "-o": 0} +{"-i": [123]} $ prog/s -i 123 -i 123 -{"-i": [123, 123], "-o": 0} +{"-i": [123, 123]} $ prog/s -i "user-error" @@ -2180,13 +2155,13 @@ Usage: foo [-oi ARG...] """ $ prog/s -{"-o": 0} # XXX: Awkward +{} $ prog/s -i 123 -{"-i": [123], "-o": 0} +{"-i": [123]} $ prog/s -i 123 -i 123 -{"-i": [123, 123], "-o": 0} +{"-i": [123, 123]} $ prog/s -i "user-error" @@ -2196,10 +2171,10 @@ Usage: foo [-oi ARG...]... """ $ prog/s -{"-o": 0} # XXX: Awkward +{} $ prog/s -i 123 -{"-i": [123], "-o": 0} +{"-i": [123]} $ prog/s -i 123 -o {"-i": [123], "-o": 1} @@ -2208,7 +2183,7 @@ $ prog/s -i 123 -o 456 "user-error" $ prog/s -i 123 -i 123 -{"-i": [123, 123], "-o": 0} +{"-i": [123, 123]} $ prog/s -i "user-error" @@ -2329,7 +2304,7 @@ Usage: foo [-] """ $ prog -{"-": false} +{} $ prog - {"-": true} @@ -2465,7 +2440,7 @@ Options: -f, --foo ARG """ $ prog -{"-b": false} +{} r""" usage: git (([-b | -f[=ARG]])) @@ -2473,7 +2448,7 @@ Options: -f, --foo ARG """ $ prog -f -{"-f": true, "--foo": true, "-b": false} +{"-f": true, "--foo": true} # # Given an option-argument to a flag is a fatal error, no pardon: