Implement mapOut, peekInput, cmd child merging
parent
b15f1ae585
commit
c1cbc77e5b
|
@ -49,6 +49,7 @@ library
|
|||
, nats
|
||||
, void
|
||||
, bifunctors
|
||||
, deque
|
||||
}
|
||||
if flag(butcher-dev) {
|
||||
build-depends:
|
||||
|
@ -110,6 +111,7 @@ test-suite tests
|
|||
, transformers
|
||||
, mtl
|
||||
, extra
|
||||
, deque
|
||||
}
|
||||
if flag(butcher-dev) {
|
||||
buildable: True
|
||||
|
|
|
@ -69,6 +69,33 @@ simpleRunTest = do
|
|||
it "flag 3" $ testRunA testCmd3 "abc -g" `shouldBe` Right 2
|
||||
it "flag 4" $ testRunA testCmd3 "abc -f -g" `shouldBe` Right 3
|
||||
it "flag 5" $ testRunA testCmd3 "abc -g -f" `shouldBe` Right 3
|
||||
describe "separated children" $ do
|
||||
it "case 1" $ testRun testCmd4 "a aa" `shouldBe` Right (Just 1)
|
||||
it "case 2" $ testRun testCmd4 "a ab" `shouldBe` Right (Just 2)
|
||||
it "case 3" $ testRun testCmd4 "b ba" `shouldBe` Right (Just 3)
|
||||
it "case 4" $ testRun testCmd4 "b bb" `shouldBe` Right (Just 4)
|
||||
it "doc" $ show (ppHelpShallow (getDoc "" testCmd4)) `shouldBe`
|
||||
List.unlines
|
||||
[ "NAME"
|
||||
, ""
|
||||
, " test"
|
||||
, ""
|
||||
, "USAGE"
|
||||
, ""
|
||||
, " test a | b"
|
||||
]
|
||||
it "doc" $ show (ppHelpShallow (getDoc "a" testCmd4)) `shouldBe`
|
||||
List.unlines
|
||||
[ "NAME"
|
||||
, ""
|
||||
, " test a"
|
||||
, ""
|
||||
, "USAGE"
|
||||
, ""
|
||||
, " test a aa | ab"
|
||||
]
|
||||
|
||||
|
||||
|
||||
|
||||
testCmd1 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()
|
||||
|
@ -110,6 +137,21 @@ testCmd3 = do
|
|||
addCmd "def" $ do
|
||||
addCmdImpl ()
|
||||
|
||||
testCmd4 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()
|
||||
testCmd4 = do
|
||||
addCmd "a" $ do
|
||||
addCmd "aa" $ do
|
||||
addCmdImpl $ WriterS.tell 1
|
||||
addCmd "b" $ do
|
||||
addCmd "bb" $ do
|
||||
addCmdImpl $ WriterS.tell 4
|
||||
addCmd "a" $ do
|
||||
addCmd "ab" $ do
|
||||
addCmdImpl $ WriterS.tell 2
|
||||
addCmd "b" $ do
|
||||
addCmd "ba" $ do
|
||||
addCmdImpl $ WriterS.tell 3
|
||||
|
||||
testParse :: CmdParser Identity out () -> String -> Maybe (CommandDesc out)
|
||||
testParse cmd s = either (const Nothing) Just
|
||||
$ snd
|
||||
|
@ -124,3 +166,6 @@ testRunA :: CmdParser (StateS.State Int) () () -> String -> Either ParsingError
|
|||
testRunA cmd str = (\((_, e), s) -> e $> s)
|
||||
$ flip StateS.runState (0::Int)
|
||||
$ runCmdParserA Nothing (InputString str) cmd
|
||||
|
||||
getDoc :: String -> CmdParser Identity out () -> CommandDesc ()
|
||||
getDoc s = fst . runCmdParser (Just "test") (InputString s)
|
||||
|
|
|
@ -28,6 +28,7 @@ module UI.Butcher.Monadic
|
|||
-- * Builtin commands
|
||||
, addHelpCommand
|
||||
, addButcherDebugCommand
|
||||
, mapOut
|
||||
)
|
||||
where
|
||||
|
||||
|
|
|
@ -34,12 +34,13 @@ addHelpCommand desc = addCmd "help" $ do
|
|||
rest <- addRestOfInputStringParam "SUBCOMMAND(s)" mempty
|
||||
addCmdImpl $ do
|
||||
let parentDesc = maybe undefined snd (_cmd_mParent desc)
|
||||
let restWords = List.words rest
|
||||
let restWords = List.words rest
|
||||
let descent :: [String] -> CommandDesc a -> CommandDesc a
|
||||
descent [] curDesc = curDesc
|
||||
descent (w:wr) curDesc = case List.lookup w $ _cmd_children curDesc of
|
||||
Nothing -> curDesc
|
||||
Just child -> descent wr child
|
||||
descent (w:wr) curDesc =
|
||||
case List.lookup w $ Data.Foldable.toList $ _cmd_children curDesc of
|
||||
Nothing -> curDesc
|
||||
Just child -> descent wr child
|
||||
print $ ppHelpShallow $ descent restWords parentDesc
|
||||
|
||||
-- | Adds a help command that prints help for the command currently in context.
|
||||
|
|
|
@ -60,10 +60,16 @@ module UI.Butcher.Monadic.Command
|
|||
, reorderStart
|
||||
, reorderStop
|
||||
, peekCmdDesc
|
||||
, peekInput
|
||||
-- * Building CmdParsers - myprog -v --input PATH
|
||||
, module UI.Butcher.Monadic.Flag
|
||||
-- * Building CmdParsers - myprog SOME_INT
|
||||
, module UI.Butcher.Monadic.Param
|
||||
-- * Low-level part functions
|
||||
, addCmdPart
|
||||
, addCmdPartMany
|
||||
, addCmdPartInp
|
||||
, addCmdPartManyInp
|
||||
)
|
||||
where
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ module UI.Butcher.Monadic.Internal.Core
|
|||
, addCmdHelp
|
||||
, addCmdHelpStr
|
||||
, peekCmdDesc
|
||||
, peekInput
|
||||
, addCmdPart
|
||||
, addCmdPartA
|
||||
, addCmdPartMany
|
||||
|
@ -28,6 +29,7 @@ module UI.Butcher.Monadic.Internal.Core
|
|||
, runCmdParserExt
|
||||
, runCmdParserA
|
||||
, runCmdParserAExt
|
||||
, mapOut
|
||||
)
|
||||
where
|
||||
|
||||
|
@ -114,9 +116,12 @@ addCmdHelpStr s = liftF $ CmdParserHelp (PP.text s) ()
|
|||
-- For best results, use this "below"
|
||||
-- any 'addCmd' invocations in the current context, e.g. directly before
|
||||
-- the 'addCmdImpl' invocation.
|
||||
peekCmdDesc :: CmdParser f out (CommandDesc out)
|
||||
peekCmdDesc :: CmdParser f out (CommandDesc ())
|
||||
peekCmdDesc = liftF $ CmdParserPeekDesc id
|
||||
|
||||
peekInput :: CmdParser f out String
|
||||
peekInput = liftF $ CmdParserPeekInput id
|
||||
|
||||
addCmdPart
|
||||
:: (Applicative f, Typeable p)
|
||||
=> PartDesc
|
||||
|
@ -236,15 +241,17 @@ data PartGatherData f
|
|||
, _pgd_many :: Bool
|
||||
}
|
||||
|
||||
data ChildGather f out = ChildGather String (CmdParser f out ()) (f ())
|
||||
|
||||
type PartParsedData = Map Int [Dynamic]
|
||||
|
||||
data CmdDescStack = StackBottom [PartDesc]
|
||||
| StackLayer [PartDesc] String CmdDescStack
|
||||
data CmdDescStack = StackBottom (Deque PartDesc)
|
||||
| StackLayer (Deque PartDesc) String CmdDescStack
|
||||
|
||||
descStackAdd :: PartDesc -> CmdDescStack -> CmdDescStack
|
||||
descStackAdd d = \case
|
||||
StackBottom l -> StackBottom $ d:l
|
||||
StackLayer l s u -> StackLayer (d:l) s u
|
||||
StackBottom l -> StackBottom $ Deque.snoc d l
|
||||
StackLayer l s u -> StackLayer (Deque.snoc d l) s u
|
||||
|
||||
|
||||
-- | Because butcher is evil (i.e. has constraints not encoded in the types;
|
||||
|
@ -262,7 +269,7 @@ checkCmdParser :: forall f out
|
|||
checkCmdParser mTopLevel cmdParser
|
||||
= (>>= final)
|
||||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ MultiRWSS.withMultiStateAS (StackBottom [])
|
||||
$ MultiRWSS.withMultiStateAS (StackBottom mempty)
|
||||
$ MultiRWSS.withMultiStateS emptyCommandDesc
|
||||
$ processMain cmdParser
|
||||
where
|
||||
|
@ -273,8 +280,7 @@ checkCmdParser mTopLevel cmdParser
|
|||
StackBottom descs -> Right
|
||||
$ descFixParentsWithTopM (mTopLevel <&> \n -> (n, emptyCommandDesc))
|
||||
$ () <$ desc
|
||||
{ _cmd_parts = reverse descs
|
||||
, _cmd_children = reverse $ _cmd_children desc
|
||||
{ _cmd_parts = Data.Foldable.toList descs
|
||||
}
|
||||
StackLayer _ _ _ -> Left "unclosed ReorderStart or GroupStart"
|
||||
processMain :: CmdParser f out ()
|
||||
|
@ -291,6 +297,8 @@ checkCmdParser mTopLevel cmdParser
|
|||
processMain next
|
||||
Free (CmdParserPeekDesc nextF) -> do
|
||||
processMain $ nextF monadMisuseError
|
||||
Free (CmdParserPeekInput nextF) -> do
|
||||
processMain $ nextF monadMisuseError
|
||||
Free (CmdParserPart desc _parseF _act nextF) -> do
|
||||
do
|
||||
descStack <- mGet
|
||||
|
@ -312,25 +320,23 @@ checkCmdParser mTopLevel cmdParser
|
|||
mSet $ descStackAdd (wrapBoundDesc bound desc) descStack
|
||||
processMain $ nextF monadMisuseError
|
||||
Free (CmdParserChild cmdStr sub _act next) -> do
|
||||
mInitialDesc <- takeCommandChild cmdStr
|
||||
cmd :: CommandDesc out <- mGet
|
||||
subCmd <- do
|
||||
stackCur :: CmdDescStack <- mGet
|
||||
mSet (emptyCommandDesc :: CommandDesc out)
|
||||
mSet $ StackBottom []
|
||||
mSet $ fromMaybe (emptyCommandDesc :: CommandDesc out) mInitialDesc
|
||||
mSet $ StackBottom mempty
|
||||
processMain sub
|
||||
c <- mGet
|
||||
stackBelow <- mGet
|
||||
mSet cmd
|
||||
mSet stackCur
|
||||
subParts <- case stackBelow of
|
||||
StackBottom descs -> return $ reverse descs
|
||||
StackBottom descs -> return $ Data.Foldable.toList descs
|
||||
StackLayer _ _ _ -> lift $ Left "unclosed ReorderStart or GroupStart"
|
||||
return c
|
||||
{ _cmd_children = reverse $ _cmd_children c
|
||||
, _cmd_parts = subParts
|
||||
}
|
||||
return c { _cmd_parts = subParts }
|
||||
mSet $ cmd
|
||||
{ _cmd_children = (cmdStr, subCmd) : _cmd_children cmd
|
||||
{ _cmd_children = (cmdStr, subCmd) `Deque.snoc` _cmd_children cmd
|
||||
}
|
||||
processMain next
|
||||
Free (CmdParserImpl out next) -> do
|
||||
|
@ -338,7 +344,7 @@ checkCmdParser mTopLevel cmdParser
|
|||
processMain $ next
|
||||
Free (CmdParserGrouped groupName next) -> do
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] groupName stackCur
|
||||
mSet $ StackLayer mempty groupName stackCur
|
||||
processMain $ next
|
||||
Free (CmdParserGroupEnd next) -> do
|
||||
stackCur <- mGet
|
||||
|
@ -348,19 +354,19 @@ checkCmdParser mTopLevel cmdParser
|
|||
StackLayer _descs "" _up -> do
|
||||
lift $ Left $ "GroupEnd found, but expected ReorderStop first"
|
||||
StackLayer descs groupName up -> do
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (Data.Foldable.toList descs))) up
|
||||
processMain $ next
|
||||
Free (CmdParserReorderStop next) -> do
|
||||
stackCur <- mGet
|
||||
case stackCur of
|
||||
StackBottom{} -> lift $ Left $ "ReorderStop without reorderStart"
|
||||
StackLayer descs "" up -> do
|
||||
mSet $ descStackAdd (PartReorder (reverse descs)) up
|
||||
mSet $ descStackAdd (PartReorder (Data.Foldable.toList descs)) up
|
||||
StackLayer{} -> lift $ Left $ "Found ReorderStop, but need GroupEnd first"
|
||||
processMain next
|
||||
Free (CmdParserReorderStart next) -> do
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] "" stackCur
|
||||
mSet $ StackLayer mempty "" stackCur
|
||||
processMain next
|
||||
|
||||
monadMisuseError :: a
|
||||
|
@ -425,7 +431,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
$ (<&> captureFinal)
|
||||
$ MultiRWSS.withMultiWriterWA
|
||||
$ MultiRWSS.withMultiStateA cmdParser
|
||||
$ MultiRWSS.withMultiStateSA (StackBottom [])
|
||||
$ MultiRWSS.withMultiStateSA (StackBottom mempty)
|
||||
$ MultiRWSS.withMultiStateSA inputInitial
|
||||
$ MultiRWSS.withMultiStateSA (PastCommandInput inputInitial)
|
||||
$ MultiRWSS.withMultiStateSA initialCommandDesc
|
||||
|
@ -480,9 +486,11 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ MultiRWSS.withMultiStateSA emptyCommandDesc
|
||||
{ _cmd_mParent = _cmd_mParent cmdCur } -- partialDesc
|
||||
$ MultiRWSS.withMultiStateS (StackBottom []) -- partialStack
|
||||
$ MultiRWSS.withMultiStateS (StackBottom mempty) -- partialStack
|
||||
$ iterM processCmdShallow $ parser
|
||||
processMain $ nextF $ postProcessCmd stack cmd
|
||||
processMain $ nextF $ () <$ postProcessCmd stack cmd
|
||||
Free (CmdParserPeekInput nextF) -> do
|
||||
processMain $ nextF $ inputToString inputInitial
|
||||
Free (CmdParserPart desc parseF actF nextF) -> do
|
||||
do
|
||||
descStack <- mGet
|
||||
|
@ -561,53 +569,81 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
r <- proc
|
||||
let act = traverse actF r
|
||||
(act *>) <$> processMain (nextF $ r)
|
||||
f@(Free (CmdParserChild cmdStr sub act next)) -> do
|
||||
f@(Free (CmdParserChild _ _ _ _)) -> do
|
||||
dropSpaces
|
||||
input <- mGet
|
||||
(gatheredChildren :: [ChildGather f out], restCmdParser) <-
|
||||
MultiRWSS.withMultiWriterWA $ childrenGather f
|
||||
let
|
||||
mRest = case input of
|
||||
InputString str | cmdStr == str ->
|
||||
Just $ InputString ""
|
||||
InputString str | (cmdStr++" ") `isPrefixOf` str ->
|
||||
Just $ InputString $ drop (length cmdStr + 1) str
|
||||
InputArgs (str:strr) | cmdStr == str ->
|
||||
Just $ InputArgs strr
|
||||
_ -> Nothing
|
||||
child_fold
|
||||
:: (Deque String, Map String (CmdParser f out (), f ()))
|
||||
-> ChildGather f out
|
||||
-> (Deque String, Map String (CmdParser f out (), f ()))
|
||||
child_fold (c_names, c_map) (ChildGather name child act) =
|
||||
case name `MapS.lookup` c_map of
|
||||
Nothing ->
|
||||
( Deque.snoc name c_names
|
||||
, MapS.insert name (child, act) c_map
|
||||
)
|
||||
Just (child', act') ->
|
||||
( c_names
|
||||
, MapS.insert name (child' >> child, act') c_map
|
||||
-- we intentionally override/ignore act here.
|
||||
-- TODO: it should be documented that we expect the same act
|
||||
-- on different child nodes with the same name.
|
||||
)
|
||||
(child_name_list, child_map) =
|
||||
foldl' child_fold (mempty, MapS.empty) gatheredChildren
|
||||
combined_child_list = Data.Foldable.toList child_name_list <&> \n ->
|
||||
(n, child_map MapS.! n)
|
||||
let mRest = asum $ combined_child_list <&> \(name, (child, act)) ->
|
||||
case input of
|
||||
InputString str | name == str ->
|
||||
Just $ (name, child, act, InputString "")
|
||||
InputString str | (name++" ") `isPrefixOf` str ->
|
||||
Just $ (name, child, act, InputString $ drop (length name + 1) str)
|
||||
InputArgs (str:strr) | name == str ->
|
||||
Just $ (name, child, act, InputArgs strr)
|
||||
_ -> Nothing
|
||||
case mRest of
|
||||
Nothing -> do
|
||||
cmd :: CommandDesc out <- mGet
|
||||
let (subCmd, subStack)
|
||||
= runIdentity
|
||||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ MultiRWSS.withMultiStateSA (emptyCommandDesc :: CommandDesc out)
|
||||
$ MultiRWSS.withMultiStateS (StackBottom [])
|
||||
$ iterM processCmdShallow sub
|
||||
mSet $ cmd
|
||||
{ _cmd_children = (cmdStr, postProcessCmd subStack subCmd)
|
||||
: _cmd_children cmd
|
||||
}
|
||||
processMain next
|
||||
Just rest -> do
|
||||
Nothing -> do -- a child not matching what we have in the input
|
||||
let initialDesc :: CommandDesc out = emptyCommandDesc
|
||||
-- get the shallow desc for the child in a separate env.
|
||||
combined_child_list `forM_` \(child_name, (child, _)) -> do
|
||||
let (subCmd, subStack)
|
||||
= runIdentity
|
||||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ MultiRWSS.withMultiStateSA initialDesc
|
||||
$ MultiRWSS.withMultiStateS (StackBottom mempty)
|
||||
$ iterM processCmdShallow child
|
||||
cmd_children %=+ Deque.snoc (child_name, postProcessCmd subStack subCmd)
|
||||
-- proceed regularly on the same layer
|
||||
processMain $ restCmdParser
|
||||
Just (name, child, act, rest) -> do -- matching child -> descend
|
||||
-- process all remaining stuff on the same layer shallowly,
|
||||
-- including the current node. This will be replaced later.
|
||||
iterM processCmdShallow f
|
||||
-- so the descend
|
||||
cmd <- do
|
||||
c :: CommandDesc out <- mGet
|
||||
prevStack :: CmdDescStack <- mGet
|
||||
return $ postProcessCmd prevStack c
|
||||
mSet $ rest
|
||||
mSet $ PastCommandInput rest
|
||||
mSet $ (emptyCommandDesc :: CommandDesc out)
|
||||
{ _cmd_mParent = Just (cmdStr, cmd)
|
||||
mSet $ emptyCommandDesc
|
||||
{ _cmd_mParent = Just (name, cmd)
|
||||
}
|
||||
mSet $ sub
|
||||
mSet $ StackBottom []
|
||||
subAct <- processMain sub
|
||||
return $ act *> subAct
|
||||
mSet $ child
|
||||
mSet $ StackBottom mempty
|
||||
childAct <- processMain child
|
||||
-- check that descending yielded
|
||||
return $ act *> childAct
|
||||
Free (CmdParserImpl out next) -> do
|
||||
cmd_out .=+ Just out
|
||||
processMain $ next
|
||||
Free (CmdParserGrouped groupName next) -> do
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] groupName stackCur
|
||||
mSet $ StackLayer mempty groupName stackCur
|
||||
processMain $ next
|
||||
Free (CmdParserGroupEnd next) -> do
|
||||
stackCur <- mGet
|
||||
|
@ -616,7 +652,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
mTell $ ["butcher interface error: group end without group start"]
|
||||
return $ pure () -- hard abort should be fine for this case.
|
||||
StackLayer descs groupName up -> do
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (Data.Foldable.toList descs))) up
|
||||
processMain $ next
|
||||
Free (CmdParserReorderStop next) -> do
|
||||
mTell $ ["butcher interface error: reorder stop without reorder start"]
|
||||
|
@ -627,7 +663,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
$ iterM reorderPartGather $ next
|
||||
let
|
||||
reorderMapInit :: Map Int (PartGatherData f)
|
||||
reorderMapInit = Map.fromList $ reorderData <&> \d -> (_pgd_id d, d)
|
||||
reorderMapInit = MapS.fromList $ reorderData <&> \d -> (_pgd_id d, d)
|
||||
tryParsePartData :: Input -> PartGatherData f -> First (Int, Dynamic, Input, Bool, f ())
|
||||
tryParsePartData input (PartGatherData pid _ pfe act allowMany) =
|
||||
First [ (pid, toDyn r, rest, allowMany, act r)
|
||||
|
@ -653,17 +689,17 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
Nothing -> return $ pure ()
|
||||
Just (pid, x, rest, more, act) -> do
|
||||
mSet rest
|
||||
mModify $ Map.insertWith (++) pid [x]
|
||||
mModify $ MapS.insertWith (++) pid [x]
|
||||
when (not more) $ do
|
||||
mSet $ Map.delete pid m
|
||||
mSet $ MapS.delete pid m
|
||||
actRest <- parseLoop
|
||||
return $ act *> actRest
|
||||
(finalMap, (fr, acts)) <- MultiRWSS.withMultiStateSA (Map.empty :: PartParsedData)
|
||||
(finalMap, (fr, acts)) <- MultiRWSS.withMultiStateSA (MapS.empty :: PartParsedData)
|
||||
$ MultiRWSS.withMultiStateA reorderMapInit
|
||||
$ do
|
||||
acts <- parseLoop -- filling the map
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] "" stackCur
|
||||
mSet $ StackLayer mempty "" stackCur
|
||||
fr <- MultiRWSS.withMultiStateA (1::Int) $ processParsedParts next
|
||||
return (fr, acts)
|
||||
-- we check that all data placed in the map has been consumed while
|
||||
|
@ -671,7 +707,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
-- there can only be any rest if the collection of parts changed
|
||||
-- between the reorderPartGather traversal and the processParsedParts
|
||||
-- consumption.
|
||||
if Map.null finalMap
|
||||
if MapS.null finalMap
|
||||
then do
|
||||
actRest <- processMain fr
|
||||
return $ acts *> actRest
|
||||
|
@ -711,6 +747,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
CmdParserHelp{} -> restCase
|
||||
CmdParserSynopsis{} -> restCase
|
||||
CmdParserPeekDesc{} -> restCase
|
||||
CmdParserPeekInput{} -> restCase
|
||||
CmdParserChild{} -> restCase
|
||||
CmdParserImpl{} -> restCase
|
||||
CmdParserReorderStart{} -> restCase
|
||||
|
@ -721,6 +758,36 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
mTell ["Did not find expected ReorderStop after the reordered parts"]
|
||||
return ()
|
||||
|
||||
childrenGather
|
||||
:: ( MonadMultiWriter [ChildGather f out] m
|
||||
, MonadMultiState (CmdParser f out ()) m
|
||||
, MonadMultiState (CommandDesc out) m
|
||||
)
|
||||
=> CmdParser f out a
|
||||
-> m (CmdParser f out a)
|
||||
childrenGather = \case
|
||||
Free (CmdParserChild cmdStr sub act next) -> do
|
||||
mTell [ChildGather cmdStr sub act]
|
||||
childrenGather next
|
||||
Free (CmdParserPeekInput nextF) -> do
|
||||
childrenGather $ nextF $ inputToString inputInitial
|
||||
Free (CmdParserPeekDesc nextF) -> do
|
||||
parser <- mGet
|
||||
-- partialDesc :: CommandDesc out <- mGet
|
||||
-- partialStack :: CmdDescStack <- mGet
|
||||
-- run the rest without affecting the actual stack
|
||||
-- to retrieve the complete cmddesc.
|
||||
cmdCur :: CommandDesc out <- mGet
|
||||
let (cmd :: CommandDesc out, stack)
|
||||
= runIdentity
|
||||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ MultiRWSS.withMultiStateSA emptyCommandDesc
|
||||
{ _cmd_mParent = _cmd_mParent cmdCur } -- partialDesc
|
||||
$ MultiRWSS.withMultiStateS (StackBottom mempty) -- partialStack
|
||||
$ iterM processCmdShallow $ parser
|
||||
childrenGather $ nextF $ () <$ postProcessCmd stack cmd
|
||||
something -> return something
|
||||
|
||||
processParsedParts
|
||||
:: forall m r w s m0 a
|
||||
. ( MonadMultiState Int m
|
||||
|
@ -747,11 +814,11 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
StackBottom{} -> do
|
||||
mTell ["unexpected stackBottom"]
|
||||
StackLayer descs _ up -> do
|
||||
mSet $ descStackAdd (PartReorder (reverse descs)) up
|
||||
mSet $ descStackAdd (PartReorder (Data.Foldable.toList descs)) up
|
||||
return next
|
||||
Free (CmdParserGrouped groupName next) -> do
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] groupName stackCur
|
||||
mSet $ StackLayer mempty groupName stackCur
|
||||
processParsedParts $ next
|
||||
Free (CmdParserGroupEnd next) -> do
|
||||
stackCur <- mGet
|
||||
|
@ -760,7 +827,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
mTell $ ["butcher interface error: group end without group start"]
|
||||
return $ next -- hard abort should be fine for this case.
|
||||
StackLayer descs groupName up -> do
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (Data.Foldable.toList descs))) up
|
||||
processParsedParts $ next
|
||||
Pure x -> return $ return $ x
|
||||
f -> do
|
||||
|
@ -780,7 +847,7 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
pid <- mGet
|
||||
mSet $ pid + 1
|
||||
parsedMap :: PartParsedData <- mGet
|
||||
mSet $ Map.delete pid parsedMap
|
||||
mSet $ MapS.delete pid parsedMap
|
||||
partMap :: Map Int (PartGatherData f) <- mGet
|
||||
input :: Input <- mGet
|
||||
let errorResult = do
|
||||
|
@ -795,8 +862,8 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
continueOrMisuse :: Maybe p -> m (CmdParser f out a)
|
||||
continueOrMisuse = maybe monadMisuseError
|
||||
(processParsedParts . nextF)
|
||||
case Map.lookup pid parsedMap of
|
||||
Nothing -> case Map.lookup pid partMap of
|
||||
case MapS.lookup pid parsedMap of
|
||||
Nothing -> case MapS.lookup pid partMap of
|
||||
Nothing -> monadMisuseError -- it would still be in the map
|
||||
-- if it never had been successfully
|
||||
-- parsed, as indicicated by the
|
||||
|
@ -823,8 +890,8 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
pid <- mGet
|
||||
mSet $ pid + 1
|
||||
m :: PartParsedData <- mGet
|
||||
mSet $ Map.delete pid m
|
||||
let partDyns = case Map.lookup pid m of
|
||||
mSet $ MapS.delete pid m
|
||||
let partDyns = case MapS.lookup pid m of
|
||||
Nothing -> []
|
||||
Just r -> r
|
||||
case mapM fromDynamic partDyns of
|
||||
|
@ -848,7 +915,9 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
mSet $ cmd { _cmd_synopsis = Just $ PP.text s }
|
||||
next
|
||||
CmdParserPeekDesc nextF -> do
|
||||
mGet >>= nextF
|
||||
mGet >>= nextF . fmap (\(_ :: out) -> ())
|
||||
CmdParserPeekInput nextF -> do
|
||||
nextF $ inputToString inputInitial
|
||||
CmdParserPart desc _parseF _act nextF -> do
|
||||
do
|
||||
stackCur <- mGet
|
||||
|
@ -870,14 +939,16 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur
|
||||
nextF monadMisuseError
|
||||
CmdParserChild cmdStr _sub _act next -> do
|
||||
cmd_children %=+ ((cmdStr, emptyCommandDesc :: CommandDesc out):)
|
||||
mExisting <- takeCommandChild cmdStr
|
||||
let childDesc :: CommandDesc out = fromMaybe emptyCommandDesc mExisting
|
||||
cmd_children %=+ Deque.snoc (cmdStr, childDesc)
|
||||
next
|
||||
CmdParserImpl out next -> do
|
||||
cmd_out .=+ Just out
|
||||
next
|
||||
CmdParserGrouped groupName next -> do
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] groupName stackCur
|
||||
mSet $ StackLayer mempty groupName stackCur
|
||||
next
|
||||
CmdParserGroupEnd next -> do
|
||||
stackCur <- mGet
|
||||
|
@ -887,19 +958,19 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
StackLayer _descs "" _up -> do
|
||||
return ()
|
||||
StackLayer descs groupName up -> do
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (reverse descs))) up
|
||||
mSet $ descStackAdd (PartRedirect groupName (PartSeq (Data.Foldable.toList descs))) up
|
||||
next
|
||||
CmdParserReorderStop next -> do
|
||||
stackCur <- mGet
|
||||
case stackCur of
|
||||
StackBottom{} -> return ()
|
||||
StackLayer descs "" up -> do
|
||||
mSet $ descStackAdd (PartReorder (reverse descs)) up
|
||||
mSet $ descStackAdd (PartReorder (Data.Foldable.toList descs)) up
|
||||
StackLayer{} -> return ()
|
||||
next
|
||||
CmdParserReorderStart next -> do
|
||||
stackCur <- mGet
|
||||
mSet $ StackLayer [] "" stackCur
|
||||
mSet $ StackLayer mempty "" stackCur
|
||||
next
|
||||
|
||||
failureCurrentShallowRerun
|
||||
|
@ -921,9 +992,8 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
postProcessCmd descStack cmd
|
||||
= descFixParents
|
||||
$ cmd { _cmd_parts = case descStack of
|
||||
StackBottom l -> reverse l
|
||||
StackBottom l -> Data.Foldable.toList l
|
||||
StackLayer{} -> []
|
||||
, _cmd_children = reverse $ _cmd_children cmd
|
||||
}
|
||||
|
||||
monadMisuseError :: a
|
||||
|
@ -955,6 +1025,45 @@ runCmdParserAExt mTopLevel inputInitial cmdParser
|
|||
InputString s -> mSet $ InputString $ dropWhile Char.isSpace s
|
||||
InputArgs{} -> return ()
|
||||
|
||||
inputToString :: Input -> String
|
||||
inputToString (InputString s) = s
|
||||
inputToString (InputArgs ss) = List.unwords ss
|
||||
|
||||
dequeLookupRemove :: String -> Deque (String, a) -> (Maybe a, Deque (String, a))
|
||||
dequeLookupRemove key deque = case Deque.uncons deque of
|
||||
Nothing -> (Nothing, mempty)
|
||||
Just ((k, v), rest) -> if k==key
|
||||
then (Just v, rest)
|
||||
else let (r, rest') = dequeLookupRemove key rest
|
||||
in (r, Deque.cons (k, v) rest')
|
||||
|
||||
takeCommandChild
|
||||
:: MonadMultiState (CommandDesc out) m
|
||||
=> String
|
||||
-> m (Maybe (CommandDesc out))
|
||||
takeCommandChild key = do
|
||||
cmd <- mGet
|
||||
let (r, children') = dequeLookupRemove key $ _cmd_children cmd
|
||||
mSet cmd { _cmd_children = children' }
|
||||
return r
|
||||
|
||||
mapOut :: (outa -> outb) -> CmdParser f outa () -> CmdParser f outb ()
|
||||
mapOut f = hoistFree $ \case
|
||||
CmdParserHelp doc r -> CmdParserHelp doc r
|
||||
CmdParserSynopsis s r -> CmdParserSynopsis s r
|
||||
CmdParserPeekDesc fr -> CmdParserPeekDesc fr
|
||||
CmdParserPeekInput fr -> CmdParserPeekInput fr
|
||||
CmdParserPart desc fp fa fr -> CmdParserPart desc fp fa fr
|
||||
CmdParserPartMany bound desc fp fa fr -> CmdParserPartMany bound desc fp fa fr
|
||||
CmdParserPartInp desc fp fa fr -> CmdParserPartInp desc fp fa fr
|
||||
CmdParserPartManyInp bound desc fp fa fr ->
|
||||
CmdParserPartManyInp bound desc fp fa fr
|
||||
CmdParserChild s child act r -> CmdParserChild s (mapOut f child) act r
|
||||
CmdParserImpl out r -> CmdParserImpl (f out) r
|
||||
CmdParserReorderStart r -> CmdParserReorderStart r
|
||||
CmdParserReorderStop r -> CmdParserReorderStop r
|
||||
CmdParserGrouped s r -> CmdParserGrouped s r
|
||||
CmdParserGroupEnd r -> CmdParserGroupEnd r
|
||||
|
||||
-- cmdActionPartial :: CommandDesc out -> Either String out
|
||||
-- cmdActionPartial = maybe (Left err) Right . _cmd_out
|
||||
|
|
|
@ -59,7 +59,8 @@ data ManyUpperBound
|
|||
data CmdParserF f out a
|
||||
= CmdParserHelp PP.Doc a
|
||||
| CmdParserSynopsis String a
|
||||
| CmdParserPeekDesc (CommandDesc out -> a)
|
||||
| CmdParserPeekDesc (CommandDesc () -> a)
|
||||
| CmdParserPeekInput (String -> a)
|
||||
-- TODO: we can clean up this duplication by providing
|
||||
-- a function (String -> Maybe (p, String)) -> (Input -> Maybe (p, Input)).
|
||||
| forall p . Typeable p => CmdParserPart PartDesc (String -> Maybe (p, String)) (p -> f ()) (p -> a)
|
||||
|
@ -112,7 +113,9 @@ data CommandDesc out = CommandDesc
|
|||
, _cmd_help :: Maybe PP.Doc
|
||||
, _cmd_parts :: [PartDesc]
|
||||
, _cmd_out :: Maybe out
|
||||
, _cmd_children :: [(String, CommandDesc out)]
|
||||
, _cmd_children :: Deque (String, CommandDesc out) -- we don't use a Map here
|
||||
-- because we'd like to
|
||||
-- retain the order.
|
||||
}
|
||||
|
||||
-- type PartSeqDesc = [PartDesc]
|
||||
|
@ -164,7 +167,7 @@ deriving instance Functor CommandDesc
|
|||
--
|
||||
|
||||
emptyCommandDesc :: CommandDesc out
|
||||
emptyCommandDesc = CommandDesc Nothing Nothing Nothing [] Nothing []
|
||||
emptyCommandDesc = CommandDesc Nothing Nothing Nothing [] Nothing mempty
|
||||
|
||||
instance Show (CommandDesc out) where
|
||||
show c = "Command help=" ++ show (_cmd_help c)
|
||||
|
|
|
@ -56,22 +56,25 @@ import UI.Butcher.Monadic.Internal.Core
|
|||
-- | ppUsage exampleDesc yields:
|
||||
--
|
||||
-- > playground [--short] NAME [version | help]
|
||||
ppUsage :: CommandDesc a
|
||||
-> PP.Doc
|
||||
ppUsage (CommandDesc mParent _help _syn parts out children) =
|
||||
pparents mParent <+> PP.fsep (partDocs ++ [subsDoc])
|
||||
where
|
||||
pparents :: Maybe (String, CommandDesc out) -> PP.Doc
|
||||
pparents Nothing = PP.empty
|
||||
pparents (Just (n, cd)) = pparents (_cmd_mParent cd) <+> PP.text n
|
||||
partDocs = parts <&> ppPartDescUsage
|
||||
subsDoc = case out of
|
||||
_ | null children -> PP.empty -- TODO: remove debug
|
||||
Nothing | null parts -> subDoc
|
||||
| otherwise -> PP.parens $ subDoc
|
||||
Just{} -> PP.brackets $ subDoc
|
||||
subDoc = PP.fcat $ PP.punctuate (PP.text " | ") $ children <&> \(n, _) ->
|
||||
PP.text n
|
||||
ppUsage :: CommandDesc a -> PP.Doc
|
||||
ppUsage (CommandDesc mParent _help _syn parts out children) = pparents mParent
|
||||
<+> PP.fsep (partDocs ++ [subsDoc])
|
||||
where
|
||||
pparents :: Maybe (String, CommandDesc out) -> PP.Doc
|
||||
pparents Nothing = PP.empty
|
||||
pparents (Just (n, cd)) = pparents (_cmd_mParent cd) <+> PP.text n
|
||||
partDocs = parts <&> ppPartDescUsage
|
||||
subsDoc = case out of
|
||||
_ | null children -> PP.empty -- TODO: remove debug
|
||||
Nothing | null parts -> subDoc
|
||||
| otherwise -> PP.parens $ subDoc
|
||||
Just{} -> PP.brackets $ subDoc
|
||||
subDoc =
|
||||
PP.fcat
|
||||
$ PP.punctuate (PP.text " | ")
|
||||
$ Data.Foldable.toList
|
||||
$ children
|
||||
<&> \(n, _) -> PP.text n
|
||||
|
||||
-- | ppUsageWithHelp exampleDesc yields:
|
||||
--
|
||||
|
@ -81,22 +84,26 @@ ppUsage (CommandDesc mParent _help _syn parts out children) =
|
|||
-- And yes, the line break is not optimal in this instance with default print.
|
||||
ppUsageWithHelp :: CommandDesc a -> PP.Doc
|
||||
ppUsageWithHelp (CommandDesc mParent help _syn parts out children) =
|
||||
pparents mParent <+> PP.fsep (partDocs ++ [subsDoc]) PP.<> helpDoc
|
||||
where
|
||||
pparents :: Maybe (String, CommandDesc out) -> PP.Doc
|
||||
pparents Nothing = PP.empty
|
||||
pparents (Just (n, cd)) = pparents (_cmd_mParent cd) <+> PP.text n
|
||||
partDocs = parts <&> ppPartDescUsage
|
||||
subsDoc = case out of
|
||||
_ | null children -> PP.empty -- TODO: remove debug
|
||||
Nothing | null parts -> subDoc
|
||||
| otherwise -> PP.parens $ subDoc
|
||||
Just{} -> PP.brackets $ subDoc
|
||||
subDoc = PP.fcat $ PP.punctuate (PP.text " | ") $ children <&> \(n, _) ->
|
||||
PP.text n
|
||||
helpDoc = case help of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text ":" PP.<+> h
|
||||
pparents mParent <+> PP.fsep (partDocs ++ [subsDoc]) PP.<> helpDoc
|
||||
where
|
||||
pparents :: Maybe (String, CommandDesc out) -> PP.Doc
|
||||
pparents Nothing = PP.empty
|
||||
pparents (Just (n, cd)) = pparents (_cmd_mParent cd) <+> PP.text n
|
||||
partDocs = parts <&> ppPartDescUsage
|
||||
subsDoc = case out of
|
||||
_ | null children -> PP.empty -- TODO: remove debug
|
||||
Nothing | null parts -> subDoc
|
||||
| otherwise -> PP.parens $ subDoc
|
||||
Just{} -> PP.brackets $ subDoc
|
||||
subDoc =
|
||||
PP.fcat
|
||||
$ PP.punctuate (PP.text " | ")
|
||||
$ Data.Foldable.toList
|
||||
$ children
|
||||
<&> \(n, _) -> PP.text n
|
||||
helpDoc = case help of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text ":" PP.<+> h
|
||||
|
||||
-- | > ppUsageAt [] = ppUsage
|
||||
--
|
||||
|
|
|
@ -91,7 +91,8 @@ import qualified Data.List as List
|
|||
|
||||
import qualified Data.IntMap as IntMap
|
||||
import qualified Data.IntMap.Strict as IntMapS
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.Map.Strict as MapS
|
||||
import qualified Data.Map.Lazy as MapL
|
||||
import qualified Data.Sequence as Seq
|
||||
import qualified Data.Set as Set
|
||||
|
||||
|
@ -128,6 +129,10 @@ import Data.Sequence ( Seq )
|
|||
import Data.Map ( Map )
|
||||
import Data.Set ( Set )
|
||||
|
||||
import Deque ( Deque )
|
||||
|
||||
import qualified Deque
|
||||
|
||||
import Prelude ( Char
|
||||
, String
|
||||
, Int
|
||||
|
|
Loading…
Reference in New Issue