Safe Haskell | None |
---|---|
Language | Haskell2010 |
re-exports everything defined in Commander.Params
and Commander.Commands
for convenience.
- module Commander.Params
- module Commander.Commands
Example Usage
The below header gives us the language extensions and imports we need
for basic usage of Commander
:
{-# LANGUAGE DataKinds, ScopedTypeVariables #-} module Main where import qualified Data.Map as Map import Commander.Params (Flag(..), Value(..)) import Commander.Commands (Command(..), commands, command, help, run, evalCommand)
The next step is to define the various commands that we care to match against, which looks something like this:
someCommands :: Command (IO ()) someCommands = commands $ do command "repeat" $ do help "Repeat a string n times" run $ \(Value str :: Value "value to repeat" String) (Flag n :: Flag '["n"] "times to repeat" Int) -> sequence_ $ replicate n (putStrLn str) command "calculate" $ do help "perform calculations" command "add" $ do help "add two numbers" run $ \(Value n1 :: Value "number 1" Int) (Value n2 :: Value "number 2" Int) (Flag verbose :: Flag '["v", "verbose"] "verbose mode" Bool) -> if verbose then putStrLn $ (show n1) ++ " + " ++ (show n2) ++ " = " ++ (show $ n1 + n2) else putStrLn (show $ n1 + n2) command "multiply" $ do help "multiply two numbers" run $ \(Value n1 :: Value "number 1" Int) (Value n2 :: Value "number 2" Int) -> putStrLn $ (show n1) ++ " x " ++ (show n2) ++ " = " ++ (show $ n1 * n2) command "login" $ do help "pretend authentication" run $ \(Value username :: Value "Username" String) (Flag mPassword :: Flag '["p", "password"] "Password" (Maybe String)) -> do pass <- case mPassword of Just password -> return password Nothing -> getLine putStrLn $ "logging in with username=" ++ username ++ " password=" ++ pass
Commands can be arbitrary nested, making it super easy to define subcommands as far down as you like.
Using a couple of pre-baked parameter types from Params
, namely Value
and Flag
,
we define in the function signature itself additional values and flags that we expect,
each fully typed and with help text (the Flag
type in addition state which flags it will
try to match against).
If you prefer alternate behaviour to the provided types, it is easy to create your own custom alternatives; it's simply a case of making your custom types instances of a few very basic typeclasses.
The return types of the provided functions must match, so that we know what we're getting back
when we try executing a command; this is the only parameter needed by the Command
type.
Making use of the above, one could do the following:
runCommand :: [String] -> [(String,String)] -> Command (IO ()) -> IO () runCommand vals flags cmds = case evalCommand vals (Map.fromList flags) cmds of Left err -> putStrLn ("Error: " ++ show err) Right res -> res main :: IO () main = do putStrLn "\nRepeat Command:" runCommand ["repeat", "hello there"] [("n","2")] someCommands putStrLn "\nAdd numbers:" runCommand ["calculate", "add", "12", "13"] [] someCommands putStrLn "\nAdd numbers (verbosely):" runCommand ["calculate", "add", "12", "13"] [("verbose", "")] someCommands putStrLn "\nMultiply numbers:" runCommand ["calculate", "multiply", "12", "13"] [] someCommands putStrLn "\nPretend Auth (password provided):" runCommand ["login", "james"] [("p", "lemons")] someCommands putStrLn "\nPretend Auth (please type a random string):" runCommand ["login", "james"] [] someCommands
Where we first define a function that makes use of evalCommand
to match the provided
flags and values against a specific command and either run it or return an error, and
either prints the error or runs the resulting IO action.
This library has no opinion on how a text based command is parsed into a path list and
Map
of flags, and so the user is free to select an approach that works best for them.
module Commander.Params
module Commander.Commands