harg 0.4.0.0 → 0.4.1.0
raw patch · 17 files changed
+562/−425 lines, 17 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Options.Harg.Pretty: ppOptErrors :: [OptError] -> String
- Options.Harg.Sources.DefaultStr: instance Data.Barbie.Internal.Functor.FunctorB a => Options.Harg.Sources.Types.RunSource Options.Harg.Sources.DefaultStr.DefaultStrSourceVal a
- Options.Harg.Sources.Env: instance Data.Barbie.Internal.Functor.FunctorB a => Options.Harg.Sources.Types.RunSource Options.Harg.Sources.Env.EnvSourceVal a
- Options.Harg.Sources.Types: OptFoundNoParse :: OptError -> SourceRunResult a
- Options.Harg.Types: OptError :: SomeOpt -> Maybe String -> String -> OptError
- Options.Harg.Types: [_oeDesc] :: OptError -> String
- Options.Harg.Types: [_oeOpt] :: OptError -> SomeOpt
- Options.Harg.Types: [_oeSource] :: OptError -> Maybe String
- Options.Harg.Types: data OptError
- Options.Harg.Types: toOptError :: Opt a -> Maybe String -> String -> OptError
+ Options.Harg: Rec :: K1 R a x -> Rec p a
+ Options.Harg: [unRec] :: Rec p a -> K1 R a x
+ Options.Harg: newtype Rec p a (x :: k) :: forall k. () => Type -> Type -> k -> Type
+ Options.Harg.Pretty: ppSourceRunErrors :: [SourceRunError] -> String
+ Options.Harg.Sources.DefaultStr: instance (Data.Barbie.Internal.Functor.FunctorB a, Data.Barbie.Internal.Traversable.TraversableB a) => Options.Harg.Sources.Types.RunSource Options.Harg.Sources.DefaultStr.DefaultStrSourceVal a
+ Options.Harg.Sources.Env: instance (Data.Barbie.Internal.Functor.FunctorB a, Data.Barbie.Internal.Traversable.TraversableB a) => Options.Harg.Sources.Types.RunSource Options.Harg.Sources.Env.EnvSourceVal a
+ Options.Harg.Sources.Types: SourceRunError :: Maybe SomeOpt -> String -> String -> SourceRunError
+ Options.Harg.Sources.Types: [_sreError] :: SourceRunError -> String
+ Options.Harg.Sources.Types: [_sreOpt] :: SourceRunError -> Maybe SomeOpt
+ Options.Harg.Sources.Types: [_sreSourceName] :: SourceRunError -> String
+ Options.Harg.Sources.Types: data SourceRunError
+ Options.Harg.Sources.Types: sourceRunError :: forall a. Opt a -> String -> String -> SourceRunError
- Options.Harg.Operations: failParser :: Parser a -> [OptError] -> IO a
+ Options.Harg.Operations: failParser :: Parser a -> [SourceRunError] -> IO a
- Options.Harg.Pretty: ppSource :: Maybe String -> String
+ Options.Harg.Pretty: ppSource :: String -> String
- Options.Harg.Sources: accumSourceResults :: forall a f. TraversableB a => [a (Compose SourceRunResult f)] -> ([OptError], [a (Compose Maybe f)])
+ Options.Harg.Sources: accumSourceResults :: forall a f. TraversableB a => [Either SourceRunError (a (Compose SourceRunResult f))] -> ([SourceRunError], [a (Compose Maybe f)])
- Options.Harg.Sources.DefaultStr: runDefaultStrSource :: forall a f. (FunctorB a, Applicative f) => a (Compose Opt f) -> a (Compose SourceRunResult f)
+ Options.Harg.Sources.DefaultStr: runDefaultStrSource :: forall a f. (FunctorB a, TraversableB a, Applicative f) => a (Compose Opt f) -> Either SourceRunError (a (Compose SourceRunResult f))
- Options.Harg.Sources.Env: runEnvVarSource :: forall a f. (FunctorB a, Applicative f) => Environment -> a (Compose Opt f) -> a (Compose SourceRunResult f)
+ Options.Harg.Sources.Env: runEnvVarSource :: forall a f. (FunctorB a, TraversableB a, Applicative f) => Environment -> a (Compose Opt f) -> Either SourceRunError (a (Compose SourceRunResult f))
- Options.Harg.Sources.JSON: JSONSourceVal :: Value -> JSONSourceVal
+ Options.Harg.Sources.JSON: JSONSourceVal :: ByteString -> JSONSourceVal
- Options.Harg.Sources.JSON: runJSONSource :: forall a f. (FunctorB a, FromJSON (a Maybe), Applicative f) => Value -> a (Compose Opt f) -> a (Compose SourceRunResult f)
+ Options.Harg.Sources.JSON: runJSONSource :: forall a f. (FunctorB a, FromJSON (a Maybe), Applicative f) => ByteString -> a (Compose Opt f) -> Either SourceRunError (a (Compose SourceRunResult f))
- Options.Harg.Sources.Types: runSource :: (RunSource s a, Applicative f) => s -> a (Compose Opt f) -> [a (Compose SourceRunResult f)]
+ Options.Harg.Sources.Types: runSource :: (RunSource s a, Applicative f) => s -> a (Compose Opt f) -> [Either SourceRunError (a (Compose SourceRunResult f))]
- Options.Harg.Sources.YAML: runYAMLSource :: forall a f. (FunctorB a, FromJSON (a Maybe), Applicative f) => ByteString -> a (Compose Opt f) -> a (Compose SourceRunResult f)
+ Options.Harg.Sources.YAML: runYAMLSource :: forall a f. (FunctorB a, FromJSON (a Maybe), Applicative f) => ByteString -> a (Compose Opt f) -> Either SourceRunError (a (Compose SourceRunResult f))
- Options.Harg.Subcommands: explMapSubcommand :: (ExplSubcommands n ts xs acc, All (RunSource s) xs, Applicative f) => SNat n -> s -> AssocListF ts xs (Compose Opt f) -> ([OptError], [Mod CommandFields (VariantF (acc ++ xs) f)])
+ Options.Harg.Subcommands: explMapSubcommand :: (ExplSubcommands n ts xs acc, All (RunSource s) xs, Applicative f) => SNat n -> s -> AssocListF ts xs (Compose Opt f) -> ([SourceRunError], [Mod CommandFields (VariantF (acc ++ xs) f)])
- Options.Harg.Subcommands: mapSubcommand :: (Subcommands ts xs, All (RunSource s) xs, Applicative f) => s -> AssocListF ts xs (Compose Opt f) -> ([OptError], [Mod CommandFields (VariantF xs f)])
+ Options.Harg.Subcommands: mapSubcommand :: (Subcommands ts xs, All (RunSource s) xs, Applicative f) => s -> AssocListF ts xs (Compose Opt f) -> ([SourceRunError], [Mod CommandFields (VariantF xs f)])
Files
- CHANGELOG.md +17/−8
- README.lhs +206/−145
- README.md +206/−145
- harg.cabal +1/−1
- src/Options/Harg.hs +1/−0
- src/Options/Harg/Cmdline.hs +1/−3
- src/Options/Harg/Construct.hs +1/−2
- src/Options/Harg/Operations.hs +5/−5
- src/Options/Harg/Pretty.hs +24/−15
- src/Options/Harg/Sources.hs +15/−16
- src/Options/Harg/Sources/DefaultStr.hs +19/−14
- src/Options/Harg/Sources/Env.hs +18/−14
- src/Options/Harg/Sources/JSON.hs +16/−19
- src/Options/Harg/Sources/Types.hs +21/−6
- src/Options/Harg/Sources/YAML.hs +9/−11
- src/Options/Harg/Subcommands.hs +2/−2
- src/Options/Harg/Types.hs +0/−19
CHANGELOG.md view
@@ -1,5 +1,11 @@ # Changelog for harg +## 0.4.1.0 [2019.12.22]++- Parsers now stop immediately if a source error is encountered+- Updated the JSON source to return a bytestring instead of an `aeson` `Value`+- Fix broken deriving for `barbie` typeclasses by re-exporting `Rec`+ ## 0.4.0.0 [2019.09.16] - Fix wrong name in previous release (`HasDefaultValStr` -> `HasDefaultStr`)@@ -8,10 +14,11 @@ ## 0.3.0.0 [2019.09.16] -- Remove `*With` variants of option constructors and make the `*With` variant behaviour the default- (meaning now options are constructed using function composition and not `toOpt`)-- Remove `opt` prefix from modifiers. Because `default` is a reserved keyword, this is now named- `defaultVal` (to mirror `defaultStr`)+- Remove `*With` variants of option constructors and make the `*With` variant+ behaviour the default (meaning now options are constructed using function+ composition and not `toOpt`)+- Remove `opt` prefix from modifiers. Because `default` is a reserved keyword,+ this is now named `defaultVal` (to mirror `defaultStr`) NOTE: the above introduce breaking changes @@ -19,8 +26,8 @@ - Trigger a parser failure when any option in the sources fails to parse - NOTE: this introduces a breaking change, in that some parsers that failed silently- and selected the default (if applicable) will now fail.+ NOTE: this introduces a breaking change, in that some parsers that failed+ silently and selected the default (if applicable) will now fail. ## 0.1.3.0 [2019.08.28] @@ -28,11 +35,13 @@ ## 0.1.2.0 [2019.08.19] -- Add `optRequired` (renamed to `required` for 0.3.0.0) to mark option as required+- Add `optRequired` (renamed to `required` for 0.3.0.0) to mark option as+ required ## 0.1.1.0 [2019.08.16] -- Add `optDefaultStr` (renamed to `defaultStr` for 0.3.0.0) to provide defaults as unparsed strings+- Add `optDefaultStr` (renamed to `defaultStr` for 0.3.0.0) to provide defaults+ as unparsed strings - Bump dependencies (`barbies` and `higgledy`) ## 0.1.0.1 [2019.07.19]
README.lhs view
@@ -1,25 +1,27 @@-# harg+# `harg` [](https://travis-ci.org/alexpeits/harg) [](https://hackage.haskell.org/package/harg) -`harg` is a library for configuring programs by scanning command line arguments, environment-variables, default values and more. Under the hood, it uses a subset of-[`optparse-applicative`](https://hackage.haskell.org/package/optparse-applicative) to expose regular-arguments, switch arguments and subcommands. The library relies heavily on the use of higher kinded-data (HKD) thanks to the [`barbies`](https://hackage.haskell.org/package/barbies) library. Using-[`higgledy`](https://hackage.haskell.org/package/higgledy) also allows to have significantly less-boilerplate code.+`harg` is a library for configuring programs by scanning command line arguments,+environment variables, default values and more. Under the hood, it uses a subset+of [`optparse-applicative`](https://hackage.haskell.org/package/optparse-applicative)+to expose regular arguments, switch arguments and subcommands. The library+relies heavily on the use of higher kinded data (HKD) thanks to the+[`barbies`](https://hackage.haskell.org/package/barbies) library. Using+[`higgledy`](https://hackage.haskell.org/package/higgledy) also allows to have+significantly less boilerplate code. -The main goal while developing `harg` was to not have to go through the usual pattern of manually-`mappend`ing the results of command line parsing, env vars and defaults.+The main goal while developing `harg` was to not have to go through the usual+pattern of manually `mappend`ing the results of command line parsing, env vars+and defaults. # Usage tl;dr: Take a look at the [example](Example.hs). -Here are some different usage scenarios. Let's first enable some language extensions and add some-imports:+Here are some different usage scenarios. Let's first enable some language+extensions and add some imports: ``` haskell {-# LANGUAGE DataKinds #-}@@ -48,8 +50,8 @@ ## One flat (non-nested) datatype -The easiest scenario is when the target configuration type is one single record with no levels of-nesting:+The easiest scenario is when the target configuration type is one single record+with no levels of nesting: ``` haskell data FlatConfig@@ -64,8 +66,8 @@ (The `Generic` instance is required for section `3` later on) -Let's first create the `Opt`s for each value in `FlatConfig`. `Opt` is the description for each-component of the configuration.+Let's first create the `Opt`s for each value in `FlatConfig`. `Opt` is the+description for each component of the configuration. ``` haskell dbHostOpt :: Opt String@@ -101,21 +103,24 @@ ) ``` -Here, we use `option` to define a command line argument that expects a value after it, `argument` to-define a standalone argument, not prefixed by a long or short indicator, and `switch` to define a-boolean command line flag that, if present, sets the target value to `True`. The `opt*` functions-(here applied using `&` to make things look more declarative) modify the option configuration.-`help` adds help text, `defaultVal` adds a default value, `short` adds a short command line-option as an alternative to the long one (the string after `option` or `switch`), `envVar` sets-the associated environment variable and `metavar` sets the metavariable to be shown in the help-text generated by `optparse-applicative`.+Here, we use `option` to define a command line argument that expects a value+after it, `argument` to define a standalone argument, not prefixed by a long or+short indicator, and `switch` to define a boolean command line flag that, if+present, sets the target value to `True`. The `opt*` functions (here applied+using `&` to make things look more declarative) modify the option configuration.+`help` adds help text, `defaultVal` adds a default value, `short` adds a short+command line option as an alternative to the long one (the string after `option`+or `switch`), `envVar` sets the associated environment variable and `metavar`+sets the metavariable to be shown in the help text generated by+`optparse-applicative`. -The first argument (`strParser` or `readParser`) is the parser for the argument, be it from the-command line or from an environment variable. The type of this function should be-`String -> Either String a`, which produces an error message or the parsed value. `strParser` is-equivalent to `pure` and always succeeds. `readParser` requires the type to have a `Read` constraint.-In order to use it with newtypes that wrap a type that has a `Read` constraint, using the `Functor`+The first argument (`strParser` or `readParser`) is the parser for the argument,+be it from the command line or from an environment variable. The type of this+function should be `String -> Either String a`, which produces an error message+or the parsed value. `strParser` is equivalent to `pure` and always succeeds.+`readParser` requires the type to have a `Read` constraint. In order to use it+with newtypes that wrap a type that has a `Read` constraint, using the `Functor` instance for `Opt` should be sufficient. E.g. for the newtype: ``` haskell@@ -130,14 +135,16 @@ = Port <$> dbPortOpt ``` -Of course, any user-defined function works as well. In addition, to use a function of type `String--> Maybe a` use `parseWith`, which runs the parser and in case of failure uses a default error-message. For example, `readParser` is defined as `parseWith readMaybe`.+Of course, any user-defined function works as well. In addition, to use a+function of type `String -> Maybe a` use `parseWith`, which runs the parser and+in case of failure uses a default error message. For example, `readParser` is+defined as `parseWith readMaybe`. -Finally, an optional option with type `a` can be specified by setting its type to `Maybe a`. The-declaration is exactly the same as it would be for `a`, and adding `optional` to the modifiers-turns turns the parser from `String -> Either String a` to `String -> Either String (Maybe a)` but-without using the `Read` instance for `Maybe`:+Finally, an optional option with type `a` can be specified by setting its type+to `Maybe a`. The declaration is exactly the same as it would be for `a`, and+adding `optional` to the modifiers turns turns the parser from `String -> Either+String a` to `String -> Either String (Maybe a)` but without using the `Read`+instance for `Maybe`: ``` haskell ignore someOpt :: Opt (Maybe Int)@@ -148,16 +155,16 @@ ) ``` -Note that `optional` can't be used with `defaultVal`. Using them together raises a type error at-compile time, to ensure there's no ambiguous behaviour (e.g. the order of declaration of modifiers-should not influence the resulting option).+Note that `optional` can't be used with `defaultVal`. Using them together raises+a type error at compile time, to ensure there's no ambiguous behaviour (e.g. the+order of declaration of modifiers should not influence the resulting option). There are 3 ways to configure this datatype. ### 1. Using a `barbie` type -`barbie` types are types of kind `(Type -> Type) -> Type`. The `barbie` type for `FlatConfig`-looks like this:+`barbie` types are types of kind `(Type -> Type) -> Type`. The `barbie` type for+`FlatConfig` looks like this: ``` haskell data FlatConfigB f@@ -170,12 +177,13 @@ deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB) ``` -I also derived some required instances that come from the `barbies` package. These instances allow-us to change the `f` (`bmap` from `FunctorB`) and traverse all types in the record producing side-effects (`btraverse` from `TraversableB`).+I also derived some required instances that come from the `barbies` package.+These instances allow us to change the `f` (`bmap` from `FunctorB`) and traverse+all types in the record producing side effects (`btraverse` from+`TraversableB`). -Now let's define the value of this datatype, which holds our option configuration. The type-constructor needed for the options is `Opt`:+Now let's define the value of this datatype, which holds our option+configuration. The type constructor needed for the options is `Opt`: ``` haskell flatConfigOpt1 :: FlatConfigB Opt@@ -183,8 +191,8 @@ = FlatConfigB dbHostOpt dbPortOpt dirOpt logOpt ``` -Because `dbHostOpt`, `dbPortOpt` and `logOpt` all have type `Opt <actual type>`, `flatConfigOpt1`-has the correct type according to `FlatConfigB Opt`.+Because `dbHostOpt`, `dbPortOpt` and `logOpt` all have type `Opt <actual type>`,+`flatConfigOpt1` has the correct type according to `FlatConfigB Opt`. Now to actually run things: @@ -192,48 +200,62 @@ getFlatConfig1 :: IO () getFlatConfig1 = do FlatConfigB host port dir log <- execOptDef flatConfigOpt1- print $ runIdentity (FlatConfig <$> host <*> port <*> dir <*> log)+ print $ runIdentity $+ FlatConfig+ <$> host+ <*> port+ <*> dir+ <*> log ``` -`execOpt` returns an `Identity x` where `x` is the type of the options we are configuring, in this-case `FlatConfigB`. Here, we pattern match on the barbie-type, and then use the `Applicative`-instance of `Identity` to get back an `Identity FlatConfig`.+`execOpt` returns an `Identity x` where `x` is the type of the options we are+configuring, in this case `FlatConfigB`. Here, we pattern match on the+barbie-type, and then use the `Applicative` instance of `Identity` to get back+an `Identity FlatConfig`. This is still a bit boilerplate-y. Let's look at another way. ### 2. Using a product type -Looking at `FlatConfigB`, it's only used because of it's `barbie`-like capabilities. Other than that-it's just a simple product type with the additional `f` before all its sub-types.+Looking at `FlatConfigB`, it's only used because of it's `barbie`-like+capabilities. Other than that it's just a simple product type with the+additional `f` before all its sub-types. -`harg` defines a type almost similar to `Product` (from `Data.Functor.Product`), which works in a-similar fashion as servant's `:<|>` type. This type is defined in `Options.Harg.Het.Prod` and is-called `:*` (the `*` stands for product). This type stores barbie-like types and also keeps the `f`-handy: `data (a :* b) f = a f :* b f`. This is also easily made an instance of `Generic`,-`FunctorB`, `TraversableB` and `ProductB`. With all that, let's rewrite the options value and the-function to get the configuration:+`harg` defines a type almost similar to `Product` (from `Data.Functor.Product`),+which works in a similar fashion as servant's `:<|>` type. This type is defined+in `Options.Harg.Het.Prod` and is called `:*` (the `*` stands for product). This+type stores barbie-like types and also keeps the `f` handy: `data (a :* b) f = a+f :* b f`. This is also easily made an instance of `Generic`, `FunctorB`,+`TraversableB` and `ProductB`. With all that, let's rewrite the options value+and the function to get the configuration: ``` haskell-flatConfigOpt2 :: (Single String :* Single Int :* Single String :* Single Bool) Opt flatConfigOpt2+ :: (Single String :* Single Int :* Single String :* Single Bool) Opt+flatConfigOpt2 = single dbHostOpt :* single dbPortOpt :* single dirOpt :* single logOpt getFlatConfig2 :: IO () getFlatConfig2 = do host :* port :* dir :* log <- execOptDef flatConfigOpt2- print $ runIdentity- (FlatConfig <$> getSingle host <*> getSingle port <*> getSingle dir <*> getSingle log)+ print $ runIdentity $+ FlatConfig+ <$> getSingle host+ <*> getSingle port+ <*> getSingle dir+ <*> getSingle log ``` -This looks aufully similar to the previous version, but without having to write another datatype and-derive all the instances. `:*` is both a type-level constructor and a value-level function that acts-like list's `:`. It is also right-associative, so for example `a :* b :* c` is the same as-`a :* (b :* c)`.+This looks aufully similar to the previous version, but without having to write+another datatype and derive all the instances. `:*` is both a type-level+constructor and a value-level function that acts like list's `:`. It is also+right-associative, so for example `a :* b :* c` is the same as `a :* (b :* c)`. -The `Single` type constructor is used when talking about a single value, rather than a nested-datatype. `Single a f` is a simple newtype over `f a`. The reason for using that is simply to switch-the order of application, so that we can later apply the `f` (here `Opt`) to the compound type-(`:*`). This makes type definitions look more similar to datatype definitions:+The `Single` type constructor is used when talking about a single value, rather+than a nested datatype. `Single a f` is a simple newtype over `f a`. The reason+for using that is simply to switch the order of application, so that we can+later apply the `f` (here `Opt`) to the compound type (`:*`). This makes type+definitions look more similar to datatype definitions: ``` haskell type FlatConfigOpt2@@ -242,10 +264,12 @@ :* Single Bool ``` -In addition, `single` is used to wrap an `f a` into a `Single a f`, and `getSingle` is used-to unwrap it. Later on we'll see how to construct nested configurations using `Nested`.+In addition, `single` is used to wrap an `f a` into a `Single a f`, and+`getSingle` is used to unwrap it. Later on we'll see how to construct nested+configurations using `Nested`. -However, the real value when having flat datatypes comes from the ability to use `higgledy`.+However, the real value when having flat datatypes comes from the ability to use+`higgledy`. ### 3. Using `HKD` from `higgledy` @@ -260,14 +284,16 @@ print $ runIdentity (construct result) ``` -This is the most straightforward way to work with flat configuration types. The `build` function-takes as arguments the options (`Opt a` where `a` is each type in `FlatConfig`) **in the order they-appear in the datatype**, and returns the generic representation of a type that's exactly the same-as `FlatConfigB`. This means that we get all the `barbie` instances for free.+This is the most straightforward way to work with flat configuration types. The+`build` function takes as arguments the options (`Opt a` where `a` is each type+in `FlatConfig`) **in the order they appear in the datatype**, and returns the+generic representation of a type that's exactly the same as `FlatConfigB`. This+means that we get all the `barbie` instances for free. -To go back from the `HKD` representation of a datatype to the base one, we use `construct`.-`construct` uses the applicative instance of the `f` which wraps each type in `FlatConfig` to give-back an `f FlatConfig` (in our case an `Identity FlatConfig`).+To go back from the `HKD` representation of a datatype to the base one, we use+`construct`. `construct` uses the applicative instance of the `f` which wraps+each type in `FlatConfig` to give back an `f FlatConfig` (in our case an+`Identity FlatConfig`). ## Nested datatypes @@ -343,8 +369,9 @@ deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB) ``` -To define the option parser, we need option parsers for every type inside it. This was true for-flat configs too, but we have to manually construct a `DbConfigB Opt` and `ServiceConfigB Opt`:+To define the option parser, we need option parsers for every type inside it.+This was true for flat configs too, but we have to manually construct a+`DbConfigB Opt` and `ServiceConfigB Opt`: ``` haskell configOpt1 :: ConfigB Opt@@ -365,18 +392,24 @@ ``` haskell getConfig1 :: IO () getConfig1 = do- ConfigB (DbConfigB dbHost dbPort) (ServiceConfigB port log) dir <- execOptDef configOpt1+ ConfigB (DbConfigB dbHost dbPort) (ServiceConfigB port log) dir <-+ execOptDef configOpt1 let db = DbConfig <$> dbHost <*> dbPort service = ServiceConfig <$> port <*> log- print $ runIdentity (Config <$> db <*> service <*> dir)+ print $ runIdentity $+ Config+ <$> db+ <*> service+ <*> dir ``` ### 2. Using `higgledy` -`higgledy` puts an `f` before every type, so doing something like `HKD Config f` doesn't make sense:-looking at `ConfigB` it seems like the `f` needs to go to the right hand side of the nested types.-We can, however, avoid the boilerplate of defining `barbie` types for the nested datatypes:+`higgledy` puts an `f` before every type, so doing something like `HKD Config f`+doesn't make sense: looking at `ConfigB` it seems like the `f` needs to go to+the right hand side of the nested types. We can, however, avoid the boilerplate+of defining `barbie` types for the nested datatypes: ``` haskell data ConfigH f@@ -406,14 +439,19 @@ getConfig2 :: IO () getConfig2 = do ConfigH db service dir <- execOptDef configOpt2- print $ runIdentity (Config <$> construct db <*> construct service <*> dir)+ print $ runIdentity $+ Config+ <$> construct db+ <*> construct service+ <*> dir ``` ### 2. Using products -Recall from previously that there's the `Single` type which in general turns `f b` into `b f`. This-means that, by using `Single` for the directory option, all `f`s are after their types, so we can-just use `:*` instead of having to declare a new datatype:+Recall from previously that there's the `Single` type which in general turns `f+b` into `b f`. This means that, by using `Single` for the directory option, all+`f`s are after their types, so we can just use `:*` instead of having to declare+a new datatype: ``` haskell type ConfigP@@ -428,11 +466,16 @@ getConfig3 :: IO () getConfig3 = do db :* service :* dir <- execOptDef configOpt3- print $ runIdentity (Config <$> construct db <*> construct service <*> getSingle dir)+ print $ runIdentity $+ Config+ <$> construct db+ <*> construct service+ <*> getSingle dir ``` -And, to make things look more orthogonal, `harg` defines a type called `Nested`, which is exactly-the same as `HKD`. There are functions that correspond to `build` and `construct`, too:+And, to make things look more orthogonal, `harg` defines a type called `Nested`,+which is exactly the same as `HKD`. There are functions that correspond to+`build` and `construct`, too: ``` Nested <-> HKD@@ -460,7 +503,11 @@ getConfig4 :: IO () getConfig4 = do db :* service :* dir <- execOptDef configOpt4- print $ runIdentity (Config <$> getNested db <*> getNested service <*> getSingle dir)+ print $ runIdentity $+ Config+ <$> getNested db+ <*> getNested service+ <*> getSingle dir ``` Pretty cool.@@ -470,12 +517,13 @@ `harg` also supports (somewhat limited) subcommands, again by using `optparse-applicative` underneath. -Because of limitations with higher kinded data when it comes to sum types, `harg` uses a different-way to define subcommands. `optparse-applicative` allows defining subcommands that result to the-same type, which means the user needs to define a sum type, and each subcommand results in a-different constructor. In contrast, `harg` defines subcommands that can return completely different-types. Instead of the result being a sum type, where the user has to pattern match on constructors,-the result is a `Variant`, which is defined (almost) like this:+Because of limitations with higher kinded data when it comes to sum types,+`harg` uses a different way to define subcommands. `optparse-applicative` allows+defining subcommands that result to the same type, which means the user needs to+define a sum type, and each subcommand results in a different constructor. In+contrast, `harg` defines subcommands that can return completely different types.+Instead of the result being a sum type, where the user has to pattern match on+constructors, the result is a `Variant`, which is defined (almost) like this: ``` haskell data Variant (xs :: [Type]) where@@ -483,10 +531,12 @@ There :: Variant xs -> Variant (y ': xs) ``` -`Variant` is like a sum type which holds all the summands in a type-level list. Instead of pattern-matching in `Left` or `Right` like when using `Either`, we pattern match on `Here x`, `There (Here x)`-etc. For a pretty thorough introduction to `Variant` and more heterogeneous types, check out-[this repo](https://github.com/i-am-tom/learn-me-a-haskell) by [i-am-tom](https://github.com/i-am-tom).+`Variant` is like a sum type which holds all the summands in a type-level list.+Instead of pattern matching in `Left` or `Right` like when using `Either`, we+pattern match on `Here x`, `There (Here x)` etc. For a pretty thorough+introduction to `Variant` and more heterogeneous types, check out+[this repo](https://github.com/i-am-tom/learn-me-a-haskell) by+[i-am-tom](https://github.com/i-am-tom). ``` haskell x :: Variant '[Int, Bool, Char]@@ -509,10 +559,11 @@ to hold a type-level list of `barbie` types and the `f` to wrap every type with. -To define a type to be used in a subcommand parser we need the target type and the subcommand name,-which is encoded as a type-level string `Symbol`. There's a handy way to define this. Suppose that-the the `Config` type above is the configuration type when the command is `app` and another type,-e.g.`TestConfig` is the configuration when the command is `test`:+To define a type to be used in a subcommand parser we need the target type and+the subcommand name, which is encoded as a type-level string `Symbol`. There's a+handy way to define this. Suppose that the the `Config` type above is the+configuration type when the command is `app` and another type, e.g.`TestConfig`+is the configuration when the command is `test`: ``` haskell data TestConfig@@ -562,8 +613,8 @@ = configOpt4 :+ testConfigOpt :+ ANil ``` -The `ANil` here marks the end of the association list (which is a heterogeneous list that associates-symbols with types).+The `ANil` here marks the end of the association list (which is a heterogeneous+list that associates symbols with types). Here's how to run this parser: @@ -573,11 +624,16 @@ result <- execCommandsDef subcommandOpt case result of HereF (db :* service :* dir)- -> print $ runIdentity- $ Config <$> getNested db <*> getNested service <*> getSingle dir+ -> print $ runIdentity $+ Config+ <$> getNested db+ <*> getNested service+ <*> getSingle dir ThereF (HereF (foo :* bar))- -> print $ runIdentity- $ TestConfig <$> getSingle foo <*> getSingle bar+ -> print $ runIdentity $+ TestConfig+ <$> getSingle foo+ <*> getSingle bar ``` Or use `fromVariantF`, which is similar to the `either` function:@@ -588,12 +644,17 @@ result <- execCommandsDef subcommandOpt fromVariantF result (\(db :* service :* dir)- -> print $ runIdentity- $ Config <$> getNested db <*> getNested service <*> getSingle dir+ -> print $ runIdentity $+ Config+ <$> getNested db+ <*> getNested service+ <*> getSingle dir ) (\(foo :* bar)- -> print $ runIdentity- $ TestConfig <$> getSingle foo <*> getSingle bar+ -> print $ runIdentity $+ TestConfig+ <$> getSingle foo+ <*> getSingle bar ) ``` @@ -609,17 +670,18 @@ -> r ``` -The signature will accept the appropriate number of functions depending on the length of the type-level list.+The signature will accept the appropriate number of functions depending on the+length of the type level list. ## More than just environment variables -You may have noticed the use of `execOptDef` and `execCommandsDef` in all of the examples up to now.-There are actually more configurable versions of these, called `execOpt` and `execCommands`-respectively. With these functions the user can select where to get options from. For example,-`execOptDef` is a shorthand for `execOpt EnvSource`, which means that options will be fetched from-environment variables only (along with the command line, which is always required, and defaults,-which can be optionally provided by the user).+You may have noticed the use of `execOptDef` and `execCommandsDef` in all of the+examples up to now. There are actually more configurable versions of these,+called `execOpt` and `execCommands` respectively. With these functions the user+can select where to get options from. For example, `execOptDef` is a shorthand+for `execOpt EnvSource`, which means that options will be fetched from+environment variables only (along with the command line, which is always+required, and defaults, which can be optionally provided by the user). The sources currently supported are environment variables, json and yaml files. @@ -674,25 +736,25 @@ = build @FlatConfig dbHostOpt dbPortOpt dirOpt logOpt ``` -To use the JSON source, a `FromJSON` instance is required. Thankfully that's easy, since-`FlatConfig` has `Generic` instance:+To use the JSON source, a `FromJSON` instance is required. Thankfully that's+easy, since `FlatConfig` has `Generic` instance: ``` haskell instance FromJSON FlatConfig ``` -In `harg`, sources are defined as products (using `:*`) of options, which means that the definition-of the sources is not very different than defining options! If we only needed the environment-variable source, the options would be:+In `harg`, sources are defined as products (using `:*`) of options, which means+that the definition of the sources is not very different than defining options!+If we only needed the environment variable source, the options would be: ``` haskell ignore envSource :: EnvSource Opt envSource = EnvSource ``` -There's no need to actually define an option for the environment because there's no meaningful-configuration for this. To use the `EnvSource` along with a json config, we use the following-option:+There's no need to actually define an option for the environment because there's+no meaningful configuration for this. To use the `EnvSource` along with a json+config, we use the following option: ``` haskell sourceOpt :: (EnvSource :* JSONSource) Opt@@ -708,8 +770,8 @@ ) ``` -Here, the type of the option for the JSON source is `ConfigFile`. This type is a wrapper around-`FilePath`, which looks like this:+Here, the type of the option for the JSON source is `ConfigFile`. This type is a+wrapper around `FilePath`, which looks like this: ``` haskell ignore data ConfigFile@@ -717,8 +779,8 @@ | NoConfigFile ``` -This has the advantage that, if the user wants to specify an optional configuration file, they can-simply say:+This has the advantage that, if the user wants to specify an optional+configuration file, they can simply say: ``` haskell jsonOpt :: Opt ConfigFile@@ -732,17 +794,16 @@ Also, because `ConfigFile` has an `IsString` instance, there's no need to say `long (ConfigFile "json")` (if `OverloadedStrings` is enabled). -There's a bit of a disconnect between `ConfigFile` and the ability to make optional options using-`Maybe` and `optional`. The reason for it is that the type that `JSONSource` wraps is not-polymorphic, since it needs to be a filepath specifically.+There's a bit of a disconnect between `ConfigFile` and the ability to make+optional options using `Maybe` and `optional`. The reason for it is that the+type that `JSONSource` wraps is not polymorphic, since it needs to be a filepath+specifically. # Roadmap - Better errors using `optparse-applicative`'s internals - Allow user to pass `optparse-applicative` preferences - Write tests-- ~~Be able to provide and get back the same type for multiple subcommands~~-- ~~Integrate config files (e.g. JSON using aeson)~~ # Credits
README.md view
@@ -1,25 +1,27 @@-# harg+# `harg` [](https://travis-ci.org/alexpeits/harg) [](https://hackage.haskell.org/package/harg) -`harg` is a library for configuring programs by scanning command line arguments, environment-variables, default values and more. Under the hood, it uses a subset of-[`optparse-applicative`](https://hackage.haskell.org/package/optparse-applicative) to expose regular-arguments, switch arguments and subcommands. The library relies heavily on the use of higher kinded-data (HKD) thanks to the [`barbies`](https://hackage.haskell.org/package/barbies) library. Using-[`higgledy`](https://hackage.haskell.org/package/higgledy) also allows to have significantly less-boilerplate code.+`harg` is a library for configuring programs by scanning command line arguments,+environment variables, default values and more. Under the hood, it uses a subset+of [`optparse-applicative`](https://hackage.haskell.org/package/optparse-applicative)+to expose regular arguments, switch arguments and subcommands. The library+relies heavily on the use of higher kinded data (HKD) thanks to the+[`barbies`](https://hackage.haskell.org/package/barbies) library. Using+[`higgledy`](https://hackage.haskell.org/package/higgledy) also allows to have+significantly less boilerplate code. -The main goal while developing `harg` was to not have to go through the usual pattern of manually-`mappend`ing the results of command line parsing, env vars and defaults.+The main goal while developing `harg` was to not have to go through the usual+pattern of manually `mappend`ing the results of command line parsing, env vars+and defaults. # Usage tl;dr: Take a look at the [example](Example.hs). -Here are some different usage scenarios. Let's first enable some language extensions and add some-imports:+Here are some different usage scenarios. Let's first enable some language+extensions and add some imports: ``` haskell {-# LANGUAGE DataKinds #-}@@ -48,8 +50,8 @@ ## One flat (non-nested) datatype -The easiest scenario is when the target configuration type is one single record with no levels of-nesting:+The easiest scenario is when the target configuration type is one single record+with no levels of nesting: ``` haskell data FlatConfig@@ -64,8 +66,8 @@ (The `Generic` instance is required for section `3` later on) -Let's first create the `Opt`s for each value in `FlatConfig`. `Opt` is the description for each-component of the configuration.+Let's first create the `Opt`s for each value in `FlatConfig`. `Opt` is the+description for each component of the configuration. ``` haskell dbHostOpt :: Opt String@@ -101,21 +103,24 @@ ) ``` -Here, we use `option` to define a command line argument that expects a value after it, `argument` to-define a standalone argument, not prefixed by a long or short indicator, and `switch` to define a-boolean command line flag that, if present, sets the target value to `True`. The `opt*` functions-(here applied using `&` to make things look more declarative) modify the option configuration.-`help` adds help text, `defaultVal` adds a default value, `short` adds a short command line-option as an alternative to the long one (the string after `option` or `switch`), `envVar` sets-the associated environment variable and `metavar` sets the metavariable to be shown in the help-text generated by `optparse-applicative`.+Here, we use `option` to define a command line argument that expects a value+after it, `argument` to define a standalone argument, not prefixed by a long or+short indicator, and `switch` to define a boolean command line flag that, if+present, sets the target value to `True`. The `opt*` functions (here applied+using `&` to make things look more declarative) modify the option configuration.+`help` adds help text, `defaultVal` adds a default value, `short` adds a short+command line option as an alternative to the long one (the string after `option`+or `switch`), `envVar` sets the associated environment variable and `metavar`+sets the metavariable to be shown in the help text generated by+`optparse-applicative`. -The first argument (`strParser` or `readParser`) is the parser for the argument, be it from the-command line or from an environment variable. The type of this function should be-`String -> Either String a`, which produces an error message or the parsed value. `strParser` is-equivalent to `pure` and always succeeds. `readParser` requires the type to have a `Read` constraint.-In order to use it with newtypes that wrap a type that has a `Read` constraint, using the `Functor`+The first argument (`strParser` or `readParser`) is the parser for the argument,+be it from the command line or from an environment variable. The type of this+function should be `String -> Either String a`, which produces an error message+or the parsed value. `strParser` is equivalent to `pure` and always succeeds.+`readParser` requires the type to have a `Read` constraint. In order to use it+with newtypes that wrap a type that has a `Read` constraint, using the `Functor` instance for `Opt` should be sufficient. E.g. for the newtype: ``` haskell@@ -130,14 +135,16 @@ = Port <$> dbPortOpt ``` -Of course, any user-defined function works as well. In addition, to use a function of type `String--> Maybe a` use `parseWith`, which runs the parser and in case of failure uses a default error-message. For example, `readParser` is defined as `parseWith readMaybe`.+Of course, any user-defined function works as well. In addition, to use a+function of type `String -> Maybe a` use `parseWith`, which runs the parser and+in case of failure uses a default error message. For example, `readParser` is+defined as `parseWith readMaybe`. -Finally, an optional option with type `a` can be specified by setting its type to `Maybe a`. The-declaration is exactly the same as it would be for `a`, and adding `optional` to the modifiers-turns turns the parser from `String -> Either String a` to `String -> Either String (Maybe a)` but-without using the `Read` instance for `Maybe`:+Finally, an optional option with type `a` can be specified by setting its type+to `Maybe a`. The declaration is exactly the same as it would be for `a`, and+adding `optional` to the modifiers turns turns the parser from `String -> Either+String a` to `String -> Either String (Maybe a)` but without using the `Read`+instance for `Maybe`: ``` haskell ignore someOpt :: Opt (Maybe Int)@@ -148,16 +155,16 @@ ) ``` -Note that `optional` can't be used with `defaultVal`. Using them together raises a type error at-compile time, to ensure there's no ambiguous behaviour (e.g. the order of declaration of modifiers-should not influence the resulting option).+Note that `optional` can't be used with `defaultVal`. Using them together raises+a type error at compile time, to ensure there's no ambiguous behaviour (e.g. the+order of declaration of modifiers should not influence the resulting option). There are 3 ways to configure this datatype. ### 1. Using a `barbie` type -`barbie` types are types of kind `(Type -> Type) -> Type`. The `barbie` type for `FlatConfig`-looks like this:+`barbie` types are types of kind `(Type -> Type) -> Type`. The `barbie` type for+`FlatConfig` looks like this: ``` haskell data FlatConfigB f@@ -170,12 +177,13 @@ deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB) ``` -I also derived some required instances that come from the `barbies` package. These instances allow-us to change the `f` (`bmap` from `FunctorB`) and traverse all types in the record producing side-effects (`btraverse` from `TraversableB`).+I also derived some required instances that come from the `barbies` package.+These instances allow us to change the `f` (`bmap` from `FunctorB`) and traverse+all types in the record producing side effects (`btraverse` from+`TraversableB`). -Now let's define the value of this datatype, which holds our option configuration. The type-constructor needed for the options is `Opt`:+Now let's define the value of this datatype, which holds our option+configuration. The type constructor needed for the options is `Opt`: ``` haskell flatConfigOpt1 :: FlatConfigB Opt@@ -183,8 +191,8 @@ = FlatConfigB dbHostOpt dbPortOpt dirOpt logOpt ``` -Because `dbHostOpt`, `dbPortOpt` and `logOpt` all have type `Opt <actual type>`, `flatConfigOpt1`-has the correct type according to `FlatConfigB Opt`.+Because `dbHostOpt`, `dbPortOpt` and `logOpt` all have type `Opt <actual type>`,+`flatConfigOpt1` has the correct type according to `FlatConfigB Opt`. Now to actually run things: @@ -192,48 +200,62 @@ getFlatConfig1 :: IO () getFlatConfig1 = do FlatConfigB host port dir log <- execOptDef flatConfigOpt1- print $ runIdentity (FlatConfig <$> host <*> port <*> dir <*> log)+ print $ runIdentity $+ FlatConfig+ <$> host+ <*> port+ <*> dir+ <*> log ``` -`execOpt` returns an `Identity x` where `x` is the type of the options we are configuring, in this-case `FlatConfigB`. Here, we pattern match on the barbie-type, and then use the `Applicative`-instance of `Identity` to get back an `Identity FlatConfig`.+`execOpt` returns an `Identity x` where `x` is the type of the options we are+configuring, in this case `FlatConfigB`. Here, we pattern match on the+barbie-type, and then use the `Applicative` instance of `Identity` to get back+an `Identity FlatConfig`. This is still a bit boilerplate-y. Let's look at another way. ### 2. Using a product type -Looking at `FlatConfigB`, it's only used because of it's `barbie`-like capabilities. Other than that-it's just a simple product type with the additional `f` before all its sub-types.+Looking at `FlatConfigB`, it's only used because of it's `barbie`-like+capabilities. Other than that it's just a simple product type with the+additional `f` before all its sub-types. -`harg` defines a type almost similar to `Product` (from `Data.Functor.Product`), which works in a-similar fashion as servant's `:<|>` type. This type is defined in `Options.Harg.Het.Prod` and is-called `:*` (the `*` stands for product). This type stores barbie-like types and also keeps the `f`-handy: `data (a :* b) f = a f :* b f`. This is also easily made an instance of `Generic`,-`FunctorB`, `TraversableB` and `ProductB`. With all that, let's rewrite the options value and the-function to get the configuration:+`harg` defines a type almost similar to `Product` (from `Data.Functor.Product`),+which works in a similar fashion as servant's `:<|>` type. This type is defined+in `Options.Harg.Het.Prod` and is called `:*` (the `*` stands for product). This+type stores barbie-like types and also keeps the `f` handy: `data (a :* b) f = a+f :* b f`. This is also easily made an instance of `Generic`, `FunctorB`,+`TraversableB` and `ProductB`. With all that, let's rewrite the options value+and the function to get the configuration: ``` haskell-flatConfigOpt2 :: (Single String :* Single Int :* Single String :* Single Bool) Opt flatConfigOpt2+ :: (Single String :* Single Int :* Single String :* Single Bool) Opt+flatConfigOpt2 = single dbHostOpt :* single dbPortOpt :* single dirOpt :* single logOpt getFlatConfig2 :: IO () getFlatConfig2 = do host :* port :* dir :* log <- execOptDef flatConfigOpt2- print $ runIdentity- (FlatConfig <$> getSingle host <*> getSingle port <*> getSingle dir <*> getSingle log)+ print $ runIdentity $+ FlatConfig+ <$> getSingle host+ <*> getSingle port+ <*> getSingle dir+ <*> getSingle log ``` -This looks aufully similar to the previous version, but without having to write another datatype and-derive all the instances. `:*` is both a type-level constructor and a value-level function that acts-like list's `:`. It is also right-associative, so for example `a :* b :* c` is the same as-`a :* (b :* c)`.+This looks aufully similar to the previous version, but without having to write+another datatype and derive all the instances. `:*` is both a type-level+constructor and a value-level function that acts like list's `:`. It is also+right-associative, so for example `a :* b :* c` is the same as `a :* (b :* c)`. -The `Single` type constructor is used when talking about a single value, rather than a nested-datatype. `Single a f` is a simple newtype over `f a`. The reason for using that is simply to switch-the order of application, so that we can later apply the `f` (here `Opt`) to the compound type-(`:*`). This makes type definitions look more similar to datatype definitions:+The `Single` type constructor is used when talking about a single value, rather+than a nested datatype. `Single a f` is a simple newtype over `f a`. The reason+for using that is simply to switch the order of application, so that we can+later apply the `f` (here `Opt`) to the compound type (`:*`). This makes type+definitions look more similar to datatype definitions: ``` haskell type FlatConfigOpt2@@ -242,10 +264,12 @@ :* Single Bool ``` -In addition, `single` is used to wrap an `f a` into a `Single a f`, and `getSingle` is used-to unwrap it. Later on we'll see how to construct nested configurations using `Nested`.+In addition, `single` is used to wrap an `f a` into a `Single a f`, and+`getSingle` is used to unwrap it. Later on we'll see how to construct nested+configurations using `Nested`. -However, the real value when having flat datatypes comes from the ability to use `higgledy`.+However, the real value when having flat datatypes comes from the ability to use+`higgledy`. ### 3. Using `HKD` from `higgledy` @@ -260,14 +284,16 @@ print $ runIdentity (construct result) ``` -This is the most straightforward way to work with flat configuration types. The `build` function-takes as arguments the options (`Opt a` where `a` is each type in `FlatConfig`) **in the order they-appear in the datatype**, and returns the generic representation of a type that's exactly the same-as `FlatConfigB`. This means that we get all the `barbie` instances for free.+This is the most straightforward way to work with flat configuration types. The+`build` function takes as arguments the options (`Opt a` where `a` is each type+in `FlatConfig`) **in the order they appear in the datatype**, and returns the+generic representation of a type that's exactly the same as `FlatConfigB`. This+means that we get all the `barbie` instances for free. -To go back from the `HKD` representation of a datatype to the base one, we use `construct`.-`construct` uses the applicative instance of the `f` which wraps each type in `FlatConfig` to give-back an `f FlatConfig` (in our case an `Identity FlatConfig`).+To go back from the `HKD` representation of a datatype to the base one, we use+`construct`. `construct` uses the applicative instance of the `f` which wraps+each type in `FlatConfig` to give back an `f FlatConfig` (in our case an+`Identity FlatConfig`). ## Nested datatypes @@ -343,8 +369,9 @@ deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB) ``` -To define the option parser, we need option parsers for every type inside it. This was true for-flat configs too, but we have to manually construct a `DbConfigB Opt` and `ServiceConfigB Opt`:+To define the option parser, we need option parsers for every type inside it.+This was true for flat configs too, but we have to manually construct a+`DbConfigB Opt` and `ServiceConfigB Opt`: ``` haskell configOpt1 :: ConfigB Opt@@ -365,18 +392,24 @@ ``` haskell getConfig1 :: IO () getConfig1 = do- ConfigB (DbConfigB dbHost dbPort) (ServiceConfigB port log) dir <- execOptDef configOpt1+ ConfigB (DbConfigB dbHost dbPort) (ServiceConfigB port log) dir <-+ execOptDef configOpt1 let db = DbConfig <$> dbHost <*> dbPort service = ServiceConfig <$> port <*> log- print $ runIdentity (Config <$> db <*> service <*> dir)+ print $ runIdentity $+ Config+ <$> db+ <*> service+ <*> dir ``` ### 2. Using `higgledy` -`higgledy` puts an `f` before every type, so doing something like `HKD Config f` doesn't make sense:-looking at `ConfigB` it seems like the `f` needs to go to the right hand side of the nested types.-We can, however, avoid the boilerplate of defining `barbie` types for the nested datatypes:+`higgledy` puts an `f` before every type, so doing something like `HKD Config f`+doesn't make sense: looking at `ConfigB` it seems like the `f` needs to go to+the right hand side of the nested types. We can, however, avoid the boilerplate+of defining `barbie` types for the nested datatypes: ``` haskell data ConfigH f@@ -406,14 +439,19 @@ getConfig2 :: IO () getConfig2 = do ConfigH db service dir <- execOptDef configOpt2- print $ runIdentity (Config <$> construct db <*> construct service <*> dir)+ print $ runIdentity $+ Config+ <$> construct db+ <*> construct service+ <*> dir ``` ### 2. Using products -Recall from previously that there's the `Single` type which in general turns `f b` into `b f`. This-means that, by using `Single` for the directory option, all `f`s are after their types, so we can-just use `:*` instead of having to declare a new datatype:+Recall from previously that there's the `Single` type which in general turns `f+b` into `b f`. This means that, by using `Single` for the directory option, all+`f`s are after their types, so we can just use `:*` instead of having to declare+a new datatype: ``` haskell type ConfigP@@ -428,11 +466,16 @@ getConfig3 :: IO () getConfig3 = do db :* service :* dir <- execOptDef configOpt3- print $ runIdentity (Config <$> construct db <*> construct service <*> getSingle dir)+ print $ runIdentity $+ Config+ <$> construct db+ <*> construct service+ <*> getSingle dir ``` -And, to make things look more orthogonal, `harg` defines a type called `Nested`, which is exactly-the same as `HKD`. There are functions that correspond to `build` and `construct`, too:+And, to make things look more orthogonal, `harg` defines a type called `Nested`,+which is exactly the same as `HKD`. There are functions that correspond to+`build` and `construct`, too: ``` Nested <-> HKD@@ -460,7 +503,11 @@ getConfig4 :: IO () getConfig4 = do db :* service :* dir <- execOptDef configOpt4- print $ runIdentity (Config <$> getNested db <*> getNested service <*> getSingle dir)+ print $ runIdentity $+ Config+ <$> getNested db+ <*> getNested service+ <*> getSingle dir ``` Pretty cool.@@ -470,12 +517,13 @@ `harg` also supports (somewhat limited) subcommands, again by using `optparse-applicative` underneath. -Because of limitations with higher kinded data when it comes to sum types, `harg` uses a different-way to define subcommands. `optparse-applicative` allows defining subcommands that result to the-same type, which means the user needs to define a sum type, and each subcommand results in a-different constructor. In contrast, `harg` defines subcommands that can return completely different-types. Instead of the result being a sum type, where the user has to pattern match on constructors,-the result is a `Variant`, which is defined (almost) like this:+Because of limitations with higher kinded data when it comes to sum types,+`harg` uses a different way to define subcommands. `optparse-applicative` allows+defining subcommands that result to the same type, which means the user needs to+define a sum type, and each subcommand results in a different constructor. In+contrast, `harg` defines subcommands that can return completely different types.+Instead of the result being a sum type, where the user has to pattern match on+constructors, the result is a `Variant`, which is defined (almost) like this: ``` haskell data Variant (xs :: [Type]) where@@ -483,10 +531,12 @@ There :: Variant xs -> Variant (y ': xs) ``` -`Variant` is like a sum type which holds all the summands in a type-level list. Instead of pattern-matching in `Left` or `Right` like when using `Either`, we pattern match on `Here x`, `There (Here x)`-etc. For a pretty thorough introduction to `Variant` and more heterogeneous types, check out-[this repo](https://github.com/i-am-tom/learn-me-a-haskell) by [i-am-tom](https://github.com/i-am-tom).+`Variant` is like a sum type which holds all the summands in a type-level list.+Instead of pattern matching in `Left` or `Right` like when using `Either`, we+pattern match on `Here x`, `There (Here x)` etc. For a pretty thorough+introduction to `Variant` and more heterogeneous types, check out+[this repo](https://github.com/i-am-tom/learn-me-a-haskell) by+[i-am-tom](https://github.com/i-am-tom). ``` haskell x :: Variant '[Int, Bool, Char]@@ -509,10 +559,11 @@ to hold a type-level list of `barbie` types and the `f` to wrap every type with. -To define a type to be used in a subcommand parser we need the target type and the subcommand name,-which is encoded as a type-level string `Symbol`. There's a handy way to define this. Suppose that-the the `Config` type above is the configuration type when the command is `app` and another type,-e.g.`TestConfig` is the configuration when the command is `test`:+To define a type to be used in a subcommand parser we need the target type and+the subcommand name, which is encoded as a type-level string `Symbol`. There's a+handy way to define this. Suppose that the the `Config` type above is the+configuration type when the command is `app` and another type, e.g.`TestConfig`+is the configuration when the command is `test`: ``` haskell data TestConfig@@ -562,8 +613,8 @@ = configOpt4 :+ testConfigOpt :+ ANil ``` -The `ANil` here marks the end of the association list (which is a heterogeneous list that associates-symbols with types).+The `ANil` here marks the end of the association list (which is a heterogeneous+list that associates symbols with types). Here's how to run this parser: @@ -573,11 +624,16 @@ result <- execCommandsDef subcommandOpt case result of HereF (db :* service :* dir)- -> print $ runIdentity- $ Config <$> getNested db <*> getNested service <*> getSingle dir+ -> print $ runIdentity $+ Config+ <$> getNested db+ <*> getNested service+ <*> getSingle dir ThereF (HereF (foo :* bar))- -> print $ runIdentity- $ TestConfig <$> getSingle foo <*> getSingle bar+ -> print $ runIdentity $+ TestConfig+ <$> getSingle foo+ <*> getSingle bar ``` Or use `fromVariantF`, which is similar to the `either` function:@@ -588,12 +644,17 @@ result <- execCommandsDef subcommandOpt fromVariantF result (\(db :* service :* dir)- -> print $ runIdentity- $ Config <$> getNested db <*> getNested service <*> getSingle dir+ -> print $ runIdentity $+ Config+ <$> getNested db+ <*> getNested service+ <*> getSingle dir ) (\(foo :* bar)- -> print $ runIdentity- $ TestConfig <$> getSingle foo <*> getSingle bar+ -> print $ runIdentity $+ TestConfig+ <$> getSingle foo+ <*> getSingle bar ) ``` @@ -609,17 +670,18 @@ -> r ``` -The signature will accept the appropriate number of functions depending on the length of the type-level list.+The signature will accept the appropriate number of functions depending on the+length of the type level list. ## More than just environment variables -You may have noticed the use of `execOptDef` and `execCommandsDef` in all of the examples up to now.-There are actually more configurable versions of these, called `execOpt` and `execCommands`-respectively. With these functions the user can select where to get options from. For example,-`execOptDef` is a shorthand for `execOpt EnvSource`, which means that options will be fetched from-environment variables only (along with the command line, which is always required, and defaults,-which can be optionally provided by the user).+You may have noticed the use of `execOptDef` and `execCommandsDef` in all of the+examples up to now. There are actually more configurable versions of these,+called `execOpt` and `execCommands` respectively. With these functions the user+can select where to get options from. For example, `execOptDef` is a shorthand+for `execOpt EnvSource`, which means that options will be fetched from+environment variables only (along with the command line, which is always+required, and defaults, which can be optionally provided by the user). The sources currently supported are environment variables, json and yaml files. @@ -674,25 +736,25 @@ = build @FlatConfig dbHostOpt dbPortOpt dirOpt logOpt ``` -To use the JSON source, a `FromJSON` instance is required. Thankfully that's easy, since-`FlatConfig` has `Generic` instance:+To use the JSON source, a `FromJSON` instance is required. Thankfully that's+easy, since `FlatConfig` has `Generic` instance: ``` haskell instance FromJSON FlatConfig ``` -In `harg`, sources are defined as products (using `:*`) of options, which means that the definition-of the sources is not very different than defining options! If we only needed the environment-variable source, the options would be:+In `harg`, sources are defined as products (using `:*`) of options, which means+that the definition of the sources is not very different than defining options!+If we only needed the environment variable source, the options would be: ``` haskell ignore envSource :: EnvSource Opt envSource = EnvSource ``` -There's no need to actually define an option for the environment because there's no meaningful-configuration for this. To use the `EnvSource` along with a json config, we use the following-option:+There's no need to actually define an option for the environment because there's+no meaningful configuration for this. To use the `EnvSource` along with a json+config, we use the following option: ``` haskell sourceOpt :: (EnvSource :* JSONSource) Opt@@ -708,8 +770,8 @@ ) ``` -Here, the type of the option for the JSON source is `ConfigFile`. This type is a wrapper around-`FilePath`, which looks like this:+Here, the type of the option for the JSON source is `ConfigFile`. This type is a+wrapper around `FilePath`, which looks like this: ``` haskell ignore data ConfigFile@@ -717,8 +779,8 @@ | NoConfigFile ``` -This has the advantage that, if the user wants to specify an optional configuration file, they can-simply say:+This has the advantage that, if the user wants to specify an optional+configuration file, they can simply say: ``` haskell jsonOpt :: Opt ConfigFile@@ -732,17 +794,16 @@ Also, because `ConfigFile` has an `IsString` instance, there's no need to say `long (ConfigFile "json")` (if `OverloadedStrings` is enabled). -There's a bit of a disconnect between `ConfigFile` and the ability to make optional options using-`Maybe` and `optional`. The reason for it is that the type that `JSONSource` wraps is not-polymorphic, since it needs to be a filepath specifically.+There's a bit of a disconnect between `ConfigFile` and the ability to make+optional options using `Maybe` and `optional`. The reason for it is that the+type that `JSONSource` wraps is not polymorphic, since it needs to be a filepath+specifically. # Roadmap - Better errors using `optparse-applicative`'s internals - Allow user to pass `optparse-applicative` preferences - Write tests-- ~~Be able to provide and get back the same type for multiple subcommands~~-- ~~Integrate config files (e.g. JSON using aeson)~~ # Credits
harg.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2 name: harg-version: 0.4.0.0+version: 0.4.1.0 synopsis: Haskell program configuration using higher kinded data description: Please see the README on GitHub at <https://github.com/alexpeits/harg#readme> homepage: https://github.com/alexpeits/harg
src/Options/Harg.hs view
@@ -81,6 +81,7 @@ , B.FunctorB , B.TraversableB , B.ProductB+ , B.Rec (..) -- *** higgledy , HKD.HKD
src/Options/Harg/Cmdline.hs view
@@ -78,9 +78,7 @@ Optparse.flag def active modifiers where mDef- = case getCompose sources of- Nothing -> _optDefaultVal- Just x -> Just x+ = getCompose sources <|> _optDefaultVal modifiers = foldMap (fromMaybe mempty) [ Optparse.long <$> _optLong
src/Options/Harg/Construct.hs view
@@ -344,8 +344,7 @@ -- with the default 'Left' value @unable to parse: \<input\>@. parseWith :: (String -> Maybe a) -- ^ Original parser- -> String -- ^ Input- -> Either String a+ -> (String -> Either String a) parseWith parser s = maybe (Left err) Right (parser s) where
src/Options/Harg/Operations.hs view
@@ -17,14 +17,14 @@ import Options.Harg.Het.HList (AssocListF, MapAssocList(..)) import Options.Harg.Het.Prod ((:*)(..)) import Options.Harg.Het.Variant (VariantF)-import Options.Harg.Pretty (ppOptErrors)+import Options.Harg.Pretty (ppSourceRunErrors) import Options.Harg.Sources ( accumSourceResults , DefaultSources, defaultSources , HiddenSources, hiddenSources )-import Options.Harg.Sources.Types (GetSource(..), RunSource(..))+import Options.Harg.Sources.Types (GetSource(..), RunSource(..), SourceRunError) import Options.Harg.Subcommands (Subcommands(..))-import Options.Harg.Types (HargCtx(..), getCtx, Opt, OptError)+import Options.Harg.Types (HargCtx(..), getCtx, Opt) import Options.Harg.Util (toDummyOpts, allToDummyOpts, compose) -- | Run the option parser and combine with values from the specified sources,@@ -209,7 +209,7 @@ failParser :: Optparse.Parser a- -> [OptError]+ -> [SourceRunError] -> IO a failParser parser errs = Optparse.handleParseResult (Optparse.Failure failure)@@ -223,7 +223,7 @@ parserInfo = Optparse.info (Optparse.helper <*> parser) Optparse.forwardOptions errStr- = ppOptErrors errs+ = ppSourceRunErrors errs -- | Run the optparse-applicative parser and return the -- 'Optparse.ParserResult'
src/Options/Harg/Pretty.hs view
@@ -1,8 +1,10 @@ module Options.Harg.Pretty where -import Data.List (intercalate, nubBy)-import Data.Maybe (fromMaybe)+import Control.Applicative ((<|>))+import Data.List (intercalate)+import Data.Maybe (fromMaybe) +import Options.Harg.Sources.Types import Options.Harg.Types @@ -12,31 +14,38 @@ ppHelp Opt{..} = (<> ppEnvVar _optEnvVar) <$> _optHelp -ppOptErrors- :: [OptError]+ppSourceRunErrors+ :: [SourceRunError] -> String-ppOptErrors+ppSourceRunErrors = intercalate "\n\n"- . map ppOptError- . nubBy cmpOptErr+ . map ppSourceRunError where- cmpOptErr (OptError (SomeOpt l) sl dl) (OptError (SomeOpt r) sr dr)- = _optLong l == _optLong r && sl == sr && dl == dr- ppOptError :: OptError -> String- ppOptError (OptError (SomeOpt opt) src desc)+ ppSourceRunError :: SourceRunError -> String+ ppSourceRunError (SourceRunError Nothing src desc)+ = "error: "+ <> desc+ <> "\n\t"+ <> ppSource src++ ppSourceRunError (SourceRunError (Just (SomeOpt opt)) src desc) = "option "- <> fromMaybe "<no opt name>" (_optLong opt)+ <> optId opt <> ": " <> desc <> "\n\t" <> ppSource src <> ppEnvVar (_optEnvVar opt) + optId Opt{..}+ = fromMaybe "<no name available>"+ $ _optLong <|> (pure <$> _optShort) <|> _optMetavar+ ppSource- :: Maybe String+ :: String -> String-ppSource- = maybe "" $ \s -> " (source: " <> s <> ")"+ppSource s+ = " (source: " <> s <> ")" ppEnvVar :: Maybe String
src/Options/Harg/Sources.hs view
@@ -1,7 +1,3 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE UndecidableInstances #-} module Options.Harg.Sources where import Data.Foldable (foldr')@@ -12,7 +8,6 @@ import Options.Harg.Sources.DefaultStr import Options.Harg.Sources.Env import Options.Harg.Sources.Types-import Options.Harg.Types -- | Accumulate all the successful source results and return them,@@ -20,26 +15,30 @@ accumSourceResults :: forall a f. B.TraversableB a- => [a (Compose SourceRunResult f)]- -> ([OptError], [a (Compose Maybe f)])+ => [Either SourceRunError (a (Compose SourceRunResult f))]+ -> ([SourceRunError], [a (Compose Maybe f)]) accumSourceResults = foldr' accumResult ([], []) where accumResult- :: a (Compose SourceRunResult f)- -> ([OptError], [a (Compose Maybe f)])- -> ([OptError], [a (Compose Maybe f)])+ :: Either SourceRunError (a (Compose SourceRunResult f))+ -> ([SourceRunError], [a (Compose Maybe f)])+ -> ([SourceRunError], [a (Compose Maybe f)]) accumResult res (e, a)- = case B.btraverse go res of- (e', a') -> (e' <> e, a' : a)+ = case res of+ Left sre -> (sre : e, a)+ Right res' ->+ case B.btraverse go res' of+ (e', a') -> (e' <> e, a' : a) go :: Compose SourceRunResult f x- -> ([OptError], Compose Maybe f x)+ -> ([SourceRunError], Compose Maybe f x) go x = case getCompose x of- OptFoundNoParse e -> ([e], Compose Nothing)- OptParsed a -> ([], Compose (Just a))- _ -> ([], Compose Nothing)+ OptParsed a+ -> ([], Compose (Just a))+ OptNotFound+ -> ([], Compose Nothing) type HiddenSources = DefaultStrSource
src/Options/Harg/Sources/DefaultStr.hs view
@@ -28,30 +28,35 @@ = pure DefaultStrSourceVal instance- B.FunctorB a => RunSource DefaultStrSourceVal a where+ ( B.FunctorB a+ , B.TraversableB a+ ) => RunSource DefaultStrSourceVal a where runSource DefaultStrSourceVal opt = [runDefaultStrSource opt] +-- TODO: this looks very similar to EnvSource, perhaps unify runDefaultStrSource :: forall a f. ( B.FunctorB a+ , B.TraversableB a , Applicative f ) => a (Compose Opt f)- -> a (Compose SourceRunResult f)+ -> Either SourceRunError (a (Compose SourceRunResult f)) runDefaultStrSource- = B.bmap go+ = B.btraverse go where- go :: Compose Opt f x -> Compose SourceRunResult f x+ go+ :: Compose Opt f x+ -> Either SourceRunError (Compose SourceRunResult f x) go (Compose opt@Opt{..})- = case _optDefaultStr of- Nothing- -> Compose $ pure <$> OptNotFound- Just str- -> Compose $ tryParse str+ = maybe toNotFound parse _optDefaultStr where- tryParse- = either- (OptFoundNoParse . toOptError opt (Just "DefaultStrSource"))- OptParsed- . _optReader+ parse+ = either toErr toParsed . _optReader+ toNotFound+ = Right $ Compose $ pure <$> OptNotFound+ toErr+ = Left . sourceRunError opt "DefaultStrSource"+ toParsed+ = Right . Compose . OptParsed
src/Options/Harg/Sources/Env.hs view
@@ -28,7 +28,9 @@ = pure (EnvSourceVal _hcEnv) instance- B.FunctorB a => RunSource EnvSourceVal a where+ ( B.FunctorB a+ , B.TraversableB a+ ) => RunSource EnvSourceVal a where runSource (EnvSourceVal e) opt = [runEnvVarSource e opt] @@ -43,24 +45,26 @@ runEnvVarSource :: forall a f. ( B.FunctorB a+ , B.TraversableB a , Applicative f ) => Environment -> a (Compose Opt f)- -> a (Compose SourceRunResult f)+ -> Either SourceRunError (a (Compose SourceRunResult f)) runEnvVarSource env- = B.bmap go+ = B.btraverse go where- go :: Compose Opt f x -> Compose SourceRunResult f x+ go+ :: Compose Opt f x+ -> Either SourceRunError (Compose SourceRunResult f x) go (Compose opt@Opt{..})- = case _optEnvVar of- Nothing- -> Compose $ pure <$> OptNotFound- Just envVar- -> Compose $ maybe OptNotFound tryParse (lookupEnv env envVar)+ = maybe toNotFound (parse . lookupEnv env) _optEnvVar where- tryParse- = either- (OptFoundNoParse . toOptError opt (Just "EnvSource"))- OptParsed- . _optReader+ parse+ = maybe toNotFound (either toErr toParsed . _optReader)+ toNotFound+ = Right $ Compose $ pure <$> OptNotFound+ toErr+ = Left . sourceRunError opt "EnvSource"+ toParsed+ = Right . Compose . OptParsed
src/Options/Harg/Sources/JSON.hs view
@@ -7,6 +7,7 @@ import Data.Functor.Compose (Compose (..)) import Data.Functor.Identity (Identity(..)) import GHC.Generics (Generic)+import qualified Data.ByteString.Lazy as LBS import qualified Data.Aeson as JSON import qualified Data.Barbie as B@@ -23,19 +24,13 @@ -- the user has specified @defaultVal NoConfigFile@. It holds the contents of -- the JSON file as a 'JSON.Value'. data JSONSourceVal- = JSONSourceVal JSON.Value+ = JSONSourceVal LBS.ByteString | JSONSourceNotRequired instance GetSource JSONSource Identity where type SourceVal JSONSource = JSONSourceVal- getSource _ctx (JSONSource (Identity (ConfigFile path))) = do- contents <- readFileLBS path- case JSON.eitherDecode contents of- Right json- -> pure $ JSONSourceVal json- Left err- -> printErrAndExit- $ "Error decoding " <> path <> " to JSON: " <> err+ getSource _ctx (JSONSource (Identity (ConfigFile path)))+ = JSONSourceVal <$> readFileLBS path getSource _ctx (JSONSource (Identity NoConfigFile)) = pure JSONSourceNotRequired @@ -54,20 +49,22 @@ , JSON.FromJSON (a Maybe) , Applicative f )- => JSON.Value+ => LBS.ByteString -> a (Compose Opt f)- -> a (Compose SourceRunResult f)-runJSONSource json opt+ -> Either SourceRunError (a (Compose SourceRunResult f))+runJSONSource json _opt = case res of- JSON.Success v -> B.bmap toSuccess v- JSON.Error _e -> B.bmap toFailure opt+ Right v -> Right $ B.bmap toSuccess v+ Left exc -> Left $ toError exc where- res :: JSON.Result (a Maybe)+ res :: Either String (a Maybe) res- = JSON.fromJSON json+ = JSON.eitherDecode json+ toSuccess :: Maybe x -> Compose SourceRunResult f x toSuccess mx = Compose $ pure <$> maybe OptNotFound OptParsed mx- toFailure :: Compose Opt f x -> Compose SourceRunResult f x- toFailure _- = Compose $ pure <$> OptNotFound++ toError :: String -> SourceRunError+ toError+ = SourceRunError Nothing "JSONSource"
src/Options/Harg/Sources/Types.hs view
@@ -5,7 +5,7 @@ import Data.Functor.Compose (Compose (..)) import Data.Kind (Type)-import Data.String (IsString(..))+import Data.String (IsString(..)) import Options.Harg.Het.Prod import Options.Harg.Types@@ -14,13 +14,28 @@ -- | Holds errors that occur when running a source. data SourceRunResult a = OptNotFound -- ^ Source doesn't include the option- | OptFoundNoParse OptError -- ^ Option cannot be parsed from source | OptParsed a -- ^ Successful parsing deriving Functor --- | This class enables a type that describes a source to fetch--- the source contents, potentially producing side effects (e.g. reading--- a file).+data SourceRunError+ = SourceRunError+ { _sreOpt :: Maybe SomeOpt+ , _sreSourceName :: String+ , _sreError :: String+ }++-- | Create a 'SourceRunError' by existentially wrapping an option in 'SomeOpt'.+sourceRunError+ :: forall a.+ Opt a+ -> String+ -> String+ -> SourceRunError+sourceRunError+ = SourceRunError . Just . SomeOpt++-- | This class enables a type that describes a source to fetch the source+-- contents, potentially producing side effects (e.g. reading a file). class GetSource (c :: (Type -> Type) -> Type) (f :: (Type -> Type)) where@@ -45,7 +60,7 @@ :: Applicative f => s -> a (Compose Opt f)- -> [a (Compose SourceRunResult f)]+ -> [Either SourceRunError (a (Compose SourceRunResult f))] instance ( RunSource l a
src/Options/Harg/Sources/YAML.hs view
@@ -53,22 +53,20 @@ ) => BS.ByteString -> a (Compose Opt f)- -> a (Compose SourceRunResult f)-runYAMLSource yaml opt+ -> Either SourceRunError (a (Compose SourceRunResult f))+runYAMLSource yaml _opt = case res of- Right v -> B.bmap toSuccess v- Left exc -> B.bmap (toFailure exc) opt+ Right v -> Right $ B.bmap toSuccess v+ Left exc -> Left $ toError exc where res :: Either YAML.ParseException (a Maybe) res = YAML.decodeEither' yaml+ toSuccess :: Maybe x -> Compose SourceRunResult f x toSuccess mx = Compose $ pure <$> maybe OptNotFound OptParsed mx- toFailure- :: YAML.ParseException- -> Compose Opt f x- -> Compose SourceRunResult f x- toFailure exc (Compose o)- = Compose- $ OptFoundNoParse (toOptError o (Just "YAMLSource") (displayException exc))++ toError :: YAML.ParseException -> SourceRunError+ toError exc+ = SourceRunError Nothing "YAMLSource" (displayException exc)
src/Options/Harg/Subcommands.hs view
@@ -60,7 +60,7 @@ ) => s -> AssocListF ts xs (Compose Opt f)- -> ([OptError], [Optparse.Mod Optparse.CommandFields (VariantF xs f)])+ -> ([SourceRunError], [Optparse.Mod Optparse.CommandFields (VariantF xs f)]) instance ExplSubcommands Z ts xs '[] => Subcommands ts xs where mapSubcommand = explMapSubcommand @Z @ts @xs @'[] SZ@@ -79,7 +79,7 @@ => SNat n -> s -> AssocListF ts xs (Compose Opt f)- -> ([OptError], [Optparse.Mod Optparse.CommandFields (VariantF (acc ++ xs) f)])+ -> ([SourceRunError], [Optparse.Mod Optparse.CommandFields (VariantF (acc ++ xs) f)]) instance ExplSubcommands n '[] '[] acc where explMapSubcommand _ _ _ = ([], [])
src/Options/Harg/Types.hs view
@@ -72,17 +72,6 @@ , _aReader :: OptReader a } --- | Datatype that holds errors that arise when running the sources.--- The reason why this is the only place where errors occur is that,--- if something goes wrong when running the parser, it will be handled--- by @optparse-applicative@.-data OptError- = OptError- { _oeOpt :: SomeOpt -- ^ Existentially quantified 'Opt'- , _oeSource :: Maybe String -- ^ Source name- , _oeDesc :: String -- ^ Error description- }- -- | Existential wrapper for 'Opt', so that many options can be carried in -- a list. data SomeOpt where@@ -119,11 +108,3 @@ pureCtx :: Environment -> Args -> HargCtx pureCtx = HargCtx--toOptError- :: Opt a- -> Maybe String- -> String- -> OptError-toOptError- = OptError . SomeOpt