1094 lines
40 KiB
Haskell
1094 lines
40 KiB
Haskell
{-# LANGUAGE DeriveFunctor #-}
|
|
{-# LANGUAGE ExistentialQuantification #-}
|
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE TypeOperators #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
module UI.Butcher.Internal.Monadic
|
|
( addCmdSynopsis
|
|
, addCmdHelp
|
|
, addCmdHelpStr
|
|
, peekCmdDesc
|
|
, peekInput
|
|
, addCmdPart
|
|
, addCmdPartA
|
|
, addCmdPartMany
|
|
, addCmdPartManyA
|
|
, addCmdPartInp
|
|
, addCmdPartInpA
|
|
, addCmdPartManyInp
|
|
, addCmdPartManyInpA
|
|
, addCmd
|
|
, addCmdHidden
|
|
, addNullCmd
|
|
, addCmdImpl
|
|
, addAlternatives
|
|
, reorderStart
|
|
, reorderStop
|
|
, toCmdDesc
|
|
, traverseBarbie
|
|
, runCmdParserCoreFromDesc
|
|
, runCmdParserCoreFromDescA
|
|
, mapOut
|
|
, varPartDesc
|
|
)
|
|
where
|
|
|
|
|
|
|
|
#include "prelude.inc"
|
|
|
|
import qualified Barbies
|
|
import qualified Barbies.Bare as Barbies
|
|
import Control.Monad.Free
|
|
import qualified Control.Monad.Trans.MultiRWS.Strict
|
|
as MultiRWSS
|
|
import qualified Control.Monad.Trans.MultiState.Strict
|
|
as MultiStateS
|
|
|
|
import Data.Monoid ( First(..) )
|
|
import qualified Lens.Micro as Lens
|
|
import Lens.Micro ( (%~)
|
|
, (.~)
|
|
)
|
|
|
|
import qualified Text.PrettyPrint as PP
|
|
import Text.PrettyPrint ( ($$)
|
|
, ($+$)
|
|
, (<+>)
|
|
)
|
|
|
|
import Data.HList.ContainsType
|
|
|
|
import Data.Dynamic
|
|
|
|
import UI.Butcher.Internal.MonadicTypes
|
|
|
|
|
|
|
|
-- general-purpose helpers
|
|
----------------------------
|
|
|
|
mModify :: MonadMultiState s m => (s -> s) -> m ()
|
|
mModify f = mGet >>= mSet . f
|
|
|
|
-- sadly, you need a degree in type inference to know when we can use
|
|
-- these operators and when it must be avoided due to type ambiguities
|
|
-- arising around s in the signatures below. That's the price of not having
|
|
-- the functional dependency in MonadMulti*T.
|
|
|
|
-- (.=+) :: MonadMultiState s m => Lens.ASetter s s a b -> b -> m ()
|
|
-- l .=+ b = mModify $ l .~ b
|
|
--
|
|
-- (%=+) :: MonadMultiState s m => Lens.ASetter s s a b -> (a -> b) -> m ()
|
|
-- l %=+ f = mModify (l %~ f)
|
|
|
|
-- inflateStateProxy :: (Monad m, ContainsType s ss)
|
|
-- => p s -> StateS.StateT s m a -> MultiRWSS.MultiRWST r w ss m a
|
|
-- inflateStateProxy _ = MultiRWSS.inflateState
|
|
|
|
-- more on-topic stuff
|
|
----------------------------
|
|
|
|
-- instance IsHelpBuilder (CmdBuilder out) where
|
|
-- help s = liftF $ CmdBuilderHelp s ()
|
|
--
|
|
-- instance IsHelpBuilder (ParamBuilder p) where
|
|
-- help s = liftF $ ParamBuilderHelp s ()
|
|
--
|
|
-- instance IsHelpBuilder FlagBuilder where
|
|
-- help s = liftF $ FlagBuilderHelp s ()
|
|
|
|
-- | Add a synopsis to the command currently in scope; at top level this will
|
|
-- be the implicit top-level command.
|
|
--
|
|
-- Adding a second synopsis will overwrite a previous synopsis;
|
|
-- 'toCmdDesc' will check that you don't (accidentally) do this however.
|
|
addCmdSynopsis :: String -> CmdParser f out ()
|
|
addCmdSynopsis s = liftF $ CmdParserSynopsis s ()
|
|
|
|
-- | Add a help document to the command currently in scope; at top level this
|
|
-- will be the implicit top-level command.
|
|
--
|
|
-- Adding a second document will overwrite a previous document;
|
|
-- 'toCmdDesc' will check that you don't (accidentally) do this however.
|
|
addCmdHelp :: PP.Doc -> CmdParser f out ()
|
|
addCmdHelp s = liftF $ CmdParserHelp s ()
|
|
|
|
-- | Like @'addCmdHelp' . PP.text@
|
|
addCmdHelpStr :: String -> CmdParser f out ()
|
|
addCmdHelpStr s = liftF $ CmdParserHelp (PP.text s) ()
|
|
|
|
-- | Get the CommandDesc on the current level of the parser
|
|
-- (i.e. for a command child, you get the child's CommandDesc).
|
|
peekCmdDesc :: CmdParser f out CommandDesc
|
|
peekCmdDesc = liftF $ CmdParserPeekDesc id
|
|
|
|
-- | Semi-hacky way of accessing the current input that is not yet processed.
|
|
-- This must not be used to do any parsing. The purpose of this function is
|
|
-- to provide a String to be used for output to the user, as feedback about
|
|
-- what command was executed. For example we may think of an interactive
|
|
-- program reacting to commandline input such as
|
|
-- "run --delay 60 fire-rockets" which shows a 60 second delay on the
|
|
-- "fire-rockets" command. The latter string could have been obtained
|
|
-- via 'peekInput' after having parsed "run --delay 60" already.
|
|
peekInput :: CmdParser f out String
|
|
peekInput = liftF $ CmdParserPeekInput id
|
|
|
|
-- | Add part that is expected to occur exactly once in the input. May
|
|
-- succeed on empty input (e.g. by having a default).
|
|
addCmdPart
|
|
:: (Applicative f, Typeable p)
|
|
=> PartDesc
|
|
-> PartParser p String
|
|
-> CmdParser f out p
|
|
addCmdPart p f = liftF $ CmdParserPart p f (\_ -> pure ()) id
|
|
|
|
addCmdPartA
|
|
:: (Typeable p)
|
|
=> PartDesc
|
|
-> PartParser p String
|
|
-> (p -> f ())
|
|
-> CmdParser f out p
|
|
addCmdPartA p f a = liftF $ CmdParserPart p f a id
|
|
|
|
-- | Add part that is not required to occur, and can occur as often as
|
|
-- indicated by 'ManyUpperBound'. Must not succeed on empty input.
|
|
addCmdPartMany
|
|
:: (Applicative f, Typeable p)
|
|
=> ManyUpperBound
|
|
-> PartDesc
|
|
-> PartParser p String
|
|
-> CmdParser f out [p]
|
|
addCmdPartMany b p f = liftF $ CmdParserPartMany b p f (\_ -> pure ()) id
|
|
|
|
addCmdPartManyA
|
|
:: (Typeable p)
|
|
=> ManyUpperBound
|
|
-> PartDesc
|
|
-> PartParser p String
|
|
-> (p -> f ())
|
|
-> CmdParser f out [p]
|
|
addCmdPartManyA b p f a = liftF $ CmdParserPartMany b p f a id
|
|
|
|
-- | Add part that is expected to occur exactly once in the input. May
|
|
-- succeed on empty input (e.g. by having a default).
|
|
--
|
|
-- Only difference to 'addCmdPart' is that it accepts 'Input', i.e. can
|
|
-- behave differently for @String@ and @[String]@ input.
|
|
addCmdPartInp
|
|
:: (Applicative f, Typeable p)
|
|
=> PartDesc
|
|
-> PartParser p Input
|
|
-> CmdParser f out p
|
|
addCmdPartInp p f = liftF $ CmdParserPartInp p f (\_ -> pure ()) id
|
|
|
|
addCmdPartInpA
|
|
:: (Typeable p)
|
|
=> PartDesc
|
|
-> PartParser p Input
|
|
-> (p -> f ())
|
|
-> CmdParser f out p
|
|
addCmdPartInpA p f a = liftF $ CmdParserPartInp p f a id
|
|
|
|
-- | Add part that is not required to occur, and can occur as often as
|
|
-- indicated by 'ManyUpperBound'. Must not succeed on empty input.
|
|
--
|
|
-- Only difference to 'addCmdPart' is that it accepts 'Input', i.e. can
|
|
-- behave differently for @String@ and @[String]@ input.
|
|
addCmdPartManyInp
|
|
:: (Applicative f, Typeable p)
|
|
=> ManyUpperBound
|
|
-> PartDesc
|
|
-> PartParser p Input
|
|
-> CmdParser f out [p]
|
|
addCmdPartManyInp b p f = liftF $ CmdParserPartManyInp b p f (\_ -> pure ()) id
|
|
|
|
addCmdPartManyInpA
|
|
:: (Typeable p)
|
|
=> ManyUpperBound
|
|
-> PartDesc
|
|
-> PartParser p Input
|
|
-> (p -> f ())
|
|
-> CmdParser f out [p]
|
|
addCmdPartManyInpA b p f a = liftF $ CmdParserPartManyInp b p f a id
|
|
|
|
-- | Add a new child command in the current context.
|
|
addCmd
|
|
:: Applicative f
|
|
=> String -- ^ command name
|
|
-> CmdParser f out () -- ^ subcommand
|
|
-> CmdParser f out ()
|
|
addCmd str sub = liftF $ CmdParserChild (Just str) Visible sub (pure ()) ()
|
|
|
|
-- | Add a new child command in the current context, but make it hidden. It
|
|
-- will not appear in docs/help generated by e.g. the functions in the
|
|
-- @Pretty@ module.
|
|
--
|
|
-- This feature is not well tested yet.
|
|
addCmdHidden
|
|
:: Applicative f
|
|
=> String -- ^ command name
|
|
-> CmdParser f out () -- ^ subcommand
|
|
-> CmdParser f out ()
|
|
addCmdHidden str sub =
|
|
liftF $ CmdParserChild (Just str) Hidden sub (pure ()) ()
|
|
|
|
-- | Add a list of sub-parsers one of which will be selected and used based
|
|
-- on the provided predicate function. The input elements consist of:
|
|
-- a) a name used for the command description of the output,
|
|
-- b) a predicate function; the first True predicate determines which element
|
|
-- to apply
|
|
-- c) a CmdParser.
|
|
addAlternatives
|
|
:: Typeable p
|
|
=> [(String, String -> Bool, CmdParser f out p)]
|
|
-> CmdParser f out p
|
|
addAlternatives elems = liftF $ CmdParserAlternatives desc alts id
|
|
where
|
|
desc = PartAlts $ [ PartVariable s | (s, _, _) <- elems ]
|
|
alts = [ (a, b) | (_, a, b) <- elems ]
|
|
|
|
-- | Create a simple PartDesc from a string.
|
|
varPartDesc :: String -> PartDesc
|
|
varPartDesc = PartVariable
|
|
|
|
-- | Add a new nameless child command in the current context. Nameless means
|
|
-- that this command matches the empty input, i.e. will always apply.
|
|
-- This feature is experimental and CommandDesc pretty-printing might not
|
|
-- correctly in presense of nullCmds.
|
|
addNullCmd :: Applicative f => CmdParser f out () -> CmdParser f out ()
|
|
addNullCmd sub = liftF $ CmdParserChild Nothing Hidden sub (pure ()) ()
|
|
|
|
-- | Add an implementation to the current command.
|
|
addCmdImpl :: out -> CmdParser f out ()
|
|
addCmdImpl o = liftF $ CmdParserImpl o ()
|
|
|
|
-- | Best explained via example:
|
|
--
|
|
-- > do
|
|
-- > reorderStart
|
|
-- > bright <- addSimpleBoolFlag "" ["bright"] mempty
|
|
-- > yellow <- addSimpleBoolFlag "" ["yellow"] mempty
|
|
-- > reorderStop
|
|
-- > ..
|
|
--
|
|
-- will accept any inputs "" "--bright" "--yellow" "--bright --yellow" "--yellow --bright".
|
|
--
|
|
-- This works for any flags/params, but bear in mind that the results might
|
|
-- be unexpected because params may match on any input.
|
|
--
|
|
-- Note that start/stop must occur in pairs, and it will be a runtime error
|
|
-- if you mess this up. Use 'toCmdDesc' if you want to check all parts
|
|
-- of your 'CmdParser' without providing inputs that provide 100% coverage.
|
|
reorderStart :: CmdParser f out ()
|
|
reorderStart = liftF $ CmdParserReorderStart ()
|
|
|
|
-- | See 'reorderStart'
|
|
reorderStop :: CmdParser f out ()
|
|
reorderStop = liftF $ CmdParserReorderStop ()
|
|
|
|
-- | If you have a higher-kinded config type (let's assume it is a plain
|
|
-- record) then this turns a record whose fields are @CmdParser@s over
|
|
-- different values into a CmdParser that returns a record with the parsed
|
|
-- values in the fields.
|
|
--
|
|
-- See the BarbieParsing example included in this package.
|
|
traverseBarbie
|
|
:: (Barbies.BareB c, Barbies.TraversableB (c Barbies.Covered))
|
|
=> c Barbies.Covered (CmdParser f out)
|
|
-> CmdParser f out (c Barbies.Bare Identity)
|
|
traverseBarbie k = do
|
|
r <- Barbies.btraverse (fmap Identity) k
|
|
pure $ Barbies.bstrip r
|
|
|
|
|
|
-- addPartHelp :: String -> CmdPartParser ()
|
|
-- addPartHelp s = liftF $ CmdPartParserHelp s ()
|
|
--
|
|
-- addPartParserBasic :: (String -> Maybe (p, String)) -> Maybe p -> CmdPartParser p
|
|
-- addPartParserBasic f def = liftF $ CmdPartParserCore f def id
|
|
--
|
|
-- addPartParserOptionalBasic :: CmdPartParser p -> CmdPartParser (Maybe p)
|
|
-- addPartParserOptionalBasic p = liftF $ CmdPartParserOptional p id
|
|
|
|
data PartGatherData f = forall p . Typeable p => PartGatherData
|
|
{ _pgd_id :: Int
|
|
, _pgd_desc :: PartDesc
|
|
, _pgd_parseF :: Either (PartParser p String) (PartParser p Input)
|
|
, _pgd_act :: p -> f ()
|
|
, _pgd_many :: Bool
|
|
}
|
|
|
|
type PartParsedData = Map Int [Dynamic]
|
|
|
|
data CmdDescStack = StackBottom (Deque PartDesc)
|
|
| StackLayer (Deque PartDesc) String CmdDescStack
|
|
deriving Show
|
|
|
|
descStackAdd :: PartDesc -> CmdDescStack -> CmdDescStack
|
|
descStackAdd d = \case
|
|
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;
|
|
-- see the README), this method can be used as a rough check that you did not
|
|
-- mess up. It traverses all possible parts of the 'CmdParser' thereby
|
|
-- ensuring that the 'CmdParser' has a valid structure.
|
|
--
|
|
-- This method also yields a _complete_ @CommandDesc@ output, where the other
|
|
-- runCmdParser* functions all traverse only a shallow structure around the
|
|
-- parts of the 'CmdParser' touched while parsing the current input.
|
|
toCmdDesc
|
|
:: forall f out
|
|
. Maybe String -- ^ top-level command name
|
|
-> CmdParser f out () -- ^ parser to check
|
|
-> Either String CommandDesc
|
|
toCmdDesc mTopLevel cmdParser =
|
|
(>>= final)
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ MultiRWSS.withMultiStateAS (StackBottom mempty)
|
|
$ MultiRWSS.withMultiStateS emptyCommandDesc
|
|
$ processMain cmdParser
|
|
where
|
|
final :: (CommandDesc, CmdDescStack) -> Either String CommandDesc
|
|
final (desc, stack) = case stack of
|
|
StackBottom descs ->
|
|
Right
|
|
$ descFixParentsWithTopM
|
|
(mTopLevel <&> \n -> (Just n, emptyCommandDesc))
|
|
$ desc { _cmd_parts = Data.Foldable.toList descs }
|
|
StackLayer _ _ _ -> Left "unclosed ReorderStart or GroupStart"
|
|
processMain
|
|
:: CmdParser f out a
|
|
-> MultiRWSS.MultiRWST
|
|
'[]
|
|
'[]
|
|
'[CommandDesc , CmdDescStack]
|
|
(Either String)
|
|
a
|
|
processMain = \case
|
|
Pure x -> return x
|
|
Free (CmdParserHelp h next) -> do
|
|
cmd :: CommandDesc <- mGet
|
|
mSet $ cmd { _cmd_help = Just h }
|
|
processMain next
|
|
Free (CmdParserSynopsis s next) -> do
|
|
cmd :: CommandDesc <- mGet
|
|
mSet
|
|
$ cmd { _cmd_synopsis = Just $ PP.fsep $ fmap PP.text $ List.words s }
|
|
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
|
|
mSet $ descStackAdd desc descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartInp desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd desc descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartMany bound desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd (wrapBoundDesc bound desc) descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartManyInp bound desc _parseF _act nextF) -> do
|
|
do
|
|
descStack <- mGet
|
|
mSet $ descStackAdd (wrapBoundDesc bound desc) descStack
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserChild cmdStr vis sub _act next) -> do
|
|
mInitialDesc <- takeCommandChild cmdStr
|
|
cmd :: CommandDesc <- mGet
|
|
subCmd <- do
|
|
stackCur :: CmdDescStack <- mGet
|
|
mSet $ Maybe.fromMaybe (emptyCommandDesc :: CommandDesc) mInitialDesc
|
|
mSet $ StackBottom mempty
|
|
processMain sub
|
|
c <- mGet
|
|
stackBelow <- mGet
|
|
mSet cmd
|
|
mSet stackCur
|
|
subParts <- case stackBelow of
|
|
StackBottom descs -> return $ Data.Foldable.toList descs
|
|
StackLayer _ _ _ -> lift $ Left "unclosed ReorderStart or GroupStart"
|
|
return c { _cmd_parts = subParts, _cmd_visibility = vis }
|
|
mSet $ cmd
|
|
{ _cmd_children = (cmdStr, subCmd) `Deque.snoc` _cmd_children cmd
|
|
}
|
|
processMain next
|
|
Free (CmdParserImpl _out next) -> do
|
|
-- no need to process _out when we just construct the CommandDesc.
|
|
-- it would be full of monadmisuse-errors anyway.
|
|
processMain $ next
|
|
Free (CmdParserGrouped groupName next) -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer mempty groupName stackCur
|
|
processMain $ next
|
|
Free (CmdParserGroupEnd next) -> do
|
|
stackCur <- mGet
|
|
case stackCur of
|
|
StackBottom{} -> do
|
|
lift $ Left $ "butcher interface error: group end without group start"
|
|
StackLayer _descs "" _up -> do
|
|
lift $ Left $ "GroupEnd found, but expected ReorderStop first"
|
|
StackLayer descs groupName up -> do
|
|
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 (Data.Foldable.toList descs)) up
|
|
StackLayer{} ->
|
|
lift $ Left $ "Found ReorderStop, but need GroupEnd first"
|
|
processMain next
|
|
Free (CmdParserReorderStart next) -> do
|
|
stackCur <- mGet
|
|
mSet $ StackLayer mempty "" stackCur
|
|
processMain next
|
|
Free (CmdParserAlternatives desc alts nextF) -> do
|
|
mModify (descStackAdd desc)
|
|
states <- MultiRWSS.mGetRawS
|
|
let go
|
|
:: [(String -> Bool, CmdParser f out p)]
|
|
-> MultiRWSS.MultiRWST
|
|
'[]
|
|
'[]
|
|
'[CommandDesc , CmdDescStack]
|
|
(Either String)
|
|
p
|
|
go [] = lift $ Left $ "Empty alternatives"
|
|
go [(_, alt) ] = processMain alt
|
|
go ((_, alt1) : altr) = do
|
|
case
|
|
MultiRWSS.runMultiRWSTNil
|
|
$ MultiRWSS.withMultiStates states (processMain alt1)
|
|
of
|
|
Left{} -> go altr
|
|
Right (p, states') -> MultiRWSS.mPutRawS states' $> p
|
|
p <- go alts
|
|
processMain $ nextF p
|
|
|
|
monadMisuseError :: a
|
|
monadMisuseError =
|
|
error
|
|
$ "CmdParser definition error -"
|
|
++ " used Monad powers where only Applicative/Arrow is allowed"
|
|
|
|
|
|
data CoreInterpreterState f out = CoreInterpreterState
|
|
{ _cis_remainingInput :: Input
|
|
, _cis_pastCommandInput :: Input
|
|
, _cis_output :: Maybe out
|
|
, _cis_currentParser :: CmdParser f out ()
|
|
, _cis_currentDesc :: CommandDesc
|
|
, _cis_expectedPartDesc :: Maybe PartDesc
|
|
}
|
|
|
|
|
|
-- | Run a @CmdParser@ on the given input, returning:
|
|
--
|
|
-- a) A @CommandDesc ()@ that accurately represents the subcommand that was
|
|
-- reached, even if parsing failed. Because this is returned always, the
|
|
-- argument is @()@ because "out" requires a successful parse.
|
|
--
|
|
-- b) The remaining input, i.e. the left-over part that did not parse
|
|
-- successfully.
|
|
-- For some input "myprog foo bar -v --wrong" where parsing fails at
|
|
-- "--wrong", this will contain the full "-v --wrong". Useful for
|
|
-- interactive feedback stuff.
|
|
|
|
-- c) Either an error or the result of a successful parse, including a proper
|
|
-- "CommandDesc out" from which an "out" can be extracted (presuming that
|
|
-- the command has an implementation).
|
|
runCmdParserCoreFromDesc
|
|
:: CommandDesc -- ^ cached desc
|
|
-> Input -- ^ input to be processed
|
|
-> CmdParser Identity out () -- ^ parser to use
|
|
-> (CommandDesc, Input, Either ParsingError (Maybe out))
|
|
runCmdParserCoreFromDesc topDesc inputInitial cmdParser =
|
|
runIdentity $ runCmdParserCoreFromDescA topDesc inputInitial cmdParser
|
|
|
|
-- | The Applicative-enabled version of 'runCmdParserCoreFromDesc'.
|
|
runCmdParserCoreFromDescA
|
|
:: forall f out
|
|
. Applicative f
|
|
=> CommandDesc -- ^ cached desc
|
|
-> Input -- ^ input to be processed
|
|
-> CmdParser f out () -- ^ parser to use
|
|
-> f (CommandDesc, Input, Either ParsingError (Maybe out))
|
|
runCmdParserCoreFromDescA topDesc inputInitial cmdParser =
|
|
runIdentity
|
|
$ MultiRWSS.runMultiRWSTNil
|
|
$ fmap captureFinal
|
|
$ MultiRWSS.withMultiWriterWA
|
|
$ MultiRWSS.withMultiStateSA initialState
|
|
$ processMain cmdParser
|
|
where
|
|
initialState = CoreInterpreterState { _cis_remainingInput = inputInitial
|
|
, _cis_pastCommandInput = inputInitial
|
|
, _cis_output = Nothing
|
|
, _cis_currentParser = cmdParser
|
|
, _cis_currentDesc = topDesc
|
|
, _cis_expectedPartDesc = Nothing
|
|
}
|
|
captureFinal
|
|
:: ([String], (CoreInterpreterState f out, f ()))
|
|
-> f (CommandDesc, Input, Either ParsingError (Maybe out))
|
|
captureFinal (errs, (finalState, act)) =
|
|
act $> (_cis_currentDesc finalState, _cis_pastCommandInput finalState, res)
|
|
where
|
|
errs' = errs ++ inputErrs
|
|
inputErrs = case _cis_remainingInput finalState of
|
|
InputString s | all Char.isSpace s -> []
|
|
InputString{} -> ["could not parse input/unprocessed input"]
|
|
InputArgs [] -> []
|
|
InputArgs{} -> ["could not parse input/unprocessed input"]
|
|
res = if null errs'
|
|
then Right (_cis_output finalState)
|
|
else Left $ ParsingError
|
|
{ _pe_messages = errs'
|
|
, _pe_remaining = _cis_remainingInput finalState
|
|
, _pe_expectedDesc = _cis_expectedPartDesc finalState
|
|
}
|
|
processMain
|
|
:: -- forall a
|
|
CmdParser f out ()
|
|
-> MultiRWSS.MultiRWS
|
|
'[]
|
|
'[[String]]
|
|
'[CoreInterpreterState f out]
|
|
(f ())
|
|
processMain = \case
|
|
Pure () -> return $ pure ()
|
|
Free (CmdParserHelp _h next) -> do
|
|
processMain next
|
|
Free (CmdParserSynopsis _s next) -> do
|
|
processMain next
|
|
Free (CmdParserPeekDesc nextF) -> do
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
processMain $ nextF (_cis_currentDesc cis)
|
|
Free (CmdParserPeekInput nextF) -> do
|
|
processMain $ nextF $ inputToString inputInitial
|
|
Free (CmdParserPart desc parseF actF nextF) -> do
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
case _cis_remainingInput cis of
|
|
InputString str -> case parseF str of
|
|
Success x rest -> do
|
|
mSet $ cis { _cis_remainingInput = InputString rest }
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
Failure errPDesc -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
trySetErrDesc errPDesc
|
|
processMain $ nextF monadMisuseError
|
|
InputArgs (str : strr) -> case parseF str of
|
|
Success x "" -> do
|
|
mSet $ cis { _cis_remainingInput = InputArgs strr }
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
Success x rest | str == rest -> do
|
|
-- no input consumed, default applied
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
Success{} -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
Failure errPDesc -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
trySetErrDesc errPDesc
|
|
processMain $ nextF monadMisuseError
|
|
InputArgs [] -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartInp desc parseF actF nextF) -> do
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
case parseF (_cis_remainingInput cis) of
|
|
Success x rest -> do
|
|
mSet $ cis { _cis_remainingInput = rest }
|
|
actRest <- processMain $ nextF x
|
|
return $ actF x *> actRest
|
|
Failure errPDesc -> do
|
|
mTell ["could not parse " ++ getPartSeqDescPositionName desc]
|
|
trySetErrDesc errPDesc
|
|
processMain $ nextF monadMisuseError
|
|
Free (CmdParserPartMany _bound _desc parseF actF nextF) -> do
|
|
let proc = do
|
|
dropSpaces
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
case _cis_remainingInput cis of
|
|
InputString str -> case parseF str of
|
|
Success x r -> do
|
|
mSet $ cis { _cis_remainingInput = InputString r }
|
|
xr <- proc
|
|
return $ x : xr
|
|
Failure errPDesc -> do
|
|
trySetErrDesc errPDesc
|
|
return []
|
|
InputArgs (str : strr) -> case parseF str of
|
|
Success x "" -> do
|
|
mSet $ cis { _cis_remainingInput = InputArgs strr }
|
|
xr <- proc
|
|
return $ x : xr
|
|
Success{} -> do
|
|
return []
|
|
Failure errPDesc -> do
|
|
trySetErrDesc errPDesc
|
|
return []
|
|
InputArgs [] -> return []
|
|
r <- proc
|
|
let act = traverse actF r
|
|
(act *>) <$> processMain (nextF $ r)
|
|
Free (CmdParserPartManyInp _bound _desc parseF actF nextF) -> do
|
|
let proc = do
|
|
dropSpaces
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
case parseF (_cis_remainingInput cis) of
|
|
Success x r -> do
|
|
mSet $ cis { _cis_remainingInput = r }
|
|
xr <- proc
|
|
return $ x : xr
|
|
Failure errPDesc -> do
|
|
trySetErrDesc errPDesc
|
|
return []
|
|
r <- proc
|
|
let act = traverse actF r
|
|
(act *>) <$> processMain (nextF $ r)
|
|
Free (CmdParserChild mName _vis inner act next) -> do
|
|
dropSpaces
|
|
input <- mGet @(CoreInterpreterState f out) <&> _cis_remainingInput
|
|
let mRest = case (mName, input) of
|
|
(Just name, InputString str) | name == str ->
|
|
Just $ (Just name, InputString "")
|
|
(Just name, InputString str) | (name ++ " ") `isPrefixOf` str ->
|
|
Just $ (Just name, InputString $ drop (length name + 1) str)
|
|
(Just name, InputArgs (str : strr)) | name == str ->
|
|
Just $ (Just name, InputArgs strr)
|
|
(Nothing, _) -> Just $ (Nothing, input)
|
|
_ -> Nothing
|
|
case mRest of
|
|
Nothing -> do -- a child not matching what we have in the input
|
|
-- get the shallow desc for the child in a separate env.
|
|
-- proceed regularly on the same layer
|
|
processMain next
|
|
Just (name, rest) -> do -- matching child -> descend
|
|
-- do the descend
|
|
mModify $ \cis -> cis
|
|
{ _cis_remainingInput = rest
|
|
, _cis_pastCommandInput = rest
|
|
, _cis_currentDesc =
|
|
case
|
|
List.find
|
|
(\(n, _) -> name == n)
|
|
(Data.Foldable.toList $ _cmd_children $ _cis_currentDesc cis)
|
|
of
|
|
Nothing ->
|
|
error "butcher internal error: inconsistent child desc"
|
|
Just (_, childDesc) -> childDesc
|
|
, _cis_currentParser = inner
|
|
}
|
|
childAct <- processMain inner
|
|
-- check that descending yielded
|
|
return $ act *> childAct
|
|
Free (CmdParserImpl out next) -> do
|
|
mModify @(CoreInterpreterState f out)
|
|
$ \cis -> cis { _cis_output = Just out }
|
|
processMain $ next
|
|
Free (CmdParserGrouped _groupName next) -> do
|
|
processMain $ next
|
|
Free (CmdParserGroupEnd next) -> do
|
|
processMain $ next
|
|
Free (CmdParserReorderStop next) -> do
|
|
mTell $ ["butcher interface error: reorder stop without reorder start"]
|
|
processMain next
|
|
Free (CmdParserReorderStart next) -> do
|
|
reorderData <-
|
|
MultiRWSS.withMultiStateA (1 :: Int)
|
|
$ MultiRWSS.withMultiWriterW
|
|
$ iterM reorderPartGather
|
|
$ next
|
|
let
|
|
reorderMapInit :: Map Int (PartGatherData f)
|
|
reorderMapInit = MapS.fromList $ reorderData <&> \d -> (_pgd_id d, d)
|
|
tryParsePartData
|
|
:: Input
|
|
-> PartGatherData f
|
|
-> First (Either PartDesc (Int, Dynamic, Input, Bool, f ()))
|
|
tryParsePartData input (PartGatherData pid _ pfe act allowMany) =
|
|
case pfe of
|
|
Left pfStr -> case input of
|
|
InputString str -> case pfStr str of
|
|
Success x r | r /= str ->
|
|
pure $ Right (pid, toDyn x, InputString r, allowMany, act x)
|
|
Failure (Just pDesc) -> pure $ Left pDesc
|
|
_ -> mempty
|
|
InputArgs (str : strr) -> case pfStr str of
|
|
Success x "" ->
|
|
pure $ Right (pid, toDyn x, InputArgs strr, allowMany, act x)
|
|
Failure (Just pDesc) -> pure $ Left pDesc
|
|
_ -> First Nothing
|
|
InputArgs [] -> First Nothing
|
|
Right pfInp -> case pfInp input of
|
|
Success x r | r /= input ->
|
|
pure $ Right (pid, toDyn x, r, allowMany, act x)
|
|
Failure (Just pDesc) -> pure $ Left pDesc
|
|
_ -> First Nothing
|
|
|
|
-- First
|
|
-- [ (pid, toDyn r, rest, allowMany, act r)
|
|
-- | (r, rest) <-
|
|
-- ]
|
|
parseLoop = do
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
m :: Map Int (PartGatherData f) <- mGet
|
|
case
|
|
getFirst $ Data.Foldable.foldMap
|
|
(tryParsePartData $ _cis_remainingInput cis)
|
|
m
|
|
of
|
|
-- i will be angry if foldMap ever decides to not fold
|
|
-- in order of keys.
|
|
Nothing -> return $ pure ()
|
|
Just (Right (pid, x, rest, more, act)) -> do
|
|
mSet cis { _cis_remainingInput = rest }
|
|
mModify $ MapS.insertWith (++) pid [x]
|
|
when (not more) $ do
|
|
mSet $ MapS.delete pid m
|
|
actRest <- parseLoop
|
|
return $ act *> actRest
|
|
Just (Left err) -> do
|
|
trySetErrDesc (Just err)
|
|
return $ pure ()
|
|
(finalMap, (fr, acts)) <-
|
|
MultiRWSS.withMultiStateSA (MapS.empty :: PartParsedData)
|
|
$ MultiRWSS.withMultiStateA reorderMapInit
|
|
$ do
|
|
acts <- parseLoop -- filling the map
|
|
fr <- MultiRWSS.withMultiStateA (1 :: Int) $ processParsedParts next
|
|
return (fr, acts)
|
|
-- we check that all data placed in the map has been consumed while
|
|
-- running the parts for which we collected the parseresults.
|
|
-- there can only be any rest if the collection of parts changed
|
|
-- between the reorderPartGather traversal and the processParsedParts
|
|
-- consumption.
|
|
if MapS.null finalMap
|
|
then do
|
|
actRest <- processMain fr
|
|
return $ acts *> actRest
|
|
else monadMisuseError
|
|
Free (CmdParserAlternatives desc alts nextF) -> do
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
case _cis_remainingInput cis of
|
|
InputString str
|
|
| Just (_, sub) <- find (\(predicate, _sub) -> predicate str) alts
|
|
-> processMain $ sub >>= nextF
|
|
InputArgs (str : _)
|
|
| Just (_, sub) <- find (\(predicate, _sub) -> predicate str) alts
|
|
-> processMain $ sub >>= nextF
|
|
_ -> do
|
|
mTell ["could not parse any of " ++ getPartSeqDescPositionName desc]
|
|
processMain $ nextF monadMisuseError
|
|
|
|
trySetErrDesc
|
|
:: (MonadMultiState (CoreInterpreterState f out) m)
|
|
=> Maybe PartDesc
|
|
-> m ()
|
|
trySetErrDesc errPDescMay = do
|
|
mModify $ \(cis :: CoreInterpreterState f out) -> cis
|
|
{ _cis_expectedPartDesc = _cis_expectedPartDesc cis <|> errPDescMay
|
|
}
|
|
reorderPartGather
|
|
:: ( MonadMultiState Int m
|
|
, MonadMultiWriter [PartGatherData f] m
|
|
, MonadMultiWriter [String] m
|
|
)
|
|
=> CmdParserF f out (m ())
|
|
-> m ()
|
|
reorderPartGather = \case
|
|
-- TODO: why do PartGatherData contain desc?
|
|
CmdParserPart desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Left parseF) actF False]
|
|
nextF $ monadMisuseError
|
|
CmdParserPartInp desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Right parseF) actF False]
|
|
nextF $ monadMisuseError
|
|
CmdParserPartMany _ desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Left parseF) actF True]
|
|
nextF $ monadMisuseError
|
|
CmdParserPartManyInp _ desc parseF actF nextF -> do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
mTell [PartGatherData pid desc (Right parseF) actF True]
|
|
nextF $ monadMisuseError
|
|
CmdParserReorderStop _next -> do
|
|
return ()
|
|
CmdParserHelp{} -> restCase
|
|
CmdParserSynopsis{} -> restCase
|
|
CmdParserPeekDesc{} -> restCase
|
|
CmdParserPeekInput{} -> restCase
|
|
CmdParserChild{} -> restCase
|
|
CmdParserImpl{} -> restCase
|
|
CmdParserReorderStart{} -> restCase
|
|
CmdParserGrouped{} -> restCase
|
|
CmdParserGroupEnd{} -> restCase
|
|
CmdParserAlternatives{} -> restCase
|
|
where
|
|
restCase = do
|
|
mTell ["Did not find expected ReorderStop after the reordered parts"]
|
|
return ()
|
|
|
|
processParsedParts
|
|
:: forall m r w s m0 a
|
|
. ( MonadMultiState Int m
|
|
, MonadMultiState PartParsedData m
|
|
, MonadMultiState (Map Int (PartGatherData f)) m
|
|
, MonadMultiState (CoreInterpreterState f out) m
|
|
, MonadMultiWriter [[Char]] m
|
|
-- , ContainsType (CoreInterpreterState f out) s
|
|
, m ~ MultiRWSS.MultiRWST r w s m0
|
|
, Monad m0
|
|
)
|
|
=> CmdParser f out a
|
|
-> m (CmdParser f out a)
|
|
processParsedParts = \case
|
|
Free (CmdParserPart desc _ _ (nextF :: p -> CmdParser f out a)) ->
|
|
part desc nextF
|
|
Free (CmdParserPartInp desc _ _ (nextF :: p -> CmdParser f out a)) ->
|
|
part desc nextF
|
|
Free (CmdParserPartMany bound desc _ _ nextF) -> partMany bound desc nextF
|
|
Free (CmdParserPartManyInp bound desc _ _ nextF) ->
|
|
partMany bound desc nextF
|
|
Free (CmdParserReorderStop next) -> do
|
|
return next
|
|
Free (CmdParserGrouped _groupName next) -> do
|
|
processParsedParts $ next
|
|
Free (CmdParserGroupEnd next) -> do
|
|
processParsedParts $ next
|
|
Pure x -> return $ return $ x
|
|
f -> do
|
|
mTell ["Did not find expected ReorderStop after the reordered parts"]
|
|
return f
|
|
where
|
|
part
|
|
:: forall p
|
|
. Typeable p
|
|
=> PartDesc
|
|
-> (p -> CmdParser f out a)
|
|
-> m (CmdParser f out a)
|
|
part desc nextF = do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
parsedMap :: PartParsedData <- mGet
|
|
mSet $ MapS.delete pid parsedMap
|
|
partMap :: Map Int (PartGatherData f) <- mGet
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
let
|
|
errorResult = do
|
|
mTell
|
|
[ "could not parse expected input "
|
|
++ getPartSeqDescPositionName desc
|
|
++ " with remaining input: "
|
|
++ show (_cis_remainingInput cis)
|
|
]
|
|
processParsedParts $ nextF monadMisuseError
|
|
continueOrMisuse :: Maybe p -> m (CmdParser f out a)
|
|
continueOrMisuse = maybe monadMisuseError (processParsedParts . nextF)
|
|
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
|
|
-- previous parsedMap Nothing lookup.
|
|
Just (PartGatherData _ _ pfe _ _) -> case pfe of
|
|
Left pf -> case pf "" of
|
|
Success dx _ -> continueOrMisuse $ cast dx
|
|
Failure _ -> errorResult
|
|
Right pf -> case pf (InputArgs []) of
|
|
Success dx _ -> continueOrMisuse $ cast dx
|
|
Failure _ -> errorResult
|
|
Just [dx] -> continueOrMisuse $ fromDynamic dx
|
|
Just _ -> monadMisuseError
|
|
partMany
|
|
:: Typeable p
|
|
=> ManyUpperBound
|
|
-> PartDesc
|
|
-> ([p] -> CmdParser f out a)
|
|
-> m (CmdParser f out a)
|
|
partMany _bound _desc nextF = do
|
|
pid <- mGet
|
|
mSet $ pid + 1
|
|
m :: PartParsedData <- mGet
|
|
mSet $ MapS.delete pid m
|
|
let partDyns = case MapS.lookup pid m of
|
|
Nothing -> []
|
|
Just r -> reverse r
|
|
case mapM fromDynamic partDyns of
|
|
Nothing -> monadMisuseError
|
|
Just xs -> processParsedParts $ nextF xs
|
|
|
|
-- postProcessCmd :: CmdDescStack -> CommandDesc out -> CommandDesc out
|
|
-- postProcessCmd descStack cmd = descFixParents $ cmd
|
|
-- { _cmd_parts = case descStack of
|
|
-- StackBottom l -> Data.Foldable.toList l
|
|
-- StackLayer{} -> []
|
|
-- }
|
|
|
|
monadMisuseError :: a
|
|
monadMisuseError =
|
|
error
|
|
$ "CmdParser definition error -"
|
|
++ " used Monad powers where only Applicative/Arrow is allowed"
|
|
|
|
|
|
getPartSeqDescPositionName :: PartDesc -> String
|
|
getPartSeqDescPositionName = \case
|
|
PartLiteral s -> s
|
|
PartVariable s -> s
|
|
PartOptional ds' -> f ds'
|
|
PartAlts alts -> f $ head alts -- this is not optimal, but probably
|
|
-- does not matter.
|
|
PartDefault _ d -> f d
|
|
PartSuggestion _ d -> f d
|
|
PartRedirect s _ -> s
|
|
PartMany ds -> f ds
|
|
PartWithHelp _ d -> f d
|
|
PartSeq ds -> List.unwords $ f <$> ds
|
|
PartReorder ds -> List.unwords $ f <$> ds
|
|
PartHidden d -> f d
|
|
where f = getPartSeqDescPositionName
|
|
|
|
dropSpaces :: MonadMultiState (CoreInterpreterState f out) m => m ()
|
|
dropSpaces = do
|
|
cis :: CoreInterpreterState f out <- mGet
|
|
case _cis_remainingInput cis of
|
|
InputString s -> mSet
|
|
$ cis { _cis_remainingInput = InputString $ dropWhile Char.isSpace s }
|
|
InputArgs{} -> return ()
|
|
|
|
inputToString :: Input -> String
|
|
inputToString (InputString s ) = s
|
|
inputToString (InputArgs ss) = List.unwords ss
|
|
|
|
dequeLookupRemove :: Eq k => k -> Deque (k, a) -> (Maybe a, Deque (k, 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 m => Maybe String -> m (Maybe CommandDesc)
|
|
takeCommandChild key = do
|
|
cmd <- mGet
|
|
let (r, children') = dequeLookupRemove key $ _cmd_children cmd
|
|
mSet cmd { _cmd_children = children' }
|
|
return r
|
|
|
|
-- | map over the @out@ type argument
|
|
mapOut :: (outa -> outb) -> CmdParser f outa a -> CmdParser f outb a
|
|
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 vis child act r ->
|
|
CmdParserChild s vis (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
|
|
CmdParserAlternatives desc alts r -> CmdParserAlternatives
|
|
desc
|
|
[ (predicate, mapOut f sub) | (predicate, sub) <- alts ]
|
|
r
|
|
|
|
-- cmdActionPartial :: CommandDesc out -> Either String out
|
|
-- cmdActionPartial = maybe (Left err) Right . _cmd_out
|
|
-- where
|
|
-- err = "command is missing implementation!"
|
|
--
|
|
-- cmdAction :: CmdParser out () -> String -> Either String out
|
|
-- cmdAction b s = case runCmdParser Nothing s b of
|
|
-- (_, Right cmd) -> cmdActionPartial cmd
|
|
-- (_, Left (ParsingError (out:_) _)) -> Left $ out
|
|
-- _ -> error "whoops"
|
|
--
|
|
-- cmdActionRun :: (CommandDesc () -> ParsingError -> out)
|
|
-- -> CmdParser out ()
|
|
-- -> String
|
|
-- -> out
|
|
-- cmdActionRun f p s = case runCmdParser Nothing s p of
|
|
-- (cmd, Right out) -> case _cmd_out out of
|
|
-- Just o -> o
|
|
-- Nothing -> f cmd (ParsingError ["command is missing implementation!"] "")
|
|
-- (cmd, Left err) -> f cmd err
|
|
|
|
wrapBoundDesc :: ManyUpperBound -> PartDesc -> PartDesc
|
|
wrapBoundDesc ManyUpperBound1 = PartOptional
|
|
wrapBoundDesc ManyUpperBoundN = PartMany
|
|
|
|
|
|
_descFixParents :: CommandDesc -> CommandDesc
|
|
_descFixParents = descFixParentsWithTopM Nothing
|
|
|
|
-- descFixParentsWithTop :: String -> CommandDesc a -> CommandDesc a
|
|
-- descFixParentsWithTop s = descFixParentsWithTopM (Just (s, emptyCommandDesc))
|
|
|
|
descFixParentsWithTopM
|
|
:: Maybe (Maybe String, CommandDesc) -> CommandDesc -> CommandDesc
|
|
descFixParentsWithTopM mTop topDesc = Data.Function.fix $ \fixed -> topDesc
|
|
{ _cmd_mParent = goUp fixed <$> (mTop <|> _cmd_mParent topDesc)
|
|
, _cmd_children = _cmd_children topDesc <&> goDown fixed
|
|
}
|
|
where
|
|
goUp
|
|
:: CommandDesc -> (Maybe String, CommandDesc) -> (Maybe String, CommandDesc)
|
|
goUp child (childName, parent) =
|
|
(,) childName $ Data.Function.fix $ \fixed -> parent
|
|
{ _cmd_mParent = goUp fixed <$> _cmd_mParent parent
|
|
, _cmd_children = _cmd_children parent <&> \(n, c) ->
|
|
if n == childName then (n, child) else (n, c)
|
|
}
|
|
goDown
|
|
:: CommandDesc -> (Maybe String, CommandDesc) -> (Maybe String, CommandDesc)
|
|
goDown parent (childName, child) =
|
|
(,) childName $ Data.Function.fix $ \fixed -> child
|
|
{ _cmd_mParent = Just (childName, parent)
|
|
, _cmd_children = _cmd_children child <&> goDown fixed
|
|
}
|
|
|
|
|
|
_tooLongText
|
|
:: Int -- max length
|
|
-> String -- alternative if actual length is bigger than max.
|
|
-> String -- text to print, if length is fine.
|
|
-> PP.Doc
|
|
_tooLongText i alt s = PP.text $ Bool.bool alt s $ null $ drop i s
|