Structurally typed arguments and zero extra rubbish
-- Stargazer's, very small, API
-- An option can take a single string argument or nothing at all.
string :: Schema String
nothing :: Schema ()
-- A sum type is built from a list of possible options.
oneOf :: [(String, Schema a)] -> Schema a
-- A product type is built from a list of possible options,
-- composed applicatively.
allOf :: AllOf a -> Schema a
-- The fields of a product type can be singletons or lists.
once :: String -> Schema a -> AllOf a
many :: String -> Schema a -> AllOf [a]
-- Using the Stargazer API
-- We're going to parse command line options to set up this ADT
-- which describes how to build a set of Haskell packages.
-- The top level of the ADT is `Install` and we nest some other
-- types inside it.
data Install = Install { tool_ :: Tool
, packages :: [Package]
}
deriving Show
data Tool = Stack Target
| Cabal Target Build
deriving Show
data Package = Package String String
deriving Show
data Target = X86 | X64
deriving Show
data Build = OldBuild | NewBuild
deriving Show
-- Writing a schema to parse into our ADT
-- An install must have exactly one `--tool` and can have any
-- number of `--package`s
install :: Schema Install
install = allOf (Install <$> once "tool" tool
<*> many "package" package)
-- stack has a target, cabal has a target and a build
tool :: Schema Tool
tool = oneOf [ ("stack", allOf (Stack <$> once "target" target))
, ("cabal", allOf (Cabal <$> once "target" target
<*> once "build" build))
]
-- A target is x86 or x64
target :: Schema Target
target = oneOf [ ("x86", X86 <$ nothing)
, ("x64", X64 <$ nothing)
]
-- A build is old-build or new-build
build :: Schema Build
build = oneOf [ ("old-build", OldBuild <$ nothing)
, ("new-build", NewBuild <$ nothing)
]
-- A package has a name and a version
package :: Schema Package
package = allOf (Package <$> once "name" string
<*> once "version" string)
This is what happens when you use it.
% ./example
error: Expected --tool
% ./example --tool
error: Expected one of --stack, --cabal
% ./example --tool --cabal
error: Expected --target
% ./example --tool --cabal --target
error: Expected one of --x86, --x64
% ./example --tool --cabal --target --x86
error: Expected --build
% ./example --tool --cabal --target --x86 --build
error: Expected one of --old-build, --new-build
% ./example --tool --cabal --target --x86 --build --new-build
Install {tool_ = Cabal X86 NewBuild, packages = []}
% ./example --tool --cabal --target --x86 --build --new-build \
--package
error: Expected --name
% ./example --tool --cabal --target --x86 --build --new-build \
--package --name aeson
error: Expected --version
% ./example --tool --cabal --target --x86 --build --new-build --package \
--name aeson --version 0.6.0.0
Install {tool_ = Cabal X86 NewBuild, packages = [Package "aeson" "0.6.0.0"]}
% ./example --tool --cabal --target --x86 --build --new-build \
--package --name aeson --version 0.6.0.0 0.4.0.0
error: Didn't expect to see 0.4.0.0 after 0.6.0.0
% ./example --tool --cabal --target --x86 --build --new-build \
--package --name aeson --version 0.6.0.0 \
--package --name lens --version 10.0.0.0
Install {tool_ = Cabal X86 NewBuild,
packages = [Package "lens" "10.0.0.0" ,Package "aeson" "0.6.0.0"]}
All the goodies:
-
useful help
-
short and long versions of arguments
-
completion