packages feed

butcher 1.2.1.0 → 1.3.0.0

raw patch · 14 files changed

+1753/−1244 lines, 14 filesdep −eitherPVP ok

version bump matches the API change (PVP)

Dependencies removed: either

API changes (from Hackage documentation)

+ UI.Butcher.Monadic: addHelpCommand2 :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()
+ UI.Butcher.Monadic: addHelpCommandWith :: Applicative f => (CommandDesc a -> IO String) -> CommandDesc a -> CmdParser f (IO ()) ()
+ UI.Butcher.Monadic: addShellCompletionCommand :: CmdParser Identity (IO ()) () -> CmdParser Identity (IO ()) ()
+ UI.Butcher.Monadic: addShellCompletionCommand' :: (CommandDesc out -> CmdParser Identity (IO ()) ()) -> CmdParser Identity (IO ()) ()
+ UI.Butcher.Monadic.BuiltinCommands: addHelpCommand2 :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()
+ UI.Butcher.Monadic.BuiltinCommands: addHelpCommandWith :: Applicative f => (CommandDesc a -> IO String) -> CommandDesc a -> CmdParser f (IO ()) ()
+ UI.Butcher.Monadic.BuiltinCommands: addShellCompletionCommand :: CmdParser Identity (IO ()) () -> CmdParser Identity (IO ()) ()
+ UI.Butcher.Monadic.BuiltinCommands: addShellCompletionCommand' :: (CommandDesc out -> CmdParser Identity (IO ()) ()) -> CmdParser Identity (IO ()) ()
+ UI.Butcher.Monadic.Command: addCmdHidden :: Applicative f => String -> CmdParser f out () -> CmdParser f out ()
+ UI.Butcher.Monadic.Flag: [_flag_visibility] :: Flag p -> Visibility
+ UI.Butcher.Monadic.Flag: flagHidden :: Flag p
+ UI.Butcher.Monadic.Interactive: shellCompletionWords :: String -> CommandDesc () -> String -> [CompletionItem]
+ UI.Butcher.Monadic.Param: addParamRestOfInputRaw :: forall f out. (Applicative f) => String -> Param Void -> CmdParser f out Input
+ UI.Butcher.Monadic.Param: paramDirectory :: Param p
+ UI.Butcher.Monadic.Param: paramFile :: Param p
+ UI.Butcher.Monadic.Pretty: ppHelpDepthOne :: CommandDesc a -> Doc
+ UI.Butcher.Monadic.Pretty: ppUsageShortSub :: CommandDesc a -> Doc
+ UI.Butcher.Monadic.Types: PartHidden :: PartDesc -> PartDesc
+ UI.Butcher.Monadic.Types: [_cmd_visibility] :: CommandDesc out -> Visibility
+ UI.Butcher.Monadic.Types: emptyCommandDesc :: CommandDesc out
- UI.Butcher.Monadic: addHelpCommand :: Applicative f => CommandDesc () -> CmdParser f (IO ()) ()
+ UI.Butcher.Monadic: addHelpCommand :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()
- UI.Butcher.Monadic: cmd_out :: forall out_ag6K. Lens' (CommandDesc out_ag6K) (Maybe out_ag6K)
+ UI.Butcher.Monadic: cmd_out :: forall out_af6I. Lens' (CommandDesc out_af6I) (Maybe out_af6I)
- UI.Butcher.Monadic.BuiltinCommands: addHelpCommand :: Applicative f => CommandDesc () -> CmdParser f (IO ()) ()
+ UI.Butcher.Monadic.BuiltinCommands: addHelpCommand :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()
- UI.Butcher.Monadic.Flag: Flag :: Maybe Doc -> Maybe p -> Flag p
+ UI.Butcher.Monadic.Flag: Flag :: Maybe Doc -> Maybe p -> Visibility -> Flag p
- UI.Butcher.Monadic.Param: Param :: Maybe p -> Maybe Doc -> Maybe [p] -> Param p
+ UI.Butcher.Monadic.Param: Param :: Maybe p -> Maybe Doc -> Maybe [CompletionItem] -> Param p
- UI.Butcher.Monadic.Param: [_param_suggestions] :: Param p -> Maybe [p]
+ UI.Butcher.Monadic.Param: [_param_suggestions] :: Param p -> Maybe [CompletionItem]
- UI.Butcher.Monadic.Param: paramSuggestions :: [p] -> Param p
+ UI.Butcher.Monadic.Param: paramSuggestions :: [String] -> Param p
- UI.Butcher.Monadic.Pretty: ppPartDescUsage :: PartDesc -> Doc
+ UI.Butcher.Monadic.Pretty: ppPartDescUsage :: PartDesc -> Maybe Doc
- UI.Butcher.Monadic.Types: CommandDesc :: Maybe (Maybe String, CommandDesc out) -> Maybe Doc -> Maybe Doc -> [PartDesc] -> Maybe out -> Deque (Maybe String, CommandDesc out) -> CommandDesc out
+ UI.Butcher.Monadic.Types: CommandDesc :: Maybe (Maybe String, CommandDesc out) -> Maybe Doc -> Maybe Doc -> [PartDesc] -> Maybe out -> Deque (Maybe String, CommandDesc out) -> Visibility -> CommandDesc out
- UI.Butcher.Monadic.Types: PartSuggestion :: [String] -> PartDesc -> PartDesc
+ UI.Butcher.Monadic.Types: PartSuggestion :: [CompletionItem] -> PartDesc -> PartDesc
- UI.Butcher.Monadic.Types: cmd_out :: forall out_ag6K. Lens' (CommandDesc out_ag6K) (Maybe out_ag6K)
+ UI.Butcher.Monadic.Types: cmd_out :: forall out_af6I. Lens' (CommandDesc out_af6I) (Maybe out_af6I)

Files

ChangeLog.md view
@@ -1,5 +1,13 @@ # Revision history for butcher +## 1.3.0.0  -- February 2018++* Experimental: Hidden commandparts (do not appear in help)+* Experimental: Bash completion+* Add addHelpCommandWith to support user-defined column count+* Fix help document printing (ribbons)+* Fix completion behaviour+ ## 1.2.1.0  -- November 2017  * Fix bug in 'ppUsageWithHelp'
butcher.cabal view
@@ -1,5 +1,5 @@ name:                butcher-version:             1.2.1.0+version:             1.3.0.0 synopsis:            Chops a command or program invocation into digestable pieces. description:         See the <https://github.com/lspitzner/butcher/blob/master/README.md README> (it is properly formatted on github). license:             BSD3@@ -45,12 +45,11 @@     { base >=4.8 && <4.11     , free     , unsafe-    , microlens-    , microlens-th+    , microlens <0.5+    , microlens-th <0.5     , multistate     , pretty     , containers-    , either     , transformers     , mtl     , extra@@ -113,7 +112,6 @@     , multistate     , pretty     , containers-    , either     , transformers     , mtl     , extra
src-tests/TestMain.hs view
@@ -10,6 +10,7 @@  import UI.Butcher.Monadic import UI.Butcher.Monadic.Types+import UI.Butcher.Monadic.Interactive   @@ -31,7 +32,7 @@ simpleParseTest :: Spec simpleParseTest = do   it "failed parse 001" $ runCmdParser Nothing (InputString "foo") testCmd1-         `shouldSatisfy` Data.Either.Combinators.isLeft . snd+         `shouldSatisfy` Data.Either.isLeft . snd   it "toplevel" $ (testParse testCmd1 "" >>= _cmd_out)                   `shouldSatisfy` Maybe.isNothing   it "hasImpl 001" $ (testParse testCmd1 "abc" >>= _cmd_out)@@ -50,8 +51,8 @@     it "flag 2" $ testRun testCmd1 "abc --flong" `shouldBe` Right (Just 101)     it "flag 3" $ testRun testCmd1 "abc -f -f" `shouldBe` Right (Just 101)     it "flag 4" $ testRun testCmd1 "abc -f -g" `shouldBe` Right (Just 103)-    it "flag 5" $ testRun testCmd1 "abc -f -g -f" `shouldSatisfy` Data.Either.Combinators.isLeft -- no reordering-    it "flag 6" $ testRun testCmd1 "abc -g -f" `shouldSatisfy` Data.Either.Combinators.isLeft -- no reordering+    it "flag 5" $ testRun testCmd1 "abc -f -g -f" `shouldSatisfy` Data.Either.isLeft -- no reordering+    it "flag 6" $ testRun testCmd1 "abc -g -f" `shouldSatisfy` Data.Either.isLeft -- no reordering     it "flag 7" $ testRun testCmd1 "abc -g -g" `shouldBe` Right (Just 102)   describe "with reordering" $ do     it "cmd 1" $ testRun testCmd2 "abc" `shouldBe` Right (Just 100)@@ -117,6 +118,18 @@     it "case 4" $ testRun' testCmd7 "abc -f" `shouldBe` Right (Just (["abc"], 1))     it "case 5" $ testRun' testCmd7 "-g abc -f" `shouldBe` Right (Just (["abc"], 3))     it "case 6" $ testRun' testCmd7 "abc -g def" `shouldBe` Right (Just (["abc", "def"], 2))+  describe "completions" $ do+    it "case  1" $ testCompletion completionTestCmd "" `shouldBe` ""+    it "case  2" $ testCompletion completionTestCmd "a" `shouldBe` "bc"+    it "case  3" $ testCompletion completionTestCmd "abc" `shouldBe` "def"+    it "case  4" $ testCompletion completionTestCmd "abc " `shouldBe` "-"+    it "case  5" $ testCompletion completionTestCmd "abc -" `shouldBe` ""+    it "case  6" $ testCompletion completionTestCmd "abc --" `shouldBe` "flag"+    it "case  7" $ testCompletion completionTestCmd "abc -f" `shouldBe` ""+    it "case  8" $ testCompletion completionTestCmd "abcd" `shouldBe` "ef"+    it "case  9" $ testCompletion completionTestCmd "gh" `shouldBe` "i"+    it "case 10" $ testCompletion completionTestCmd "ghi" `shouldBe` ""+    it "case 11" $ testCompletion completionTestCmd "ghi " `shouldBe` "jkl"   @@ -201,6 +214,23 @@     when f $ WriterS.tell 1     when g $ WriterS.tell 2     pure args++completionTestCmd :: CmdParser Identity () ()+completionTestCmd = do+  addCmd "abc" $ do+    _ <- addSimpleBoolFlag "f" ["flag"] mempty+    addCmdImpl ()+  addCmd "abcdef" $ do+    _ <- addSimpleBoolFlag "f" ["flag"] mempty+    addCmdImpl ()+  addCmd "ghi" $ do+    addCmd "jkl" $ do+      addCmdImpl ()++testCompletion :: CmdParser Identity a () -> String -> String+testCompletion p inp = case runCmdParserExt Nothing (InputString inp) p of+  (cDesc, InputString cRest, _) -> simpleCompletion inp cDesc cRest+  _ -> error "wut"   testParse :: CmdParser Identity out () -> String -> Maybe (CommandDesc out)
src/UI/Butcher/Monadic.hs view
@@ -28,7 +28,11 @@   -- , test3     -- * Builtin commands   , addHelpCommand+  , addHelpCommand2+  , addHelpCommandWith   , addButcherDebugCommand+  , addShellCompletionCommand+  , addShellCompletionCommand'   , mapOut   ) where
src/UI/Butcher/Monadic/BuiltinCommands.hs view
@@ -1,8 +1,12 @@ -- | Some CmdParser actions that add predefined commands. module UI.Butcher.Monadic.BuiltinCommands   ( addHelpCommand+  , addHelpCommand2+  , addHelpCommandWith   , addHelpCommandShallow   , addButcherDebugCommand+  , addShellCompletionCommand+  , addShellCompletionCommand'   ) where @@ -21,6 +25,7 @@ import           UI.Butcher.Monadic.Internal.Core import           UI.Butcher.Monadic.Pretty import           UI.Butcher.Monadic.Param+import           UI.Butcher.Monadic.Interactive  import           System.IO @@ -29,19 +34,50 @@ -- | Adds a proper full help command. To obtain the 'CommandDesc' value, see -- 'UI.Butcher.Monadic.cmdRunParserWithHelpDesc' or -- 'UI.Butcher.Monadic.IO.mainFromCmdParserWithHelpDesc'.-addHelpCommand :: Applicative f => CommandDesc () -> CmdParser f (IO ()) ()-addHelpCommand desc = addCmd "help" $ do+--+-- > addHelpCommand = addHelpCommandWith+-- >   (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpShallow)+addHelpCommand :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()+addHelpCommand = addHelpCommandWith+  (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpShallow)++-- | Adds a proper full help command. In contrast to 'addHelpCommand',+-- this version is a bit more verbose about available subcommands as it+-- includes their synopses.+--+-- To obtain the 'CommandDesc' value, see+-- 'UI.Butcher.Monadic.cmdRunParserWithHelpDesc' or+-- 'UI.Butcher.Monadic.IO.mainFromCmdParserWithHelpDesc'.+--+-- > addHelpCommand2 = addHelpCommandWith+-- >   (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpDepthOne)+addHelpCommand2 :: Applicative f => CommandDesc a -> CmdParser f (IO ()) ()+addHelpCommand2 = addHelpCommandWith+  (pure . PP.renderStyle PP.style { PP.ribbonsPerLine = 1.0 } . ppHelpDepthOne)++-- | Adds a proper full help command, using the specified function to turn+-- the relevant subcommand's 'CommandDesc' into a String.+addHelpCommandWith+  :: Applicative f+  => (CommandDesc a -> IO String)+  -> CommandDesc a+  -> CmdParser f (IO ()) ()+addHelpCommandWith f desc = addCmd "help" $ do+  addCmdSynopsis "print help about this command"   rest <- addParamRestOfInput "SUBCOMMAND(s)" mempty   addCmdImpl $ do-    let parentDesc = maybe undefined snd (_cmd_mParent desc)-    let restWords  = List.words rest-    let descent :: [String] -> CommandDesc a -> CommandDesc a-        descent [] curDesc = curDesc-        descent (w:wr) curDesc =-          case List.lookup (Just w) $ Data.Foldable.toList $ _cmd_children curDesc of+    let restWords = List.words rest+    let+      descent :: [String] -> CommandDesc a -> CommandDesc a+      descent [] curDesc = curDesc+      descent (w:wr) curDesc =+        case+            List.lookup (Just w) $ Data.Foldable.toList $ _cmd_children curDesc+          of             Nothing    -> curDesc             Just child -> descent wr child-    print $ ppHelpShallow $ descent restWords parentDesc+    s <- f $ descent restWords desc+    putStrLn s  -- | Adds a help command that prints help for the command currently in context. --@@ -65,4 +101,71 @@   desc <- peekCmdDesc   addCmdImpl $ do     print $ maybe undefined snd (_cmd_mParent desc)++-- | Adds the "completion" command and several subcommands.+--+-- This command can be used in the following manner:+--+-- > $ source <(foo completion bash-script foo)+addShellCompletionCommand+  :: CmdParser Identity (IO ()) () -> CmdParser Identity (IO ()) ()+addShellCompletionCommand mainCmdParser = do+  addCmdHidden "completion" $ do+    addCmdSynopsis "utilites to enable bash-completion"+    addCmd "bash-script" $ do+      addCmdSynopsis "generate a bash script for completion functionality"+      exeName <- addParamString "EXENAME" mempty+      addCmdImpl $ do+        putStr $ completionScriptBash exeName+    addCmd "bash-gen" $ do+      addCmdSynopsis+        "generate possible completions for given input arguments"+      rest <- addParamRestOfInputRaw "REALCOMMAND" mempty+      addCmdImpl $ do+        let (cdesc, remaining, _result) =+              runCmdParserExt Nothing rest mainCmdParser+        let+          compls = shellCompletionWords (inputString rest)+                                        cdesc+                                        (inputString remaining)+        let lastWord =+              reverse $ takeWhile (not . Char.isSpace) $ reverse $ inputString+                rest+        putStrLn $ List.unlines $ compls <&> \case+          CompletionString s  -> s+          CompletionFile      -> "$(compgen -f -- " ++ lastWord ++ ")"+          CompletionDirectory -> "$(compgen -d -- " ++ lastWord ++ ")"+ where+  inputString (InputString s ) = s+  inputString (InputArgs   as) = List.unwords as++-- | Adds the "completion" command and several subcommands+--+-- This command can be used in the following manner:+--+-- > $ source <(foo completion bash-script foo)+addShellCompletionCommand'+  :: (CommandDesc out -> CmdParser Identity (IO ()) ())+  -> CmdParser Identity (IO ()) ()+addShellCompletionCommand' f = addShellCompletionCommand (f emptyCommandDesc)++completionScriptBash :: String -> String+completionScriptBash exeName =+  List.unlines+    $ [ "function _" ++ exeName ++ "()"+      , "{"+      , "  local IFS=$'\\n'"+      , "  COMPREPLY=()"+      , "  local result=$("+      ++ exeName+      ++ " completion bash-gen \"${COMP_WORDS[@]:1}\")"+      , "  for r in ${result[@]}; do"+      , "    local IFS=$'\\n '"+      , "    for s in $(eval echo ${r}); do"+      , "      COMPREPLY+=(${s})"+      , "    done"+      , "  done"+      , "}"+      , "complete -F _" ++ exeName ++ " " ++ exeName+      ] 
src/UI/Butcher/Monadic/Command.hs view
@@ -53,6 +53,7 @@  module UI.Butcher.Monadic.Command   ( addCmd+  , addCmdHidden   , addNullCmd   , addCmdImpl   , addCmdSynopsis
src/UI/Butcher/Monadic/Flag.hs view
@@ -15,6 +15,7 @@   , flagHelp   , flagHelpStr   , flagDefault+  , flagHidden   , addSimpleBoolFlag   , addSimpleCountFlag   , addSimpleFlagA@@ -74,13 +75,19 @@ -- | flag-description monoid. You probably won't need to use the constructor; -- mzero or any (<>) of flag(Help|Default) works well. data Flag p = Flag-  { _flag_help    :: Maybe PP.Doc-  , _flag_default :: Maybe p+  { _flag_help       :: Maybe PP.Doc+  , _flag_default    :: Maybe p+  , _flag_visibility :: Visibility   }  instance Monoid (Flag p) where-  mempty = Flag Nothing Nothing-  Flag a1 b1 `mappend` Flag a2 b2 = Flag (a1 <|> a2) (b1 <|> b2)+  mempty = Flag Nothing Nothing Visible+  Flag a1 b1 c1 `mappend` Flag a2 b2 c2 = Flag (a1 <|> a2)+                                               (b1 <|> b2)+                                               (appVis c1 c2)+   where+    appVis Visible Visible = Visible+    appVis _       _       = Hidden  -- | Create a 'Flag' with just a help text. flagHelp :: PP.Doc -> Flag p@@ -88,12 +95,25 @@  -- | Create a 'Flag' with just a help text. flagHelpStr :: String -> Flag p-flagHelpStr s = mempty { _flag_help = Just $ PP.text s }+flagHelpStr s =+  mempty { _flag_help = Just $ PP.fsep $ fmap PP.text $ List.words s }  -- | Create a 'Flag' with just a default value. flagDefault :: p -> Flag p flagDefault d = mempty { _flag_default = Just d } +-- | Create a 'Flag' marked as hidden. Similar to hidden commands, hidden+-- flags will not included in pretty-printing (help, usage etc.)+--+-- This feature is not well tested yet.+flagHidden :: Flag p+flagHidden = mempty { _flag_visibility = Hidden }++wrapHidden :: Flag p -> PartDesc -> PartDesc+wrapHidden f = case _flag_visibility f of+  Visible -> id+  Hidden  -> PartHidden+ -- | A no-parameter flag where non-occurence means False, occurence means True. addSimpleBoolFlag   :: Applicative f@@ -121,7 +141,7 @@   -> f ()   -> CmdParser f out Bool addSimpleBoolFlagAll shorts longs flag a = fmap (not . null)-  $ addCmdPartManyA ManyUpperBound1 desc parseF (\() -> a)+  $ addCmdPartManyA ManyUpperBound1 (wrapHidden flag desc) parseF (\() -> a)  where   allStrs = fmap (\c -> "-" ++ [c]) shorts ++ fmap (\s -> "--" ++ s) longs   desc :: PartDesc@@ -148,7 +168,7 @@                    -> Flag Void -- ^ properties                    -> CmdParser f out Int addSimpleCountFlag shorts longs flag = fmap length-  $ addCmdPartMany ManyUpperBoundN desc parseF+  $ addCmdPartMany ManyUpperBoundN (wrapHidden flag desc) parseF  where     -- we _could_ allow this to parse repeated short flags, like "-vvv"     -- (meaning "-v -v -v") correctly.@@ -179,7 +199,7 @@   -> Flag p -- ^ properties   -> CmdParser f out p addFlagReadParam shorts longs name flag =-  addCmdPartInpA desc parseF (\_ -> pure ())+  addCmdPartInpA (wrapHidden flag desc) parseF (\_ -> pure ())  where   allStrs =     [ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]@@ -261,7 +281,7 @@      -> CmdParser f out [p] addFlagReadParamsAll shorts longs name flag act = addCmdPartManyInpA   ManyUpperBoundN-  desc+  (wrapHidden flag desc)   parseF   act  where@@ -310,9 +330,8 @@      -> String -- ^ param name      -> Flag String -- ^ properties      -> CmdParser f out String-addFlagStringParam shorts longs name flag = addCmdPartInpA desc-                                                           parseF-                                                           (\_ -> pure ())+addFlagStringParam shorts longs name flag =+  addCmdPartInpA (wrapHidden flag desc) parseF (\_ -> pure ())  where   allStrs =     [ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]@@ -389,7 +408,7 @@      -> CmdParser f out [String] addFlagStringParamsAll shorts longs name flag act = addCmdPartManyInpA   ManyUpperBoundN-  desc+  (wrapHidden flag desc)   parseF   act  where
src/UI/Butcher/Monadic/Interactive.hs view
@@ -2,6 +2,7 @@ -- e.g. a REPL. module UI.Butcher.Monadic.Interactive   ( simpleCompletion+  , shellCompletionWords   , interactiveHelpDoc   , partDescStrings   )@@ -29,34 +30,72 @@                     -- subcommand. See 'UI.Butcher.Monadic.runCmdParserExt'.   -> String         -- ^ completion, i.e. a string that might be appended                     -- to the current prompt when user presses tab.-simpleCompletion line cdesc pcRest =-  List.drop (List.length lastWord) $ case choices of-    [] -> ""-    (c1:cr) ->-      case-          filter (\s -> List.all (s`isPrefixOf`) cr) $ reverse $ List.inits c1-        of-          []    -> ""-          (x:_) -> x+simpleCompletion line cdesc pcRest = List.drop (List.length lastWord)+  $ longestCommonPrefix choices  where+  longestCommonPrefix [] = ""+  longestCommonPrefix (c1:cr) =+    case find (\s -> List.all (s `isPrefixOf`) cr) $ reverse $ List.inits c1 of+      Nothing -> ""+      Just x  -> x   nameDesc = case _cmd_mParent cdesc of-    Nothing                        -> cdesc-    Just (_, parent) | null pcRest -> parent-    Just{}                         -> cdesc+    Nothing -> cdesc+    Just (_, parent) | null pcRest && not (null lastWord) -> parent+        -- not finished writing a command. if we have commands abc and abcdef,+        -- we may want "def" as a completion after "abc".+    Just{}  -> cdesc   lastWord = reverse $ takeWhile (not . Char.isSpace) $ reverse $ line   choices :: [String]   choices = join     [ [ r       | (Just r, _) <- Data.Foldable.toList (_cmd_children nameDesc)       , lastWord `isPrefixOf` r+      , lastWord /= r       ]     , [ s       | s <- partDescStrings =<< _cmd_parts nameDesc       , lastWord `isPrefixOf` s+      , lastWord /= s       ]     ]  +-- | Derives a list of completion items from a given input string and a given+-- 'CommandDesc'. Considers potential subcommands and where available the+-- completion info present in 'PartDesc's.+--+-- See 'addShellCompletion' which uses this.+shellCompletionWords+  :: String         -- ^ input string+  -> CommandDesc () -- ^ CommandDesc obtained on that input string+  -> String         -- ^ "remaining" input after the last successfully parsed+                    -- subcommand. See 'UI.Butcher.Monadic.runCmdParserExt'.+  -> [CompletionItem]+shellCompletionWords line cdesc pcRest = choices+ where+  nameDesc = case _cmd_mParent cdesc of+    Nothing -> cdesc+    Just (_, parent) | null pcRest && not (null lastWord) -> parent+        -- not finished writing a command. if we have commands abc and abcdef,+        -- we may want "def" as a completion after "abc".+    Just{}  -> cdesc+  lastWord = reverse $ takeWhile (not . Char.isSpace) $ reverse $ line+  choices :: [CompletionItem]+  choices = join+    [ [ CompletionString r+      | (Just r, _) <- Data.Foldable.toList (_cmd_children nameDesc)+      , lastWord `isPrefixOf` r+      , lastWord /= r+      ]+    , [ c+      | c <- partDescCompletions =<< _cmd_parts cdesc+      , case c of+        CompletionString s -> lastWord `isPrefixOf` s && lastWord /= s+        _                  -> True+      ]+    ]++ -- | Produces a 'PP.Doc' as a hint for the user during interactive command -- input. Takes the current (incomplete) prompt line into account. For example -- when you have commands (among others) \'config set-email\' and@@ -124,9 +163,32 @@   PartSeq      []     -> []   PartSeq      (x:_)  -> partDescStrings x   PartDefault    _  x -> partDescStrings x-  PartSuggestion ss x -> [ s | s <- ss ] ++ partDescStrings x+  PartSuggestion ss x -> [ s | CompletionString s <- ss ] ++ partDescStrings x   PartRedirect   _  x -> partDescStrings x   PartReorder xs      -> xs >>= partDescStrings   PartMany    x       -> partDescStrings x   PartWithHelp _h x   -> partDescStrings x -- TODO: handle help+  PartHidden{}        -> [] ++-- | Obtains a list of "expected"/potential strings for a command part+-- described in the 'PartDesc'. In constrast to the 'simpleCompletion'+-- function this function does not take into account any current input, and+-- consequently the output elements can in general not be appended to partial+-- input to form valid input.+partDescCompletions :: PartDesc -> [CompletionItem]+partDescCompletions = \case+  PartLiteral  s      -> [CompletionString s]+  PartVariable _      -> []+  -- TODO: we could handle seq of optional and such much better+  PartOptional x      -> partDescCompletions x+  PartAlts     alts   -> alts >>= partDescCompletions+  PartSeq      []     -> []+  PartSeq      (x:_)  -> partDescCompletions x+  PartDefault    _  x -> partDescCompletions x+  PartSuggestion ss x -> ss ++ partDescCompletions x+  PartRedirect   _  x -> partDescCompletions x+  PartReorder xs      -> xs >>= partDescCompletions+  PartMany    x       -> partDescCompletions x+  PartWithHelp _h x   -> partDescCompletions x -- TODO: handle help+  PartHidden{}        -> []
src/UI/Butcher/Monadic/Internal/Core.hs view
@@ -5,1153 +5,1245 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TemplateHaskell #-}--module UI.Butcher.Monadic.Internal.Core-  ( addCmdSynopsis-  , addCmdHelp-  , addCmdHelpStr-  , peekCmdDesc-  , peekInput-  , addCmdPart-  , addCmdPartA-  , addCmdPartMany-  , addCmdPartManyA-  , addCmdPartInp-  , addCmdPartInpA-  , addCmdPartManyInp-  , addCmdPartManyInpA-  , addCmd-  , addNullCmd-  , addCmdImpl-  , reorderStart-  , reorderStop-  , checkCmdParser-  , runCmdParser-  , runCmdParserExt-  , runCmdParserA-  , runCmdParserAExt-  , mapOut-  )-where----#include "prelude.inc"-import           Control.Monad.Free-import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS-import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS--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.Monadic.Internal.Types------ 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;--- 'checkCmdParser' 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;--- 'checkCmdParser' 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) ()---- | Semi-hacky way of accessing the output CommandDesc from inside of a--- 'CmdParser'. This is not implemented via knot-tying, i.e. the CommandDesc--- you get is _not_ equivalent to the CommandDesc returned by 'runCmdParser'.--- Also see 'runCmdParserWithHelpDesc' which does knot-tying.------ 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 ())-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-  -> (String -> Maybe (p, String))-  -> CmdParser f out p-addCmdPart p f = liftF $ CmdParserPart p f (\_ -> pure ()) id--addCmdPartA-  :: (Typeable p)-  => PartDesc-  -> (String -> Maybe (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-  -> (String -> Maybe (p, String))-  -> CmdParser f out [p]-addCmdPartMany b p f = liftF $ CmdParserPartMany b p f (\_ -> pure ()) id--addCmdPartManyA-  :: (Typeable p)-  => ManyUpperBound-  -> PartDesc-  -> (String -> Maybe (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-  -> (Input -> Maybe (p, Input))-  -> CmdParser f out p-addCmdPartInp p f = liftF $ CmdParserPartInp p f (\_ -> pure ()) id--addCmdPartInpA-  :: (Typeable p)-  => PartDesc-  -> (Input -> Maybe (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-  -> (Input -> Maybe (p, Input))-  -> CmdParser f out [p]-addCmdPartManyInp b p f = liftF $ CmdParserPartManyInp b p f (\_ -> pure ()) id--addCmdPartManyInpA-  :: (Typeable p)-  => ManyUpperBound-  -> PartDesc-  -> (Input -> Maybe (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) sub (pure ()) ()---- | 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 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 'checkCmdParser' 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 ()---- 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 (String -> Maybe (p, String))-                            (Input  -> Maybe (p, Input))-    , _pgd_act    :: p -> f ()-    , _pgd_many   :: Bool-    }--data ChildGather f out = ChildGather (Maybe String) (CmdParser f out ()) (f ())--type PartParsedData = Map Int [Dynamic]--data CmdDescStack = StackBottom (Deque PartDesc)-                  | StackLayer  (Deque PartDesc) String CmdDescStack--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.-checkCmdParser :: forall f out-                . Maybe String -- ^ top-level command name-               -> CmdParser f out () -- ^ parser to check-               -> Either String (CommandDesc ())-checkCmdParser mTopLevel cmdParser-    = (>>= final)-    $ MultiRWSS.runMultiRWSTNil-    $ MultiRWSS.withMultiStateAS (StackBottom mempty)-    $ MultiRWSS.withMultiStateS emptyCommandDesc-    $ processMain cmdParser-  where-    final :: (CommandDesc out, 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 ()-                -> MultiRWSS.MultiRWST '[] '[] '[CommandDesc out, CmdDescStack] (Either String) ()-    processMain = \case-      Pure x -> return x-      Free (CmdParserHelp h next) -> do-        cmd :: CommandDesc out <- mGet-        mSet $ cmd { _cmd_help = Just h }-        processMain next-      Free (CmdParserSynopsis s next) -> do-        cmd :: CommandDesc out <- mGet-        mSet $ cmd { _cmd_synopsis = Just $ PP.text 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 sub _act next) -> do-        mInitialDesc <- takeCommandChild cmdStr-        cmd :: CommandDesc out <- mGet-        subCmd <- do-          stackCur :: CmdDescStack <- mGet-          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 $ Data.Foldable.toList descs-            StackLayer _ _ _ -> lift $ Left "unclosed ReorderStart or GroupStart"-          return c { _cmd_parts = subParts }-        mSet $ cmd-          { _cmd_children = (cmdStr, subCmd) `Deque.snoc` _cmd_children cmd-          }-        processMain next-      Free (CmdParserImpl out next) -> do-        cmd_out .=+ Just out-        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--    monadMisuseError :: a-    monadMisuseError = error "CmdParser definition error - used Monad powers where only Applicative/Arrow is allowed"--newtype PastCommandInput = PastCommandInput Input----- | 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) 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).-runCmdParser-  :: Maybe String -- ^ program name to be used for the top-level @CommandDesc@-  -> Input -- ^ input to be processed-  -> CmdParser Identity out () -- ^ parser to use-  -> (CommandDesc (), Either ParsingError (CommandDesc out))-runCmdParser mTopLevel inputInitial cmdParser-  = runIdentity-  $ runCmdParserA mTopLevel inputInitial cmdParser---- | Like 'runCmdParser', but also returning all input after the last--- successfully parsed subcommand. E.g. 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.-runCmdParserExt-  :: Maybe String -- ^ program name to be used for the top-level @CommandDesc@-  -> Input -- ^ input to be processed-  -> CmdParser Identity out () -- ^ parser to use-  -> (CommandDesc (), Input, Either ParsingError (CommandDesc out))-runCmdParserExt mTopLevel inputInitial cmdParser-  = runIdentity-  $ runCmdParserAExt mTopLevel inputInitial cmdParser---- | The Applicative-enabled version of 'runCmdParser'.-runCmdParserA :: forall f out-               . Applicative f-              => Maybe String -- ^ program name to be used for the top-level @CommandDesc@-              -> Input -- ^ input to be processed-              -> CmdParser f out () -- ^ parser to use-              -> f ( CommandDesc ()-                   , Either ParsingError (CommandDesc out)-                   )-runCmdParserA mTopLevel inputInitial cmdParser =-  (\(x, _, z) -> (x, z)) <$> runCmdParserAExt mTopLevel inputInitial cmdParser---- | The Applicative-enabled version of 'runCmdParserExt'.-runCmdParserAExt-  :: forall f out . Applicative f-  => Maybe String -- ^ program name to be used for the top-level @CommandDesc@-  -> Input -- ^ input to be processed-  -> CmdParser f out () -- ^ parser to use-  -> f (CommandDesc (), Input, Either ParsingError (CommandDesc out))-runCmdParserAExt mTopLevel inputInitial cmdParser-    = runIdentity-    $ MultiRWSS.runMultiRWSTNil-    $ (<&> captureFinal)-    $ MultiRWSS.withMultiWriterWA-    $ MultiRWSS.withMultiStateA cmdParser-    $ MultiRWSS.withMultiStateSA (StackBottom mempty)-    $ MultiRWSS.withMultiStateSA inputInitial-    $ MultiRWSS.withMultiStateSA (PastCommandInput inputInitial)-    $ MultiRWSS.withMultiStateSA initialCommandDesc-    $ processMain cmdParser-  where-    initialCommandDesc = emptyCommandDesc-      { _cmd_mParent = mTopLevel <&> \n -> (Just n, emptyCommandDesc) }-    captureFinal-      :: ([String], (CmdDescStack, (Input, (PastCommandInput, (CommandDesc out, f())))))-      -> f (CommandDesc (), Input, Either ParsingError (CommandDesc out))-    captureFinal (errs, (descStack, (inputRest, (PastCommandInput pastCmdInput, (cmd, act))))) =-        act $> (() <$ cmd', pastCmdInput, res)-      where-        errs' = errs ++ inputErrs ++ stackErrs-        inputErrs = case inputRest of-          InputString s | all Char.isSpace s -> []-          InputString{} -> ["could not parse input/unprocessed input"]-          InputArgs [] -> []-          InputArgs{} -> ["could not parse input/unprocessed input"]-        stackErrs = case descStack of-          StackBottom{} -> []-          _ -> ["butcher interface error: unclosed group"]-        cmd' = postProcessCmd descStack cmd-        res = if null errs'-          then Right cmd'-          else Left $ ParsingError errs' inputRest-    processMain :: CmdParser f out ()-                -> MultiRWSS.MultiRWS-                     '[]-                     '[[String]]-                     '[CommandDesc out, PastCommandInput, Input, CmdDescStack, CmdParser f out ()]-                     (f ())-    processMain = \case-      Pure () -> return $ pure $ ()-      Free (CmdParserHelp h next) -> do-        cmd :: CommandDesc out <- mGet-        mSet $ cmd { _cmd_help = Just h }-        processMain next-      Free (CmdParserSynopsis s next) -> do-        cmd :: CommandDesc out <- mGet-        mSet $ cmd { _cmd_synopsis = Just $ PP.text s }-        processMain next-      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-        processMain $ nextF $ () <$ postProcessCmd stack cmd-      Free (CmdParserPeekInput nextF) -> do-        processMain $ nextF $ inputToString inputInitial-      Free (CmdParserPart desc parseF actF nextF) -> do-        do-          descStack <- mGet-          mSet $ descStackAdd desc descStack-        input <- mGet-        case input of-          InputString str -> case parseF str of-            Just (x, rest) -> do-              mSet $ InputString rest-              actRest <- processMain $ nextF x-              return $ actF x *> actRest-            Nothing -> do-              mTell ["could not parse " ++ getPartSeqDescPositionName desc]-              processMain $ nextF monadMisuseError-          InputArgs (str:strr) -> case parseF str of-            Just (x, "") -> do-              mSet $ InputArgs strr-              actRest <- processMain $ nextF x-              return $ actF x *> actRest-            _ -> do-              mTell ["could not parse " ++ getPartSeqDescPositionName desc]-              processMain $ nextF monadMisuseError-          InputArgs [] -> do-            mTell ["could not parse " ++ getPartSeqDescPositionName desc]-            processMain $ nextF monadMisuseError-      Free (CmdParserPartInp desc parseF actF nextF) -> do-        do-          descStack <- mGet-          mSet $ descStackAdd desc descStack-        input <- mGet-        case parseF input of-          Just (x, rest) -> do-            mSet $ rest-            actRest <- processMain $ nextF x-            return $ actF x *> actRest-          Nothing -> do-            mTell ["could not parse " ++ getPartSeqDescPositionName desc]-            processMain $ nextF monadMisuseError-      Free (CmdParserPartMany bound desc parseF actF nextF) -> do-        do-          descStack <- mGet-          mSet $ descStackAdd (wrapBoundDesc bound desc) descStack-        let proc = do-              dropSpaces-              input <- mGet-              case input of-                InputString str -> case parseF str of-                  Just (x, r) -> do-                    mSet $ InputString r-                    xr <- proc-                    return $ x:xr-                  Nothing -> return []-                InputArgs (str:strr) -> case parseF str of-                  Just (x, "") -> do-                    mSet $ InputArgs strr-                    xr <- proc-                    return $ x:xr-                  _ -> return []-                InputArgs [] -> return []-        r <- proc-        let act = traverse actF r-        (act *>) <$> processMain (nextF $ r)-      Free (CmdParserPartManyInp bound desc parseF actF nextF) -> do-        do-          descStack <- mGet-          mSet $ descStackAdd (wrapBoundDesc bound desc) descStack-        let proc = do-              dropSpaces-              input <- mGet-              case parseF input of-                Just (x, r) -> do-                  mSet $ r-                  xr <- proc-                  return $ x:xr-                Nothing -> return []-        r <- proc-        let act = traverse actF r-        (act *>) <$> processMain (nextF $ r)-      f@(Free (CmdParserChild _ _ _ _)) -> do-        dropSpaces-        input <- mGet-        (gatheredChildren :: [ChildGather f out], restCmdParser) <--          MultiRWSS.withMultiWriterWA $ childrenGather f-        let-          child_fold-            :: (Deque (Maybe String), Map (Maybe String) (CmdParser f out (), f ()))-            -> ChildGather f out-            -> (Deque (Maybe String), Map (Maybe 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 <&> \(mname, (child, act)) ->-              case (mname, input) of-                (Just name, InputString str) | name == str ->-                  Just $ (Just name, child, act, InputString "")-                (Just name, InputString str) | (name++" ") `isPrefixOf` str ->-                  Just $ (Just name, child, act, InputString $ drop (length name + 1) str)-                (Just name, InputArgs (str:strr)) | name == str ->-                  Just $ (Just name, child, act, InputArgs strr)-                (Nothing, _) ->-                  Just $ (Nothing, child, act, input)-                _ -> Nothing-        case mRest of-          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-              { _cmd_mParent = Just (name, cmd)-              }-            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 mempty groupName stackCur-        processMain $ next-      Free (CmdParserGroupEnd next) -> do-        stackCur <- mGet-        case stackCur of-          StackBottom{} -> do-            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 (Data.Foldable.toList descs))) up-            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 (Int, Dynamic, Input, Bool, f ())-          tryParsePartData input (PartGatherData pid _ pfe act allowMany) =-            First [ (pid, toDyn r, rest, allowMany, act r)-                  | (r, rest) <- case pfe of-                      Left pfStr -> case input of-                        InputString str -> case pfStr str of-                          Just (x, r) | r/=str -> Just (x, InputString r)-                          _ -> Nothing-                        InputArgs (str:strr) -> case pfStr str of-                          Just (x, "") -> Just (x, InputArgs strr)-                          _ -> Nothing-                        InputArgs [] -> Nothing-                      Right pfInp -> case pfInp input of-                        Just (x, r) | r/=input -> Just (x, r)-                        _ -> Nothing-                  ]-          parseLoop = do-            input <- mGet-            m :: Map Int (PartGatherData f) <- mGet-            case getFirst $ Data.Foldable.foldMap (tryParsePartData input) m of-                       -- i will be angry if foldMap ever decides to not fold-                       -- in order of keys.-              Nothing -> return $ pure ()-              Just (pid, x, rest, more, act) -> do-                mSet rest-                mModify $ MapS.insertWith (++) pid [x]-                when (not more) $ do-                  mSet $ MapS.delete pid m-                actRest <- parseLoop-                return $ act *> actRest-        (finalMap, (fr, acts)) <- MultiRWSS.withMultiStateSA (MapS.empty :: PartParsedData)-                                $ MultiRWSS.withMultiStateA reorderMapInit-                                $ do-          acts <- parseLoop -- filling the map-          stackCur <- mGet-          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-        -- 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--    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-      where-        restCase = do-          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-         , MonadMultiState PartParsedData m-         , MonadMultiState (Map Int (PartGatherData f)) m-         , MonadMultiState Input m-         , MonadMultiState (CommandDesc out) m-         , MonadMultiWriter [[Char]] m-         , m ~ MultiRWSS.MultiRWST r w s m0-         , ContainsType (CmdParser f out ()) s-         , ContainsType CmdDescStack s-         , 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-        stackCur <- mGet-        case stackCur of-          StackBottom{} -> do-            mTell ["unexpected stackBottom"]-          StackLayer descs _ up -> do-            mSet $ descStackAdd (PartReorder (Data.Foldable.toList descs)) up-        return next-      Free (CmdParserGrouped groupName next) -> do-        stackCur <- mGet-        mSet $ StackLayer mempty groupName stackCur-        processParsedParts $ next-      Free (CmdParserGroupEnd next) -> do-        stackCur <- mGet-        case stackCur of-          StackBottom{} -> do-            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 (Data.Foldable.toList descs))) up-            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-          do-            stackCur <- mGet-            mSet $ descStackAdd desc stackCur-          pid <- mGet-          mSet $ pid + 1-          parsedMap :: PartParsedData <- mGet-          mSet $ MapS.delete pid parsedMap-          partMap :: Map Int (PartGatherData f) <- mGet-          input :: Input <- mGet-          let errorResult = do-                mTell ["could not parse expected input "-                    ++ getPartSeqDescPositionName desc-                    ++ " with remaining input: "-                    ++ show input-                    ]-                failureCurrentShallowRerun-                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-                  Nothing -> errorResult-                  Just (dx, _) -> continueOrMisuse $ cast dx-                Right pf -> case pf (InputArgs []) of-                  Nothing -> errorResult-                  Just (dx, _) -> continueOrMisuse $ cast dx-            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-          do-            stackCur <- mGet-            mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur-          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--    -- this does no error reporting at all.-    -- user needs to use check for that purpose instead.-    processCmdShallow :: ( MonadMultiState (CommandDesc out) m-                         , MonadMultiState CmdDescStack m-                         )-                      => CmdParserF f out (m ())-                      -> m ()-    processCmdShallow = \case-      CmdParserHelp h next -> do-        cmd :: CommandDesc out <- mGet-        mSet $ cmd { _cmd_help = Just h }-        next-      CmdParserSynopsis s next -> do-        cmd :: CommandDesc out <- mGet-        mSet $ cmd { _cmd_synopsis = Just $ PP.text s }-        next-      CmdParserPeekDesc nextF -> do-        mGet >>= nextF . fmap (\(_ :: out) -> ())-      CmdParserPeekInput nextF -> do-        nextF $ inputToString inputInitial-      CmdParserPart desc _parseF _act nextF -> do-        do-          stackCur <- mGet-          mSet $ descStackAdd desc stackCur-        nextF monadMisuseError-      CmdParserPartInp desc _parseF _act nextF -> do-        do-          stackCur <- mGet-          mSet $ descStackAdd desc stackCur-        nextF monadMisuseError-      CmdParserPartMany bound desc _parseF _act nextF -> do-        do-          stackCur <- mGet-          mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur-        nextF monadMisuseError-      CmdParserPartManyInp bound desc _parseF _act nextF -> do-        do-          stackCur <- mGet-          mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur-        nextF monadMisuseError-      CmdParserChild cmdStr _sub _act next -> do-        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 mempty groupName stackCur-        next-      CmdParserGroupEnd next -> do-        stackCur <- mGet-        case stackCur of-          StackBottom{} -> do-            return ()-          StackLayer _descs "" _up -> do-            return ()-          StackLayer descs groupName up -> do-            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 (Data.Foldable.toList descs)) up-          StackLayer{} -> return ()-        next-      CmdParserReorderStart next -> do-        stackCur <- mGet-        mSet $ StackLayer mempty "" stackCur-        next--    failureCurrentShallowRerun-      :: ( m ~ MultiRWSS.MultiRWST r w s m0-         , MonadMultiState (CmdParser f out ()) m-         , MonadMultiState (CommandDesc out) m-         , ContainsType CmdDescStack s-         , Monad m0-         )-      => m ()-    failureCurrentShallowRerun = do-      parser <- mGet-      cmd :: CommandDesc out -        <- MultiRWSS.withMultiStateS emptyCommandDesc-         $ iterM processCmdShallow parser-      mSet cmd--    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--      where-        f = getPartSeqDescPositionName--    dropSpaces :: MonadMultiState Input m => m ()-    dropSpaces = do-      inp <- mGet-      case inp of-        InputString s -> mSet $ 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 out) m-  => Maybe 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---- | map over the @out@ type argument-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---   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 a -> CommandDesc a-descFixParents = descFixParentsWithTopM Nothing---- descFixParentsWithTop :: String -> CommandDesc a -> CommandDesc a--- descFixParentsWithTop s = descFixParentsWithTopM (Just (s, emptyCommandDesc))--descFixParentsWithTopM :: Maybe (Maybe String, CommandDesc a) -> CommandDesc a -> CommandDesc a-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 a -> (Maybe String, CommandDesc a) -> (Maybe String, CommandDesc a)-    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 a -> (Maybe String, CommandDesc a) -> (Maybe String, CommandDesc a)-    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+module UI.Butcher.Monadic.Internal.Core+  ( addCmdSynopsis+  , addCmdHelp+  , addCmdHelpStr+  , peekCmdDesc+  , peekInput+  , addCmdPart+  , addCmdPartA+  , addCmdPartMany+  , addCmdPartManyA+  , addCmdPartInp+  , addCmdPartInpA+  , addCmdPartManyInp+  , addCmdPartManyInpA+  , addCmd+  , addCmdHidden+  , addNullCmd+  , addCmdImpl+  , reorderStart+  , reorderStop+  , checkCmdParser+  , runCmdParser+  , runCmdParserExt+  , runCmdParserA+  , runCmdParserAExt+  , mapOut+  )+where++++#include "prelude.inc"+import           Control.Monad.Free+import qualified Control.Monad.Trans.MultiRWS.Strict+                                               as MultiRWSS+import qualified Control.Monad.Trans.MultiState.Strict+                                               as MultiStateS++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.Monadic.Internal.Types++++-- 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;+-- 'checkCmdParser' 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;+-- 'checkCmdParser' 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) ()++-- | Semi-hacky way of accessing the output CommandDesc from inside of a+-- 'CmdParser'. This is not implemented via knot-tying, i.e. the CommandDesc+-- you get is _not_ equivalent to the CommandDesc returned by 'runCmdParser'.+-- Also see 'runCmdParserWithHelpDesc' which does knot-tying.+--+-- 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 ())+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+  -> (String -> Maybe (p, String))+  -> CmdParser f out p+addCmdPart p f = liftF $ CmdParserPart p f (\_ -> pure ()) id++addCmdPartA+  :: (Typeable p)+  => PartDesc+  -> (String -> Maybe (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+  -> (String -> Maybe (p, String))+  -> CmdParser f out [p]+addCmdPartMany b p f = liftF $ CmdParserPartMany b p f (\_ -> pure ()) id++addCmdPartManyA+  :: (Typeable p)+  => ManyUpperBound+  -> PartDesc+  -> (String -> Maybe (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+  -> (Input -> Maybe (p, Input))+  -> CmdParser f out p+addCmdPartInp p f = liftF $ CmdParserPartInp p f (\_ -> pure ()) id++addCmdPartInpA+  :: (Typeable p)+  => PartDesc+  -> (Input -> Maybe (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+  -> (Input -> Maybe (p, Input))+  -> CmdParser f out [p]+addCmdPartManyInp b p f = liftF $ CmdParserPartManyInp b p f (\_ -> pure ()) id++addCmdPartManyInpA+  :: (Typeable p)+  => ManyUpperBound+  -> PartDesc+  -> (Input -> Maybe (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 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 'checkCmdParser' 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 ()++-- 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 (String -> Maybe (p, String))+                            (Input  -> Maybe (p, Input))+    , _pgd_act    :: p -> f ()+    , _pgd_many   :: Bool+    }++data ChildGather f out =+  ChildGather (Maybe String) Visibility (CmdParser f out ()) (f ())++type PartParsedData = Map Int [Dynamic]++data CmdDescStack = StackBottom (Deque PartDesc)+                  | StackLayer  (Deque PartDesc) String CmdDescStack++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.+checkCmdParser+  :: forall f out+   . Maybe String -- ^ top-level command name+  -> CmdParser f out () -- ^ parser to check+  -> Either String (CommandDesc ())+checkCmdParser mTopLevel cmdParser =+  (>>= final)+    $ MultiRWSS.runMultiRWSTNil+    $ MultiRWSS.withMultiStateAS (StackBottom mempty)+    $ MultiRWSS.withMultiStateS emptyCommandDesc+    $ processMain cmdParser+ where+  final :: (CommandDesc out, 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 ()+    -> MultiRWSS.MultiRWST+         '[]+         '[]+         '[CommandDesc out, CmdDescStack]+         (Either String)+         ()+  processMain = \case+    Pure x                      -> return x+    Free (CmdParserHelp h next) -> do+      cmd :: CommandDesc out <- mGet+      mSet $ cmd { _cmd_help = Just h }+      processMain next+    Free (CmdParserSynopsis s next) -> do+      cmd :: CommandDesc out <- 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 out <- mGet+      subCmd                 <- do+        stackCur :: CmdDescStack <- mGet+        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 $ 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+      cmd_out .=+ Just out+      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++  monadMisuseError :: a+  monadMisuseError =+    error+      $  "CmdParser definition error -"+      ++ " used Monad powers where only Applicative/Arrow is allowed"++newtype PastCommandInput = PastCommandInput Input+++-- | 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) 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).+runCmdParser+  :: Maybe String -- ^ program name to be used for the top-level @CommandDesc@+  -> Input -- ^ input to be processed+  -> CmdParser Identity out () -- ^ parser to use+  -> (CommandDesc (), Either ParsingError (CommandDesc out))+runCmdParser mTopLevel inputInitial cmdParser =+  runIdentity $ runCmdParserA mTopLevel inputInitial cmdParser++-- | Like 'runCmdParser', but also returning all input after the last+-- successfully parsed subcommand. E.g. 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.+runCmdParserExt+  :: Maybe String -- ^ program name to be used for the top-level @CommandDesc@+  -> Input -- ^ input to be processed+  -> CmdParser Identity out () -- ^ parser to use+  -> (CommandDesc (), Input, Either ParsingError (CommandDesc out))+runCmdParserExt mTopLevel inputInitial cmdParser =+  runIdentity $ runCmdParserAExt mTopLevel inputInitial cmdParser++-- | The Applicative-enabled version of 'runCmdParser'.+runCmdParserA+  :: forall f out+   . Applicative f+  => Maybe String -- ^ program name to be used for the top-level @CommandDesc@+  -> Input -- ^ input to be processed+  -> CmdParser f out () -- ^ parser to use+  -> f (CommandDesc (), Either ParsingError (CommandDesc out))+runCmdParserA mTopLevel inputInitial cmdParser =+  (\(x, _, z) -> (x, z)) <$> runCmdParserAExt mTopLevel inputInitial cmdParser++-- | The Applicative-enabled version of 'runCmdParserExt'.+runCmdParserAExt+  :: forall f out+   . Applicative f+  => Maybe String -- ^ program name to be used for the top-level @CommandDesc@+  -> Input -- ^ input to be processed+  -> CmdParser f out () -- ^ parser to use+  -> f+       ( CommandDesc ()+       , Input+       , Either ParsingError (CommandDesc out)+       )+runCmdParserAExt mTopLevel inputInitial cmdParser =+  runIdentity+    $ MultiRWSS.runMultiRWSTNil+    $ (<&> captureFinal)+    $ MultiRWSS.withMultiWriterWA+    $ MultiRWSS.withMultiStateA cmdParser+    $ MultiRWSS.withMultiStateSA (StackBottom mempty)+    $ MultiRWSS.withMultiStateSA inputInitial+    $ MultiRWSS.withMultiStateSA (PastCommandInput inputInitial)+    $ MultiRWSS.withMultiStateSA initialCommandDesc+    $ processMain cmdParser+ where+  initialCommandDesc = emptyCommandDesc+    { _cmd_mParent = mTopLevel <&> \n -> (Just n, emptyCommandDesc)+    }+  captureFinal+    :: ( [String]+       , (CmdDescStack, (Input, (PastCommandInput, (CommandDesc out, f ()))))+       )+    -> f (CommandDesc (), Input, Either ParsingError (CommandDesc out))+  captureFinal tuple1 = act $> (() <$ cmd', pastCmdInput, res)+   where+    (errs                         , tuple2) = tuple1+    (descStack                    , tuple3) = tuple2+    (inputRest                    , tuple4) = tuple3+    (PastCommandInput pastCmdInput, tuple5) = tuple4+    (cmd                          , act   ) = tuple5+    errs'     = errs ++ inputErrs ++ stackErrs+    inputErrs = case inputRest of+      InputString s | all Char.isSpace s -> []+      InputString{} -> ["could not parse input/unprocessed input"]+      InputArgs [] -> []+      InputArgs{} -> ["could not parse input/unprocessed input"]+    stackErrs = case descStack of+      StackBottom{} -> []+      _             -> ["butcher interface error: unclosed group"]+    cmd' = postProcessCmd descStack cmd+    res =+      if null errs' then Right cmd' else Left $ ParsingError errs' inputRest+  processMain+    :: CmdParser f out ()+    -> MultiRWSS.MultiRWS+         '[]+         '[[String]]+         '[CommandDesc out, PastCommandInput, Input, CmdDescStack, CmdParser+           f+           out+           ()]+         (f ())+  processMain = \case+    Pure ()                     -> return $ pure $ ()+    Free (CmdParserHelp h next) -> do+      cmd :: CommandDesc out <- mGet+      mSet $ cmd { _cmd_help = Just h }+      processMain next+    Free (CmdParserSynopsis s next) -> do+      cmd :: CommandDesc out <- mGet+      mSet+        $ cmd { _cmd_synopsis = Just $ PP.fsep $ fmap PP.text $ List.words s }+      processMain next+    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+      processMain $ nextF $ () <$ postProcessCmd stack cmd+    Free (CmdParserPeekInput nextF) -> do+      processMain $ nextF $ inputToString inputInitial+    Free (CmdParserPart desc parseF actF nextF) -> do+      do+        descStack <- mGet+        mSet $ descStackAdd desc descStack+      input <- mGet+      case input of+        InputString str -> case parseF str of+          Just (x, rest) -> do+            mSet $ InputString rest+            actRest <- processMain $ nextF x+            return $ actF x *> actRest+          Nothing -> do+            mTell ["could not parse " ++ getPartSeqDescPositionName desc]+            processMain $ nextF monadMisuseError+        InputArgs (str:strr) -> case parseF str of+          Just (x, "") -> do+            mSet $ InputArgs strr+            actRest <- processMain $ nextF x+            return $ actF x *> actRest+          _ -> do+            mTell ["could not parse " ++ getPartSeqDescPositionName desc]+            processMain $ nextF monadMisuseError+        InputArgs [] -> do+          mTell ["could not parse " ++ getPartSeqDescPositionName desc]+          processMain $ nextF monadMisuseError+    Free (CmdParserPartInp desc parseF actF nextF) -> do+      do+        descStack <- mGet+        mSet $ descStackAdd desc descStack+      input <- mGet+      case parseF input of+        Just (x, rest) -> do+          mSet $ rest+          actRest <- processMain $ nextF x+          return $ actF x *> actRest+        Nothing -> do+          mTell ["could not parse " ++ getPartSeqDescPositionName desc]+          processMain $ nextF monadMisuseError+    Free (CmdParserPartMany bound desc parseF actF nextF) -> do+      do+        descStack <- mGet+        mSet $ descStackAdd (wrapBoundDesc bound desc) descStack+      let proc = do+            dropSpaces+            input <- mGet+            case input of+              InputString str -> case parseF str of+                Just (x, r) -> do+                  mSet $ InputString r+                  xr <- proc+                  return $ x : xr+                Nothing -> return []+              InputArgs (str:strr) -> case parseF str of+                Just (x, "") -> do+                  mSet $ InputArgs strr+                  xr <- proc+                  return $ x : xr+                _ -> return []+              InputArgs [] -> return []+      r <- proc+      let act = traverse actF r+      (act *>) <$> processMain (nextF $ r)+    Free (CmdParserPartManyInp bound desc parseF actF nextF) -> do+      do+        descStack <- mGet+        mSet $ descStackAdd (wrapBoundDesc bound desc) descStack+      let proc = do+            dropSpaces+            input <- mGet+            case parseF input of+              Just (x, r) -> do+                mSet $ r+                xr <- proc+                return $ x : xr+              Nothing -> return []+      r <- proc+      let act = traverse actF r+      (act *>) <$> processMain (nextF $ r)+    f@(Free (CmdParserChild _ _ _ _ _)) -> do+      dropSpaces+      input <- mGet+      (gatheredChildren :: [ChildGather f out], restCmdParser) <-+        MultiRWSS.withMultiWriterWA $ childrenGather f+      let+        child_fold+          :: ( Deque (Maybe String)+             , Map (Maybe String) (Visibility, CmdParser f out (), f ())+             )+          -> ChildGather f out+          -> ( Deque (Maybe String)+             , Map (Maybe String) (Visibility, CmdParser f out (), f ())+             )+        child_fold (c_names, c_map) (ChildGather name vis child act) =+          case name `MapS.lookup` c_map of+            Nothing ->+              ( Deque.snoc name c_names+              , MapS.insert name (vis, child, act) c_map+              )+            Just (vis', child', act') ->+              ( c_names+              , MapS.insert name (vis', 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 <&> \(mname, (child, act, vis)) ->+          case (mname, input) of+            (Just name, InputString str) | name == str ->+              Just $ (Just name, child, act, vis, InputString "")+            (Just name, InputString str) | (name ++ " ") `isPrefixOf` str ->+              Just+                $ ( Just name+                  , child+                  , act+                  , vis+                  , InputString $ drop (length name + 1) str+                  )+            (Just name, InputArgs (str:strr)) | name == str ->+              Just $ (Just name, child, act, vis, InputArgs strr)+            (Nothing, _) -> Just $ (Nothing, child, act, vis, input)+            _            -> Nothing+      case mRest of+        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, (vis, 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 { _cmd_visibility = vis }+              )+          -- proceed regularly on the same layer+          processMain $ restCmdParser+        Just (name, vis, 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 { _cmd_mParent    = Just (name, cmd)+                                  , _cmd_visibility = vis+                                  }+          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 mempty groupName stackCur+      processMain $ next+    Free (CmdParserGroupEnd next) -> do+      stackCur <- mGet+      case stackCur of+        StackBottom{} -> do+          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 (Data.Foldable.toList descs)))+            up+          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 (Int, Dynamic, Input, Bool, f ())+        tryParsePartData input (PartGatherData pid _ pfe act allowMany) = First+          [ (pid, toDyn r, rest, allowMany, act r)+          | (r, rest) <- case pfe of+            Left pfStr -> case input of+              InputString str -> case pfStr str of+                Just (x, r) | r /= str -> Just (x, InputString r)+                _                      -> Nothing+              InputArgs (str:strr) -> case pfStr str of+                Just (x, "") -> Just (x, InputArgs strr)+                _            -> Nothing+              InputArgs [] -> Nothing+            Right pfInp -> case pfInp input of+              Just (x, r) | r /= input -> Just (x, r)+              _                        -> Nothing+          ]+        parseLoop = do+          input                           <- mGet+          m :: Map Int (PartGatherData f) <- mGet+          case getFirst $ Data.Foldable.foldMap (tryParsePartData input) m of+                     -- i will be angry if foldMap ever decides to not fold+                     -- in order of keys.+            Nothing                        -> return $ pure ()+            Just (pid, x, rest, more, act) -> do+              mSet rest+              mModify $ MapS.insertWith (++) pid [x]+              when (not more) $ do+                mSet $ MapS.delete pid m+              actRest <- parseLoop+              return $ act *> actRest+      (finalMap, (fr, acts)) <-+        MultiRWSS.withMultiStateSA (MapS.empty :: PartParsedData)+        $ MultiRWSS.withMultiStateA reorderMapInit+        $ do+            acts     <- parseLoop -- filling the map+            stackCur <- mGet+            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+      -- 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++  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+   where+    restCase = do+      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 vis sub act next) -> do+      mTell [ChildGather cmdStr vis 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+       , MonadMultiState PartParsedData m+       , MonadMultiState (Map Int (PartGatherData f)) m+       , MonadMultiState Input m+       , MonadMultiState (CommandDesc out) m+       , MonadMultiWriter [[Char]] m+       , m ~ MultiRWSS.MultiRWST r w s m0+       , ContainsType (CmdParser f out ()) s+       , ContainsType CmdDescStack s+       , 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+      stackCur <- mGet+      case stackCur of+        StackBottom{} -> do+          mTell ["unexpected stackBottom"]+        StackLayer descs _ up -> do+          mSet $ descStackAdd (PartReorder (Data.Foldable.toList descs)) up+      return next+    Free (CmdParserGrouped groupName next) -> do+      stackCur <- mGet+      mSet $ StackLayer mempty groupName stackCur+      processParsedParts $ next+    Free (CmdParserGroupEnd next) -> do+      stackCur <- mGet+      case stackCur of+        StackBottom{} -> do+          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 (Data.Foldable.toList descs)))+            up+          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+      do+        stackCur <- mGet+        mSet $ descStackAdd desc stackCur+      pid <- mGet+      mSet $ pid + 1+      parsedMap :: PartParsedData <- mGet+      mSet $ MapS.delete pid parsedMap+      partMap :: Map Int (PartGatherData f) <- mGet+      input :: Input                        <- mGet+      let+        errorResult = do+          mTell+            [ "could not parse expected input "+              ++ getPartSeqDescPositionName desc+              ++ " with remaining input: "+              ++ show input+            ]+          failureCurrentShallowRerun+          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+              Nothing      -> errorResult+              Just (dx, _) -> continueOrMisuse $ cast dx+            Right pf -> case pf (InputArgs []) of+              Nothing      -> errorResult+              Just (dx, _) -> continueOrMisuse $ cast dx+        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+      do+        stackCur <- mGet+        mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur+      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++  -- this does no error reporting at all.+  -- user needs to use check for that purpose instead.+  processCmdShallow+    :: (MonadMultiState (CommandDesc out) m, MonadMultiState CmdDescStack m)+    => CmdParserF f out (m ())+    -> m ()+  processCmdShallow = \case+    CmdParserHelp h next -> do+      cmd :: CommandDesc out <- mGet+      mSet $ cmd { _cmd_help = Just h }+      next+    CmdParserSynopsis s next -> do+      cmd :: CommandDesc out <- mGet+      mSet+        $ cmd { _cmd_synopsis = Just $ PP.fsep $ fmap PP.text $ List.words s }+      next+    CmdParserPeekDesc nextF -> do+      mGet >>= nextF . fmap (\(_ :: out) -> ())+    CmdParserPeekInput nextF -> do+      nextF $ inputToString inputInitial+    CmdParserPart desc _parseF _act nextF -> do+      do+        stackCur <- mGet+        mSet $ descStackAdd desc stackCur+      nextF monadMisuseError+    CmdParserPartInp desc _parseF _act nextF -> do+      do+        stackCur <- mGet+        mSet $ descStackAdd desc stackCur+      nextF monadMisuseError+    CmdParserPartMany bound desc _parseF _act nextF -> do+      do+        stackCur <- mGet+        mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur+      nextF monadMisuseError+    CmdParserPartManyInp bound desc _parseF _act nextF -> do+      do+        stackCur <- mGet+        mSet $ descStackAdd (wrapBoundDesc bound desc) stackCur+      nextF monadMisuseError+    CmdParserChild cmdStr vis _sub _act next -> do+      mExisting <- takeCommandChild cmdStr+      let childDesc :: CommandDesc out =+            fromMaybe emptyCommandDesc { _cmd_visibility = vis } 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 mempty groupName stackCur+      next+    CmdParserGroupEnd next -> do+      stackCur <- mGet+      case stackCur of+        StackBottom{} -> do+          return ()+        StackLayer _descs "" _up -> do+          return ()+        StackLayer descs groupName up -> do+          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 (Data.Foldable.toList descs)) up+        StackLayer{} -> return ()+      next+    CmdParserReorderStart next -> do+      stackCur <- mGet+      mSet $ StackLayer mempty "" stackCur+      next++  failureCurrentShallowRerun+    :: ( m ~ MultiRWSS.MultiRWST r w s m0+       , MonadMultiState (CmdParser f out ()) m+       , MonadMultiState (CommandDesc out) m+       , ContainsType CmdDescStack s+       , Monad m0+       )+    => m ()+  failureCurrentShallowRerun = do+    parser                 <- mGet+    cmd :: CommandDesc out <-+      MultiRWSS.withMultiStateS emptyCommandDesc+        $ iterM processCmdShallow parser+    mSet cmd++  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 Input m => m ()+  dropSpaces = do+    inp <- mGet+    case inp of+      InputString s -> mSet $ 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 out) m+  => Maybe 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++-- | map over the @out@ type argument+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 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++-- 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 a -> CommandDesc a+descFixParents = descFixParentsWithTopM Nothing++-- descFixParentsWithTop :: String -> CommandDesc a -> CommandDesc a+-- descFixParentsWithTop s = descFixParentsWithTopM (Just (s, emptyCommandDesc))++descFixParentsWithTopM+  :: Maybe (Maybe String, CommandDesc a) -> CommandDesc a -> CommandDesc a+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 a+    -> (Maybe String, CommandDesc a)+    -> (Maybe String, CommandDesc a)+  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 a+    -> (Maybe String, CommandDesc a)+    -> (Maybe String, CommandDesc a)+  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
src/UI/Butcher/Monadic/Internal/Types.hs view
@@ -13,6 +13,7 @@   , cmd_parts   , cmd_out   , cmd_children+  , cmd_visibility   , emptyCommandDesc   , CmdParserF (..)   , CmdParser@@ -21,6 +22,8 @@   , ParsingError (..)   , addSuggestion   , ManyUpperBound (..)+  , Visibility (..)+  , CompletionItem (..)   ) where @@ -57,6 +60,9 @@   = ManyUpperBound1   | ManyUpperBoundN +data Visibility = Visible | Hidden+  deriving (Show, Eq)+ data CmdParserF f out a   =                          CmdParserHelp PP.Doc a   |                          CmdParserSynopsis String a@@ -68,7 +74,7 @@   | forall p . Typeable p => CmdParserPartMany ManyUpperBound PartDesc (String -> Maybe (p, String)) (p -> f ()) ([p] -> a)   | forall p . Typeable p => CmdParserPartInp PartDesc (Input -> Maybe (p, Input)) (p -> f ()) (p -> a)   | forall p . Typeable p => CmdParserPartManyInp ManyUpperBound PartDesc (Input -> Maybe (p, Input)) (p -> f ()) ([p] -> a)-  |                          CmdParserChild (Maybe String) (CmdParser f out ()) (f ()) a+  |                          CmdParserChild (Maybe String) Visibility (CmdParser f out ()) (f ()) a   |                          CmdParserImpl  out                                a   |                          CmdParserReorderStart                             a   |                          CmdParserReorderStop                              a@@ -117,6 +123,7 @@   , _cmd_children :: Deque (Maybe String, CommandDesc out)                      -- we don't use a Map here because we'd like to                      -- retain the order.+  , _cmd_visibility :: Visibility   }  -- type PartSeqDesc = [PartDesc]@@ -134,18 +141,27 @@   | PartSeq [PartDesc]   | PartDefault String -- default representation                 PartDesc-  | PartSuggestion [String] PartDesc+  | PartSuggestion [CompletionItem] PartDesc   | PartRedirect String -- name for the redirection                  PartDesc   | PartReorder [PartDesc]   | PartMany PartDesc   | PartWithHelp PP.Doc PartDesc+  | PartHidden PartDesc   deriving Show -addSuggestion :: Maybe [String] -> PartDesc -> PartDesc+addSuggestion :: Maybe [CompletionItem] -> PartDesc -> PartDesc addSuggestion Nothing     = id addSuggestion (Just sugs) = PartSuggestion sugs ++data CompletionItem+  = CompletionString String+  | CompletionDirectory+  | CompletionFile+  deriving Show++ {- command documentation structure 1. terminals. e.g. "--dry-run"@@ -167,8 +183,10 @@  -- +-- | Empty 'CommandDesc' value. Mostly for butcher-internal usage. emptyCommandDesc :: CommandDesc out-emptyCommandDesc = CommandDesc Nothing Nothing Nothing [] Nothing mempty+emptyCommandDesc =+  CommandDesc Nothing Nothing Nothing [] Nothing mempty Visible  instance Show (CommandDesc out) where   show c = "Command help=" ++ show (_cmd_help c)@@ -208,3 +226,8 @@ --   show (CmdParserChild s _ _) = "(CmdParserChild " ++ s ++ ")" --   show (CmdParserRun _) = "CmdParserRun" +instance Alternative Deque where+  empty = mempty+  (<|>) = Deque.prepend++instance MonadPlus Deque
src/UI/Butcher/Monadic/Param.hs view
@@ -9,6 +9,8 @@   , paramHelpStr   , paramDefault   , paramSuggestions+  , paramFile+  , paramDirectory   , addParamRead   , addParamReadOpt   , addParamString@@ -18,6 +20,7 @@   , addParamNoFlagStringOpt   , addParamNoFlagStrings   , addParamRestOfInput+  , addParamRestOfInputRaw   , -- * Deprecated for more consistent naming     addReadParam   , addReadParamOpt@@ -49,7 +52,7 @@ data Param p = Param   { _param_default :: Maybe p   , _param_help :: Maybe PP.Doc-  , _param_suggestions :: Maybe [p]+  , _param_suggestions :: Maybe [CompletionItem]   }  instance Monoid (Param p) where@@ -77,9 +80,18 @@ paramDefault d = mempty { _param_default = Just d }  -- | Create a 'Param' with just a list of suggestion values.-paramSuggestions :: [p] -> Param p-paramSuggestions ss = mempty { _param_suggestions = Just ss }+paramSuggestions :: [String] -> Param p+paramSuggestions ss =+  mempty { _param_suggestions = Just $ CompletionString <$> ss } +-- | Create a 'Param' that is a file path.+paramFile :: Param p+paramFile = mempty { _param_suggestions = Just [CompletionFile] }++-- | Create a 'Param' that is a directory path.+paramDirectory :: Param p+paramDirectory = mempty { _param_suggestions = Just [CompletionDirectory] }+ -- | Add a parameter to the 'CmdParser' by making use of a 'Text.Read.Read' -- instance. Take care not to use this to return Strings unless you really -- want that, because it will require the quotation marks and escaping as@@ -99,7 +111,8 @@ addReadParam name par = addCmdPart desc parseF   where     desc :: PartDesc-    desc = (maybe id PartWithHelp $ _param_help par)+    desc = addSuggestion (_param_suggestions par)+         $ (maybe id PartWithHelp $ _param_help par)          $ (maybe id (PartDefault . show) $ _param_default par)          $ PartVariable name     parseF :: String -> Maybe (a, String)@@ -124,7 +137,8 @@ addReadParamOpt name par = addCmdPart desc parseF   where     desc :: PartDesc-    desc = PartOptional+    desc = addSuggestion (_param_suggestions par)+         $ PartOptional          $ (maybe id PartWithHelp $ _param_help par)          $ PartVariable name     parseF :: String -> Maybe (Maybe a, String)@@ -180,7 +194,8 @@ addStringParamOpt name par = addCmdPartInp desc parseF   where     desc :: PartDesc-    desc = PartOptional+    desc = addSuggestion (_param_suggestions par)+         $ PartOptional          $ (maybe id PartWithHelp $ _param_help par)          $ PartVariable name     parseF :: Input -> Maybe (Maybe String, Input)@@ -213,7 +228,10 @@ addStringParams name par = addCmdPartManyInp ManyUpperBoundN desc parseF  where   desc :: PartDesc-  desc = (maybe id PartWithHelp $ _param_help par) $ PartVariable name+  desc =+    addSuggestion (_param_suggestions par)+      $ (maybe id PartWithHelp $ _param_help par)+      $ PartVariable name   parseF :: Input -> Maybe (String, Input)   parseF (InputString str) =     case break Char.isSpace $ dropWhile Char.isSpace str of@@ -284,7 +302,10 @@ addParamNoFlagStrings name par = addCmdPartManyInp ManyUpperBoundN desc parseF  where   desc :: PartDesc-  desc = (maybe id PartWithHelp $ _param_help par) $ PartVariable name+  desc =+    addSuggestion (_param_suggestions par)+      $ (maybe id PartWithHelp $ _param_help par)+      $ PartVariable name   parseF :: Input -> Maybe (String, Input)   parseF (InputString str) =     case break Char.isSpace $ dropWhile Char.isSpace str of@@ -307,15 +328,38 @@ addParamRestOfInput = addRestOfInputStringParam {-# DEPRECATED addRestOfInputStringParam "use 'addParamRestOfInput'" #-} addRestOfInputStringParam-  :: forall f out . (Applicative f)+  :: forall f out+   . (Applicative f)   => String   -> Param Void   -> CmdParser f out String addRestOfInputStringParam name par = addCmdPartInp desc parseF-  where-    desc :: PartDesc-    desc = (maybe id PartWithHelp $ _param_help par)-         $ PartVariable name-    parseF :: Input -> Maybe (String, Input)-    parseF (InputString str) = Just (str, InputString "")-    parseF (InputArgs args)  = Just (List.unwords args, InputArgs [])+ where+  desc :: PartDesc+  desc =+    addSuggestion (_param_suggestions par)+      $ (maybe id PartWithHelp $ _param_help par)+      $ PartVariable name+  parseF :: Input -> Maybe (String, Input)+  parseF (InputString str ) = Just (str, InputString "")+  parseF (InputArgs   args) = Just (List.unwords args, InputArgs [])+++-- | Add a parameter that consumes _all_ remaining input, returning a raw+-- 'Input' value.+addParamRestOfInputRaw+  :: forall f out . (Applicative f)+  => String+  -> Param Void+  -> CmdParser f out Input+addParamRestOfInputRaw name par = addCmdPartInp desc parseF+ where+  desc :: PartDesc+  desc =+    addSuggestion (_param_suggestions par)+      $ (maybe id PartWithHelp $ _param_help par)+      $ PartVariable name+  parseF :: Input -> Maybe (Input, Input)+  parseF i@InputString{} = Just (i, InputString "")+  parseF i@InputArgs{}   = Just (i, InputArgs [])+
src/UI/Butcher/Monadic/Pretty.hs view
@@ -28,8 +28,10 @@ -- >       else putStrLn $ "hello, " ++ name ++ ", welcome from butcher!" module UI.Butcher.Monadic.Pretty   ( ppUsage+  , ppUsageShortSub   , ppUsageAt   , ppHelpShallow+  , ppHelpDepthOne   , ppUsageWithHelp   , ppPartDescUsage   , ppPartDescHeader@@ -58,16 +60,18 @@ -- -- > example [--short] NAME [version | help] ppUsage :: CommandDesc a -> PP.Doc-ppUsage (CommandDesc mParent _syn _help parts out children) = pparents mParent-  <+> PP.sep [PP.fsep partDocs, subsDoc]+ppUsage (CommandDesc mParent _syn _help parts out children _hidden) =+  pparents mParent <+> PP.sep [PP.fsep partDocs, subsDoc]  where   pparents :: Maybe (Maybe String, CommandDesc out) -> PP.Doc   pparents Nothing              = PP.empty   pparents (Just (Just n , cd)) = pparents (_cmd_mParent cd) <+> PP.text n   pparents (Just (Nothing, cd)) = pparents (_cmd_mParent cd)-  partDocs = parts <&> ppPartDescUsage-  subsDoc  = case out of-    _ | null children -> PP.empty -- TODO: remove debug+  partDocs = Maybe.mapMaybe ppPartDescUsage parts+  visibleChildren =+    [ (n, c) | (Just n, c) <- children, _cmd_visibility c == Visible ]+  subsDoc = case out of+    _ | null visibleChildren -> PP.empty     Nothing | null parts -> subDoc             | otherwise  -> PP.parens $ subDoc     Just{} -> PP.brackets $ subDoc@@ -75,8 +79,31 @@     PP.fcat       $ PP.punctuate (PP.text " | ")       $ Data.Foldable.toList-      $ [ PP.text n | (Just n, _) <- children ]+      $ (PP.text . fst) <$> visibleChildren +-- | ppUsageShortSub exampleDesc yields:+--+-- > example [--short] NAME <command>+--+-- I.e. Subcommands are abbreviated using the @<command>@ label, instead+-- of being listed.+ppUsageShortSub :: CommandDesc a -> PP.Doc+ppUsageShortSub (CommandDesc mParent _syn _help parts out children _hidden) =+  pparents mParent <+> PP.sep [PP.fsep partDocs, subsDoc]+ where+  pparents :: Maybe (Maybe String, CommandDesc out) -> PP.Doc+  pparents Nothing              = PP.empty+  pparents (Just (Just n , cd)) = pparents (_cmd_mParent cd) <+> PP.text n+  pparents (Just (Nothing, cd)) = pparents (_cmd_mParent cd)+  partDocs = Maybe.mapMaybe ppPartDescUsage parts+  visibleChildren =+    [ (n, c) | (Just n, c) <- children, _cmd_visibility c == Visible ]+  subsDoc = case out of+    _ | null visibleChildren -> PP.empty+    Nothing                  -> subDoc+    Just{}                   -> PP.brackets $ subDoc+  subDoc = if null visibleChildren then PP.empty else PP.text "<command>"+ -- | ppUsageWithHelp exampleDesc yields: -- -- > example [--short] NAME@@ -84,14 +111,14 @@ -- -- And yes, the line break is not optimal in this instance with default print. ppUsageWithHelp :: CommandDesc a -> PP.Doc-ppUsageWithHelp (CommandDesc mParent _syn help parts out children) =+ppUsageWithHelp (CommandDesc mParent _syn help parts out children _hidden) =   pparents mParent <+> PP.fsep (partDocs ++ [subsDoc]) PP.<> helpDoc  where   pparents :: Maybe (Maybe String, CommandDesc out) -> PP.Doc   pparents Nothing              = PP.empty   pparents (Just (Just n , cd)) = pparents (_cmd_mParent cd) <+> PP.text n   pparents (Just (Nothing, cd)) = pparents (_cmd_mParent cd)-  partDocs = parts <&> ppPartDescUsage+  partDocs = Maybe.mapMaybe ppPartDescUsage parts   subsDoc  = case out of     _ | null children -> PP.empty -- TODO: remove debug     Nothing | null parts -> subDoc@@ -101,7 +128,7 @@     PP.fcat       $ PP.punctuate (PP.text " | ")       $ Data.Foldable.toList-      $ [ PP.text n | (Just n, _) <- children ]+      $ [ PP.text n | (Just n, c) <- children, _cmd_visibility c == Visible ]   helpDoc = case help of     Nothing -> PP.empty     Just h  -> PP.text ":" PP.<+> h@@ -119,7 +146,7 @@     [] -> Just $ ppUsage desc     (s:sr) -> find ((Just s==) . fst) (_cmd_children desc) >>= ppUsageAt sr . snd --- | ppHelpShalloe exampleDesc yields:+-- | ppHelpShallow exampleDesc yields: -- -- > NAME -- > @@ -138,13 +165,14 @@ -- >   --short             make the greeting short -- >   NAME                your name, so you can be greeted properly ppHelpShallow :: CommandDesc a -> PP.Doc-ppHelpShallow desc@(CommandDesc mParent syn help parts _out _children) =+ppHelpShallow desc =   nameSection     $+$ usageSection     $+$ descriptionSection     $+$ partsSection     $+$ PP.text ""  where+  CommandDesc mParent syn help parts _out _children _hidden = desc   nameSection = case mParent of     Nothing -> PP.empty     Just{} ->@@ -183,24 +211,121 @@       PartDefault    _ p -> go p       PartSuggestion _ p -> go p       PartRedirect s p ->-        [PP.text s $$ PP.nest 20 (ppPartDescUsage p)] ++ (PP.nest 2 <$> go p)+        [PP.text s $$ PP.nest 20 (fromMaybe PP.empty $ ppPartDescUsage p)]+          ++ (PP.nest 2 <$> go p)       PartReorder ps     -> ps >>= go       PartMany    p      -> go p       PartWithHelp doc p -> [ppPartDescHeader p $$ PP.nest 20 doc] ++ go p+      PartHidden{}       -> [] +-- | ppHelpDepthOne exampleDesc yields:+--+-- > NAME+-- > +-- >   example - a simple butcher example program+-- > +-- > USAGE+-- > +-- >   example [--short] NAME <command>+-- > +-- > DESCRIPTION+-- > +-- >   a very long help document+-- > +-- > COMMANDS+-- > +-- >   version+-- >   help+-- > +-- > ARGUMENTS+-- > +-- >   --short             make the greeting short+-- >   NAME                your name, so you can be greeted properly+ppHelpDepthOne :: CommandDesc a -> PP.Doc+ppHelpDepthOne desc =+  nameSection+    $+$ usageSection+    $+$ descriptionSection+    $+$ commandSection+    $+$ partsSection+    $+$ PP.text ""+ where+  CommandDesc mParent syn help parts _out children _hidden = desc+  nameSection = case mParent of+    Nothing -> PP.empty+    Just{} ->+      PP.text "NAME"+        $+$ PP.text ""+        $+$ PP.nest+              2+              ( case syn of+                Nothing -> pparents mParent+                Just s  -> pparents mParent <+> PP.text "-" <+> s+              )+        $+$ PP.text ""+  pparents :: Maybe (Maybe String, CommandDesc out) -> PP.Doc+  pparents Nothing              = PP.empty+  pparents (Just (Just n , cd)) = pparents (_cmd_mParent cd) PP.<+> PP.text n+  pparents (Just (Nothing, cd)) = pparents (_cmd_mParent cd)+  usageSection =+    PP.text "USAGE" $+$ PP.text "" $+$ PP.nest 2 (ppUsageShortSub desc)+  descriptionSection = case help of+    Nothing -> PP.empty+    Just h ->+      PP.text "" $+$ PP.text "DESCRIPTION" $+$ PP.text "" $+$ PP.nest 2 h+  visibleChildren =+    [ (n, c) | (Just n, c) <- children, _cmd_visibility c == Visible ]+  childDescs = visibleChildren <&> \(n, c) ->+    PP.text n $$ PP.nest 20 (fromMaybe PP.empty (_cmd_synopsis c))+  commandSection = if null visibleChildren+    then PP.empty+    else PP.text "" $+$ PP.text "COMMANDS" $+$ PP.text "" $+$ PP.nest+      2+      (PP.vcat $ Data.Foldable.toList childDescs)+  partsSection = if null partsTuples+    then PP.empty+    else PP.text "" $+$ PP.text "ARGUMENTS" $+$ PP.text "" $+$ PP.nest+      2+      (PP.vcat partsTuples)+  partsTuples :: [PP.Doc]+  partsTuples = parts >>= go+   where+    go = \case+      PartLiteral{}      -> []+      PartVariable{}     -> []+      PartOptional p     -> go p+      PartAlts     ps    -> ps >>= go+      PartSeq      ps    -> ps >>= go+      PartDefault    _ p -> go p+      PartSuggestion _ p -> go p+      PartRedirect s p ->+        [PP.text s $$ PP.nest 20 (fromMaybe PP.empty $ ppPartDescUsage p)]+          ++ (PP.nest 2 <$> go p)+      PartReorder ps     -> ps >>= go+      PartMany    p      -> go p+      PartWithHelp doc p -> [ppPartDescHeader p $$ PP.nest 20 doc] ++ go p+      PartHidden{}       -> []+ -- | Internal helper; users probably won't need this.-ppPartDescUsage :: PartDesc -> PP.Doc+ppPartDescUsage :: PartDesc -> Maybe PP.Doc ppPartDescUsage = \case-  PartLiteral  s  -> PP.text s-  PartVariable s  -> PP.text s-  PartOptional p  -> PP.brackets $ rec p-  PartAlts     ps -> PP.fcat $ PP.punctuate (PP.text ",") $ rec <$> ps-  PartSeq      ps -> PP.fsep $ rec <$> ps-  PartDefault _ p -> PP.brackets $ rec p-  PartSuggestion s p ->-    PP.parens $ PP.fcat $ PP.punctuate (PP.text "|") $ fmap PP.text s ++ [rec p]-  PartRedirect s _ -> PP.text s-  PartMany p       -> rec p <> PP.text "+"+  PartLiteral  s -> Just $ PP.text s+  PartVariable s -> Just $ PP.text s+  PartOptional p -> PP.brackets <$> rec p+  PartAlts ps ->+    [ PP.fcat $ PP.punctuate (PP.text ",") ds+    | let ds = Maybe.mapMaybe rec ps+    , not (null ds)+    ]+  PartSeq ps -> [ PP.fsep ds | let ds = Maybe.mapMaybe rec ps, not (null ds) ]+  PartDefault    _   p -> PP.brackets <$> rec p+  PartSuggestion sgs p -> rec p <&> \d ->+    case [ PP.text s | CompletionString s <- sgs ] of+      [] -> d+      sgsDocs ->+        PP.parens $ PP.fcat $ PP.punctuate (PP.text "|") $ sgsDocs ++ [d]+  PartRedirect s _ -> Just $ PP.text s+  PartMany p       -> rec p <&> (<> PP.text "+")   PartWithHelp _ p -> rec p   PartReorder ps ->     let flags  = [ d | PartMany d <- ps ]@@ -210,11 +335,12 @@             _          -> True           )           ps-    in  PP.sep-          [(PP.fsep $ PP.brackets . rec <$> flags), PP.fsep (rec <$> params)]-- where-  rec = ppPartDescUsage+    in  Just $ PP.sep+          [ (PP.fsep $ PP.brackets <$> Maybe.mapMaybe rec flags)+          , PP.fsep (Maybe.mapMaybe rec params)+          ]+  PartHidden{} -> Nothing+  where rec = ppPartDescUsage  -- | Internal helper; users probably won't need this. ppPartDescHeader :: PartDesc -> PP.Doc@@ -230,8 +356,8 @@   PartWithHelp _ d   -> rec d   PartSeq     ds     -> PP.hsep $ rec <$> ds   PartReorder ds     -> PP.vcat $ rec <$> ds- where-  rec = ppPartDescHeader+  PartHidden  d      -> rec d+  where rec = ppPartDescHeader  -- | Simple conversion from 'ParsingError' to 'String'. parsingErrorString :: ParsingError -> String
src/UI/Butcher/Monadic/Types.hs view
@@ -8,6 +8,7 @@   , Input (..)   , ParsingError (..)   , PartDesc(..)+  , emptyCommandDesc   ) where 
srcinc/prelude.inc view
@@ -26,8 +26,6 @@ import qualified System.Process.Extra import qualified System.Time.Extra -import qualified Data.Either.Combinators- import qualified Control.Monad.Trans.MultiRWS.Lazy import qualified Control.Monad.Trans.MultiRWS.Strict import qualified Control.Monad.Trans.MultiReader