1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 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
|