packages feed

harg 0.4.1.0 → 0.4.2.0

raw patch · 29 files changed

+1010/−1677 lines, 29 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Options.Harg: toOpt :: IsOpt o attr => o attr a -> Opt a
- Options.Harg.Cmdline: mkParser :: Compose Maybe f a -> Compose Opt f a -> Compose Parser f a
- Options.Harg.Cmdline: toArgumentParser :: Compose Maybe f a -> Compose Opt f a -> Compose Parser f a
- Options.Harg.Cmdline: toFlagParser :: Compose Maybe f a -> Compose Opt f a -> f a -> Compose Parser f a
- Options.Harg.Cmdline: toOptionParser :: Compose Maybe f a -> Compose Opt f a -> Compose Parser f a
- Options.Harg.Construct: class HasDefaultStr o (attr :: [OptAttr])
- Options.Harg.Construct: class HasDefaultVal o (attr :: [OptAttr])
- Options.Harg.Construct: class HasEnvVar o (attr :: [OptAttr])
- Options.Harg.Construct: class HasHelp o (attr :: [OptAttr])
- Options.Harg.Construct: class HasLong o (attr :: [OptAttr])
- Options.Harg.Construct: class HasMetavar o (attr :: [OptAttr])
- Options.Harg.Construct: class HasOptional o (attr :: [OptAttr])
- Options.Harg.Construct: class HasRequired o (attr :: [OptAttr])
- Options.Harg.Construct: class HasShort o (attr :: [OptAttr])
- Options.Harg.Construct: class IsOpt o (attr :: [OptAttr])
- Options.Harg.Construct: toOpt :: IsOpt o attr => o attr a -> Opt a
- Options.Harg.Construct: type DuplicateAttrErr attr = QuoteSym attr :<>: 'Text " is already specified."
- Options.Harg.Construct: type DuplicateAttrMultipleErr attr rest = QuoteSym attr :<>: 'Text (CommaSep rest) :<>: 'Text " has already been specified."
- Options.Harg.Construct: type IncompatibleAttrsErr l r = QuoteSym l :<>: 'Text " and " :<>: QuoteSym r :<>: 'Text " cannot be mixed in an option definition."
- Options.Harg.Construct: type QuoteSym (s :: Symbol) = 'Text "`" :<>: 'Text s :<>: 'Text "`"
- Options.Harg.Construct: type family CommaSep' (s :: Symbol) (xs :: [Symbol]) :: Symbol
- Options.Harg.Het.Proofs: class ProofNil xs
- Options.Harg.Het.Proofs: proofNil :: ProofNil xs => (xs ++ '[]) :~~: xs
- Options.Harg.Het.Variant: class FromVariantF xs result f
- Options.Harg.Het.Variant: class IgnoreF (args :: [(Type -> Type) -> Type]) result f
- Options.Harg.Het.Variant: ignoreF :: IgnoreF args result f => result -> FoldSignatureF args result f
- Options.Harg.Het.Variant: type family FoldSignatureF (xs :: [(Type -> Type) -> Type]) r f
- Options.Harg.Nested: type family Nest (a :: Type) (f :: Type -> Type) = (res :: Type) | res -> a
- Options.Harg.Operations: execParser :: HargCtx -> Parser a -> IO a
- Options.Harg.Operations: execParserPure :: [String] -> Parser a -> ParserResult a
- Options.Harg.Operations: failParser :: Parser a -> [SourceRunError] -> IO a
- Options.Harg.Pretty: ppEnvVar :: Maybe String -> String
- Options.Harg.Pretty: ppSource :: String -> String
- Options.Harg.Sources.DefaultStr: DefaultStrSourceVal :: DefaultStrSourceVal
- Options.Harg.Sources.DefaultStr: data DefaultStrSourceVal
- 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: lookupEnv :: Environment -> String -> Maybe String
- 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: JSONSourceNotRequired :: JSONSourceVal
- Options.Harg.Sources.JSON: JSONSourceVal :: ByteString -> JSONSourceVal
- Options.Harg.Sources.JSON: data JSONSourceVal
- 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.NoSource: NoSource :: NoSource
- Options.Harg.Sources.YAML: YAMLSourceNotRequired :: YAMLSourceVal
- Options.Harg.Sources.YAML: YAMLSourceVal :: ByteString -> YAMLSourceVal
- Options.Harg.Sources.YAML: data YAMLSourceVal
- 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: class ExplSubcommands (n :: Nat) (ts :: [Symbol]) (xs :: [(Type -> Type) -> Type]) (acc :: [(Type -> Type) -> Type])
- 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.Types: type Args = [String]
- Options.Harg.Util: printErrAndExit :: forall a. String -> IO a
- Options.Harg.Util: readFileWith :: (FilePath -> IO a) -> FilePath -> IO a
+ Options.Harg: execCommandsWithCtx :: forall c ts xs. (TraversableB (VariantF xs), TraversableB c, ProductB c, Subcommands ts xs, GetSource c Identity, All (RunSource (SourceVal (c :* HiddenSources))) xs, All (RunSource ()) xs, MapAssocList xs) => HargCtx -> c Opt -> AssocListF ts xs Opt -> IO (VariantF xs Identity)
+ Options.Harg: execCommandsWithCtxDef :: forall ts xs. (TraversableB (VariantF xs), Subcommands ts xs, All (RunSource (SourceVal (DefaultSources :* HiddenSources))) xs, All (RunSource ()) xs, MapAssocList xs) => HargCtx -> AssocListF ts xs Opt -> IO (VariantF xs Identity)
+ Options.Harg: execOptWithCtx :: forall c a. (TraversableB a, ProductB a, TraversableB c, ProductB c, GetSource c Identity, RunSource (SourceVal c) a) => HargCtx -> c Opt -> a Opt -> IO (a Identity)
+ Options.Harg: execOptWithCtxDef :: forall a. (TraversableB a, ProductB a) => HargCtx -> a Opt -> IO (a Identity)
- Options.Harg.Het.All: type family AllF (c :: k -> Constraint) (xs :: [(Type -> Type) -> Type]) (f :: Type -> Type) :: Constraint
+ Options.Harg.Het.All: type family All (c :: k -> Constraint) (xs :: [k]) :: Constraint

Files

CHANGELOG.md view
@@ -1,5 +1,10 @@ # Changelog for harg +## 0.4.2.0 [2020.01.27]++- Add explicit export and import lists+- Some documentation fixes+ ## 0.4.1.0 [2019.12.22]  - Parsers now stop immediately if a source error is encountered
− README.lhs
@@ -1,812 +0,0 @@-# `harg`--[![Build Status](https://travis-ci.org/alexpeits/harg.svg?branch=master)](https://travis-ci.org/alexpeits/harg)-[![Hackage](https://img.shields.io/hackage/v/harg.svg)](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.--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:--``` haskell-{-# LANGUAGE DataKinds          #-}-{-# LANGUAGE DeriveAnyClass     #-}-{-# LANGUAGE DeriveGeneric      #-}-{-# LANGUAGE FlexibleInstances  #-}-{-# LANGUAGE GADTs              #-}-{-# LANGUAGE KindSignatures     #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeApplications   #-}-{-# LANGUAGE TypeOperators      #-}--import           Data.Functor.Identity (Identity (..))-import           Data.Kind             (Type)-import           GHC.Generics          (Generic)--import qualified Data.Barbie           as B-import           Data.Aeson            (FromJSON)-import           Data.Generic.HKD      (HKD, build, construct)--import           Options.Harg--main :: IO ()-main = putStrLn "this is a literate haskell file"-```--## One flat (non-nested) datatype--The easiest scenario is when the target configuration type is one single record-with no levels of nesting:--``` haskell-data FlatConfig-  = FlatConfig-      { _fcDbHost :: String-      , _fcDbPort :: Int-      , _fcDir    :: String-      , _fcLog    :: Bool  -- whether to log or not-      }-  deriving (Show, Generic)-```--(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.--``` haskell-dbHostOpt :: Opt String-dbHostOpt-  = option strParser-      ( long "host"-      . short 'h'-      . metavar "DB_HOST"-      . help "The database host"-      )--dbPortOpt :: Opt Int-dbPortOpt-  = option readParser-      ( long "port"-      . help "The database port"-      . envVar "DB_PORT"-      . defaultVal 5432-      )--dirOpt :: Opt String-dirOpt-  = argument strParser-      ( help "Some directory"-      . defaultVal "/home/user/something"-      )--logOpt :: Opt Bool-logOpt-  = switch-      ( long "log"-      . help "Whether to log or not"-      )-```--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`-instance for `Opt` should be sufficient. E.g. for the newtype:--``` haskell-newtype Port = Port Int-```--we can define the following option:--``` haskell-dbPortOpt' :: Opt Port-dbPortOpt'-  = 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`.--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)-someOpt-  = option readParser-      ( long "something"-      . optional-      )-```--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:--``` haskell-data FlatConfigB f-  = FlatConfigB-      { _fcDbHostB :: f String-      , _fcDbPortB :: f Int-      , _fcDirB    :: f String-      , _fcLogB    :: f Bool-      }-  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`).--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-flatConfigOpt1-  = 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`.--Now to actually run things:--``` haskell-getFlatConfig1 :: IO ()-getFlatConfig1 = do-  FlatConfigB host port dir log <- execOptDef flatConfigOpt1-  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`.--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.--`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 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-```--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:--``` haskell-type FlatConfigOpt2-  =  Single String-  :* Single Int-  :* 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`.--However, the real value when having flat datatypes comes from the ability to use-`higgledy`.--### 3. Using `HKD` from `higgledy`--``` haskell-flatConfigOpt3 :: HKD FlatConfig Opt-flatConfigOpt3-  = build @FlatConfig dbHostOpt dbPortOpt dirOpt logOpt--getFlatConfig3 :: IO ()-getFlatConfig3 = do-  result <- execOptDef flatConfigOpt3-  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.--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--Let's say now that we have these two datatypes:--``` haskell-data DbConfig-  = DbConfig-      { _dcHost :: String-      , _dcPort :: Int-      }-  deriving (Show, Generic)--data ServiceConfig-  = ServiceConfig-      { _scPort :: Int-      , _scLog  :: Bool-      }-  deriving (Show, Generic)-```--And the datatype to be configured is this:--``` haskell-data Config-  = Config-      { _cDb      :: DbConfig-      , _cService :: ServiceConfig-      , _cDir     :: String-      }-  deriving (Show, Generic)-```--And a new option required for the service port:--``` haskell-portOpt :: Opt Int-portOpt-  = option readParser-      ( long "port"-      . help "The service port"-      . defaultVal 8080-      )-```--Again, there are several ways to configure these options.--### 1. Using `barbie` types--Since we now have 3 types, there's a bit more boilerplate to write:--``` haskell-data ConfigB f-  = ConfigB-      { _cDbB      :: DbConfigB f-      , _cServiceB :: ServiceConfigB f-      , _cDirB     :: f String-      }-  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)--data DbConfigB f-  = DbConfigB-      { _dcHostB :: f String-      , _dcPortB :: f Int-      }-  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)--data ServiceConfigB f-  = ServiceConfigB-      { _scPortB :: f Int-      , _scLogB  :: f Bool-      }-  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`:--``` haskell-configOpt1 :: ConfigB Opt-configOpt1-  = ConfigB dbOpt serviceOpt dirOpt--dbOpt :: DbConfigB Opt-dbOpt-  = DbConfigB dbHostOpt dbPortOpt--serviceOpt :: ServiceConfigB Opt-serviceOpt-  = ServiceConfigB portOpt logOpt-```--And to run the parser:--``` haskell-getConfig1 :: IO ()-getConfig1 = do-  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-```--### 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:--``` haskell-data ConfigH f-  = ConfigH-      { _cDbH      :: HKD DbConfig f-      , _cServiceH :: HKD ServiceConfig f-      , _cDirH     :: f String-      }-  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)--configOpt2 :: ConfigH Opt-configOpt2-  = ConfigH dbOptH serviceOptH dirOpt--dbOptH :: HKD DbConfig Opt-dbOptH-  = build @DbConfig dbHostOpt dbPortOpt--serviceOptH :: HKD ServiceConfig Opt-serviceOptH-  = build @ServiceConfig portOpt logOpt-```--And to run the parser:--``` haskell-getConfig2 :: IO ()-getConfig2 = do-  ConfigH db service dir <- execOptDef configOpt2-  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:--``` haskell-type ConfigP-  =  HKD DbConfig-  :* HKD ServiceConfig-  :* Single String--configOpt3 :: ConfigP Opt-configOpt3-  = dbOptH :* serviceOptH :* single dirOpt--getConfig3 :: IO ()-getConfig3 = do-  db :* service :* dir <- execOptDef configOpt3-  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:--```-Nested    <-> HKD-nested    <-> build-getNested <-> construct-```--This means that the previous code block might as well be:--``` haskell-type ConfigP'-  =  Nested DbConfig-  :* Nested ServiceConfig-  :* Single String--configOpt4 :: ConfigP' Opt-configOpt4-  = dbOptN :* serviceOptN :* single dirOpt-  where-    dbOptN-      = nested @DbConfig dbHostOpt dbPortOpt-    serviceOptN-      = nested @ServiceConfig portOpt logOpt--getConfig4 :: IO ()-getConfig4 = do-  db :* service :* dir <- execOptDef configOpt4-  print $ runIdentity $-    Config-    <$> getNested db-    <*> getNested service-    <*> getSingle dir-```--Pretty cool.--## Subcommands--`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:--``` haskell-data Variant (xs :: [Type]) where-  Here :: x -> Variant (x ': xs)-  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).--``` haskell-x :: Variant '[Int, Bool, Char]-x = There (Here True)--run :: Variant '[Int, Bool, Char] -> Maybe Bool-run (Here _)                 = Nothing-run (There (Here b))         = Just b-run (There (There (Here _))) = Nothing---- > run x--- Just True-```--`harg` defines another kind of variant called `VariantF`:--``` haskell ignore-data VariantF (xs :: [(Type -> Type) -> Type]) (f :: Type -> Type) where-```--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`:--``` haskell-data TestConfig-  = TestConfig-      { _tcFoo :: String-      , _tcBar :: Int-      }-  deriving Show--fooOpt :: Opt String-fooOpt-  = option strParser-      ( short 'f'-      . help "Something foo"-      . defaultVal "this is the default foo"-      )--barOpt :: Opt Int-barOpt-  = option readParser-      ( short 'b'-      . help "Something bar"-      . defaultVal 42-      )--type TestConfigP-  = Single String :* Single Int--testConfigOpt :: TestConfigP Opt-testConfigOpt-  = single fooOpt :* single barOpt-```--The subcommand type looks like this:--``` haskell-type SubcommandConfig-  =  "app" :-> ConfigP'-  :+ "test" :-> TestConfigP-```--The `+` here stands for sum. The associated option type is:--``` haskell-subcommandOpt :: SubcommandConfig Opt-subcommandOpt-  = configOpt4 :+ testConfigOpt :+ ANil-```--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:--``` haskell-getSubcommand :: IO ()-getSubcommand = do-  result <- execCommandsDef subcommandOpt-  case result of-    HereF (db :* service :* dir)-      -> print $ runIdentity $-           Config-           <$> getNested db-           <*> getNested service-           <*> getSingle dir-    ThereF (HereF (foo :* bar))-      -> print $ runIdentity $-           TestConfig-           <$> getSingle foo-           <*> getSingle bar-```--Or use `fromVariantF`, which is similar to the `either` function:--``` haskell-getSubcommand' :: IO ()-getSubcommand' = do-  result <- execCommandsDef subcommandOpt-  fromVariantF result-    (\(db :* service :* dir)-       -> print $ runIdentity $-            Config-            <$> getNested db-            <*> getNested service-            <*> getSingle dir-    )-    (\(foo :* bar)-       -> print $ runIdentity $-            TestConfig-            <$> getSingle foo-            <*> getSingle bar-    )-```--The type of `fromVariantF` can be thought of as being:--``` haskell ignore-fromVariantF-  :: VariantF '[a, b, c, ...] f-  -> (a f -> r)-  -> (b f -> r)-  -> (c f -> r)-  -> ...-  -> r-```--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).--The sources currently supported are environment variables, json and yaml files.--### Configuring using a json file--First of all, let's use `FlatConfig` from the first example:--``` haskell ignore-data FlatConfig-  = FlatConfig-      { _fcDbHost :: String-      , _fcDbPort :: Int-      , _fcDir    :: String-      , _fcLog    :: Bool  -- whether to log or not-      }-  deriving (Show, Generic)--dbHostOpt :: Opt String-dbHostOpt-  = option strParser-      ( long "host"-      . short 'h'-      . metavar "DB_HOST"-      . help "The database host"-      )--dbPortOpt :: Opt Int-dbPortOpt-  = option readParser-      ( long "port"-      . help "The database port"-      . envVar "DB_PORT"-      . defaultVal 5432-      )--dirOpt :: Opt String-dirOpt-  = argument strParser-      ( help "Some directory"-      . defaultVal "/home/user/something"-      )--logOpt :: Opt Bool-logOpt-  = switch-      ( long "log"-      . help "Whether to log or not"-      )--flatConfigOpt3 :: HKD FlatConfig Opt-flatConfigOpt3-  = 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:--``` 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:--``` 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:--``` haskell-sourceOpt :: (EnvSource :* JSONSource) Opt-sourceOpt-  = EnvSource :* JSONSource jsonOpt-  where-    jsonOpt :: Opt ConfigFile-    jsonOpt-      = option strParser-          ( long "json"-          . short 'j'-          . help "JSON config filepath"-          )-```--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-  = ConfigFile FilePath-  | NoConfigFile-```--This has the advantage that, if the user wants to specify an optional-configuration file, they can simply say:--``` haskell-jsonOpt :: Opt ConfigFile-jsonOpt-  = option strParser-      ( long "json"-      . defaultVal NoConfigFile-      )-```--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.--# Roadmap--- Better errors using `optparse-applicative`'s internals-- Allow user to pass `optparse-applicative` preferences-- Write tests--# Credits--- [jcpetruzza](https://github.com/jcpetruzza)-- [i-am-tom](https://github.com/i-am-tom)-- [jmackie](https://github.com/jmackie)
README.md view
@@ -1,4 +1,4 @@-# `harg`+# harg :nut_and_bolt:  [![Build Status](https://travis-ci.org/alexpeits/harg.svg?branch=master)](https://travis-ci.org/alexpeits/harg) [![Hackage](https://img.shields.io/hackage/v/harg.svg)](https://hackage.haskell.org/package/harg)@@ -9,803 +9,23 @@ 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.--# 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:--``` haskell-{-# LANGUAGE DataKinds          #-}-{-# LANGUAGE DeriveAnyClass     #-}-{-# LANGUAGE DeriveGeneric      #-}-{-# LANGUAGE FlexibleInstances  #-}-{-# LANGUAGE GADTs              #-}-{-# LANGUAGE KindSignatures     #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE TypeApplications   #-}-{-# LANGUAGE TypeOperators      #-}--import           Data.Functor.Identity (Identity (..))-import           Data.Kind             (Type)-import           GHC.Generics          (Generic)--import qualified Data.Barbie           as B-import           Data.Aeson            (FromJSON)-import           Data.Generic.HKD      (HKD, build, construct)--import           Options.Harg--main :: IO ()-main = putStrLn "this is a literate haskell file"-```--## One flat (non-nested) datatype--The easiest scenario is when the target configuration type is one single record-with no levels of nesting:--``` haskell-data FlatConfig-  = FlatConfig-      { _fcDbHost :: String-      , _fcDbPort :: Int-      , _fcDir    :: String-      , _fcLog    :: Bool  -- whether to log or not-      }-  deriving (Show, Generic)-```--(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.--``` haskell-dbHostOpt :: Opt String-dbHostOpt-  = option strParser-      ( long "host"-      . short 'h'-      . metavar "DB_HOST"-      . help "The database host"-      )--dbPortOpt :: Opt Int-dbPortOpt-  = option readParser-      ( long "port"-      . help "The database port"-      . envVar "DB_PORT"-      . defaultVal 5432-      )--dirOpt :: Opt String-dirOpt-  = argument strParser-      ( help "Some directory"-      . defaultVal "/home/user/something"-      )--logOpt :: Opt Bool-logOpt-  = switch-      ( long "log"-      . help "Whether to log or not"-      )-```--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`-instance for `Opt` should be sufficient. E.g. for the newtype:--``` haskell-newtype Port = Port Int-```--we can define the following option:--``` haskell-dbPortOpt' :: Opt Port-dbPortOpt'-  = 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`.--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)-someOpt-  = option readParser-      ( long "something"-      . optional-      )-```--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:--``` haskell-data FlatConfigB f-  = FlatConfigB-      { _fcDbHostB :: f String-      , _fcDbPortB :: f Int-      , _fcDirB    :: f String-      , _fcLogB    :: f Bool-      }-  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`).--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-flatConfigOpt1-  = 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`.--Now to actually run things:--``` haskell-getFlatConfig1 :: IO ()-getFlatConfig1 = do-  FlatConfigB host port dir log <- execOptDef flatConfigOpt1-  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`.--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.--`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 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-```--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:--``` haskell-type FlatConfigOpt2-  =  Single String-  :* Single Int-  :* 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`.--However, the real value when having flat datatypes comes from the ability to use-`higgledy`.--### 3. Using `HKD` from `higgledy`--``` haskell-flatConfigOpt3 :: HKD FlatConfig Opt-flatConfigOpt3-  = build @FlatConfig dbHostOpt dbPortOpt dirOpt logOpt--getFlatConfig3 :: IO ()-getFlatConfig3 = do-  result <- execOptDef flatConfigOpt3-  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.--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--Let's say now that we have these two datatypes:--``` haskell-data DbConfig-  = DbConfig-      { _dcHost :: String-      , _dcPort :: Int-      }-  deriving (Show, Generic)--data ServiceConfig-  = ServiceConfig-      { _scPort :: Int-      , _scLog  :: Bool-      }-  deriving (Show, Generic)-```--And the datatype to be configured is this:--``` haskell-data Config-  = Config-      { _cDb      :: DbConfig-      , _cService :: ServiceConfig-      , _cDir     :: String-      }-  deriving (Show, Generic)-```--And a new option required for the service port:--``` haskell-portOpt :: Opt Int-portOpt-  = option readParser-      ( long "port"-      . help "The service port"-      . defaultVal 8080-      )-```--Again, there are several ways to configure these options.--### 1. Using `barbie` types--Since we now have 3 types, there's a bit more boilerplate to write:--``` haskell-data ConfigB f-  = ConfigB-      { _cDbB      :: DbConfigB f-      , _cServiceB :: ServiceConfigB f-      , _cDirB     :: f String-      }-  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)--data DbConfigB f-  = DbConfigB-      { _dcHostB :: f String-      , _dcPortB :: f Int-      }-  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)--data ServiceConfigB f-  = ServiceConfigB-      { _scPortB :: f Int-      , _scLogB  :: f Bool-      }-  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`:--``` haskell-configOpt1 :: ConfigB Opt-configOpt1-  = ConfigB dbOpt serviceOpt dirOpt--dbOpt :: DbConfigB Opt-dbOpt-  = DbConfigB dbHostOpt dbPortOpt--serviceOpt :: ServiceConfigB Opt-serviceOpt-  = ServiceConfigB portOpt logOpt-```--And to run the parser:--``` haskell-getConfig1 :: IO ()-getConfig1 = do-  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-```--### 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:--``` haskell-data ConfigH f-  = ConfigH-      { _cDbH      :: HKD DbConfig f-      , _cServiceH :: HKD ServiceConfig f-      , _cDirH     :: f String-      }-  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)--configOpt2 :: ConfigH Opt-configOpt2-  = ConfigH dbOptH serviceOptH dirOpt--dbOptH :: HKD DbConfig Opt-dbOptH-  = build @DbConfig dbHostOpt dbPortOpt--serviceOptH :: HKD ServiceConfig Opt-serviceOptH-  = build @ServiceConfig portOpt logOpt-```--And to run the parser:--``` haskell-getConfig2 :: IO ()-getConfig2 = do-  ConfigH db service dir <- execOptDef configOpt2-  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:--``` haskell-type ConfigP-  =  HKD DbConfig-  :* HKD ServiceConfig-  :* Single String--configOpt3 :: ConfigP Opt-configOpt3-  = dbOptH :* serviceOptH :* single dirOpt--getConfig3 :: IO ()-getConfig3 = do-  db :* service :* dir <- execOptDef configOpt3-  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:--```-Nested    <-> HKD-nested    <-> build-getNested <-> construct-```--This means that the previous code block might as well be:--``` haskell-type ConfigP'-  =  Nested DbConfig-  :* Nested ServiceConfig-  :* Single String--configOpt4 :: ConfigP' Opt-configOpt4-  = dbOptN :* serviceOptN :* single dirOpt-  where-    dbOptN-      = nested @DbConfig dbHostOpt dbPortOpt-    serviceOptN-      = nested @ServiceConfig portOpt logOpt--getConfig4 :: IO ()-getConfig4 = do-  db :* service :* dir <- execOptDef configOpt4-  print $ runIdentity $-    Config-    <$> getNested db-    <*> getNested service-    <*> getSingle dir-```--Pretty cool.--## Subcommands--`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:--``` haskell-data Variant (xs :: [Type]) where-  Here :: x -> Variant (x ': xs)-  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).--``` haskell-x :: Variant '[Int, Bool, Char]-x = There (Here True)--run :: Variant '[Int, Bool, Char] -> Maybe Bool-run (Here _)                 = Nothing-run (There (Here b))         = Just b-run (There (There (Here _))) = Nothing---- > run x--- Just True-```--`harg` defines another kind of variant called `VariantF`:--``` haskell ignore-data VariantF (xs :: [(Type -> Type) -> Type]) (f :: Type -> Type) where-```--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`:--``` haskell-data TestConfig-  = TestConfig-      { _tcFoo :: String-      , _tcBar :: Int-      }-  deriving Show--fooOpt :: Opt String-fooOpt-  = option strParser-      ( short 'f'-      . help "Something foo"-      . defaultVal "this is the default foo"-      )--barOpt :: Opt Int-barOpt-  = option readParser-      ( short 'b'-      . help "Something bar"-      . defaultVal 42-      )--type TestConfigP-  = Single String :* Single Int--testConfigOpt :: TestConfigP Opt-testConfigOpt-  = single fooOpt :* single barOpt-```--The subcommand type looks like this:--``` haskell-type SubcommandConfig-  =  "app" :-> ConfigP'-  :+ "test" :-> TestConfigP-```--The `+` here stands for sum. The associated option type is:--``` haskell-subcommandOpt :: SubcommandConfig Opt-subcommandOpt-  = configOpt4 :+ testConfigOpt :+ ANil-```--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:--``` haskell-getSubcommand :: IO ()-getSubcommand = do-  result <- execCommandsDef subcommandOpt-  case result of-    HereF (db :* service :* dir)-      -> print $ runIdentity $-           Config-           <$> getNested db-           <*> getNested service-           <*> getSingle dir-    ThereF (HereF (foo :* bar))-      -> print $ runIdentity $-           TestConfig-           <$> getSingle foo-           <*> getSingle bar-```--Or use `fromVariantF`, which is similar to the `either` function:--``` haskell-getSubcommand' :: IO ()-getSubcommand' = do-  result <- execCommandsDef subcommandOpt-  fromVariantF result-    (\(db :* service :* dir)-       -> print $ runIdentity $-            Config-            <$> getNested db-            <*> getNested service-            <*> getSingle dir-    )-    (\(foo :* bar)-       -> print $ runIdentity $-            TestConfig-            <$> getSingle foo-            <*> getSingle bar-    )-```--The type of `fromVariantF` can be thought of as being:--``` haskell ignore-fromVariantF-  :: VariantF '[a, b, c, ...] f-  -> (a f -> r)-  -> (b f -> r)-  -> (c f -> r)-  -> ...-  -> r-```--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).--The sources currently supported are environment variables, json and yaml files.--### Configuring using a json file--First of all, let's use `FlatConfig` from the first example:--``` haskell ignore-data FlatConfig-  = FlatConfig-      { _fcDbHost :: String-      , _fcDbPort :: Int-      , _fcDir    :: String-      , _fcLog    :: Bool  -- whether to log or not-      }-  deriving (Show, Generic)--dbHostOpt :: Opt String-dbHostOpt-  = option strParser-      ( long "host"-      . short 'h'-      . metavar "DB_HOST"-      . help "The database host"-      )--dbPortOpt :: Opt Int-dbPortOpt-  = option readParser-      ( long "port"-      . help "The database port"-      . envVar "DB_PORT"-      . defaultVal 5432-      )--dirOpt :: Opt String-dirOpt-  = argument strParser-      ( help "Some directory"-      . defaultVal "/home/user/something"-      )--logOpt :: Opt Bool-logOpt-  = switch-      ( long "log"-      . help "Whether to log or not"-      )--flatConfigOpt3 :: HKD FlatConfig Opt-flatConfigOpt3-  = 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:--``` 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:--``` 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:--``` haskell-sourceOpt :: (EnvSource :* JSONSource) Opt-sourceOpt-  = EnvSource :* JSONSource jsonOpt-  where-    jsonOpt :: Opt ConfigFile-    jsonOpt-      = option strParser-          ( long "json"-          . short 'j'-          . help "JSON config filepath"-          )-```--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-  = ConfigFile FilePath-  | NoConfigFile-```--This has the advantage that, if the user wants to specify an optional-configuration file, they can simply say:--``` haskell-jsonOpt :: Opt ConfigFile-jsonOpt-  = option strParser-      ( long "json"-      . defaultVal NoConfigFile-      )-```+[`higgledy`](https://hackage.haskell.org/package/higgledy) also helps reduce+boilerplate code significantly. -Also, because `ConfigFile` has an `IsString` instance, there's no need to say-`long (ConfigFile "json")` (if `OverloadedStrings` is enabled).+## Documentation -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.+To find out more, check out the [docs](https://alexpeits.github.io/harg) or the+package page on [hackage](https://hackage.haskell.org/package/harg). There is+also an [example](https://github.com/alexpeits/harg/blob/master/Example.hs)+module that serves as an extensive demonstration of the library. -# Roadmap+## Roadmap  - Better errors using `optparse-applicative`'s internals - Allow user to pass `optparse-applicative` preferences - Write tests -# Credits+## Credits  - [jcpetruzza](https://github.com/jcpetruzza) - [i-am-tom](https://github.com/i-am-tom)
+ docs/docs.lhs view
@@ -0,0 +1,799 @@+<!-- -*- tab-width: 2; -*- -->+# 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 helps reduce+boilerplate code significantly.++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.++This file is also a literate haskell file which is checked when tests are run,+so all examples should compile and work, and be up to date with the latest+version of the library.++# Usage++Here are some different usage scenarios. Let's first enable some language+extensions and add some imports:++``` haskell+{-# LANGUAGE DataKinds          #-}+{-# LANGUAGE DeriveAnyClass     #-}+{-# LANGUAGE DeriveGeneric      #-}+{-# LANGUAGE FlexibleInstances  #-}+{-# LANGUAGE GADTs              #-}+{-# LANGUAGE KindSignatures     #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications   #-}+{-# LANGUAGE TypeOperators      #-}++import           Data.Functor.Identity (Identity (..))+import           Data.Kind             (Type)+import           GHC.Generics          (Generic)++import qualified Data.Barbie           as B+import           Data.Aeson            (FromJSON)+import           Data.Generic.HKD      (HKD, build, construct)++import           Options.Harg++main :: IO ()+main = putStrLn "this is a literate haskell file"+```++## One flat (non-nested) datatype++The easiest scenario is when the target configuration type is one single record+with no levels of nesting:++``` haskell+data FlatConfig+  = FlatConfig+      { _fcDbHost :: String+      , _fcDbPort :: Int+      , _fcDir    :: String+      , _fcLog    :: Bool  -- whether to log or not+      }+  deriving (Show, Generic)+```++(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.++``` haskell+dbHostOpt :: Opt String+dbHostOpt+  = option strParser+      ( long "host"+      . short 'h'+      . metavar "DB_HOST"+      . help "The database host"+      )++dbPortOpt :: Opt Int+dbPortOpt+  = option readParser+      ( long "port"+      . help "The database port"+      . envVar "DB_PORT"+      . defaultVal 5432+      )++dirOpt :: Opt String+dirOpt+  = argument strParser+      ( help "Some directory"+      . defaultVal "/home/user/something"+      )++logOpt :: Opt Bool+logOpt+  = switch+      ( long "log"+      . help "Whether to log or not"+      )+```++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`+instance for `Opt` should be sufficient. E.g. for the newtype:++``` haskell+newtype Port = Port Int+```++we can define the following option:++``` haskell+dbPortOpt' :: Opt Port+dbPortOpt'+  = 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`.++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`:++``` hs+someOpt :: Opt (Maybe Int)+someOpt+  = option readParser+      ( long "something"+      . optional+      )+```++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:++``` haskell+data FlatConfigB f+  = FlatConfigB+      { _fcDbHostB :: f String+      , _fcDbPortB :: f Int+      , _fcDirB    :: f String+      , _fcLogB    :: f Bool+      }+  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`).++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+flatConfigOpt1+  = 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`.++Now to actually run things:++``` haskell+getFlatConfig1 :: IO ()+getFlatConfig1 = do+  FlatConfigB host port dir log <- execOptDef flatConfigOpt1+  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`.++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.++`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 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+```++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:++``` haskell+type FlatConfigOpt2+  =  Single String+  :* Single Int+  :* 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`.++However, the real value when having flat datatypes comes from the ability to use+`higgledy`.++### 3. Using `HKD` from `higgledy`++``` haskell+flatConfigOpt3 :: HKD FlatConfig Opt+flatConfigOpt3+  = build @FlatConfig dbHostOpt dbPortOpt dirOpt logOpt++getFlatConfig3 :: IO ()+getFlatConfig3 = do+  result <- execOptDef flatConfigOpt3+  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.++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++Let's say now that we have these two datatypes:++``` haskell+data DbConfig+  = DbConfig+      { _dcHost :: String+      , _dcPort :: Int+      }+  deriving (Show, Generic)++data ServiceConfig+  = ServiceConfig+      { _scPort :: Int+      , _scLog  :: Bool+      }+  deriving (Show, Generic)+```++And the datatype to be configured is this:++``` haskell+data Config+  = Config+      { _cDb      :: DbConfig+      , _cService :: ServiceConfig+      , _cDir     :: String+      }+  deriving (Show, Generic)+```++And a new option required for the service port:++``` haskell+portOpt :: Opt Int+portOpt+  = option readParser+      ( long "port"+      . help "The service port"+      . defaultVal 8080+      )+```++Again, there are several ways to configure these options.++### 1. Using `barbie` types++Since we now have 3 types, there's a bit more boilerplate to write:++``` haskell+data ConfigB f+  = ConfigB+      { _cDbB      :: DbConfigB f+      , _cServiceB :: ServiceConfigB f+      , _cDirB     :: f String+      }+  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)++data DbConfigB f+  = DbConfigB+      { _dcHostB :: f String+      , _dcPortB :: f Int+      }+  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)++data ServiceConfigB f+  = ServiceConfigB+      { _scPortB :: f Int+      , _scLogB  :: f Bool+      }+  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`:++``` haskell+configOpt1 :: ConfigB Opt+configOpt1+  = ConfigB dbOpt serviceOpt dirOpt++dbOpt :: DbConfigB Opt+dbOpt+  = DbConfigB dbHostOpt dbPortOpt++serviceOpt :: ServiceConfigB Opt+serviceOpt+  = ServiceConfigB portOpt logOpt+```++And to run the parser:++``` haskell+getConfig1 :: IO ()+getConfig1 = do+  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+```++### 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:++``` haskell+data ConfigH f+  = ConfigH+      { _cDbH      :: HKD DbConfig f+      , _cServiceH :: HKD ServiceConfig f+      , _cDirH     :: f String+      }+  deriving (Generic, B.FunctorB, B.TraversableB, B.ProductB)++configOpt2 :: ConfigH Opt+configOpt2+  = ConfigH dbOptH serviceOptH dirOpt++dbOptH :: HKD DbConfig Opt+dbOptH+  = build @DbConfig dbHostOpt dbPortOpt++serviceOptH :: HKD ServiceConfig Opt+serviceOptH+  = build @ServiceConfig portOpt logOpt+```++And to run the parser:++``` haskell+getConfig2 :: IO ()+getConfig2 = do+  ConfigH db service dir <- execOptDef configOpt2+  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:++``` haskell+type ConfigP+  =  HKD DbConfig+  :* HKD ServiceConfig+  :* Single String++configOpt3 :: ConfigP Opt+configOpt3+  = dbOptH :* serviceOptH :* single dirOpt++getConfig3 :: IO ()+getConfig3 = do+  db :* service :* dir <- execOptDef configOpt3+  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:++```+Nested    <-> HKD+nested    <-> build+getNested <-> construct+```++This means that the previous code block might as well be:++``` haskell+type ConfigP'+  =  Nested DbConfig+  :* Nested ServiceConfig+  :* Single String++configOpt4 :: ConfigP' Opt+configOpt4+  = dbOptN :* serviceOptN :* single dirOpt+  where+    dbOptN+      = nested @DbConfig dbHostOpt dbPortOpt+    serviceOptN+      = nested @ServiceConfig portOpt logOpt++getConfig4 :: IO ()+getConfig4 = do+  db :* service :* dir <- execOptDef configOpt4+  print $ runIdentity $+    Config+    <$> getNested db+    <*> getNested service+    <*> getSingle dir+```++Pretty cool.++## Subcommands++`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:++``` haskell+data Variant (xs :: [Type]) where+  Here :: x -> Variant (x ': xs)+  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).++``` haskell+x :: Variant '[Int, Bool, Char]+x = There (Here True)++run :: Variant '[Int, Bool, Char] -> Maybe Bool+run (Here _)                 = Nothing+run (There (Here b))         = Just b+run (There (There (Here _))) = Nothing++-- > run x+-- Just True+```++`harg` defines another kind of variant called `VariantF`:++``` hs+data VariantF (xs :: [(Type -> Type) -> Type]) (f :: Type -> Type) where+```++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`:++``` haskell+data TestConfig+  = TestConfig+      { _tcFoo :: String+      , _tcBar :: Int+      }+  deriving Show++fooOpt :: Opt String+fooOpt+  = option strParser+      ( short 'f'+      . help "Something foo"+      . defaultVal "this is the default foo"+      )++barOpt :: Opt Int+barOpt+  = option readParser+      ( short 'b'+      . help "Something bar"+      . defaultVal 42+      )++type TestConfigP+  = Single String :* Single Int++testConfigOpt :: TestConfigP Opt+testConfigOpt+  = single fooOpt :* single barOpt+```++The subcommand type looks like this:++``` haskell+type SubcommandConfig+  =  "app" :-> ConfigP'+  :+ "test" :-> TestConfigP+```++The `+` here stands for sum. The associated option type is:++``` haskell+subcommandOpt :: SubcommandConfig Opt+subcommandOpt+  = configOpt4 :+ testConfigOpt :+ ANil+```++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:++``` haskell+getSubcommand :: IO ()+getSubcommand = do+  result <- execCommandsDef subcommandOpt+  case result of+    HereF (db :* service :* dir)+      -> print $ runIdentity $+           Config+           <$> getNested db+           <*> getNested service+           <*> getSingle dir+    ThereF (HereF (foo :* bar))+      -> print $ runIdentity $+           TestConfig+           <$> getSingle foo+           <*> getSingle bar+```++Or use `fromVariantF`, which is similar to the `either` function:++``` haskell+getSubcommand' :: IO ()+getSubcommand' = do+  result <- execCommandsDef subcommandOpt+  fromVariantF result+    (\(db :* service :* dir)+       -> print $ runIdentity $+            Config+            <$> getNested db+            <*> getNested service+            <*> getSingle dir+    )+    (\(foo :* bar)+       -> print $ runIdentity $+            TestConfig+            <$> getSingle foo+            <*> getSingle bar+    )+```++The type of `fromVariantF` can be thought of as being:++``` hs+fromVariantF+  :: VariantF '[a, b, c, ...] f+  -> (a f -> r)+  -> (b f -> r)+  -> (c f -> r)+  -> ...+  -> r+```++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).++The sources currently supported are environment variables, json and yaml files.++### Configuring using a json file++First of all, let's use `FlatConfig` from the first example:++``` hs+data FlatConfig+  = FlatConfig+      { _fcDbHost :: String+      , _fcDbPort :: Int+      , _fcDir    :: String+      , _fcLog    :: Bool  -- whether to log or not+      }+  deriving (Show, Generic)++dbHostOpt :: Opt String+dbHostOpt+  = option strParser+      ( long "host"+      . short 'h'+      . metavar "DB_HOST"+      . help "The database host"+      )++dbPortOpt :: Opt Int+dbPortOpt+  = option readParser+      ( long "port"+      . help "The database port"+      . envVar "DB_PORT"+      . defaultVal 5432+      )++dirOpt :: Opt String+dirOpt+  = argument strParser+      ( help "Some directory"+      . defaultVal "/home/user/something"+      )++logOpt :: Opt Bool+logOpt+  = switch+      ( long "log"+      . help "Whether to log or not"+      )++flatConfigOpt3 :: HKD FlatConfig Opt+flatConfigOpt3+  = 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:++``` 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:++``` hs+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:++``` haskell+sourceOpt :: (EnvSource :* JSONSource) Opt+sourceOpt+  = EnvSource :* JSONSource jsonOpt+  where+    jsonOpt :: Opt ConfigFile+    jsonOpt+      = option strParser+          ( long "json"+          . short 'j'+          . help "JSON config filepath"+          )+```++Here, the type of the option for the JSON source is `ConfigFile`. This type is a+wrapper around `FilePath`, which looks like this:++``` hs+data ConfigFile+  = ConfigFile FilePath+  | NoConfigFile+```++This has the advantage that, if the user wants to specify an optional+configuration file, they can simply say:++``` haskell+jsonOpt :: Opt ConfigFile+jsonOpt+  = option strParser+      ( long "json"+      . defaultVal NoConfigFile+      )+```++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.
harg.cabal view
@@ -1,7 +1,7 @@ cabal-version: 2.2  name:           harg-version:        0.4.1.0+version:        0.4.2.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@@ -86,9 +86,10 @@                   , harg   default-language: Haskell2010 -test-suite readme-test+test-suite docs-test   type:               exitcode-stdio-1.0-  main-is:            README.lhs+  hs-source-dirs:     docs+  main-is:            docs.lhs   ghc-options:        -Wall                       -Wno-unticked-promoted-constructors                       -threaded
src/Options/Harg.hs view
@@ -15,8 +15,8 @@   , fromSingle    , Nested (..)-  , nested   , getNested+  , nested   , fromNested    , AssocListF (..)@@ -37,7 +37,6 @@   , defaultStr   , required   , optional-  , toOpt   , Opt    -- ** Option parsers@@ -50,8 +49,12 @@   -- ** Executing options   , execOpt   , execOptDef+  , execOptWithCtx+  , execOptWithCtxDef   , execCommands   , execCommandsDef+  , execCommandsWithCtx+  , execCommandsWithCtxDef    -- ** Option sources   , EnvSource (..)
src/Options/Harg/Cmdline.hs view
@@ -1,4 +1,6 @@-module Options.Harg.Cmdline where+module Options.Harg.Cmdline+  ( mkOptparseParser+  ) where  import           Control.Applicative  ((<|>)) import           Data.Functor.Compose (Compose (..))@@ -8,7 +10,7 @@ import qualified Data.Barbie          as B import qualified Options.Applicative  as Optparse -import           Options.Harg.Pretty+import           Options.Harg.Pretty  (ppHelp) import           Options.Harg.Types  
src/Options/Harg/Config.hs view
@@ -1,4 +1,7 @@-module Options.Harg.Config where+module Options.Harg.Config+  ( mkConfigParser+  , getConfig+  ) where  import           Data.Functor.Compose       (Compose (..)) import           Data.Kind                  (Type)@@ -6,9 +9,9 @@ import qualified Data.Barbie                as B import qualified Options.Applicative        as Optparse -import           Options.Harg.Cmdline-import           Options.Harg.Sources-import           Options.Harg.Sources.Env+import           Options.Harg.Cmdline       (mkOptparseParser)+import           Options.Harg.Sources       (accumSourceResults)+import           Options.Harg.Sources.Env   (EnvSourceVal(..)) import           Options.Harg.Sources.Types import           Options.Harg.Types 
src/Options/Harg/Construct.hs view
@@ -2,7 +2,27 @@ {-# LANGUAGE TypeFamilies            #-} {-# LANGUAGE UndecidableInstances    #-} {-# LANGUAGE UndecidableSuperClasses #-}-module Options.Harg.Construct where+module Options.Harg.Construct+  ( option+  , flag+  , switch+  , switch'+  , argument+  , long+  , short+  , help+  , metavar+  , envVar+  , defaultVal+  , defaultStr+  , required+  , optional+  , parseWith+  , readParser+  , strParser+  , boolParser+  , manyParser+  ) where  import Data.Char          (toLower) import Data.Kind          (Constraint)
src/Options/Harg/Het/All.hs view
@@ -1,6 +1,8 @@+{-# LANGUAGE PolyKinds    #-} {-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE PolyKinds #-}-module Options.Harg.Het.All where+module Options.Harg.Het.All+  ( All+  ) where  import Data.Kind (Type, Constraint) 
src/Options/Harg/Het/HList.hs view
@@ -2,7 +2,13 @@ {-# LANGUAGE RankNTypes           #-} {-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Het.HList where+module Options.Harg.Het.HList+  ( MapAssocList (..)+  , AssocListF (..)+  , (:+)+  , pattern (:+)+  , (:->)+  ) where  import           Data.Kind    (Type) import           GHC.TypeLits (ErrorMessage(..), TypeError, Symbol)
src/Options/Harg/Het/Nat.hs view
@@ -1,4 +1,7 @@-module Options.Harg.Het.Nat where+module Options.Harg.Het.Nat+  ( Nat (..)+  , SNat (..)+  ) where  -- | Type-level Peano natural number. data Nat
src/Options/Harg/Het/Prod.hs view
@@ -5,7 +5,10 @@ {-# LANGUAGE PolyKinds                  #-} {-# LANGUAGE StandaloneDeriving         #-} {-# LANGUAGE UndecidableInstances       #-}-module Options.Harg.Het.Prod where+module Options.Harg.Het.Prod+  ( (:*) (..)+  , Tagged (..)+  ) where  import           Data.Functor.Identity (Identity) import           Data.Kind             (Type)
src/Options/Harg/Het/Proofs.hs view
@@ -6,7 +6,11 @@ {-# LANGUAGE RankNTypes           #-} {-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Het.Proofs where+module Options.Harg.Het.Proofs+  ( type (++)+  , Proof (..)+  , hgcastWith+  ) where  import Data.Kind          (Type) import Data.Type.Equality
src/Options/Harg/Het/Variant.hs view
@@ -3,7 +3,16 @@ {-# LANGUAGE PatternSynonyms        #-} {-# LANGUAGE TypeFamilies           #-} {-# LANGUAGE UndecidableInstances   #-}-module Options.Harg.Het.Variant where+module Options.Harg.Het.Variant+  ( VariantF (..)+  , fromVariantF+  , InjectPosF (..)+  , pattern In1+  , pattern In2+  , pattern In3+  , pattern In4+  , pattern In5+  ) where  import           Data.Kind            (Type) 
src/Options/Harg/Nested.hs view
@@ -7,7 +7,12 @@ {-# LANGUAGE UndecidableInstances       #-}  {-# OPTIONS_GHC -fno-warn-orphans #-}-module Options.Harg.Nested where+module Options.Harg.Nested+  ( Nested (..)+  , nested+  , getNested+  , fromNested+  ) where  import           Data.Coerce           (Coercible, coerce) import           Data.Functor.Identity (Identity(..))
src/Options/Harg/Operations.hs view
@@ -4,28 +4,37 @@ {-# LANGUAGE RankNTypes           #-} {-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Operations where+module Options.Harg.Operations+  ( execOpt+  , execOptDef+  , execOptWithCtx+  , execOptWithCtxDef+  , execCommands+  , execCommandsDef+  , execCommandsWithCtx+  , execCommandsWithCtxDef+  ) where -import           Data.Functor.Identity           (Identity(..))+import           Data.Functor.Identity      (Identity(..)) -import qualified Data.Barbie                     as B-import qualified Options.Applicative             as Optparse+import qualified Data.Barbie                as B+import qualified Options.Applicative        as Optparse -import           Options.Harg.Cmdline            (mkOptparseParser)-import           Options.Harg.Config             (mkConfigParser, getConfig)-import           Options.Harg.Het.All            (All)-import           Options.Harg.Het.HList          (AssocListF, MapAssocList(..))-import           Options.Harg.Het.Prod           ((:*)(..))-import           Options.Harg.Het.Variant        (VariantF)-import           Options.Harg.Pretty             (ppSourceRunErrors)-import           Options.Harg.Sources            ( accumSourceResults-                                                 , DefaultSources, defaultSources-                                                 , HiddenSources, hiddenSources-                                                 )-import           Options.Harg.Sources.Types      (GetSource(..), RunSource(..), SourceRunError)-import           Options.Harg.Subcommands        (Subcommands(..))-import           Options.Harg.Types              (HargCtx(..), getCtx, Opt)-import           Options.Harg.Util               (toDummyOpts, allToDummyOpts, compose)+import           Options.Harg.Cmdline       (mkOptparseParser)+import           Options.Harg.Config        (mkConfigParser, getConfig)+import           Options.Harg.Het.All       (All)+import           Options.Harg.Het.HList     (AssocListF, MapAssocList(..))+import           Options.Harg.Het.Prod      ((:*)(..))+import           Options.Harg.Het.Variant   (VariantF)+import           Options.Harg.Pretty        (ppSourceRunErrors)+import           Options.Harg.Sources       ( accumSourceResults+                                            , DefaultSources, defaultSources+                                            , HiddenSources, hiddenSources+                                            )+import           Options.Harg.Sources.Types (GetSource(..), RunSource(..), SourceRunError)+import           Options.Harg.Subcommands   (Subcommands(..))+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, -- passing the context explicitly.
src/Options/Harg/Pretty.hs view
@@ -1,4 +1,7 @@-module Options.Harg.Pretty where+module Options.Harg.Pretty+  ( ppHelp+  , ppSourceRunErrors+  ) where  import Control.Applicative        ((<|>)) import Data.List                  (intercalate)
src/Options/Harg/Single.hs view
@@ -3,7 +3,11 @@ {-# LANGUAGE PolyKinds                  #-} {-# LANGUAGE StandaloneDeriving         #-} {-# LANGUAGE UndecidableInstances       #-}-module Options.Harg.Single where+module Options.Harg.Single+  ( Single (..)+  , single+  , fromSingle+  ) where  import           Data.Functor.Identity (Identity(..)) import qualified Data.Functor.Product  as P@@ -11,7 +15,6 @@ import           GHC.Generics          (Generic)  import qualified Data.Aeson            as JSON- import qualified Data.Barbie           as B  
src/Options/Harg/Sources.hs view
@@ -1,12 +1,18 @@-module Options.Harg.Sources where+module Options.Harg.Sources+  ( accumSourceResults+  , HiddenSources+  , hiddenSources+  , DefaultSources+  , defaultSources+  ) where -import           Data.Foldable              (foldr')-import           Data.Functor.Compose       (Compose (..))+import           Data.Foldable                   (foldr')+import           Data.Functor.Compose            (Compose(..)) -import qualified Data.Barbie                as B+import qualified Data.Barbie                     as B -import           Options.Harg.Sources.DefaultStr-import           Options.Harg.Sources.Env+import           Options.Harg.Sources.DefaultStr (DefaultStrSource(..))+import           Options.Harg.Sources.Env        (EnvSource(..)) import           Options.Harg.Sources.Types  @@ -44,12 +50,10 @@  -- | Sources hidden from user that are always enabled hiddenSources :: HiddenSources f-hiddenSources-  = DefaultStrSource+hiddenSources = DefaultStrSource  type DefaultSources = EnvSource  -- | Default sources, equivalent to 'EnvSource' defaultSources :: DefaultSources f-defaultSources-  = EnvSource+defaultSources = EnvSource
src/Options/Harg/Sources/DefaultStr.hs view
@@ -1,7 +1,9 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric  #-} {-# LANGUAGE TypeFamilies   #-}-module Options.Harg.Sources.DefaultStr where+module Options.Harg.Sources.DefaultStr+  ( DefaultStrSource (..)+  ) where  import           Data.Functor.Compose       (Compose (..)) import           Data.Kind                  (Type)
src/Options/Harg/Sources/Env.hs view
@@ -1,7 +1,10 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric  #-} {-# LANGUAGE TypeFamilies   #-}-module Options.Harg.Sources.Env where+module Options.Harg.Sources.Env+  ( EnvSource (..)+  , EnvSourceVal (..)+  ) where  import           Data.Functor.Compose       (Compose (..)) import           Data.Kind                  (Type)
src/Options/Harg/Sources/JSON.hs view
@@ -2,19 +2,21 @@ {-# LANGUAGE DeriveGeneric        #-} {-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Sources.JSON where+module Options.Harg.Sources.JSON+  ( JSONSource (..)+  ) where +import qualified Data.ByteString.Lazy       as LBS 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  import           Options.Harg.Sources.Types import           Options.Harg.Types-import           Options.Harg.Util+import           Options.Harg.Util          (readFileLBS)  -- | Source that enables a parser to read options from a JSON file. newtype JSONSource f = JSONSource (f ConfigFile)
src/Options/Harg/Sources/NoSource.hs view
@@ -1,7 +1,10 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric  #-} {-# LANGUAGE TypeFamilies   #-}-module Options.Harg.Sources.NoSource where+module Options.Harg.Sources.NoSource+  ( NoSource+  , noSources+  ) where  import           Data.Kind                  (Type) import           GHC.Generics               (Generic)@@ -21,5 +24,4 @@  -- | Shorthand for writing 'NoSource'. noSources :: NoSource f-noSources-  = NoSource+noSources = NoSource
src/Options/Harg/Sources/Types.hs view
@@ -1,13 +1,20 @@ {-# LANGUAGE DeriveFunctor        #-} {-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Sources.Types where+module Options.Harg.Sources.Types+  ( GetSource (..)+  , RunSource (..)+  , ConfigFile (..)+  , SourceRunResult (..)+  , SourceRunError (..)+  , sourceRunError+  ) where -import Data.Functor.Compose  (Compose (..))+import Data.Functor.Compose  (Compose(..)) import Data.Kind             (Type) import Data.String           (IsString(..)) -import Options.Harg.Het.Prod+import Options.Harg.Het.Prod ((:*)(..)) import Options.Harg.Types  
src/Options/Harg/Sources/YAML.hs view
@@ -2,7 +2,9 @@ {-# LANGUAGE DeriveGeneric        #-} {-# LANGUAGE TypeFamilies         #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Sources.YAML where+module Options.Harg.Sources.YAML+  ( YAMLSource (..)+  ) where  import           Control.Exception          (displayException) import qualified Data.ByteString            as BS@@ -15,7 +17,7 @@  import           Options.Harg.Sources.Types import           Options.Harg.Types-import           Options.Harg.Util+import           Options.Harg.Util          (readFileBS)   -- | Source that enables a parser to read options from a YAML file.
src/Options/Harg/Subcommands.hs view
@@ -1,6 +1,8 @@ {-# LANGUAGE AllowAmbiguousTypes  #-} {-# LANGUAGE UndecidableInstances #-}-module Options.Harg.Subcommands where+module Options.Harg.Subcommands+  ( Subcommands (..)+  ) where  import           Data.Functor.Compose       (Compose (..)) import           Data.Kind                  (Type)@@ -10,13 +12,13 @@ import qualified Data.Barbie                as B import qualified Options.Applicative        as Optparse -import           Options.Harg.Cmdline-import           Options.Harg.Het.All-import           Options.Harg.Het.HList+import           Options.Harg.Cmdline       (mkOptparseParser)+import           Options.Harg.Het.All       (All)+import           Options.Harg.Het.HList     (AssocListF(..)) import           Options.Harg.Het.Nat-import           Options.Harg.Het.Proofs-import           Options.Harg.Het.Variant-import           Options.Harg.Sources+import           Options.Harg.Het.Proofs    (type (++), Proof(..), hgcastWith)+import           Options.Harg.Het.Variant   (VariantF, InjectPosF(..))+import           Options.Harg.Sources       (accumSourceResults) import           Options.Harg.Sources.Types import           Options.Harg.Types 
src/Options/Harg/Types.hs view
@@ -1,5 +1,20 @@ {-# LANGUAGE DeriveFunctor #-}-module Options.Harg.Types where+module Options.Harg.Types+  ( Opt (..)+  , OptionOpt (..)+  , FlagOpt (..)+  , ArgumentOpt (..)+  , OptAttr (..)+  , OptType (..)+  , SomeOpt (..)+  , OptReader+  , HargCtx (..)+  , Environment+  , getCtx+  , ctxFromArgs+  , ctxFromEnv+  , pureCtx+  ) where  import System.Environment (getArgs, getEnvironment) 
src/Options/Harg/Util.hs view
@@ -1,5 +1,11 @@ {-# LANGUAGE RankNTypes #-}-module Options.Harg.Util where+module Options.Harg.Util+  ( toDummyOpts+  , allToDummyOpts+  , compose+  , readFileBS+  , readFileLBS+  ) where  import qualified Control.Exception          as Exc import qualified Data.ByteString            as BS@@ -11,7 +17,7 @@  import qualified Data.Barbie                as B -import           Options.Harg.Het.HList+import           Options.Harg.Het.HList     (AssocListF, MapAssocList(..)) import           Options.Harg.Types