commander: pattern matching against string based commands

[ bsd3, library, program, text ] [ Propose Tags ]

An extensible, format-agnostic command parsing library designed to be easy to use and syntactically light weight.

Assuming we write a parser to convert a command such as

calculator add 1 2 -v=yes

Into path and flags such as ["calculator", "add"] and Map.fromList [("v","yes")], This library will then match said path and flags against a nested record type of commands built up using lightweight monadic syntax and tries to execute the associated function if the matching and value converting works, or returns an error if the path/flags fail to match any command.

To get started, see the documentation for the Commander module below. Additionally, an examples folder is included in the source illustrating usage - see https://github.com/jsdw/hs-commander for more.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.1.0.0
Dependencies base (>=4.8 && <5), commander, containers (>=0.5 && <0.6), mtl (>=2.2 && <2.3), transformers (>=0.4.2 && <0.5) [details]
License BSD-3-Clause
Copyright (c) 2016 James Wilson
Author James Wilson
Maintainer me@unbui.lt
Category Text
Home page https://github.com/jsdw/hs-commander
Source repo head: git clone https://github.com/jsdw/hs-commander
Uploaded by jsdw at 2016-03-11T19:59:08Z
Distributions
Executables example1
Downloads 837 total (6 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2016-03-11 [all 1 reports]

Readme for commander-0.1.0.0

[back to package description]

Commander: A Haskell Library for parsing commands

This library aims to make it super easy to create and use nested commands with flags in the form of an object that is easy to traverse and run custom actions on, and easy to extend with handling for safe casting to values from their input strings.

As an example of usage (take a look in the examples folder for more) we can define commands as follows:

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)

And this leads to someCommands being a nested record of type Command (IO ()) (where IO () corresponds to the output from the functions you provide). This record can then be traversed and interacted with.

The novelty of this approach (compared to others I have seen, which is a non exhaustive list!) is the use of the functions input parameter types to cast-from-string and inject the appropriate values into the function safely at runtime, as well as to generate documentation. This means that we only declare the flags and values each command requires in one place, and do not execute any of the output unless all parameters are fully satisfied. These functions are then safely hidden away inside an existential type, and so we don't need any other type level magic or handling to work with the output.

Installation

  1. Add this repository to your stack.yaml file under the packages folder, so we end up with something that looks a bit like:

    packages:
    - '.'
    - location:
       git: https://github.com/jsdw/hs-commander
       commit: abcdef123456789abcdef1234
    
  2. run stack install.

  3. import Commander into your library.

you can run the example code by running stack install :example1 && example1 assuming that the directory stack copies binaries to is present in your PATH.

Documentation

if you clone this repository locally somewhere, you can use stack haddock inside its folder in order to generate haddock documentation for it.

Disclaimer

This project is still under heavy development and could well change drastically!