Allow --flag=value syntax in addition to --flag value
parent
6a45f4b3a6
commit
12b886ad31
|
@ -94,6 +94,15 @@ simpleRunTest = do
|
||||||
, ""
|
, ""
|
||||||
, " test a aa | ab"
|
, " test a aa | ab"
|
||||||
]
|
]
|
||||||
|
describe "read flags" $ do
|
||||||
|
it "flag 1" $ testRun testCmd5 "abc" `shouldBe` Right (Just 10)
|
||||||
|
it "flag 2" $ testRun testCmd5 "abc -f 2" `shouldBe` Right (Just 2)
|
||||||
|
it "flag 3" $ testRun testCmd5 "abc --flag 3" `shouldBe` Right (Just 3)
|
||||||
|
it "flag 4" $ testRun testCmd5 "abc -f=4" `shouldBe` Right (Just 4)
|
||||||
|
it "flag 5" $ testRun testCmd5 "abc --flag=5" `shouldBe` Right (Just 5)
|
||||||
|
it "flag 6" $ testRun testCmd5 "abc -f" `shouldSatisfy` Data.Either.isLeft
|
||||||
|
it "flag 6" $ testRun testCmd5 "abc -flag 0" `shouldSatisfy` Data.Either.isLeft
|
||||||
|
it "flag 6" $ testRun testCmd5 "abc --f 0" `shouldSatisfy` Data.Either.isLeft
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -152,6 +161,13 @@ testCmd4 = do
|
||||||
addCmd "ba" $ do
|
addCmd "ba" $ do
|
||||||
addCmdImpl $ WriterS.tell 3
|
addCmdImpl $ WriterS.tell 3
|
||||||
|
|
||||||
|
testCmd5 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()
|
||||||
|
testCmd5 = do
|
||||||
|
addCmd "abc" $ do
|
||||||
|
x <- addFlagReadParam "f" ["flag"] "flag" (flagDefault (10::Int))
|
||||||
|
addCmdImpl $ WriterS.tell (Sum x)
|
||||||
|
|
||||||
|
|
||||||
testParse :: CmdParser Identity out () -> String -> Maybe (CommandDesc out)
|
testParse :: CmdParser Identity out () -> String -> Maybe (CommandDesc out)
|
||||||
testParse cmd s = either (const Nothing) Just
|
testParse cmd s = either (const Nothing) Just
|
||||||
$ snd
|
$ snd
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
|
||||||
-- | Flags are arguments to your current command that are prefixed with "-" or
|
-- | Flags are arguments to your current command that are prefixed with "-" or
|
||||||
-- "--", for example "-v" or "--verbose". These flags can have zero or one
|
-- "--", for example "-v" or "--verbose". These flags can have zero or one
|
||||||
|
@ -43,6 +44,32 @@ import Data.List.Extra ( firstJust )
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO: perhaps move this to Types module and refactor all code to use it
|
||||||
|
newtype InpParseString a = InpParseString (StateS.StateT String Maybe a)
|
||||||
|
deriving (Functor, Applicative, Monad, State.Class.MonadState String, Alternative, MonadPlus)
|
||||||
|
|
||||||
|
runInpParseString :: String -> InpParseString a -> Maybe (a, String)
|
||||||
|
runInpParseString s (InpParseString m) = StateS.runStateT m s
|
||||||
|
|
||||||
|
pExpect :: String -> InpParseString ()
|
||||||
|
pExpect s = InpParseString $ do
|
||||||
|
inp <- StateS.get
|
||||||
|
case List.stripPrefix s inp of
|
||||||
|
Nothing -> mzero
|
||||||
|
Just rest -> StateS.put rest
|
||||||
|
|
||||||
|
pExpectEof :: InpParseString ()
|
||||||
|
pExpectEof =
|
||||||
|
InpParseString $ StateS.get >>= \inp -> if null inp then pure () else mzero
|
||||||
|
|
||||||
|
-- pDropSpace :: InpParseString ()
|
||||||
|
-- pDropSpace = InpParseString $ StateS.modify (dropWhile (==' '))
|
||||||
|
|
||||||
|
pOption :: InpParseString () -> InpParseString ()
|
||||||
|
pOption m = m <|> return ()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- | flag-description monoid. You probably won't need to use the constructor;
|
-- | flag-description monoid. You probably won't need to use the constructor;
|
||||||
-- mzero or any (<>) of flag(Help|Default) works well.
|
-- mzero or any (<>) of flag(Help|Default) works well.
|
||||||
data Flag p = Flag
|
data Flag p = Flag
|
||||||
|
@ -142,30 +169,49 @@ addFlagReadParam
|
||||||
-> String -- ^ param name
|
-> String -- ^ param name
|
||||||
-> Flag p -- ^ properties
|
-> Flag p -- ^ properties
|
||||||
-> CmdParser f out p
|
-> CmdParser f out p
|
||||||
addFlagReadParam shorts longs name flag = addCmdPartInpA desc parseF (\_ -> pure ())
|
addFlagReadParam shorts longs name flag = addCmdPartInpA desc
|
||||||
where
|
parseF
|
||||||
allStrs = fmap (\c -> "-"++[c]) shorts
|
(\_ -> pure ())
|
||||||
++ fmap (\s -> "--"++s) longs
|
where
|
||||||
desc = (maybe id PartWithHelp $ _flag_help flag)
|
allStrs =
|
||||||
$ maybe id (PartDefault . show) (_flag_default flag)
|
[ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]
|
||||||
$ PartSeq [desc1, desc2]
|
desc =
|
||||||
desc1 :: PartDesc
|
(maybe id PartWithHelp $ _flag_help flag)
|
||||||
desc1 = PartAlts $ PartLiteral <$> allStrs
|
$ maybe id (PartDefault . show) (_flag_default flag)
|
||||||
desc2 = PartVariable name
|
$ PartSeq [desc1, desc2]
|
||||||
parseF :: Input -> Maybe (p, Input)
|
desc1 :: PartDesc
|
||||||
parseF inp = case inp of
|
desc1 = PartAlts $ PartLiteral . either id id <$> allStrs
|
||||||
InputString str -> case flip firstJust allStrs $ \s ->
|
desc2 = PartVariable name
|
||||||
[drop (length s + 1) str | (s ++ " ") `isPrefixOf` str]
|
parseF :: Input -> Maybe (p, Input)
|
||||||
of
|
parseF inp = case inp of
|
||||||
Nothing -> _flag_default flag <&> \x -> (x, InputString str)
|
InputString str ->
|
||||||
Just rest -> case Text.Read.reads rest of
|
maybe (_flag_default flag <&> \x -> (x, inp)) (Just . second InputString)
|
||||||
((x, ' ':r):_) -> Just (x, InputString $ dropWhile Char.isSpace r)
|
$ parseResult
|
||||||
((x, ""):_) -> Just (x, InputString $ "")
|
where
|
||||||
_ -> Nothing
|
parseResult = runInpParseString str $ do
|
||||||
InputArgs (arg1:argR) | any (==arg1) allStrs -> case argR of
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
[] -> Nothing
|
Left s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")
|
||||||
|
Right s -> pExpect s *> (pExpect " " <|> pExpect "=")
|
||||||
|
InpParseString $ do
|
||||||
|
i <- StateS.get
|
||||||
|
case Text.Read.reads i of
|
||||||
|
((x, ' ':r):_) -> StateS.put (dropWhile Char.isSpace r) $> x
|
||||||
|
((x, "" ):_) -> StateS.put "" $> x
|
||||||
|
_ -> mzero
|
||||||
|
InputArgs (arg1:argR) -> case runInpParseString arg1 parser of
|
||||||
|
Just ((), "") -> case argR of
|
||||||
|
[] -> Nothing
|
||||||
(arg2:rest) -> readMaybe arg2 <&> \x -> (x, InputArgs rest)
|
(arg2:rest) -> readMaybe arg2 <&> \x -> (x, InputArgs rest)
|
||||||
InputArgs _ -> _flag_default flag <&> \d -> (d, inp)
|
Just ((), remainingStr) ->
|
||||||
|
readMaybe remainingStr <&> \x -> (x, InputArgs argR)
|
||||||
|
Nothing -> _flag_default flag <&> \d -> (d, inp)
|
||||||
|
where
|
||||||
|
parser :: InpParseString ()
|
||||||
|
parser = do
|
||||||
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
|
Left s -> pExpect s *> pOption (pExpect "=")
|
||||||
|
Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)
|
||||||
|
InputArgs _ -> _flag_default flag <&> \d -> (d, inp)
|
||||||
|
|
||||||
-- | One-argument flag, where the argument is parsed via its Read instance.
|
-- | One-argument flag, where the argument is parsed via its Read instance.
|
||||||
-- This version can accumulate multiple values by using the same flag with
|
-- This version can accumulate multiple values by using the same flag with
|
||||||
|
@ -199,71 +245,100 @@ addFlagReadParams shorts longs name flag
|
||||||
-- = void $ addFlagReadParamsAll shorts longs name flag act
|
-- = void $ addFlagReadParamsAll shorts longs name flag act
|
||||||
|
|
||||||
addFlagReadParamsAll
|
addFlagReadParamsAll
|
||||||
:: forall f p out
|
:: forall f p out . (Typeable p, Text.Read.Read p, Show p) => String -- ^ short flag chars, i.e. "v" for -v
|
||||||
. (Typeable p, Text.Read.Read p, Show p)
|
-> [String] -- ^ list of long names, i.e. ["verbose"]
|
||||||
=> String -- ^ short flag chars, i.e. "v" for -v
|
-> String -- ^ param name
|
||||||
-> [String] -- ^ list of long names, i.e. ["verbose"]
|
-> Flag p -- ^ properties
|
||||||
-> String -- ^ param name
|
-> (p -> f ()) -- ^ action to execute when ths param matches
|
||||||
-> Flag p -- ^ properties
|
-> CmdParser f out [p]
|
||||||
-> (p -> f ()) -- ^ action to execute when ths param matches
|
addFlagReadParamsAll shorts longs name flag act = addCmdPartManyInpA
|
||||||
-> CmdParser f out [p]
|
ManyUpperBoundN
|
||||||
addFlagReadParamsAll shorts longs name flag act =
|
desc
|
||||||
addCmdPartManyInpA ManyUpperBoundN desc parseF act
|
parseF
|
||||||
where
|
act
|
||||||
allStrs = fmap (\c -> "-"++[c]) shorts
|
where
|
||||||
++ fmap (\s -> "--"++s) longs
|
allStrs =
|
||||||
desc = (maybe id PartWithHelp $ _flag_help flag)
|
[ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]
|
||||||
$ PartSeq [desc1, desc2]
|
desc = (maybe id PartWithHelp $ _flag_help flag) $ PartSeq [desc1, desc2]
|
||||||
desc1 :: PartDesc
|
desc1 :: PartDesc
|
||||||
desc1 = PartAlts $ PartLiteral <$> allStrs
|
desc1 = PartAlts $ PartLiteral . either id id <$> allStrs
|
||||||
desc2 = (maybe id (PartDefault . show) $ _flag_default flag)
|
desc2 =
|
||||||
$ PartVariable name
|
(maybe id (PartDefault . show) $ _flag_default flag) $ PartVariable name
|
||||||
parseF :: Input -> Maybe (p, Input)
|
parseF :: Input -> Maybe (p, Input)
|
||||||
parseF inp = case inp of
|
parseF inp = case inp of
|
||||||
InputString str -> flip firstJust allStrs $ \s ->
|
InputString str -> fmap (second InputString) $ parseResult
|
||||||
[ t
|
where
|
||||||
| (s ++ " ") `isPrefixOf` str
|
parseResult = runInpParseString str $ do
|
||||||
, let rest = drop (length s + 1) str
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
, t <- case Text.Read.reads rest of
|
Left s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")
|
||||||
((x, ' ':r):_) -> Just (x, InputString $ dropWhile Char.isSpace r)
|
Right s -> pExpect s *> (pExpect " " <|> pExpect "=")
|
||||||
((x, ""):_) -> Just (x, InputString $ "")
|
InpParseString $ do
|
||||||
_ -> _flag_default flag <&> \x -> (x, InputString rest)
|
i <- StateS.get
|
||||||
]
|
case Text.Read.reads i of
|
||||||
InputArgs (arg1:argR) | any (==arg1) allStrs -> case argR of
|
((x, ' ':r):_) -> StateS.put (dropWhile Char.isSpace r) $> x
|
||||||
[] -> _flag_default flag <&> \d -> (d, InputArgs argR)
|
((x, "" ):_) -> StateS.put "" $> x
|
||||||
(arg2:rest) -> case readMaybe arg2 of
|
_ -> lift $ _flag_default flag
|
||||||
Just x -> Just (x, InputArgs rest)
|
InputArgs (arg1:argR) -> case runInpParseString arg1 parser of
|
||||||
Nothing -> _flag_default flag <&> \d -> (d, InputArgs argR)
|
Just ((), "") -> case argR of
|
||||||
InputArgs _ -> Nothing
|
[] -> mdef
|
||||||
|
(arg2:rest) -> (readMaybe arg2 <&> \x -> (x, InputArgs rest)) <|> mdef
|
||||||
|
where mdef = _flag_default flag <&> \p -> (p, InputArgs argR)
|
||||||
|
Just ((), remainingStr) ->
|
||||||
|
readMaybe remainingStr <&> \x -> (x, InputArgs argR)
|
||||||
|
Nothing -> Nothing
|
||||||
|
where
|
||||||
|
parser :: InpParseString ()
|
||||||
|
parser = do
|
||||||
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
|
Left s -> pExpect s *> pOption (pExpect "=")
|
||||||
|
Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)
|
||||||
|
InputArgs _ -> Nothing
|
||||||
|
|
||||||
-- | One-argument flag where the argument can be an arbitrary string.
|
-- | One-argument flag where the argument can be an arbitrary string.
|
||||||
addFlagStringParam
|
addFlagStringParam
|
||||||
:: forall f out
|
:: forall f out . (Applicative f) => String -- ^ short flag chars, i.e. "v" for -v
|
||||||
. (Applicative f)
|
-> [String] -- ^ list of long names, i.e. ["verbose"]
|
||||||
=> String -- ^ short flag chars, i.e. "v" for -v
|
-> String -- ^ param name
|
||||||
-> [String] -- ^ list of long names, i.e. ["verbose"]
|
-> Flag String -- ^ properties
|
||||||
-> String -- ^ param name
|
-> CmdParser f out String
|
||||||
-> Flag String -- ^ properties
|
addFlagStringParam shorts longs name flag = addCmdPartInpA desc
|
||||||
-> CmdParser f out String
|
parseF
|
||||||
addFlagStringParam shorts longs name flag = addCmdPartInpA desc parseF (\_ -> pure ())
|
(\_ -> pure ())
|
||||||
where
|
where
|
||||||
allStrs = fmap (\c -> "-"++[c]) shorts
|
allStrs =
|
||||||
++ fmap (\s -> "--"++s) longs
|
[ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]
|
||||||
desc = (maybe id PartWithHelp $ _flag_help flag)
|
desc = (maybe id PartWithHelp $ _flag_help flag) $ PartSeq [desc1, desc2]
|
||||||
$ PartSeq [desc1, desc2]
|
desc1 :: PartDesc
|
||||||
desc1 :: PartDesc
|
desc1 = PartAlts $ PartLiteral . either id id <$> allStrs
|
||||||
desc1 = PartAlts $ PartLiteral <$> allStrs
|
desc2 = PartVariable name
|
||||||
desc2 = PartVariable name
|
parseF :: Input -> Maybe (String, Input)
|
||||||
parseF :: Input -> Maybe (String, Input)
|
parseF inp = case inp of
|
||||||
parseF (InputString str) = case flip firstJust allStrs $ \s ->
|
InputString str ->
|
||||||
[drop (length s + 1) str | (s ++ " ") `isPrefixOf` str]
|
maybe (_flag_default flag <&> \x -> (x, inp)) (Just . second InputString)
|
||||||
of
|
$ parseResult
|
||||||
Nothing -> _flag_default flag <&> \x -> (x, InputString str)
|
where
|
||||||
Just rest1 -> let (x, rest2) = break Char.isSpace $ dropWhile Char.isSpace rest1
|
parseResult = runInpParseString str $ do
|
||||||
in Just (x, InputString rest2)
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
parseF (InputArgs (s1:s2:sr)) | any (==s1) allStrs = Just (s2, InputArgs sr)
|
Left s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")
|
||||||
parseF inp@(InputArgs _) = _flag_default flag <&> \x -> (x, inp)
|
Right s -> pExpect s *> (pExpect " " <|> pExpect "=")
|
||||||
|
InpParseString $ do
|
||||||
|
i <- StateS.get
|
||||||
|
let (x, rest) = break Char.isSpace $ dropWhile Char.isSpace i
|
||||||
|
StateS.put rest
|
||||||
|
pure x
|
||||||
|
InputArgs (arg1:argR) -> case runInpParseString arg1 parser of
|
||||||
|
Just ((), "" ) -> case argR of
|
||||||
|
[] -> Nothing
|
||||||
|
(x:rest) -> Just (x, InputArgs rest)
|
||||||
|
Just ((), remainingStr) -> Just (remainingStr, InputArgs argR)
|
||||||
|
Nothing -> _flag_default flag <&> \d -> (d, inp)
|
||||||
|
where
|
||||||
|
parser :: InpParseString ()
|
||||||
|
parser = do
|
||||||
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
|
Left s -> pExpect s *> pOption (pExpect "=")
|
||||||
|
Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)
|
||||||
|
InputArgs _ -> _flag_default flag <&> \d -> (d, inp)
|
||||||
|
|
||||||
-- | One-argument flag where the argument can be an arbitrary string.
|
-- | One-argument flag where the argument can be an arbitrary string.
|
||||||
-- This version can accumulate multiple values by using the same flag with
|
-- This version can accumulate multiple values by using the same flag with
|
||||||
|
@ -296,36 +371,49 @@ addFlagStringParams shorts longs name flag
|
||||||
-- = void $ addFlagStringParamsAll shorts longs name flag act
|
-- = void $ addFlagStringParamsAll shorts longs name flag act
|
||||||
|
|
||||||
addFlagStringParamsAll
|
addFlagStringParamsAll
|
||||||
:: forall f out
|
:: forall f out . String
|
||||||
. String
|
-> [String]
|
||||||
-> [String]
|
-> String
|
||||||
-> String
|
-> Flag Void -- we forbid the default because it has bad interaction
|
||||||
-> Flag Void -- we forbid the default because it has bad interaction
|
|
||||||
-- with the eat-anything behaviour of the string parser.
|
-- with the eat-anything behaviour of the string parser.
|
||||||
-> (String -> f ())
|
-> (String -> f ())
|
||||||
-> CmdParser f out [String]
|
-> CmdParser f out [String]
|
||||||
addFlagStringParamsAll shorts longs name flag act =
|
addFlagStringParamsAll shorts longs name flag act = addCmdPartManyInpA
|
||||||
addCmdPartManyInpA ManyUpperBoundN desc parseF act
|
ManyUpperBoundN
|
||||||
where
|
desc
|
||||||
allStrs = fmap (\c -> "-"++[c]) shorts
|
parseF
|
||||||
++ fmap (\s -> "--"++s) longs
|
act
|
||||||
desc = (maybe id PartWithHelp $ _flag_help flag)
|
where
|
||||||
$ PartSeq [desc1, desc2]
|
allStrs =
|
||||||
desc1 :: PartDesc
|
[ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]
|
||||||
desc1 = PartAlts $ PartLiteral <$> allStrs
|
desc = (maybe id PartWithHelp $ _flag_help flag) $ PartSeq [desc1, desc2]
|
||||||
desc2 = (maybe id (PartDefault . show) $ _flag_default flag)
|
desc1 :: PartDesc
|
||||||
$ PartVariable name
|
desc1 = PartAlts $ PartLiteral . either id id <$> allStrs
|
||||||
parseF :: Input -> Maybe (String, Input)
|
desc2 =
|
||||||
parseF (InputString str)
|
(maybe id (PartDefault . show) $ _flag_default flag) $ PartVariable name
|
||||||
= flip firstJust allStrs
|
parseF :: Input -> Maybe (String, Input)
|
||||||
$ \s -> [ (x, InputString rest2)
|
parseF inp = case inp of
|
||||||
| (s ++ " ") `isPrefixOf` str
|
InputString str -> fmap (second InputString) $ parseResult
|
||||||
, let rest1 = drop (length s + 1) str
|
where
|
||||||
, let (x, rest2) = break Char.isSpace $ dropWhile Char.isSpace rest1
|
parseResult = runInpParseString str $ do
|
||||||
]
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
parseF (InputArgs (s1:s2:sr))
|
Left s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")
|
||||||
= flip firstJust allStrs
|
Right s -> pExpect s *> (pExpect " " <|> pExpect "=")
|
||||||
$ \s -> [ (s2, InputArgs sr)
|
InpParseString $ do
|
||||||
| s == s1
|
i <- StateS.get
|
||||||
]
|
let (x, rest) = break Char.isSpace $ dropWhile Char.isSpace i
|
||||||
parseF (InputArgs _) = Nothing
|
StateS.put rest
|
||||||
|
pure x
|
||||||
|
InputArgs (arg1:argR) -> case runInpParseString arg1 parser of
|
||||||
|
Just ((), "" ) -> case argR of
|
||||||
|
[] -> Nothing
|
||||||
|
(x:rest) -> Just (x, InputArgs rest)
|
||||||
|
Just ((), remainingStr) -> Just (remainingStr, InputArgs argR)
|
||||||
|
Nothing -> Nothing
|
||||||
|
where
|
||||||
|
parser :: InpParseString ()
|
||||||
|
parser = do
|
||||||
|
Data.Foldable.msum $ allStrs <&> \case
|
||||||
|
Left s -> pExpect s *> pOption (pExpect "=")
|
||||||
|
Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)
|
||||||
|
InputArgs _ -> Nothing
|
||||||
|
|
Loading…
Reference in New Issue