Skip to content

Instantly share code, notes, and snippets.

@tomjaguarpaw
Last active April 15, 2018 18:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomjaguarpaw/b24cfaf2c84ce6ea79372637f1a12a33 to your computer and use it in GitHub Desktop.
Save tomjaguarpaw/b24cfaf2c84ce6ea79372637f1a12a33 to your computer and use it in GitHub Desktop.

stargazer

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"]}

To do

All the goodies:

  • useful help

  • short and long versions of arguments

  • completion

jsonargs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment