packages feed

butcher (empty) → 1.1.0.0

raw patch · 17 files changed

+3700/−0 lines, 17 filesdep +basedep +bifunctorsdep +butchersetup-changed

Dependencies added: base, bifunctors, butcher, containers, deque, either, extra, free, hspec, microlens, microlens-th, mtl, multistate, pretty, transformers, unsafe, void

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for cmdparse-applicative++## 1.1.0.0  -- May 2017++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2016, Lennart Spitzner++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Lennart Spitzner nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,241 @@+# butcher++#### Chops a command or program invocation into digestable pieces.++Similar to the `optparse-applicative` package, but less features,+more flexibility and more evil.++The main differences are:++* Provides a pure interface by default++* Exposes an evil monadic interface, which allows for much nicer binding of+  command part results to some variable name.++    In `optparse-applicative` you easily lose track of what field you are+    modifying after the 5th `<*>` (admittedly, i think -XRecordWildCards+    improves on that issue already.)++    Evil, because you are not allowed to use the monad's full power in this+    case, i.e. there is a constraint that is not statically enforced.+    See below.++* The monadic interface allows much clearer definitions of commandparses+  with (nested) subcommands. No pesky sum-types are necessary.++## Examples++The minimal example is++~~~~.hs+main = mainFromCmdParser $ addCmdImpl $ putStrLn "Hello, World!"+~~~~++But lets look at a more feature-complete example:++~~~~.hs+main = mainFromCmdParserWithHelpDesc $ \helpDesc -> do++  addCmdSynopsis "a simple butcher example program"+  addCmdHelpStr "a very long help document"++  addCmd "version" $ do+    porcelain <- addSimpleBoolFlag "" ["porcelain"]+      (flagHelpStr "print nothing but the numeric version")+    addCmdHelpStr "prints the version of this program"+    addCmdImpl $ putStrLn $ if porcelain+      then "0.0.0.999"+      else "example, version 0.0.0.999"++  addCmd "help" $ addCmdImpl $ print $ ppHelpShallow helpDesc++  short <- addSimpleBoolFlag "" ["short"]+    (flagHelpStr "make the greeting short")+  name <- addStringParam "NAME"+    (paramHelpStr "your name, so you can be greeted properly")++  addCmdImpl $ do+    if short+      then putStrLn $ "hi, " ++ name ++ "!"+      else putStrLn $ "hello, " ++ name ++ ", welcome from butcher!"+~~~~++Further:++- [Full description of the above example, including sample behaviour](example1.md)+- [Example of a pure usage of a CmdParser](example2.md)+- [Example of using a CmdParser on interactive input](example3.md)+- The [brittany](https://github.com/lspitzner/brittany) formatting tool is a+  program that uses butcher for implementing its commandline interface. See+  its [main module source](https://github.com/lspitzner/brittany/blob/master/src-brittany/Main.hs)+  or [the config flag parser](https://github.com/lspitzner/brittany/blob/master/src/Language/Haskell/Brittany/Config.hs).++## The evil monadic interface++As long as you only use Applicative or (Kleisli) Arrow, you can use the+interface freely. When you use Monad, there is one rule: Whenever you read+any command-parts like in++~~~~+f <- addFlag ...+p <- addParam ...+~~~~++you are only allowed to use bindings bound thusly in any command's+implemenation, i.e. inside the parameter to `addCmdImpl`. You are _not_+allowed to force/inspect/patternmatch on them before that. _good_ usage is:++~~~~+addCmdImpl $ do+  print x+  print y+~~~~++while _bad_ would be++~~~~+f <- addFlag+when f $ do+  p <- addParam+  -- evil: the existence of the param `p`+  -- depends on parse result for the flag `f`.+~~~~++That means that checking if a combination of flags is allowed must be done+after parsing. (But different commands and their subcommands (can) have+separate sets of flags.)++## (abstract) Package intentions++Consider a commandline invocation like "ghc -O -i src -Main.hs -o Main". This+package provides a way for the programmer to simultaneously define the+semantics of your program based on its arguments and retrieve documentation+for the user. More specifically, i had three goals in mind:++1. Straight-forward description of (sub)command and flag-specific behaviour+2. Extract understandable usage/help commandline documents/texts from that+   descriptions, think of `ghc --help` or `stack init --help`.+3. Extract necessary information to compute commandline completion results+   from any partial input. (This is not implemented to any serious degree.)++## Semantics++Basic elements of a command are flags, parameters and subcommands. These can+be composed in certain ways, i.e. flags can have a (or possibly multiple?)+parameters; parameters can be grouped into sequences, and commands can have+subcommands.++Commands are essentially `String -> Either ParseError out` where `out` can+be chosen by the user. It could for example be `IO ()`.++To allow more flexible composition, the parts of a command have the "classic"+parser's type: `String -> Maybe (p, String)` where `p` depends on the part.+Parse a prefix of the input and return something and the remaining input, or+fail with `Nothing`.++A command-parser contains a sequence of parts and then a number of subcommands+and/or some implementation. ++### Commands and Child-Commands++- ~~~~ .hs+  myParser :: CmdParser Identity Int ()+  myParser = return ()+  ~~~~++  input | `runCmdParserSimple input myParser`+  ----- | -------------+  "" | Left "command has no implementation"+  "x" | Left "error parsing arguments: could not parse input/unprocessed input at: \"x\"."++- ~~~~ .hs+  myParser :: CmdParser Identity Int ()+  myParser = do+    addCmd "foo" $ addCmdImpl 2+    addCmd "bar" $ addCmdImpl 3+    addCmd "noimpl" $ pure ()+    addCmd "twoimpls" $ do+      addCmdImpl 4+      addCmdImpl 5+    addCmdImpl 1+  ~~~~+  +  input | `runCmdParserSimple input myParser`+  ----- | -------------+  "" | Right 1+  "x" | Left "error parsing arguments: could not parse input/unprocessed input at: \"x\"."+  "foo" | Right 2+  "bar" | Right 3+  "foo bar" | Left "error parsing arguments: could not parse input/unprocessed input at: \"bar\"."+  "noimpl" | Left "command has no implementation"+  "twoimpls" | Right 5++### Flags++- without any annotation, no reodering is allowed and the flags must appear in order:+  ~~~~ .hs+  myParser :: CmdParser Identity (Bool, Int, Int) ()+  myParser = do+    b <- addSimpleBoolFlag "b" [] mempty+    c <- addSimpleCountFlag "c" [] mempty+    i <- addFlagReadParam "i" [] "number" (flagDefault 42)+    addCmdImpl $ (b, c, i)+  ~~~~+  +  input | `runCmdParserSimple input myParser`+  ----- | -------------+  "" | Right (False,0,42)+  "-b -c -i 3" | Right (True,1,3)+  "-c -b" | Left "error parsing arguments: could not parse input/unprocessed input at: \"-b\"."+  "-c -c -c" | Right (False,3,42)++- this time with reordering; also "j" has no default and thus becomes mandatory, still it must not+  occur more than once:+  ~~~~ .hs+  myParser :: CmdParser Identity (Bool, Int, Int, Int) ()+  myParser = do+    reorderStart -- this time with reordering+    b <- addSimpleBoolFlag "b" [] mempty+    c <- addSimpleCountFlag "c" [] mempty+    i <- addFlagReadParam "i" [] "number" (flagDefault 42)+    j <- addFlagReadParam "j" [] "number" mempty -- no default: flag mandatory+    reorderStop+    addCmdImpl $ (b, c, i, j)+  ~~~~+  +  input | `runCmdParserSimple input myParser`+  ---------------------------- | -------------+  "-b" | Left "error parsing arguments:<br>could not parse expected input -j number with remaining input:<br>InputString \"\" at the end of input."+  "-j=5" | Right (False,0,42,5)+  "-c -b -b -j=5" | Right (True,1,42,5)+  "-j=5 -i=1 -c -b" | Right (True,1,1,5)+  "-c -j=5 -c -i=5 -c" | Right (False,3,5,5)+  "-j=5 -j=5" | Left "error parsing arguments: could not parse input/unprocessed input at: \"-j=5\"."++- addFlagReadParams - these can occur more than once. Note that defaults have slightly different semantics:+  ~~~~ .hs+  myParser :: CmdParser Identity (Int, [Int]) ()+  myParser = do+    reorderStart+    i <- addFlagReadParam "i" [] "number" (flagDefault 42)+    js <- addFlagReadParams "j" [] "number" (flagDefault 50)+    reorderStop+    addCmdImpl $ (i, js)+  ~~~~+  +  input | `runCmdParserSimple input myParser`+  ---------------------------- | -------------+  "" | Right (42,[])+  "-i" | Left "error parsing arguments: could not parse input/unprocessed input at: \"-i\"."+  "-j=1 -j=2 -j=3" | Right (42,[1,2,3])+  "-j" | Right (42,[50])+  "-i=1" | Right (1,[])+  "-j=2" | Right (42,[2])+  "-j=2 -i=1 -j=3" | Right (1,[2,3])+  +### Params++TODO+++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ butcher.cabal view
@@ -0,0 +1,158 @@+name:                butcher+version:             1.1.0.0+synopsis:            Chops a command or program invocation into digestable pieces.+description:         See the README (it is properly formatted on github)+license:             BSD3+license-file:        LICENSE+author:              Lennart Spitzner+maintainer:          lsp@informatik.uni-kiel.de+copyright:           Copyright (C) 2016-2017 Lennart Spitzner+category:            UI+build-type:          Simple+Stability:           experimental+extra-source-files: {+  ChangeLog.md+  srcinc/prelude.inc+  README.md+}+cabal-version:       >=1.10+homepage:            https://github.com/lspitzner/butcher/+bug-reports:         https://github.com/lspitzner/butcher/issues++source-repository head {+  type: git+  location: https://github.com/lspitzner/butcher.git+}++flag butcher-dev+  description: dev options+  default: False+  manual: True++library+  exposed-modules:     UI.Butcher.Monadic.Types+                       UI.Butcher.Monadic+                       UI.Butcher.Monadic.Command+                       UI.Butcher.Monadic.Param+                       UI.Butcher.Monadic.Flag+                       UI.Butcher.Monadic.Pretty+                       UI.Butcher.Monadic.IO+                       UI.Butcher.Monadic.BuiltinCommands+  other-modules:       UI.Butcher.Monadic.Internal.Types+                       UI.Butcher.Monadic.Internal.Core+  build-depends:+    { base >=4.8 && <4.10+    , free+    , unsafe+    , microlens+    , microlens-th+    , multistate+    , pretty+    , containers+    , either+    , transformers+    , mtl+    , extra+    , void+    , bifunctors+    , deque+    }+  if flag(butcher-dev) {+    build-depends:+      hspec+  }+  hs-source-dirs:      src+  default-language:    Haskell2010+  default-extensions: {+    CPP++    NoImplicitPrelude++    GADTs++    FlexibleContexts+    FlexibleInstances+    ScopedTypeVariables+    MonadComprehensions+    LambdaCase+    MultiWayIf+    KindSignatures+  }+  other-extensions: {+    DeriveFunctor+    ExistentialQuantification+    GeneralizedNewtypeDeriving+    StandaloneDeriving+    DataKinds+    TypeOperators+    TemplateHaskell+  }+  ghc-options: {+    -Wall+    -fno-spec-constr+    -j+    -fno-warn-unused-imports+    -fno-warn-orphans+  }+  if flag(butcher-dev) {+    ghc-options: -O0 -Werror -fprof-auto -fprof-cafs+  }+  include-dirs:+    srcinc++test-suite tests+  type:             exitcode-stdio-1.0+  default-language: Haskell2010+  build-depends:+    { base >=4.8 && <4.10+    , butcher+    , free+    , unsafe+    , microlens+    , microlens-th+    , multistate+    , pretty+    , containers+    , either+    , transformers+    , mtl+    , extra+    , deque+    }+  if flag(butcher-dev) {+    buildable:        True+    build-depends:+      hspec+  } else {+    buildable:        False+  }+  ghc-options:      -Wall+  main-is:          TestMain.hs+  other-modules:    +  hs-source-dirs:   src-tests+  default-extensions: {+    CPP++    NoImplicitPrelude++    GADTs++    FlexibleContexts+    FlexibleInstances+    ScopedTypeVariables+    MonadComprehensions+    LambdaCase+    MultiWayIf+    KindSignatures+  }+  ghc-options: {+    -Wall+    -fno-spec-constr+    -j+    -fno-warn-unused-imports+    -fno-warn-orphans+  }+  if flag(butcher-dev) {+    ghc-options: -Werror -fprof-auto -fprof-cafs -O0+  }+
+ src-tests/TestMain.hs view
@@ -0,0 +1,187 @@+module Main where++++#include "prelude.inc"++import Test.Hspec++-- import NeatInterpolation++import UI.Butcher.Monadic+import UI.Butcher.Monadic.Types++++main :: IO ()+main = hspec $ tests++tests :: Spec+tests = do+  describe "checkTests" checkTests+  describe "simpleParseTest" simpleParseTest+  describe "simpleRunTest" simpleRunTest+++checkTests :: Spec+checkTests = do+  before_ pending $ it "check001" $ True `shouldBe` True+++simpleParseTest :: Spec+simpleParseTest = do+  it "failed parse 001" $ runCmdParser Nothing (InputString "foo") testCmd1+         `shouldSatisfy` Data.Either.Combinators.isLeft . snd+  it "toplevel" $ (testParse testCmd1 "" >>= _cmd_out)+                  `shouldSatisfy` Maybe.isNothing+  it "hasImpl 001" $ (testParse testCmd1 "abc" >>= _cmd_out)+                  `shouldSatisfy` Maybe.isJust+  it "hasImpl 002" $ (testParse testCmd1 "def" >>= _cmd_out)+                  `shouldSatisfy` Maybe.isJust+++simpleRunTest :: Spec+simpleRunTest = do+  it "failed run" $ testRun testCmd1 "" `shouldBe` Right Nothing+  describe "no reordering" $ do+    it "cmd 1" $ testRun testCmd1 "abc" `shouldBe` Right (Just 100)+    it "cmd 2" $ testRun testCmd1 "def" `shouldBe` Right (Just 200)+    it "flag 1" $ testRun testCmd1 "abc -f" `shouldBe` Right (Just 101)+    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 7" $ testRun testCmd1 "abc -g -g" `shouldBe` Right (Just 102)+  describe "with reordering" $ do+    it "cmd 1" $ testRun testCmd2 "abc" `shouldBe` Right (Just 100)+    it "cmd 2" $ testRun testCmd2 "def" `shouldBe` Right (Just 200)+    it "flag 1" $ testRun testCmd2 "abc -f" `shouldBe` Right (Just 101)+    it "flag 2" $ testRun testCmd2 "abc --flong" `shouldBe` Right (Just 101)+    it "flag 3" $ testRun testCmd2 "abc -f -f" `shouldBe` Right (Just 101)+    it "flag 4" $ testRun testCmd2 "abc -f -g" `shouldBe` Right (Just 103)+    it "flag 5" $ testRun testCmd2 "abc -f -g -f" `shouldBe` Right (Just 103)+    it "flag 6" $ testRun testCmd2 "abc -g -f" `shouldBe` Right (Just 103)+    it "flag 7" $ testRun testCmd2 "abc -g -g" `shouldBe` Right (Just 102)+  describe "with action" $ do+    it "flag 1" $ testRunA testCmd3 "abc" `shouldBe` Right 0+    it "flag 2" $ testRunA testCmd3 "abc -f" `shouldBe` Right 1+    it "flag 3" $ testRunA testCmd3 "abc -g" `shouldBe` Right 2+    it "flag 4" $ testRunA testCmd3 "abc -f -g" `shouldBe` Right 3+    it "flag 5" $ testRunA testCmd3 "abc -g -f" `shouldBe` Right 3+  describe "separated children" $ do+    it "case 1" $ testRun testCmd4 "a aa" `shouldBe` Right (Just 1)+    it "case 2" $ testRun testCmd4 "a ab" `shouldBe` Right (Just 2)+    it "case 3" $ testRun testCmd4 "b ba" `shouldBe` Right (Just 3)+    it "case 4" $ testRun testCmd4 "b bb" `shouldBe` Right (Just 4)+    it "doc" $ show (ppHelpShallow (getDoc "" testCmd4)) `shouldBe`+      List.unlines+        [ "NAME"+        , ""+        , "  test"+        , ""+        , "USAGE"+        , ""+        , "  test a | b"+        ]+    it "doc" $ show (ppHelpShallow (getDoc "a" testCmd4)) `shouldBe`+      List.unlines+        [ "NAME"+        , ""+        , "  test a"+        , ""+        , "USAGE"+        , ""+        , "  test a aa | ab"+        ]+  describe "read flags" $ do+    it "flag 1" $ testRun testCmd5 "abc" `shouldBe` Right (Just 10)+    it "flag 2" $ testRun testCmd5 "abc -f 2" `shouldBe` Right (Just 2)+    it "flag 3" $ testRun testCmd5 "abc --flag 3" `shouldBe` Right (Just 3)+    it "flag 4" $ testRun testCmd5 "abc -f=4" `shouldBe` Right (Just 4)+    it "flag 5" $ testRun testCmd5 "abc --flag=5" `shouldBe` Right (Just 5)+    it "flag 6" $ testRun testCmd5 "abc -f" `shouldSatisfy` Data.Either.isLeft+    it "flag 6" $ testRun testCmd5 "abc -flag 0" `shouldSatisfy` Data.Either.isLeft+    it "flag 6" $ testRun testCmd5 "abc --f 0" `shouldSatisfy` Data.Either.isLeft+++++testCmd1 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()+testCmd1 = do+  addCmd "abc" $ do+    f <- addSimpleBoolFlag "f" ["flong"] mempty+    g <- addSimpleBoolFlag "g" ["glong"] mempty+    addCmdImpl $ do+      when f $ WriterS.tell 1+      when g $ WriterS.tell 2+      WriterS.tell 100+  addCmd "def" $ do+    addCmdImpl $ do+      WriterS.tell 200++testCmd2 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()+testCmd2 = do+  addCmd "abc" $ do+    reorderStart+    f <- addSimpleBoolFlag "f" ["flong"] mempty+    g <- addSimpleBoolFlag "g" ["glong"] mempty+    reorderStop+    addCmdImpl $ do+      when f $ WriterS.tell 1+      when g $ WriterS.tell 2+      WriterS.tell 100+  addCmd "def" $ do+    addCmdImpl $ do+      WriterS.tell 200++testCmd3 :: CmdParser (StateS.State Int) () ()+testCmd3 = do+  addCmd "abc" $ do+    reorderStart+    addSimpleFlagA "f" ["flong"] mempty (StateS.modify (+1))+    addSimpleFlagA "g" ["glong"] mempty (StateS.modify (+2))+    reorderStop+    addCmdImpl ()+  addCmd "def" $ do+    addCmdImpl ()++testCmd4 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()+testCmd4 = do+  addCmd "a" $ do+    addCmd "aa" $ do+      addCmdImpl $ WriterS.tell 1+  addCmd "b" $ do+    addCmd "bb" $ do+      addCmdImpl $ WriterS.tell 4+  addCmd "a" $ do+    addCmd "ab" $ do+      addCmdImpl $ WriterS.tell 2+  addCmd "b" $ do+    addCmd "ba" $ do+      addCmdImpl $ WriterS.tell 3++testCmd5 :: CmdParser Identity (WriterS.Writer (Sum Int) ()) ()+testCmd5 = do+  addCmd "abc" $ do+    x <- addFlagReadParam "f" ["flag"] "flag" (flagDefault (10::Int))+    addCmdImpl $ WriterS.tell (Sum x)+++testParse :: CmdParser Identity out () -> String -> Maybe (CommandDesc out)+testParse cmd s = either (const Nothing) Just+                $ snd+                $ runCmdParser Nothing (InputString s) cmd++testRun :: CmdParser Identity (WriterS.Writer (Sum Int) ()) () -> String -> Either ParsingError (Maybe Int)+testRun cmd s = fmap (fmap (getSum . WriterS.execWriter) . _cmd_out)+              $ snd+              $ runCmdParser Nothing (InputString s) cmd++testRunA :: CmdParser (StateS.State Int) () () -> String -> Either ParsingError Int+testRunA cmd str = (\((_, e), s) -> e $> s)+                 $ flip StateS.runState (0::Int)+                 $ runCmdParserA Nothing (InputString str) cmd++getDoc :: String -> CmdParser Identity out () -> CommandDesc ()+getDoc s = fst . runCmdParser (Just "test") (InputString s)
+ src/UI/Butcher/Monadic.hs view
@@ -0,0 +1,167 @@+-- | Main module of the butcher interface. It reexports everything that is+-- exposed in the submodules.+module UI.Butcher.Monadic+  ( -- * Types+    Input (..)+  , CmdParser+  , ParsingError (..)+  , CommandDesc(_cmd_out)+  , cmd_out+  , -- * Run or Check CmdParsers+    runCmdParserSimple+  , runCmdParser+  , runCmdParserExt+  , runCmdParserA+  , runCmdParserAExt+  , runCmdParserWithHelpDesc+  , checkCmdParser+  , -- * Building CmdParsers+    module UI.Butcher.Monadic.Command+    -- * PrettyPrinting CommandDescs (usage/help)+  , module  UI.Butcher.Monadic.Pretty+    -- * Wrapper around System.Environment.getArgs+  , module  UI.Butcher.Monadic.IO+  -- , cmds+  -- , sample+  -- , test+  -- , test2+  -- , test3+    -- * Builtin commands+  , addHelpCommand+  , addButcherDebugCommand+  , mapOut+  )+where++++#include "prelude.inc"++import UI.Butcher.Monadic.Types+import UI.Butcher.Monadic.Internal.Types+import UI.Butcher.Monadic.Command+import UI.Butcher.Monadic.BuiltinCommands+import UI.Butcher.Monadic.Internal.Core+import UI.Butcher.Monadic.Pretty+import UI.Butcher.Monadic.IO++import qualified Text.PrettyPrint as PP++++#ifdef HLINT+{-# ANN module "HLint: ignore Use import/export shortcut" #-}+#endif++++-- | Like 'runCmdParser', but with one additional twist: You get access+-- to a knot-tied complete CommandDesc for this full command. Useful in+-- combination with 'UI.Butcher.Monadic.BuiltinCommands.addHelpCommand'.+--+-- Note that the @CommandDesc ()@ in the output is _not_ the same value as the+-- parameter passed to the parser function: The output value contains a more+-- "shallow" description. This is more efficient for complex CmdParsers when+-- used interactively, because non-relevant parts of the CmdParser are not+-- traversed unless the parser function argument is forced.+runCmdParserWithHelpDesc+  :: Maybe String -- ^ program name to be used for the top-level @CommandDesc@+  -> Input -- ^ input to be processed+  -> (CommandDesc () -> CmdParser Identity out ()) -- ^ parser to use+  -> (CommandDesc (), Either ParsingError (CommandDesc out))+runCmdParserWithHelpDesc mProgName input cmdF =+  let (checkResult, fullDesc)+        -- knot-tying at its finest..+        = ( checkCmdParser mProgName (cmdF fullDesc)+          , either (const emptyCommandDesc) id $ checkResult+          )+  in runCmdParser mProgName input (cmdF fullDesc)+++-- | Wrapper around 'runCmdParser' for very simple usage: Accept a @String@+-- input and return only the output from the parser, returning @Nothing@ in+-- any error case.+runCmdParserSimple :: String -> CmdParser Identity out () -> Either String out+runCmdParserSimple s p = case snd $ runCmdParser Nothing (InputString s) p of+  Left e -> Left $ parsingErrorString e+  Right desc ->+    maybe (Left "command has no implementation") Right $ _cmd_out desc+++--------------------------------------+-- all below is for testing purposes+--------------------------------------+++_cmds :: CmdParser Identity (IO ()) ()+_cmds = do+  addCmd "echo" $ do+    addCmdHelpStr "print its parameter to output"+    str <- addReadParam "STRING" (paramHelpStr "the string to print")+    addCmdImpl $ do+      putStrLn str+  addCmd "hello" $ do+    addCmdHelpStr "greet the user"+    reorderStart+    short <- addSimpleBoolFlag "" ["short"] mempty+    name <- addReadParam "NAME" (paramHelpStr "your name, so you can be greeted properly"+                              <> paramDefault "user")+    reorderStop+    addCmdImpl $ do+      if short+        then putStrLn $ "hi, " ++ name ++ "!"+        else putStrLn $ "hello, " ++ name ++ ", welcome from butcher!"+  addCmd "foo" $ do+    addCmdHelpStr "foo"+    desc <- peekCmdDesc+    addCmdImpl $ do+      putStrLn "foo"+      print $ ppHelpShallow desc+  addCmd "help" $ do+    desc <- peekCmdDesc+    addCmdImpl $ do+      print $ ppHelpShallow $ maybe undefined snd (_cmd_mParent desc)++data Sample = Sample+  { _hello :: Int+  , _s1   :: String+  , _s2   :: String+  , _quiet :: Bool+  }+  deriving Show++-- sample :: OPA.Parser Sample+-- sample = Sample+--      <$> OPA.option OPA.auto+--          ( OPA.long "hello"+--         <> OPA.metavar "TARGET"+--         <> OPA.help "Target for the greeting" )+--      <*> OPA.strArgument (OPA.metavar "S1")+--      <*> OPA.strArgument (OPA.metavar "S2")+--      <*> OPA.switch+--          ( OPA.long "quiet"+--         <> OPA.help "Whether to be quiet" )+-- +-- test :: String -> OPA.ParserResult Sample+-- test s = OPA.execParserPure OPA.defaultPrefs (OPA.ParserInfo sample True mempty mempty mempty (-13) True) (List.words s)++_test2 :: IO ()+_test2 = case checkCmdParser (Just "butcher") _cmds of+  Left e -> putStrLn $ "LEFT: " ++ e+  Right desc -> do+    print $ ppUsage desc+    print $ maybe undefined id $ ppUsageAt ["hello"] desc++_test3 :: String -> IO ()+_test3 s = case runCmdParser (Just "butcher") (InputString s) _cmds of+  (desc, Left e) -> do+    print e+    print $ ppHelpShallow desc+    _cmd_mParent desc `forM_` \(_, d) -> do+      print $ ppUsage d+  (desc, Right out) -> do+    case _cmd_out out of+      Nothing -> do+        putStrLn "command is missing implementation!"+        print $ ppHelpShallow desc+      Just f -> f
+ src/UI/Butcher/Monadic/BuiltinCommands.hs view
@@ -0,0 +1,68 @@+-- | Some CmdParser actions that add predefined commands.+module UI.Butcher.Monadic.BuiltinCommands+  ( addHelpCommand+  , addHelpCommandShallow+  , addButcherDebugCommand+  )+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 Text.PrettyPrint as PP++import           Data.HList.ContainsType++import           UI.Butcher.Monadic.Internal.Types+import           UI.Butcher.Monadic.Internal.Core+import           UI.Butcher.Monadic.Pretty+import           UI.Butcher.Monadic.Param++import           System.IO++++-- | 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+  rest <- addRestOfInputStringParam "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 w $ Data.Foldable.toList $ _cmd_children curDesc of+            Nothing    -> curDesc+            Just child -> descent wr child+    print $ ppHelpShallow $ descent restWords parentDesc++-- | Adds a help command that prints help for the command currently in context.+--+-- This version does _not_ include further childcommands, i.e. "help foo" will+-- not print the help for subcommand "foo".+--+-- This also yields slightly different output depending on if it is used+-- before or after adding other subcommands. In general 'addHelpCommand'+-- should be preferred.+addHelpCommandShallow :: Applicative f => CmdParser f (IO ()) ()+addHelpCommandShallow = addCmd "help" $ do+  desc <- peekCmdDesc+  _rest <- addRestOfInputStringParam "SUBCOMMAND(s)" mempty+  addCmdImpl $ do+    let parentDesc = maybe undefined snd (_cmd_mParent desc)+    print $ ppHelpShallow $ parentDesc++-- | Prints the raw CommandDesc structure.+addButcherDebugCommand :: Applicative f => CmdParser f (IO ()) ()+addButcherDebugCommand = addCmd "butcherdebug" $ do+  desc <- peekCmdDesc+  addCmdImpl $ do+    print $ maybe undefined snd (_cmd_mParent desc)+
+ src/UI/Butcher/Monadic/Command.hs view
@@ -0,0 +1,95 @@+-- this module only re-exports the appropriate user-facing stuff from some+-- other modules.+-- | Building-blocks of a CmdParser.+--+-- The simplest sensible CmdParser is just+--+-- > addCmdImpl $ putStrLn "hello, world!"+--+-- (assuming out is IO ()).+--+-- The empty CmdParser is also valid:+--+-- > return ()+--+-- But not very interesting - you won't get an 'out' value from this (e.g. an+-- IO-action to execute) when this matches (on the empty input).+--+-- > do+-- >   addCmd "sub" $ do+-- >     addCmdImpl $ putStrLn "sub successful"+--+-- Here, note that there is no implementation at the top-level. This means that+-- on the empty input the resulting CommandDesc has no out-value, but on "sub"+-- it has. Executed as a program, the user would be shown the usage on empty+-- input, and the putStrLn would happen on "sub".+--+-- More than one subcommand? easy:+--+-- > do+-- >   addCmd "foo" $ do {..}+-- >   addCmd "bar" $ do {..}+--+-- Basic flag usage:+--+-- > do+-- >   shouldVerbose <- addSimpleBoolFlag "v" ["verbose"] mzero+-- >   addCmdImpl $ if shouldVerbose+-- >     then putStrLn "Hello, World!!!!!"+-- >     else putStrLn "hi."+--+-- Basic param usage:+--+-- > addCmd "echo" $ do+-- >   addCmdHelpStr "print its parameter to output"+-- >   str <- addRestOfInputStringParam "STRING" (paramHelpStr "the string to print")+-- >   addCmdImpl $ putStrLn str+-- > addCmd "echoInt" $ do+-- >   i <- addReadParam "INT" mempty+-- >   addCmdImpl $ print (i::Int) -- need to disambiguate via typesig.+--+-- There are some other flag/param methods in the respective modules.+-- Also note the example at 'reorderStart'.++module UI.Butcher.Monadic.Command+  ( addCmd+  , addCmdImpl+  , addCmdSynopsis+  , addCmdHelp+  , addCmdHelpStr+  , reorderStart+  , reorderStop+  , withReorder+  , peekCmdDesc+  , peekInput+    -- * Building CmdParsers - myprog -v --input PATH+  , module  UI.Butcher.Monadic.Flag+    -- * Building CmdParsers - myprog SOME_INT+  , module  UI.Butcher.Monadic.Param+    -- * Low-level part functions+  , addCmdPart+  , addCmdPartMany+  , addCmdPartInp+  , addCmdPartManyInp+  , ManyUpperBound (..)+  )+where++++#include "prelude.inc"++++import           UI.Butcher.Monadic.Internal.Types+import           UI.Butcher.Monadic.Internal.Core+import           UI.Butcher.Monadic.Flag+import           UI.Butcher.Monadic.Param++++-- | Safe wrapper around 'reorderStart'/'reorderStop' for cases where reducing+-- to a single binding is possible/preferable.+withReorder :: CmdParser f out a -> CmdParser f out a+withReorder x = reorderStart *> x <* reorderStop+
+ src/UI/Butcher/Monadic/Flag.hs view
@@ -0,0 +1,419 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++-- | Flags are arguments to your current command that are prefixed with "-" or+-- "--", for example "-v" or "--verbose". These flags can have zero or one+-- argument. (Butcher internally has more general concept of "CmdPart" that+-- could handle any number of arguments, so take this as what this module aims+-- to provide, not what you could theoretically implement on top of butcher).++-- Note that the current implementation only accepts "--foo param" but not+-- "--foo=param". Someone really ought to implement support for the latter+-- at some point :)+module UI.Butcher.Monadic.Flag+  ( Flag(..)+  , flagHelp+  , flagHelpStr+  , flagDefault+  , addSimpleBoolFlag+  , addSimpleCountFlag+  , addSimpleFlagA+  , addFlagReadParam+  , addFlagReadParams+  -- , addFlagReadParamA+  , addFlagStringParam+  , addFlagStringParams+  -- , addFlagStringParamA+  )+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 Text.PrettyPrint as PP++import           Data.HList.ContainsType++import           UI.Butcher.Monadic.Internal.Types+import           UI.Butcher.Monadic.Internal.Core++import           Data.List.Extra ( firstJust )++++-- TODO: perhaps move this to Types module and refactor all code to use it+newtype InpParseString a = InpParseString (StateS.StateT String Maybe a)+  deriving (Functor, Applicative, Monad, State.Class.MonadState String, Alternative, MonadPlus)++runInpParseString :: String -> InpParseString a -> Maybe (a, String)+runInpParseString s (InpParseString m) = StateS.runStateT m s++pExpect :: String -> InpParseString ()+pExpect s = InpParseString $ do+  inp <- StateS.get+  case List.stripPrefix s inp of+    Nothing -> mzero+    Just rest -> StateS.put rest++pExpectEof :: InpParseString ()+pExpectEof =+  InpParseString $ StateS.get >>= \inp -> if null inp then pure () else mzero++-- pDropSpace :: InpParseString ()+-- pDropSpace = InpParseString $ StateS.modify (dropWhile (==' '))++pOption :: InpParseString () -> InpParseString ()+pOption m = m <|> return ()++++-- | flag-description monoid. You probably won't need to use the constructor;+-- mzero or any (<>) of flag(Help|Default) works well.+data Flag p = Flag+  { _flag_help    :: Maybe PP.Doc+  , _flag_default :: Maybe p+  }++instance Monoid (Flag p) where+  mempty = Flag Nothing Nothing+  Flag a1 b1 `mappend` Flag a2 b2 = Flag (a1 <|> a2) (b1 <|> b2)++-- | Create a 'Flag' with just a help text.+flagHelp :: PP.Doc -> Flag p+flagHelp h = mempty { _flag_help = Just h }++-- | Create a 'Flag' with just a help text.+flagHelpStr :: String -> Flag p+flagHelpStr s = mempty { _flag_help = Just $ PP.text s }++-- | Create a 'Flag' with just a default value.+flagDefault :: p -> Flag p+flagDefault d = mempty { _flag_default = Just d }++-- | A no-parameter flag where non-occurence means False, occurence means True.+addSimpleBoolFlag+  :: Applicative f+  => String -- ^ short flag chars, i.e. "v" for -v+  -> [String] -- ^ list of long names, e.g. ["verbose"]+  -> Flag Void -- ^ properties+  -> CmdParser f out Bool+addSimpleBoolFlag shorts longs flag =+  addSimpleBoolFlagAll shorts longs flag (pure ())++-- | Applicative-enabled version of 'addSimpleFlag'+addSimpleFlagA+  :: String -- ^ short flag chars, i.e. "v" for -v+  -> [String] -- ^ list of long names, e.g. ["verbose"]+  -> Flag Void -- ^ properties+  -> f () -- ^ action to execute whenever this matches+  -> CmdParser f out ()+addSimpleFlagA shorts longs flag act+  = void $ addSimpleBoolFlagAll shorts longs flag act++addSimpleBoolFlagAll+  :: String+  -> [String]+  -> Flag Void+  -> f ()+  -> CmdParser f out Bool+addSimpleBoolFlagAll shorts longs flag a+  = fmap (not . null)+  $ addCmdPartManyA ManyUpperBound1 desc parseF (\() -> a)+  where+    allStrs = fmap (\c -> "-"++[c]) shorts+           ++ fmap (\s -> "--"++s)  longs+    desc :: PartDesc+    desc = (maybe id PartWithHelp $ _flag_help flag)+         $ PartAlts $ PartLiteral <$> allStrs+    parseF :: String -> Maybe ((), String)+    parseF str = ( firstJust (\s -> [ ((), drop (length s) str) | s==str ])+                             allStrs)+             <|> ( firstJust (\s -> [ ((), drop (length s + 1) str)+                                    | (s ++ " ") `isPrefixOf` str ])+                             allStrs)++-- | A no-parameter flag that can occur multiple times. Returns the number of+-- occurences (0 or more).+addSimpleCountFlag :: Applicative f+                   => String -- ^ short flag chars, i.e. "v" for -v+                   -> [String] -- ^ list of long names, i.e. ["verbose"]+                   -> Flag Void -- ^ properties+                   -> CmdParser f out Int+addSimpleCountFlag shorts longs flag+  = fmap length+  $ addCmdPartMany ManyUpperBoundN desc parseF+  where+    -- we _could_ allow this to parse repeated short flags, like "-vvv"+    -- (meaning "-v -v -v") correctly.+    allStrs = fmap (\c -> "-"++[c]) shorts+           ++ fmap (\s -> "--"++s)  longs+    desc :: PartDesc+    desc = (maybe id PartWithHelp $ _flag_help flag)+         $ PartAlts $ PartLiteral <$> allStrs+    parseF :: String -> Maybe ((), String)+    parseF str = ( firstJust (\s -> [ ((), drop (length s) str) | s==str ])+                             allStrs)+             <|> ( firstJust (\s -> [ ((), drop (length s + 1) str)+                                    | (s ++ " ") `isPrefixOf` str ])+                             allStrs)++-- | One-argument flag, where the argument is parsed via its Read instance.+addFlagReadParam+  :: forall f p out+   . (Applicative f, Typeable p, Text.Read.Read p, Show p)+  => String -- ^ short flag chars, i.e. "v" for -v+  -> [String] -- ^ list of long names, i.e. ["verbose"]+  -> String -- ^ param name+  -> Flag p -- ^ properties+  -> CmdParser f out p+addFlagReadParam shorts longs name flag = addCmdPartInpA desc+                                                         parseF+                                                         (\_ -> pure ())+ where+  allStrs =+    [ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]+  desc =+    (maybe id PartWithHelp $ _flag_help flag)+      $ maybe id (PartDefault . show) (_flag_default flag)+      $ PartSeq [desc1, desc2]+  desc1 :: PartDesc+  desc1 = PartAlts $ PartLiteral . either id id <$> allStrs+  desc2 = PartVariable name+  parseF :: Input -> Maybe (p, Input)+  parseF inp = case inp of+    InputString str ->+      maybe (_flag_default flag <&> \x -> (x, inp)) (Just . second InputString)+        $ parseResult+     where+      parseResult = runInpParseString str $ do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")+          Right s -> pExpect s *> (pExpect " " <|> pExpect "=")+        InpParseString $ do+          i <- StateS.get+          case Text.Read.reads i of+            ((x, ' ':r):_) -> StateS.put (dropWhile Char.isSpace r) $> x+            ((x, ""   ):_) -> StateS.put "" $> x+            _              -> mzero+    InputArgs (arg1:argR) -> case runInpParseString arg1 parser of+      Just ((), "") -> case argR of+        []          -> Nothing+        (arg2:rest) -> readMaybe arg2 <&> \x -> (x, InputArgs rest)+      Just ((), remainingStr) ->+        readMaybe remainingStr <&> \x -> (x, InputArgs argR)+      Nothing -> _flag_default flag <&> \d -> (d, inp)+     where+      parser :: InpParseString ()+      parser = do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect "=")+          Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)+    InputArgs _ -> _flag_default flag <&> \d -> (d, inp)++-- | One-argument flag, where the argument is parsed via its Read instance.+-- This version can accumulate multiple values by using the same flag with+-- different arguments multiple times.+--+-- E.g. "--foo 3 --foo 5" yields [3,5].+addFlagReadParams+  :: forall f p out+   . (Applicative f, Typeable p, Text.Read.Read p, Show p)+  => String -- ^ short flag chars, i.e. "v" for -v+  -> [String] -- ^ list of long names, i.e. ["verbose"]+  -> String -- ^ param name+  -> Flag p -- ^ properties+  -> CmdParser f out [p]+addFlagReadParams shorts longs name flag+  = addFlagReadParamsAll shorts longs name flag (\_ -> pure ())++-- TODO: this implementation is wrong, because it uses addCmdPartManyInpA+--       while this really is no Many.+-- | Applicative-enabled version of 'addFlagReadParam'+-- addFlagReadParamA+--   :: forall f p out+--    . (Typeable p, Text.Read.Read p, Show p)+--   => String -- ^ short flag chars, i.e. "v" for -v+--   -> [String] -- ^ list of long names, i.e. ["verbose"]+--   -> String -- ^ param name+--   -> Flag p -- ^ properties+--   -> (p -> f ()) -- ^ action to execute when ths param matches+--   -> CmdParser f out ()+-- addFlagReadParamA shorts longs name flag act+--   = void $ addFlagReadParamsAll shorts longs name flag act++addFlagReadParamsAll+  :: forall f p out . (Typeable p, Text.Read.Read p, Show p) => String -- ^ short flag chars, i.e. "v" for -v+     -> [String] -- ^ list of long names, i.e. ["verbose"]+     -> String -- ^ param name+     -> Flag p -- ^ properties+     -> (p -> f ()) -- ^ action to execute when ths param matches+     -> CmdParser f out [p]+addFlagReadParamsAll shorts longs name flag act = addCmdPartManyInpA+  ManyUpperBoundN+  desc+  parseF+  act+ where+  allStrs =+    [ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]+  desc = (maybe id PartWithHelp $ _flag_help flag) $ PartSeq [desc1, desc2]+  desc1 :: PartDesc+  desc1 = PartAlts $ PartLiteral . either id id <$> allStrs+  desc2 =+    (maybe id (PartDefault . show) $ _flag_default flag) $ PartVariable name+  parseF :: Input -> Maybe (p, Input)+  parseF inp = case inp of+    InputString str -> fmap (second InputString) $ parseResult+     where+      parseResult = runInpParseString str $ do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")+          Right s -> pExpect s *> (pExpect " " <|> pExpect "=")+        InpParseString $ do+          i <- StateS.get+          case Text.Read.reads i of+            ((x, ' ':r):_) -> StateS.put (dropWhile Char.isSpace r) $> x+            ((x, ""   ):_) -> StateS.put "" $> x+            _              -> lift $ _flag_default flag+    InputArgs (arg1:argR) -> case runInpParseString arg1 parser of+      Just ((), "") -> case argR of+        []          -> mdef+        (arg2:rest) -> (readMaybe arg2 <&> \x -> (x, InputArgs rest)) <|> mdef+        where mdef = _flag_default flag <&> \p -> (p, InputArgs argR)+      Just ((), remainingStr) ->+        readMaybe remainingStr <&> \x -> (x, InputArgs argR)+      Nothing -> Nothing+     where+      parser :: InpParseString ()+      parser = do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect "=")+          Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)+    InputArgs _ -> Nothing++-- | One-argument flag where the argument can be an arbitrary string.+addFlagStringParam+  :: forall f out . (Applicative f) => String -- ^ short flag chars, i.e. "v" for -v+     -> [String] -- ^ list of long names, i.e. ["verbose"]+     -> String -- ^ param name+     -> Flag String -- ^ properties+     -> CmdParser f out String+addFlagStringParam shorts longs name flag = addCmdPartInpA desc+                                                           parseF+                                                           (\_ -> pure ())+ where+  allStrs =+    [ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]+  desc = (maybe id PartWithHelp $ _flag_help flag) $ PartSeq [desc1, desc2]+  desc1 :: PartDesc+  desc1 = PartAlts $ PartLiteral . either id id <$> allStrs+  desc2 = PartVariable name+  parseF :: Input -> Maybe (String, Input)+  parseF inp = case inp of+    InputString str ->+      maybe (_flag_default flag <&> \x -> (x, inp)) (Just . second InputString)+        $ parseResult+     where+      parseResult = runInpParseString str $ do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")+          Right s -> pExpect s *> (pExpect " " <|> pExpect "=")+        InpParseString $ do+          i <- StateS.get+          let (x, rest) = break Char.isSpace $ dropWhile Char.isSpace i+          StateS.put rest+          pure x+    InputArgs (arg1:argR) -> case runInpParseString arg1 parser of+      Just ((), ""          ) -> case argR of+        []       -> Nothing+        (x:rest) -> Just (x, InputArgs rest)+      Just ((), remainingStr) -> Just (remainingStr, InputArgs argR)+      Nothing                 -> _flag_default flag <&> \d -> (d, inp)+     where+      parser :: InpParseString ()+      parser = do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect "=")+          Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)+    InputArgs _ -> _flag_default flag <&> \d -> (d, inp)++-- | One-argument flag where the argument can be an arbitrary string.+-- This version can accumulate multiple values by using the same flag with+-- different arguments multiple times.+--+-- E.g. "--foo abc --foo def" yields ["abc", "def"].+addFlagStringParams+  :: forall f out+   . (Applicative f)+  => String -- ^ short flag chars, i.e. "v" for -v+  -> [String] -- ^ list of long names, i.e. ["verbose"]+  -> String -- ^ param name+  -> Flag Void -- ^ properties+  -> CmdParser f out [String]+addFlagStringParams shorts longs name flag+  = addFlagStringParamsAll shorts longs name flag (\_ -> pure ())++-- TODO: this implementation is wrong, because it uses addCmdPartManyInpA+--       while this really is no Many.+-- -- | Applicative-enabled version of 'addFlagStringParam'+-- addFlagStringParamA+--   :: forall f out+--   .  String -- ^ short flag chars, i.e. "v" for -v+--   -> [String] -- ^ list of long names, i.e. ["verbose"]+--   -> String -- ^ param name+--   -> Flag Void -- ^ properties+--   -> (String -> f ()) -- ^ action to execute when ths param matches+--   -> CmdParser f out ()+-- addFlagStringParamA shorts longs name flag act+--   = void $ addFlagStringParamsAll shorts longs name flag act++addFlagStringParamsAll+  :: forall f out . String+     -> [String]+     -> String+     -> Flag Void -- we forbid the default because it has bad interaction+               -- with the eat-anything behaviour of the string parser.+     -> (String -> f ())+     -> CmdParser f out [String]+addFlagStringParamsAll shorts longs name flag act = addCmdPartManyInpA+  ManyUpperBoundN+  desc+  parseF+  act+ where+  allStrs =+    [ Left $ "-" ++ [c] | c <- shorts ] ++ [ Right $ "--" ++ l | l <- longs ]+  desc = (maybe id PartWithHelp $ _flag_help flag) $ PartSeq [desc1, desc2]+  desc1 :: PartDesc+  desc1 = PartAlts $ PartLiteral . either id id <$> allStrs+  desc2 =+    (maybe id (PartDefault . show) $ _flag_default flag) $ PartVariable name+  parseF :: Input -> Maybe (String, Input)+  parseF inp = case inp of+    InputString str -> fmap (second InputString) $ parseResult+     where+      parseResult = runInpParseString str $ do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect " " <|> pExpect "=")+          Right s -> pExpect s *> (pExpect " " <|> pExpect "=")+        InpParseString $ do+          i <- StateS.get+          let (x, rest) = break Char.isSpace $ dropWhile Char.isSpace i+          StateS.put rest+          pure x+    InputArgs (arg1:argR) -> case runInpParseString arg1 parser of+      Just ((), ""          ) -> case argR of+        []       -> Nothing+        (x:rest) -> Just (x, InputArgs rest)+      Just ((), remainingStr) -> Just (remainingStr, InputArgs argR)+      Nothing                 -> Nothing+     where+      parser :: InpParseString ()+      parser = do+        Data.Foldable.msum $ allStrs <&> \case+          Left  s -> pExpect s *> pOption (pExpect "=")+          Right s -> pExpect s *> (pExpect "=" <|> pExpectEof)+    InputArgs _ -> Nothing
+ src/UI/Butcher/Monadic/IO.hs view
@@ -0,0 +1,117 @@+-- | Turn your CmdParser into an IO () to be used as your program @main@.+module UI.Butcher.Monadic.IO+  ( mainFromCmdParser+  , mainFromCmdParserWithHelpDesc+  )+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 Text.PrettyPrint as PP++import           Data.HList.ContainsType++import           UI.Butcher.Monadic.Internal.Types+import           UI.Butcher.Monadic.Internal.Core+import           UI.Butcher.Monadic.Pretty+import           UI.Butcher.Monadic.Param++import           System.IO++++-- | Utility method that allows using a 'CmdParser' as your @main@ function:+--+-- > main = mainFromCmdParser $ do+-- >   addCmdImpl $ putStrLn "This is a fairly boring program."+--+-- Uses @System.Environment.getProgName@ as program name and+-- @System.Environment.getArgs@ as the input to be parsed. Prints some+-- appropriate messages if parsing fails or if the command has no+-- implementation; if all is well executes the \'out\' action (the IO ()).+mainFromCmdParser :: CmdParser Identity (IO ()) () -> IO ()+mainFromCmdParser cmd = do+  progName <- System.Environment.getProgName+  case checkCmdParser (Just progName) cmd of+    Left  e -> do+      putStrErrLn+        $ progName+        ++ ": internal error: failed sanity check for butcher main command parser!"+      putStrErrLn $ "(" ++ e ++ ")"+      putStrErrLn $ "aborting."+    Right _ -> do+      args <- System.Environment.getArgs+      case runCmdParser (Just progName) (InputArgs args) cmd of+        (desc, Left (ParsingError mess remaining)) -> do+          putStrErrLn+            $  progName+            ++ ": error parsing arguments: "+            ++ case mess of+                 []    -> ""+                 (m:_) -> m+          putStrErrLn $ case remaining of+            InputString ""  -> "at the end of input."+            InputString str -> case show str of+              s | length s < 42 -> "at: " ++ s ++ "."+              s                 -> "at: " ++ take 40 s ++ "..\"."+            InputArgs   []  -> "at the end of input"+            InputArgs   xs  -> case List.unwords $ show <$> xs of+              s | length s < 42 -> "at: " ++ s ++ "."+              s                 -> "at: " ++ take 40 s ++ "..\"."+          putStrErrLn $ "usage:"+          printErr $ ppUsage desc+        (desc, Right out                         ) -> case _cmd_out out of+          Nothing -> do+            putStrErrLn $ "usage:"+            printErr $ ppUsage desc+          Just a  -> a++-- | Same as mainFromCmdParser, but with one additional twist: You get access+-- to a knot-tied complete CommandDesc for this full command. Useful in+-- combination with 'UI.Butcher.Monadic.BuiltinCommands.addHelpCommand'+mainFromCmdParserWithHelpDesc+  :: (CommandDesc () -> CmdParser Identity (IO ()) ()) -> IO ()+mainFromCmdParserWithHelpDesc cmdF = do+  progName <- System.Environment.getProgName+  let (checkResult, fullDesc)+        -- knot-tying at its finest..+        = ( checkCmdParser (Just progName) (cmdF fullDesc)+          , either (const emptyCommandDesc) id $ checkResult+          )+  case checkResult of+    Left e -> do+      putStrErrLn $ progName ++ ": internal error: failed sanity check for butcher main command parser!"+      putStrErrLn $ "(" ++ e ++ ")"+      putStrErrLn $ "aborting."+    Right _ -> do+      args <- System.Environment.getArgs+      case runCmdParser (Just progName) (InputArgs args) (cmdF fullDesc) of+        (desc, Left (ParsingError mess remaining)) -> do+          putStrErrLn $ progName ++ ": error parsing arguments: " ++ head mess+          putStrErrLn $ case remaining of+            InputString "" -> "at the end of input."+            InputString str -> case show str of+              s | length s < 42 -> "at: " ++ s ++ "."+              s -> "at: " ++ take 40 s ++ "..\"."+            InputArgs [] -> "at the end of input"+            InputArgs xs -> case List.unwords $ show <$> xs of+              s | length s < 42 -> "at: " ++ s ++ "."+              s -> "at: " ++ take 40 s ++ "..\"."+          putStrErrLn $ "usage:"+          printErr $ ppUsage desc+        (desc, Right out) -> case _cmd_out out of+          Nothing -> do+            putStrErrLn $ "usage:"+            printErr $ ppUsage desc+          Just a -> a++putStrErrLn :: String -> IO ()+putStrErrLn s = hPutStrLn stderr s++printErr :: Show a => a -> IO ()+printErr = putStrErrLn . show
+ src/UI/Butcher/Monadic/Internal/Core.hs view
@@ -0,0 +1,1147 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# 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+  , 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 str 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 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 -> (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 -> (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 String, Map String (CmdParser f out (), f ()))+            -> ChildGather f out+            -> (Deque String, Map String (CmdParser f out (), f ()))+          child_fold (c_names, c_map) (ChildGather name child act) =+            case name `MapS.lookup` c_map of+              Nothing ->+                ( Deque.snoc name c_names+                , MapS.insert name (child, act) c_map+                )+              Just (child', act') ->+                ( c_names+                , MapS.insert name (child' >> child, act') c_map+                   -- we intentionally override/ignore act here.+                   -- TODO: it should be documented that we expect the same act+                   -- on different child nodes with the same name.+                )+          (child_name_list, child_map) =+            foldl' child_fold (mempty, MapS.empty) gatheredChildren+          combined_child_list = Data.Foldable.toList child_name_list <&> \n ->+            (n, child_map MapS.! n)+        let mRest = asum $ combined_child_list <&> \(name, (child, act)) ->+              case input of+                InputString str | name == str ->+                  Just $ (name, child, act, InputString "")+                InputString str | (name++" ") `isPrefixOf` str ->+                  Just $ (name, child, act, InputString $ drop (length name + 1) str)+                InputArgs (str:strr) | name == str ->+                  Just $ (name, child, act, InputArgs strr)+                _ -> Nothing+        case mRest of+          Nothing -> do -- 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 :: String -> Deque (String, a) -> (Maybe a, Deque (String, a))+dequeLookupRemove key deque = case Deque.uncons deque of+  Nothing -> (Nothing, mempty)+  Just ((k, v), rest) -> if k==key+    then (Just v, rest)+    else let (r, rest') = dequeLookupRemove key rest+          in (r, Deque.cons (k, v) rest')++takeCommandChild+  :: MonadMultiState (CommandDesc out) m+  => String+  -> m (Maybe (CommandDesc out))+takeCommandChild key = do+  cmd <- mGet+  let (r, children') = dequeLookupRemove key $ _cmd_children cmd+  mSet cmd { _cmd_children = children' }+  return r++-- | 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 (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 -> (String, CommandDesc a) -> (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 -> (String, CommandDesc a) -> (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
@@ -0,0 +1,210 @@+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TemplateHaskell #-}++module UI.Butcher.Monadic.Internal.Types+  ( CommandDesc (..)+  , cmd_mParent+  , cmd_help+  , cmd_synopsis+  , cmd_parts+  , cmd_out+  , cmd_children+  , emptyCommandDesc+  , CmdParserF (..)+  , CmdParser+  , PartDesc (..)+  , Input (..)+  , ParsingError (..)+  , addSuggestion+  , ManyUpperBound (..)+  )+where++++#include "prelude.inc"+import           Control.Monad.Free+import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS++import qualified Lens.Micro.TH as LensTH++import qualified Text.PrettyPrint as PP++++-- | Butcher supports two input modi: @String@ and @[String]@. Program+-- arguments have the latter form, while parsing interactive command input+-- (e.g. when you implement a terminal of sorts) is easier when you can+-- process the full @String@ without having to wordify it first by some+-- means (and List.words is not the right approach in many situations.)+data Input = InputString String | InputArgs [String]+  deriving (Show, Eq)++-- | Information about an error that occured when trying to parse some @Input@+-- using some @CmdParser@.+data ParsingError = ParsingError+  { _pe_messages  :: [String]+  , _pe_remaining :: Input+  }+  deriving (Show, Eq)++-- | Specifies whether we accept 0-1 or 0-n for @CmdParserPart@s.+data ManyUpperBound+  = ManyUpperBound1+  | ManyUpperBoundN++data CmdParserF f out a+  =                          CmdParserHelp PP.Doc a+  |                          CmdParserSynopsis String a+  |                          CmdParserPeekDesc (CommandDesc () -> a)+  |                          CmdParserPeekInput (String -> a)+  -- TODO: we can clean up this duplication by providing+  -- a function (String -> Maybe (p, String)) -> (Input -> Maybe (p, Input)).+  | forall p . Typeable p => CmdParserPart PartDesc (String -> Maybe (p, String)) (p -> f ()) (p -> a)+  | 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 String (CmdParser f out ()) (f ()) a+  |                          CmdParserImpl  out                                a+  |                          CmdParserReorderStart                             a+  |                          CmdParserReorderStop                              a+  |                          CmdParserGrouped String                           a+  |                          CmdParserGroupEnd                                 a++-- | The CmdParser monad type. It is a free monad over some functor but users+-- of butcher don't need to know more than that 'CmdParser' is a 'Monad'.+type CmdParser f out = Free (CmdParserF f out)+++-- type CmdParser a = CmdParserM a a++-- data CmdPartParserF a+--   = CmdPartParserHelp String a+--   | forall p . CmdPartParserCore (String -> Maybe (p, String)) -- parser+--                                  (Maybe p) -- optional default value+--                                  (p -> a)+--   | forall p . CmdPartParserOptional (CmdPartParser p)+--                                      (Maybe p -> a)+--   -- the idea here was to allow adding some dynamic data to each "node" of+--   -- the output CommandDesc so the user can potentially add custom additional+--   -- information, and write a custom pretty-printer for e.g. help output+--   -- from that dynamically-enriched CommandDesc structure.+--   -- disabled for now, because i am not sure what exactly "adding to every+--   -- node" involves, because the mapping from Functor to Desc is nontrivial.+--   -- (and because i don't have a direct use-case at the moment..)+--   -- | CmdPartParserCustom Dynamic a+-- +-- type CmdPartParser = Free CmdPartParserF++---------++-- | A representation/description of a command parser built via the+-- 'CmdParser' monad. Can be transformed into a pretty Doc to display+-- as usage/help via 'ppUsage' and related functions.+--+-- Note that there is the '_cmd_out' accessor that contains @Maybe out@ which+-- might be useful after successful parsing.+data CommandDesc out = CommandDesc+  { _cmd_mParent  :: Maybe (String, CommandDesc out)+  , _cmd_synopsis :: Maybe PP.Doc+  , _cmd_help     :: Maybe PP.Doc+  , _cmd_parts    :: [PartDesc]+  , _cmd_out      :: Maybe out+  , _cmd_children :: Deque (String, CommandDesc out) -- we don't use a Map here+                                                     -- because we'd like to+                                                     -- retain the order.+  }++-- type PartSeqDesc = [PartDesc]++-- | A representation/description of a command's parts, i.e. flags or params.+-- As a butcher user, the higher-level pretty-printing functions for+-- 'CommandDesc' are probably sufficient.+data PartDesc+  = PartLiteral String -- expect a literal string, like "--dry-run"+  | PartVariable String -- expect some user-provided input. The+                               -- string represents the name for the variable+                               -- used in the documentation, e.g. "FILE"+  | PartOptional PartDesc+  | PartAlts [PartDesc]+  | PartSeq [PartDesc]+  | PartDefault String -- default representation+                PartDesc+  | PartSuggestion [String] PartDesc+  | PartRedirect String -- name for the redirection+                 PartDesc+  | PartReorder [PartDesc]+  | PartMany PartDesc+  | PartWithHelp PP.Doc PartDesc+  deriving Show++addSuggestion :: Maybe [String] -> PartDesc -> PartDesc+addSuggestion Nothing     = id+addSuggestion (Just sugs) = PartSuggestion sugs++{-+command documentation structure+1. terminals. e.g. "--dry-run"+2. non-terminals, e.g. "FILES"+3. sequences, e.g. "<program> FLAGS NUMBER PATH"+-- 4. alternatives, e.g. "--date=(relative|local|iso|rfc|..)"+5. sub-commands: git (init|commit|push|clone|..)+   compared to 4, the subcommands have their own flags and params;+   they essentially "take over".+6. optional, e.g. "cabal run [COMPONENT]"+7. default, e.g. "-O(LEVEL=1)"+8. indirection, e.g. "cabal COMMAND\n\nCOMMAND: ..."+-}++--++deriving instance Functor (CmdParserF f out)+deriving instance Functor CommandDesc++--++emptyCommandDesc :: CommandDesc out+emptyCommandDesc = CommandDesc Nothing Nothing Nothing [] Nothing mempty++instance Show (CommandDesc out) where+  show c = "Command help=" ++ show (_cmd_help c)+        ++ " synopsis=" ++ show (_cmd_synopsis c)+        ++ " mParent=" ++ show (fst <$> _cmd_mParent c)+        ++ " out=" ++ maybe "(none)" (\_ -> "(smth)") (_cmd_out c)+        ++ " parts.length=" ++ show (length $ _cmd_parts c)+        ++ " parts=" ++ show (_cmd_parts c)+        ++ " children=" ++ show (fst <$> _cmd_children c)++--++LensTH.makeLenses ''CommandDesc+LensTH.makeLenses ''PartDesc++--++++-- instance Show FlagDesc where+--   show (FlagDesc _ short long helpM params) = show (short, long, helpM, params) -- TODO: improve++-- class Typeable a => IsParam a where+--   paramParse :: String -> Maybe (a, String, String) -- value, representation, rest+--   paramStaticDef :: a++-- emptyParamDesc :: ParamDesc a+-- emptyParamDesc = ParamDesc Nothing Nothing++-- deriving instance Show a => Show (ParamDesc a)+++-- instance Show a => Show (CmdParserF out a) where+--   show (CmdParserHelp s x) = "(CmdParserHelp " ++ show s ++ " " ++ show x ++ ")"+--   show (CmdParserFlag shorts longs _ _) = "(CmdParserFlag -" ++ shorts ++ " " ++ show longs ++ ")"+--   show (CmdParserParam s _ _) = "(CmdParserParam " ++ s ++ ")"+--   show (CmdParserChild s _ _) = "(CmdParserChild " ++ s ++ ")"+--   show (CmdParserRun _) = "CmdParserRun"+
+ src/UI/Butcher/Monadic/Param.hs view
@@ -0,0 +1,171 @@++-- | Parameters are arguments of your current command that are not prefixed+-- by some flag. Typical commandline interface is something like+-- "PROGRAM [FLAGS] INPUT". Here, FLAGS are Flags in butcher, and INPUT is+-- a Param, in this case a String representing a path, for example.+module UI.Butcher.Monadic.Param+  ( Param(..)+  , paramHelp+  , paramHelpStr+  , paramDefault+  , paramSuggestions+  , addReadParam+  , addReadParamOpt+  , addStringParam+  , addStringParamOpt+  , addRestOfInputStringParam+  )+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 Text.PrettyPrint as PP++import           Data.HList.ContainsType++import           UI.Butcher.Monadic.Internal.Types+import           UI.Butcher.Monadic.Internal.Core++++-- | param-description monoid. You probably won't need to use the constructor;+-- mzero or any (<>) of param(Help|Default|Suggestion) works well.+data Param p = Param+  { _param_default :: Maybe p+  , _param_help :: Maybe PP.Doc+  , _param_suggestions :: Maybe [p]+  }++instance Monoid (Param p) where+  mempty = Param Nothing Nothing Nothing+  mappend (Param a1 b1 c1)+          (Param a2 b2 c2)+    = Param+          (a1 `f` a2)+          (b1 `mappend` b2)+          (c1 `mappend` c2)+    where+      f Nothing x = x+      f x       _ = x++-- | Create a 'Param' with just a help text.+paramHelpStr :: String -> Param p+paramHelpStr s = mempty { _param_help = Just $ PP.text s }++-- | Create a 'Param' with just a help text.+paramHelp :: PP.Doc -> Param p+paramHelp h = mempty { _param_help = Just h }++-- | Create a 'Param' with just a default value.+paramDefault :: p -> Param p+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 }++-- | 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+-- is normal for the Show/Read instances for String.+addReadParam :: forall f out a+              . (Applicative f, Typeable a, Show a, Text.Read.Read a)+             => String -- ^ paramater name, for use in usage/help texts+             -> Param a -- ^ properties+             -> CmdParser f out a+addReadParam name par = addCmdPart desc parseF+  where+    desc :: PartDesc+    desc = (maybe id PartWithHelp $ _param_help par)+         $ (maybe id (PartDefault . show) $ _param_default par)+         $ PartVariable name+    parseF :: String -> Maybe (a, String)+    parseF s = case Text.Read.reads s of+      ((x, ' ':r):_) -> Just (x, dropWhile Char.isSpace r)+      ((x, []):_)    -> Just (x, [])+      _ -> _param_default par <&> \x -> (x, s)++-- | Like addReadParam, but optional. I.e. if reading fails, returns Nothing.+addReadParamOpt :: forall f out a+                 . (Applicative f, Typeable a, Text.Read.Read a)+                => String -- ^ paramater name, for use in usage/help texts+                -> Param a -- ^ properties+                -> CmdParser f out (Maybe a)+addReadParamOpt name par = addCmdPart desc parseF+  where+    desc :: PartDesc+    desc = PartOptional+         $ (maybe id PartWithHelp $ _param_help par)+         $ PartVariable name+    parseF :: String -> Maybe (Maybe a, String)+    parseF s = case Text.Read.reads s of+      ((x, ' ':r):_) -> Just (Just x, dropWhile Char.isSpace r)+      ((x, []):_)    -> Just (Just x, [])+      _ -> Just (Nothing, s) -- TODO: we could warn about a default..++-- | Add a parameter that matches any string of non-space characters if input+-- String, or one full argument if input is [String]. See the 'Input' doc for+-- this distinction.+addStringParam+  :: forall f out . (Applicative f)+  => String+  -> Param String+  -> CmdParser f out String+addStringParam name par = addCmdPartInp desc parseF+  where+    desc :: PartDesc+    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+          ("", rest) -> _param_default par <&> \x -> (x, InputString rest)+          (x, rest) -> Just (x, InputString rest)+    parseF (InputArgs args) = case args of+      (s1:sR) -> Just (s1, InputArgs sR)+      []      -> _param_default par <&> \x -> (x, InputArgs args)++-- | Like 'addStringParam', but optional, I.e. succeeding with Nothing if+-- there is no remaining input.+addStringParamOpt+  :: forall f out . (Applicative f)+  => String+  -> Param Void+  -> CmdParser f out (Maybe String)+addStringParamOpt name par = addCmdPartInp desc parseF+  where+    desc :: PartDesc+    desc = PartOptional+         $ (maybe id PartWithHelp $ _param_help par)+         $ PartVariable name+    parseF :: Input -> Maybe (Maybe String, Input)+    parseF (InputString str)+      = case break Char.isSpace $ dropWhile Char.isSpace str of+          ("", rest) -> Just (Nothing, InputString rest)+          (x, rest) -> Just (Just x, InputString rest)+    parseF (InputArgs args) = case args of+      (s1:sR) -> Just (Just s1, InputArgs sR)+      []      -> Just (Nothing, InputArgs [])+++-- | Add a parameter that consumes _all_ remaining input. Typical usecase is+-- after a "--" as common in certain (unix?) commandline tools.+addRestOfInputStringParam+  :: 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 [])
+ src/UI/Butcher/Monadic/Pretty.hs view
@@ -0,0 +1,254 @@++-- | Pretty-print of CommandDescs. To explain what the different functions+-- do, we will use an example CmdParser. The CommandDesc derived from that+-- CmdParser will serve as example input to the functions in this module.+--+-- > main = mainFromCmdParserWithHelpDesc $ \helpDesc -> do+-- > +-- >   addCmdSynopsis "a simple butcher example program"+-- >   addCmdHelpStr "a very long help document"+-- > +-- >   addCmd "version" $ do+-- >     porcelain <- addSimpleBoolFlag "" ["porcelain"]+-- >       (flagHelpStr "print nothing but the numeric version")+-- >     addCmdHelpStr "prints the version of this program"+-- >     addCmdImpl $ putStrLn $ if porcelain+-- >       then "0.0.0.999"+-- >       else "example, version 0.0.0.999"+-- > +-- >   addCmd "help" $ addCmdImpl $ print $ ppHelpShallow helpDesc+-- > +-- >   short <- addSimpleBoolFlag "" ["short"] (flagHelpStr "make the greeting short")+-- >   name <- addStringParam "NAME"+-- >     (paramHelpStr "your name, so you can be greeted properly")+-- > +-- >   addCmdImpl $ do+-- >     if short+-- >       then putStrLn $ "hi, " ++ name ++ "!"+-- >       else putStrLn $ "hello, " ++ name ++ ", welcome from butcher!"+module UI.Butcher.Monadic.Pretty+  ( ppUsage+  , ppUsageAt+  , ppHelpShallow+  , ppUsageWithHelp+  , ppPartDescUsage+  , ppPartDescHeader+  , parsingErrorString+  )+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 Text.PrettyPrint as PP+import           Text.PrettyPrint ( (<+>), ($$), ($+$) )++import           Data.HList.ContainsType++import           UI.Butcher.Monadic.Internal.Types+import           UI.Butcher.Monadic.Internal.Core++++-- | ppUsage exampleDesc yields:+--+-- > playground [--short] NAME [version | help]+ppUsage :: CommandDesc a -> PP.Doc+ppUsage (CommandDesc mParent _help _syn parts out children) = pparents mParent+  <+> PP.fsep (partDocs ++ [subsDoc])+ where+  pparents :: Maybe (String, CommandDesc out) -> PP.Doc+  pparents Nothing        = PP.empty+  pparents (Just (n, cd)) = pparents (_cmd_mParent cd) <+> PP.text n+  partDocs = parts <&> ppPartDescUsage+  subsDoc  = case out of+    _ | null children -> PP.empty -- TODO: remove debug+    Nothing | null parts -> subDoc+            | otherwise  -> PP.parens $ subDoc+    Just{} -> PP.brackets $ subDoc+  subDoc =+    PP.fcat+      $   PP.punctuate (PP.text " | ")+      $   Data.Foldable.toList+      $   children+      <&> \(n, _) -> PP.text n++-- | ppUsageWithHelp exampleDesc yields:+--+-- > playground [--short] NAME+-- >            [version | help]: a simple butcher example program+--+-- And yes, the line break is not optimal in this instance with default print.+ppUsageWithHelp :: CommandDesc a -> PP.Doc+ppUsageWithHelp (CommandDesc mParent help _syn parts out children) =+  pparents mParent <+> PP.fsep (partDocs ++ [subsDoc]) PP.<> helpDoc+ where+  pparents :: Maybe (String, CommandDesc out) -> PP.Doc+  pparents Nothing        = PP.empty+  pparents (Just (n, cd)) = pparents (_cmd_mParent cd) <+> PP.text n+  partDocs = parts <&> ppPartDescUsage+  subsDoc  = case out of+    _ | null children -> PP.empty -- TODO: remove debug+    Nothing | null parts -> subDoc+            | otherwise  -> PP.parens $ subDoc+    Just{} -> PP.brackets $ subDoc+  subDoc =+    PP.fcat+      $   PP.punctuate (PP.text " | ")+      $   Data.Foldable.toList+      $   children+      <&> \(n, _) -> PP.text n+  helpDoc = case help of+    Nothing -> PP.empty+    Just h  -> PP.text ":" PP.<+> h++-- | > ppUsageAt [] = ppUsage+--+-- fromJust $ ppUsageAt ["version"] exampleDesc yields:+--+-- > example version [--porcelain]+ppUsageAt :: [String] -- (sub)command sequence+          -> CommandDesc a+          -> Maybe PP.Doc+ppUsageAt strings desc =+  case strings of+    [] -> Just $ ppUsage desc+    (s:sr) -> find ((s==) . fst) (_cmd_children desc) >>= ppUsageAt sr . snd++-- | ppHelpShalloe exampleDesc yields:+--+-- > NAME+-- > +-- >   example - a simple butcher example program+-- > +-- > USAGE+-- > +-- >   example [--short] NAME [version | help]+-- > +-- > DESCRIPTION+-- > +-- >   a very long help document+-- > +-- > ARGUMENTS+-- > +-- >   --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) =+        nameSection+    $+$ usageSection+    $+$ descriptionSection+    $+$ partsSection+    $+$ PP.text ""+  where+    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 (String, CommandDesc out) -> PP.Doc+    pparents Nothing = PP.empty+    pparents (Just (n, cd)) = pparents (_cmd_mParent cd) PP.<+> PP.text n+    usageSection =+            PP.text "USAGE"+        $+$ PP.text ""+        $+$ PP.nest 2 (ppUsage desc)+    descriptionSection = case help of+      Nothing -> PP.empty+      Just h ->+            PP.text ""+        $+$ PP.text "DESCRIPTION"+        $+$ PP.text ""+        $+$ PP.nest 2 h+    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 (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++-- | Internal helper; users probably won't need this.+ppPartDescUsage :: PartDesc -> 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 "+"+  PartWithHelp _ p -> rec p+  PartReorder ps ->+    let flags  = [ d | PartMany d <- ps ]+        params = filter ( \case+                          PartMany{} -> False+                          _          -> True+                        )+                        ps+    in  PP.brackets (PP.fsep $ rec <$> flags) <+> PP.fsep (rec <$> params)+ where+  rec = ppPartDescUsage++-- | Internal helper; users probably won't need this.+ppPartDescHeader :: PartDesc -> PP.Doc+ppPartDescHeader = \case+  PartLiteral  s     -> PP.text s+  PartVariable s     -> PP.text s+  PartOptional ds'   -> rec ds'+  PartAlts     alts  -> PP.hcat $ List.intersperse (PP.text ",") $ rec <$> alts+  PartDefault    _ d -> rec d+  PartSuggestion _ d -> rec d+  PartRedirect   s _ -> PP.text s+  PartMany ds        -> rec ds+  PartWithHelp _ d   -> rec d+  PartSeq     ds     -> PP.hsep $ rec <$> ds+  PartReorder ds     -> PP.vcat $ rec <$> ds+ where+  rec = ppPartDescHeader++-- | Simple conversion from 'ParsingError' to 'String'.+parsingErrorString :: ParsingError -> String+parsingErrorString (ParsingError mess remaining) =+  "error parsing arguments: " ++ messStr ++ remainingStr+ where+  messStr      = case mess of+    []    -> ""+    (m:_) -> m ++ " "+  remainingStr = case remaining of+    InputString ""  -> "at the end of input."+    InputString str -> case show str of+      s | length s < 42 -> "at: " ++ s ++ "."+      s                 -> "at: " ++ take 40 s ++ "..\"."+    InputArgs   []  -> "at the end of input"+    InputArgs   xs  -> case List.unwords $ show <$> xs of+      s | length s < 42 -> "at: " ++ s ++ "."+      s                 -> "at: " ++ take 40 s ++ "..\"."+
+ src/UI/Butcher/Monadic/Types.hs view
@@ -0,0 +1,20 @@+-- this module only re-exports the appropriate user-facing stuff from the+-- internal Types module.+-- | Types used in the butcher interface.+module UI.Butcher.Monadic.Types+  ( CommandDesc(..)+  , cmd_out+  , CmdParser+  , Input (..)+  , ParsingError (..)+  , PartDesc(..)+  )+where++++#include "prelude.inc"++++import           UI.Butcher.Monadic.Internal.Types
+ srcinc/prelude.inc view
@@ -0,0 +1,409 @@+import qualified Data.Graph+import qualified Data.IntMap+import qualified Data.IntMap.Lazy+import qualified Data.IntMap.Strict+import qualified Data.IntSet+import qualified Data.Map+import qualified Data.Map.Lazy+import qualified Data.Map.Strict+import qualified Data.Sequence+import qualified Data.Set+import qualified Data.Tree++import qualified Control.Concurrent.Extra+import qualified Control.Exception.Extra+import qualified Control.Monad.Extra+import qualified Data.Either.Extra+import qualified Data.IORef.Extra+import qualified Data.List.Extra+import qualified Data.Tuple.Extra+import qualified Data.Version.Extra+import qualified Numeric.Extra+import qualified System.Directory.Extra+import qualified System.Environment.Extra+import qualified System.IO.Extra+import qualified System.Info.Extra+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+import qualified Control.Monad.Trans.MultiReader.Class+import qualified Control.Monad.Trans.MultiReader.Lazy+import qualified Control.Monad.Trans.MultiReader.Strict+import qualified Control.Monad.Trans.MultiState+import qualified Control.Monad.Trans.MultiState.Class+import qualified Control.Monad.Trans.MultiState.Lazy+import qualified Control.Monad.Trans.MultiState.Strict+import qualified Control.Monad.Trans.MultiWriter+import qualified Control.Monad.Trans.MultiWriter.Class+import qualified Control.Monad.Trans.MultiWriter.Lazy+import qualified Control.Monad.Trans.MultiWriter.Strict++import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS+import qualified Control.Monad.Trans.MultiRWS.Lazy as MultiRWSL++import qualified Data.Bifunctor+import qualified Data.Bits+import qualified Data.Bool+import qualified Data.Char+import qualified Data.Coerce+import qualified Data.Complex+import qualified Data.Data+import qualified Data.Dynamic+import qualified Data.Either+import qualified Data.Eq+import qualified Data.Fixed+import qualified Data.Foldable+import qualified Data.Function+import qualified Data.Functor+import qualified Data.Functor.Identity+import qualified Data.IORef+import qualified Data.Int+import qualified Data.Ix+import qualified Data.List+import qualified Data.Maybe+import qualified Data.Monoid+import qualified Data.Ord+import qualified Data.Proxy+import qualified Debug.Trace+import qualified Numeric+import qualified Numeric.Natural+import qualified System.Environment+import qualified System.IO+import qualified Text.Read+import qualified Text.Show+import qualified Unsafe.Coerce++import qualified Data.Bool as Bool+import qualified Data.Char as Char+import qualified Data.Maybe as Maybe+import qualified Control.Monad.Trans.Writer.Strict as WriterS++#if MIN_VERSION_base(4,9,0)+import qualified GHC.OldList as List+#else+import qualified Data.List as List+#endif++import qualified Data.IntMap as IntMap+import qualified Data.IntMap.Strict as IntMapS+import qualified Data.Map.Strict as MapS+import qualified Data.Map.Lazy as MapL+import qualified Data.Sequence as Seq+import qualified Data.Set as Set++import qualified Control.Monad.RWS.Class as RWS.Class+import qualified Control.Monad.Reader.Class as Reader.Class+import qualified Control.Monad.State.Class as State.Class+import qualified Control.Monad.Writer.Class as Writer.Class++import qualified Control.Monad.Trans.State as State+import qualified Control.Monad.Trans.State.Lazy as StateL+import qualified Control.Monad.Trans.State.Strict as StateS++import Data.Functor.Identity ( Identity(..) )+import Control.Concurrent.Chan ( Chan )+import Control.Concurrent.MVar ( MVar )+import Data.Int ( Int )+import Data.Word ( Word )+import Prelude ( Integer, Float, Double )+import Control.Monad.ST ( ST )+import Data.Bool ( Bool(..) )+import Data.Char ( Char )+import Data.Either ( Either(..) )+import Data.IORef ( IORef )+import Data.Maybe ( Maybe(..) )+import Data.Monoid ( Endo(..), All(..), Any(..), Sum(..), Product(..), First(..), Last(..), Alt(..), )+import Data.Ord ( Ordering(..), Down(..) )+import Data.Ratio ( Ratio, Rational )+import Data.String ( String )+import Data.Void ( Void )+import System.IO ( IO )+import Data.Proxy ( Proxy(..) )+import Data.Sequence ( Seq )++import Data.Map ( Map )+import Data.Set ( Set )++import Deque ( Deque )++import qualified Deque++import Prelude                            ( Char+                                          , String+                                          , Int+                                          , Integer+                                          , Float+                                          , Double+                                          , Bool (..)+                                          , undefined+                                          , Eq (..)+                                          , Ord (..)+                                          , Enum (..)+                                          , Bounded (..)+                                          , Maybe (..)+                                          , Either (..)+                                          , IO+                                          , (<$>)+                                          , (.)+                                          , ($)+                                          , ($!)+                                          , Num (..)+                                          , Integral (..)+                                          , Fractional (..)+                                          , Floating (..)+                                          , RealFrac (..)+                                          , RealFloat (..)+                                          , fromIntegral+                                          , error+                                          , foldr+                                          , foldl+                                          , foldr1+                                          , id+                                          , map+                                          , subtract+                                          , putStrLn+                                          , putStr+                                          , Show (..)+                                          , print+                                          , fst+                                          , snd+                                          , (++)+                                          , not+                                          , (&&)+                                          , (||)+                                          , curry+                                          , uncurry+                                          , Ordering (..)+                                          , flip+                                          , const+                                          , seq+                                          , reverse+                                          , otherwise+                                          , traverse+                                          , realToFrac+                                          , or+                                          , and+                                          , head+                                          , any+                                          , (^)+                                          , Foldable+                                          , Traversable+                                          )++import Data.Foldable                      ( foldl'+                                          , foldr'+                                          , fold+                                          , asum+                                          )++import Data.List                          ( partition+                                          , null+                                          , elem+                                          , notElem+                                          , minimum+                                          , maximum+                                          , length+                                          , all+                                          , take+                                          , drop+                                          , find+                                          , sum+                                          , zip+                                          , zip3+                                          , zipWith+                                          , repeat+                                          , replicate+                                          , iterate+                                          , nub+                                          , filter+                                          , intersperse+                                          , intercalate+                                          , isSuffixOf+                                          , isPrefixOf+                                          , dropWhile+                                          , takeWhile+                                          , unzip+                                          , break+                                          , transpose+                                          , sortBy+                                          , mapAccumL+                                          , mapAccumR+                                          , uncons+                                          )++import Data.Tuple                         ( swap+                                          )++import Data.Char                          ( ord+                                          , chr+                                          )++import Data.Maybe                         ( fromMaybe+                                          , maybe+                                          , listToMaybe+                                          , maybeToList+                                          , catMaybes+                                          )++import Data.Word                          ( Word32+                                          )++import Data.Ord                           ( comparing+                                          , Down (..)+                                          )++import Data.Either                        ( either+                                          )++import Data.Ratio                         ( Ratio+                                          , (%)+                                          , numerator+                                          , denominator+                                          )++import Text.Read                          ( readMaybe+                                          )++import Control.Monad                      ( Functor (..)+                                          , Monad (..)+                                          , MonadPlus (..)+                                          , mapM+                                          , mapM_+                                          , forM+                                          , forM_+                                          , sequence+                                          , sequence_+                                          , (=<<)+                                          , (>=>)+                                          , (<=<)+                                          , forever+                                          , void+                                          , join+                                          , replicateM+                                          , replicateM_+                                          , guard+                                          , when+                                          , unless+                                          , liftM+                                          , liftM2+                                          , liftM3+                                          , liftM4+                                          , liftM5+                                          , filterM+                                          , (<$!>)+                                          )++import Control.Applicative                ( Applicative (..)+                                          , Alternative (..)+                                          )++import Foreign.Storable                   ( Storable )+import GHC.Exts                           ( Constraint )++import Control.Concurrent                 ( threadDelay+                                          , forkIO+                                          , forkOS+                                          )++import Control.Concurrent.MVar            ( MVar+                                          , newEmptyMVar+                                          , newMVar+                                          , putMVar+                                          , readMVar+                                          , takeMVar+                                          , swapMVar+                                          )++import Control.Exception                  ( evaluate+                                          , bracket+                                          , assert+                                          )++import Debug.Trace                        ( trace+                                          , traceId+                                          , traceShowId+                                          , traceShow+                                          , traceStack+                                          , traceShowId+                                          , traceIO+                                          , traceM+                                          , traceShowM+                                          )++import Foreign.ForeignPtr                 ( ForeignPtr+                                          )++import Data.Monoid                        ( (<>)+                                          , mconcat+                                          , Monoid (..)+                                          )++import Data.Bifunctor                     ( bimap )+import Data.Functor                       ( (<$), ($>) )+import Data.Function                      ( (&) )+import System.IO                          ( hFlush+                                          , stdout+                                          )++import Data.Typeable                      ( Typeable+                                          )++import Control.Arrow                      ( first+                                          , second+                                          , (***)+                                          , (&&&)+                                          , (>>>)+                                          , (<<<)+                                          )++import Data.Functor.Identity              ( Identity (..)+                                          )++import Data.Proxy                         ( Proxy (..)+                                          )++import Data.Version                       ( showVersion+                                          )++import Data.List.Extra                    ( nubOrd+                                          , stripSuffix+                                          )+import Control.Monad.Extra                ( whenM+                                          , unlessM+                                          , ifM+                                          , notM+                                          , orM+                                          , andM+                                          , anyM+                                          , allM+                                          )++import Data.Tree                          ( Tree(..)+                                          )++import Control.Monad.Trans.MultiRWS       ( MonadMultiReader(..)+                                          , MonadMultiWriter(..)+                                          , MonadMultiState(..)+                                          )++import Control.Monad.Trans.MultiReader    ( runMultiReaderTNil+                                          , runMultiReaderTNil_+                                          , MultiReaderT (..)+                                          , MultiReader+                                          , MultiReaderTNull+                                          )++import Control.Monad.IO.Class             ( MonadIO (..)+                                          )++import Control.Monad.Trans.Class          ( lift+                                          )+import Control.Monad.Trans.Maybe          ( MaybeT (..)+                                          )+import Lens.Micro                         ( (<&>)+                                          )