packages feed

cfg 0.0.1.0 → 0.0.2.0

raw patch · 33 files changed

+2549/−1245 lines, 33 filesdep +doctestdep +freedep ~pretty-simplePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: doctest, free

Dependency ranges changed: pretty-simple

API changes (from Hackage documentation)

- Cfg: instance Cfg.Parser.NestedParser Cfg.EnvRedisConfig
- Cfg: instance Cfg.Parser.NestedParser Cfg.EnvWarpConfig
- Cfg: instance Cfg.Parser.NestedParser Cfg.Environment
- Cfg: instance Cfg.Parser.NestedParser Cfg.RedisConfig
- Cfg: instance Cfg.Parser.NestedParser Cfg.WarpConfig
- Cfg: instance Cfg.Parser.RootParser Cfg.AppConfig
- Cfg: instance Cfg.Parser.RootParser Cfg.EnvAppConfig
- Cfg: instance Cfg.Source.NestedConfig Cfg.EnvRedisConfig
- Cfg: instance Cfg.Source.NestedConfig Cfg.EnvWarpConfig
- Cfg: instance Cfg.Source.NestedConfig Cfg.Environment
- Cfg: instance Cfg.Source.NestedConfig Cfg.RedisConfig
- Cfg: instance Cfg.Source.NestedConfig Cfg.WarpConfig
- Cfg: instance Cfg.Source.RootConfig Cfg.AppConfig
- Cfg: instance Cfg.Source.RootConfig Cfg.EnvAppConfig
- Cfg: instance GHC.Generics.Generic Cfg.EnvAppConfig
- Cfg: instance GHC.Generics.Generic Cfg.EnvRedisConfig
- Cfg: instance GHC.Generics.Generic Cfg.EnvWarpConfig
- Cfg: instance GHC.Show.Show Cfg.EnvAppConfig
- Cfg: instance GHC.Show.Show Cfg.EnvRedisConfig
- Cfg: instance GHC.Show.Show Cfg.EnvWarpConfig
- Cfg.Deriving.ConfigRoot: ConfigRoot :: a -> ConfigRoot a
- Cfg.Deriving.ConfigRoot: ConfigRootOpts :: a -> ConfigRootOpts t t' a
- Cfg.Deriving.ConfigRoot: [unConfigRootOpts] :: ConfigRootOpts t t' a -> a
- Cfg.Deriving.ConfigRoot: [unConfigRoot] :: ConfigRoot a -> a
- Cfg.Deriving.ConfigRoot: class (LabelModifier t, LabelModifier t') => GetConfigRootOptions t t'
- Cfg.Deriving.ConfigRoot: getConfigRootOptions :: GetConfigRootOptions t t' => RootOptions
- Cfg.Deriving.ConfigRoot: instance (Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.RootConfig a, GHC.Generics.Generic a, Cfg.Parser.ConfigParser.GRootConfigParser (GHC.Generics.Rep a)) => Cfg.Parser.RootParser (Cfg.Deriving.ConfigRoot.ConfigRoot a)
- Cfg.Deriving.ConfigRoot: instance (Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.RootConfig a, GHC.Generics.Generic a, Cfg.Source.RootConfig.GConfigTree (GHC.Generics.Rep a)) => Cfg.Source.RootConfig (Cfg.Deriving.ConfigRoot.ConfigRoot a)
- Cfg.Deriving.ConfigRoot: instance (Cfg.Deriving.LabelModifier.LabelModifier t, Cfg.Deriving.LabelModifier.LabelModifier t') => Cfg.Deriving.ConfigRoot.GetConfigRootOptions t t'
- Cfg.Deriving.ConfigRoot: instance (Cfg.Deriving.LabelModifier.LabelModifier t, Cfg.Deriving.LabelModifier.LabelModifier t', Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.RootConfig a, GHC.Generics.Generic a, Cfg.Parser.ConfigParser.GRootConfigParser (GHC.Generics.Rep a)) => Cfg.Parser.RootParser (Cfg.Deriving.ConfigRoot.ConfigRootOpts t t' a)
- Cfg.Deriving.ConfigRoot: instance (Cfg.Deriving.LabelModifier.LabelModifier t, Cfg.Deriving.LabelModifier.LabelModifier t', Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.RootConfig a, GHC.Generics.Generic a, Cfg.Source.RootConfig.GConfigTree (GHC.Generics.Rep a)) => Cfg.Source.RootConfig (Cfg.Deriving.ConfigRoot.ConfigRootOpts t t' a)
- Cfg.Deriving.ConfigRoot: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.ConfigRoot.ConfigRoot a)
- Cfg.Deriving.ConfigRoot: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.ConfigRoot.ConfigRootOpts t t' a)
- Cfg.Deriving.ConfigRoot: newtype ConfigRoot a
- Cfg.Deriving.ConfigRoot: newtype ConfigRootOpts t t' a
- Cfg.Deriving.ConfigValue: ConfigValue :: a -> ConfigValue a
- Cfg.Deriving.ConfigValue: [unConfigValue] :: ConfigValue a -> a
- Cfg.Deriving.ConfigValue: instance (GHC.Generics.Generic a, Cfg.Parser.ValueParser.GValueParser (GHC.Generics.Rep a)) => Cfg.Parser.NestedParser (Cfg.Deriving.ConfigValue.ConfigValue a)
- Cfg.Deriving.ConfigValue: instance (GHC.Generics.Generic a, Cfg.Parser.ValueParser.GValueParser (GHC.Generics.Rep a)) => Cfg.Parser.ValueParser (Cfg.Deriving.ConfigValue.ConfigValue a)
- Cfg.Deriving.ConfigValue: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.ConfigValue.ConfigValue a)
- Cfg.Deriving.ConfigValue: newtype ConfigValue a
- Cfg.Deriving.LabelModifier: class LabelModifier t
- Cfg.Deriving.LabelModifier: data Ident
- Cfg.Deriving.LabelModifier: data ToLower
- Cfg.Deriving.LabelModifier: data ToUpper
- Cfg.Deriving.LabelModifier: getLabelModifier :: LabelModifier t => Text -> Text
- Cfg.Deriving.LabelModifier: instance Cfg.Deriving.LabelModifier.LabelModifier Cfg.Deriving.LabelModifier.Ident
- Cfg.Deriving.LabelModifier: instance Cfg.Deriving.LabelModifier.LabelModifier Cfg.Deriving.LabelModifier.ToLower
- Cfg.Deriving.LabelModifier: instance Cfg.Deriving.LabelModifier.LabelModifier Cfg.Deriving.LabelModifier.ToUpper
- Cfg.Deriving.SubConfig: SubConfig :: a -> SubConfig a
- Cfg.Deriving.SubConfig: SubConfigOpts :: a -> SubConfigOpts t a
- Cfg.Deriving.SubConfig: [unSubConfigOpts] :: SubConfigOpts t a -> a
- Cfg.Deriving.SubConfig: [unSubConfig] :: SubConfig a -> a
- Cfg.Deriving.SubConfig: class GetConfigOptions t
- Cfg.Deriving.SubConfig: getConfigOptions :: GetConfigOptions t => ConfigOptions
- Cfg.Deriving.SubConfig: instance (Cfg.Deriving.SubConfig.GetConfigOptions t, GHC.Generics.Generic a, Cfg.Parser.ConfigParser.GNestedParser (GHC.Generics.Rep a)) => Cfg.Parser.NestedParser (Cfg.Deriving.SubConfig.SubConfigOpts t a)
- Cfg.Deriving.SubConfig: instance (Cfg.Deriving.SubConfig.GetConfigOptions t, GHC.Generics.Generic a, Cfg.Source.NestedConfig.GConfigForest (GHC.Generics.Rep a)) => Cfg.Source.NestedConfig (Cfg.Deriving.SubConfig.SubConfigOpts t a)
- Cfg.Deriving.SubConfig: instance (GHC.Generics.Generic a, Cfg.Parser.ConfigParser.GNestedParser (GHC.Generics.Rep a)) => Cfg.Parser.NestedParser (Cfg.Deriving.SubConfig.SubConfig a)
- Cfg.Deriving.SubConfig: instance (GHC.Generics.Generic a, Cfg.Source.NestedConfig.GConfigForest (GHC.Generics.Rep a)) => Cfg.Source.NestedConfig (Cfg.Deriving.SubConfig.SubConfig a)
- Cfg.Deriving.SubConfig: instance Cfg.Deriving.LabelModifier.LabelModifier t => Cfg.Deriving.SubConfig.GetConfigOptions t
- Cfg.Deriving.SubConfig: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.SubConfig.SubConfig a)
- Cfg.Deriving.SubConfig: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.SubConfig.SubConfigOpts t a)
- Cfg.Deriving.SubConfig: newtype SubConfig a
- Cfg.Deriving.SubConfig: newtype SubConfigOpts t a
- Cfg.Options: ConfigOptions :: (Text -> Text) -> ConfigOptions
- Cfg.Options: [configOptionsLabelModifier] :: ConfigOptions -> Text -> Text
- Cfg.Options: [rootOptionsFieldOptions] :: RootOptions -> ConfigOptions
- Cfg.Options: [rootOptionsLabelModifier] :: RootOptions -> Text -> Text
- Cfg.Parser: MismatchedKeyAndField :: Text -> (Text, Text) -> ConfigParseError
- Cfg.Parser: MismatchedRootKey :: Text -> Text -> ConfigParseError
- Cfg.Parser: MissingKeys :: [Text] -> ConfigParseError
- Cfg.Parser: MissingValue :: Text -> ConfigParseError
- Cfg.Parser: UnexpectedKeys :: Text -> [Tree Text] -> ConfigParseError
- Cfg.Parser: UnmatchedFields :: [Tree Text] -> ConfigParseError
- Cfg.Parser: class NestedParser a
- Cfg.Parser: class RootParser a
- Cfg.Parser: decimal :: Parser Text
- Cfg.Parser: fractional :: Read a => Parser a
- Cfg.Parser: instance (Cfg.Parser.ValueParser a, Cfg.Parser.ValueParser b) => Cfg.Parser.NestedParser (a, b)
- Cfg.Parser: instance Cfg.Parser.NestedParser ()
- Cfg.Parser: instance Cfg.Parser.NestedParser Data.ByteString.Internal.Type.ByteString
- Cfg.Parser: instance Cfg.Parser.NestedParser Data.ByteString.Lazy.Internal.ByteString
- Cfg.Parser: instance Cfg.Parser.NestedParser Data.Text.Internal.Lazy.Text
- Cfg.Parser: instance Cfg.Parser.NestedParser Data.Text.Internal.Text
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Int.Int16
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Int.Int32
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Int.Int64
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Int.Int8
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Num.Integer.Integer
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Types.Bool
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Types.Char
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Types.Double
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Types.Float
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Types.Int
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Types.Word
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Word.Word16
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Word.Word32
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Word.Word64
- Cfg.Parser: instance Cfg.Parser.NestedParser GHC.Word.Word8
- Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.NestedParser (GHC.Base.NonEmpty a)
- Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.NestedParser (GHC.Maybe.Maybe a)
- Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.NestedParser [a]
- Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.ValueParser (GHC.Maybe.Maybe a)
- Cfg.Parser: integral :: Read a => Parser a
- Cfg.Parser: minus :: Parser Text
- Cfg.Parser: number :: Parser Text
- Cfg.Parser: parseNestedConfig :: (NestedParser a, ValueParser a) => Tree Text -> Either ConfigParseError a
- Cfg.Parser: parseRootConfig :: RootParser a => Tree Text -> Either ConfigParseError a
- Cfg.Parser: plus :: Parser Text
- Cfg.Parser: rd :: Read a => Text -> a
- Cfg.Parser: sp :: Parsec Void Text ()
- Cfg.Parser.ConfigParser: class FieldParser a
- Cfg.Parser.ConfigParser: class GFieldParser (f :: Type -> Type)
- Cfg.Parser.ConfigParser: class GNestedParser (f :: Type -> Type)
- Cfg.Parser.ConfigParser: class GRootConfigParser (f :: Type -> Type)
- Cfg.Parser.ConfigParser: defaultParseNestedConfig :: forall a. (Generic a, GNestedParser (Rep a)) => ConfigOptions -> Tree Text -> Either ConfigParseError a
- Cfg.Parser.ConfigParser: defaultParseRootConfig :: forall a. (Generic a, GRootConfigParser (Rep a)) => RootOptions -> Tree Text -> Either ConfigParseError a
- Cfg.Parser.ConfigParser: gParseFields :: GFieldParser f => ConfigOptions -> [Tree Text] -> Either ConfigParseError (f p)
- Cfg.Parser.ConfigParser: gParseNestedConfig :: GNestedParser f => ConfigOptions -> Tree Text -> Either ConfigParseError (f p)
- Cfg.Parser.ConfigParser: gParseRootConfig :: GRootConfigParser f => RootOptions -> Tree Text -> Either ConfigParseError (f p)
- Cfg.Parser.ConfigParser: instance (Cfg.Parser.ConfigParser.GFieldParser a, Cfg.Parser.ConfigParser.GFieldParser b) => Cfg.Parser.ConfigParser.GFieldParser (a GHC.Generics.:*: b)
- Cfg.Parser.ConfigParser: instance (GHC.Generics.Constructor c, Cfg.Parser.ConfigParser.GNestedParser f) => Cfg.Parser.ConfigParser.GNestedParser (GHC.Generics.M1 GHC.Generics.C c f)
- Cfg.Parser.ConfigParser: instance (GHC.Generics.Constructor c, Cfg.Parser.ConfigParser.GRootConfigParser f) => Cfg.Parser.ConfigParser.GRootConfigParser (GHC.Generics.M1 GHC.Generics.C c f)
- Cfg.Parser.ConfigParser: instance (GHC.Generics.Selector s, Cfg.Parser.ConfigParser.GNestedParser f) => Cfg.Parser.ConfigParser.GFieldParser (GHC.Generics.M1 GHC.Generics.S s f)
- Cfg.Parser.ConfigParser: instance (GHC.Generics.Selector s, Cfg.Parser.ConfigParser.GNestedParser f) => Cfg.Parser.ConfigParser.GNestedParser (GHC.Generics.M1 GHC.Generics.S s f)
- Cfg.Parser.ConfigParser: instance (GHC.Generics.Selector s, Cfg.Parser.ConfigParser.GNestedParser f) => Cfg.Parser.ConfigParser.GRootConfigParser (GHC.Generics.M1 GHC.Generics.S s f)
- Cfg.Parser.ConfigParser: instance Cfg.Parser.ConfigParser.GFieldParser (a GHC.Generics.:*: b) => Cfg.Parser.ConfigParser.GNestedParser (a GHC.Generics.:*: b)
- Cfg.Parser.ConfigParser: instance Cfg.Parser.ConfigParser.GFieldParser (a GHC.Generics.:*: b) => Cfg.Parser.ConfigParser.GRootConfigParser (a GHC.Generics.:*: b)
- Cfg.Parser.ConfigParser: instance Cfg.Parser.ConfigParser.GNestedParser f => Cfg.Parser.ConfigParser.GNestedParser (GHC.Generics.M1 GHC.Generics.D c f)
- Cfg.Parser.ConfigParser: instance Cfg.Parser.ConfigParser.GRootConfigParser f => Cfg.Parser.ConfigParser.GRootConfigParser (GHC.Generics.M1 GHC.Generics.D s f)
- Cfg.Parser.ConfigParser: instance Cfg.Parser.NestedParser a => Cfg.Parser.ConfigParser.GNestedParser (GHC.Generics.K1 GHC.Generics.R a)
- Cfg.Parser.ConfigParser: parseFields :: FieldParser a => [Tree Text] -> Either ConfigParseError a
- Cfg.Parser.ValueParser: class GValueParser (f :: Type -> Type)
- Cfg.Parser.ValueParser: defaultValueParser :: forall a. (Generic a, GValueParser (Rep a)) => Parser a
- Cfg.Parser.ValueParser: gParser :: GValueParser f => Parser (f p)
- Cfg.Parser.ValueParser: instance (Cfg.Parser.ValueParser.GValueParser a, Cfg.Parser.ValueParser.GValueParser b) => Cfg.Parser.ValueParser.GValueParser (a GHC.Generics.:*: b)
- Cfg.Parser.ValueParser: instance (Cfg.Parser.ValueParser.GValueParser a, Cfg.Parser.ValueParser.GValueParser b) => Cfg.Parser.ValueParser.GValueParser (a GHC.Generics.:+: b)
- Cfg.Parser.ValueParser: instance Cfg.Parser.ValueParser a => Cfg.Parser.ValueParser.GValueParser (GHC.Generics.K1 GHC.Generics.R a)
- Cfg.Parser.ValueParser: instance Cfg.Parser.ValueParser.GValueParser GHC.Generics.U1
- Cfg.Parser.ValueParser: instance Cfg.Parser.ValueParser.GValueParser GHC.Generics.V1
- Cfg.Parser.ValueParser: instance Cfg.Parser.ValueParser.GValueParser f => Cfg.Parser.ValueParser.GValueParser (GHC.Generics.M1 GHC.Generics.D s f)
- Cfg.Parser.ValueParser: instance Cfg.Parser.ValueParser.GValueParser f => Cfg.Parser.ValueParser.GValueParser (GHC.Generics.M1 GHC.Generics.S s f)
- Cfg.Parser.ValueParser: instance GHC.Generics.Constructor c => Cfg.Parser.ValueParser.GValueParser (GHC.Generics.M1 GHC.Generics.C c GHC.Generics.U1)
- Cfg.Source: class NestedConfig a
- Cfg.Source: class RootConfig a
- Cfg.Source: instance Cfg.Source.NestedConfig ()
- Cfg.Source: instance Cfg.Source.NestedConfig (Cfg.Deriving.ConfigValue.ConfigValue a)
- Cfg.Source: instance Cfg.Source.NestedConfig (Data.Vector.Vector a)
- Cfg.Source: instance Cfg.Source.NestedConfig (GHC.Base.NonEmpty a)
- Cfg.Source: instance Cfg.Source.NestedConfig (GHC.Maybe.Maybe a)
- Cfg.Source: instance Cfg.Source.NestedConfig (a, b)
- Cfg.Source: instance Cfg.Source.NestedConfig Data.ByteString.Internal.Type.ByteString
- Cfg.Source: instance Cfg.Source.NestedConfig Data.ByteString.Lazy.Internal.ByteString
- Cfg.Source: instance Cfg.Source.NestedConfig Data.Text.Internal.Lazy.Text
- Cfg.Source: instance Cfg.Source.NestedConfig Data.Text.Internal.Text
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Int.Int16
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Int.Int32
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Int.Int64
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Int.Int8
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Num.Integer.Integer
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Types.Bool
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Types.Char
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Types.Double
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Types.Float
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Types.Int
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Types.Word
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Word.Word16
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Word.Word32
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Word.Word64
- Cfg.Source: instance Cfg.Source.NestedConfig GHC.Word.Word8
- Cfg.Source: instance Cfg.Source.NestedConfig [a]
- Cfg.Source: toNestedConfig :: NestedConfig a => [Tree Text]
- Cfg.Source: toRootConfig :: RootConfig a => Tree Text
- Cfg.Source.NestedConfig: class GConfigForest (a :: Type -> Type)
- Cfg.Source.NestedConfig: defaultToNestedConfig :: forall a. (Generic a, GConfigForest (Rep a)) => ConfigOptions -> [Tree Text]
- Cfg.Source.NestedConfig: gToForest :: GConfigForest a => ConfigOptions -> [Tree Text]
- Cfg.Source.NestedConfig: instance (Cfg.Source.NestedConfig.GConfigForest a, Cfg.Source.NestedConfig.GConfigForest b) => Cfg.Source.NestedConfig.GConfigForest (a GHC.Generics.:*: b)
- Cfg.Source.NestedConfig: instance (GHC.Generics.Constructor c, Cfg.Source.NestedConfig.GConfigForest f) => Cfg.Source.NestedConfig.GConfigForest (GHC.Generics.M1 GHC.Generics.C c f)
- Cfg.Source.NestedConfig: instance (GHC.Generics.Selector s, Cfg.Source.NestedConfig.GConfigForest f) => Cfg.Source.NestedConfig.GConfigForest (GHC.Generics.M1 GHC.Generics.S s f)
- Cfg.Source.NestedConfig: instance Cfg.Source.NestedConfig a => Cfg.Source.NestedConfig.GConfigForest (GHC.Generics.K1 GHC.Generics.R a)
- Cfg.Source.NestedConfig: instance Cfg.Source.NestedConfig.GConfigForest (a GHC.Generics.:+: b)
- Cfg.Source.NestedConfig: instance Cfg.Source.NestedConfig.GConfigForest GHC.Generics.U1
- Cfg.Source.NestedConfig: instance Cfg.Source.NestedConfig.GConfigForest GHC.Generics.V1
- Cfg.Source.NestedConfig: instance Cfg.Source.NestedConfig.GConfigForest f => Cfg.Source.NestedConfig.GConfigForest (GHC.Generics.M1 GHC.Generics.D s f)
- Cfg.Source.RootConfig: class GConfigTree (a :: Type -> Type)
- Cfg.Source.RootConfig: defaultToRootConfig :: forall a. (Generic a, GConfigTree (Rep a)) => RootOptions -> Tree Text
- Cfg.Source.RootConfig: gToTree :: GConfigTree a => RootOptions -> Tree Text
- Cfg.Source.RootConfig: instance (GHC.Generics.Constructor c, Cfg.Source.NestedConfig.GConfigForest f) => Cfg.Source.RootConfig.GConfigTree (GHC.Generics.M1 GHC.Generics.C c f)
- Cfg.Source.RootConfig: instance Cfg.Source.RootConfig.GConfigTree f => Cfg.Source.RootConfig.GConfigTree (GHC.Generics.M1 GHC.Generics.D s f)
- Cfg.Source.RootConfig: instance Cfg.Source.RootConfig.GConfigTree f => Cfg.Source.RootConfig.GConfigTree (GHC.Generics.M1 GHC.Generics.S s f)
- Tree.Append: appendLeaf :: (a -> b) -> ([a] -> b) -> [a] -> Tree a -> Tree b
- Tree.Append: appendLeafA :: Applicative f => ([a] -> f a) -> [a] -> Tree a -> Tree (f a)
- Tree.Append: mayAppendLeaf :: (a -> b) -> ([a] -> Maybe b) -> [a] -> Tree a -> Tree b
- Tree.Append: mayAppendLeafA :: Applicative f => ([a] -> Maybe (f a)) -> [a] -> Tree a -> Tree (f a)
- Tree.Append: mayAppendLeafA' :: Applicative f => ([a] -> f (Maybe a)) -> [a] -> Tree a -> f (Tree a)
- Tree.Append: travAppendLeafA :: Applicative f => ([a] -> f a) -> [a] -> Tree a -> f (Tree a)
- Tree.Append: travMayAppendLeafA :: (Traversable f, Applicative f) => ([a] -> f (Maybe a)) -> [a] -> Tree a -> f (Tree a)
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.AppConfig
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.AppConfig2
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.AppConfig3
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.AppConfig4
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.Environment
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.RedisConfig
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.RedisConfig2
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.RedisConfig3
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.WarpConfig
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.WarpConfig2
+ Cfg: instance Cfg.Parser.ConfigParser Cfg.WarpConfig3
+ Cfg: instance Cfg.Source.ConfigSource Cfg.AppConfig
+ Cfg: instance Cfg.Source.ConfigSource Cfg.AppConfig2
+ Cfg: instance Cfg.Source.ConfigSource Cfg.AppConfig3
+ Cfg: instance Cfg.Source.ConfigSource Cfg.AppConfig4
+ Cfg: instance Cfg.Source.ConfigSource Cfg.Environment
+ Cfg: instance Cfg.Source.ConfigSource Cfg.RedisConfig
+ Cfg: instance Cfg.Source.ConfigSource Cfg.RedisConfig2
+ Cfg: instance Cfg.Source.ConfigSource Cfg.RedisConfig3
+ Cfg: instance Cfg.Source.ConfigSource Cfg.WarpConfig
+ Cfg: instance Cfg.Source.ConfigSource Cfg.WarpConfig2
+ Cfg: instance Cfg.Source.ConfigSource Cfg.WarpConfig3
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.AppConfig
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.AppConfig2
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.AppConfig3
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.AppConfig4
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.Environment
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.RedisConfig
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.RedisConfig2
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.RedisConfig3
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.WarpConfig
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.WarpConfig2
+ Cfg: instance Cfg.Source.Default.DefaultSource Cfg.WarpConfig3
+ Cfg: instance GHC.Generics.Generic Cfg.AppConfig2
+ Cfg: instance GHC.Generics.Generic Cfg.AppConfig3
+ Cfg: instance GHC.Generics.Generic Cfg.AppConfig4
+ Cfg: instance GHC.Generics.Generic Cfg.RedisConfig2
+ Cfg: instance GHC.Generics.Generic Cfg.RedisConfig3
+ Cfg: instance GHC.Generics.Generic Cfg.WarpConfig2
+ Cfg: instance GHC.Generics.Generic Cfg.WarpConfig3
+ Cfg: instance GHC.Show.Show Cfg.AppConfig2
+ Cfg: instance GHC.Show.Show Cfg.AppConfig3
+ Cfg: instance GHC.Show.Show Cfg.AppConfig4
+ Cfg: instance GHC.Show.Show Cfg.RedisConfig2
+ Cfg: instance GHC.Show.Show Cfg.RedisConfig3
+ Cfg: instance GHC.Show.Show Cfg.WarpConfig2
+ Cfg: instance GHC.Show.Show Cfg.WarpConfig3
+ Cfg.Deriving.Config: Config :: a -> Config a
+ Cfg.Deriving.Config: ConfigOpts :: a -> ConfigOpts fieldModifier a
+ Cfg.Deriving.Config: ConfigRoot :: a -> ConfigRoot rootType fieldModifier a
+ Cfg.Deriving.Config: [unConfigOptions] :: ConfigOpts fieldModifier a -> a
+ Cfg.Deriving.Config: [unConfigRoot] :: ConfigRoot rootType fieldModifier a -> a
+ Cfg.Deriving.Config: [unConfig] :: Config a -> a
+ Cfg.Deriving.Config: class (KeyModifier r, KeyModifier f) => ConfigRootOptions r f
+ Cfg.Deriving.Config: class (KeyModifier t) => GetConfigOptions t
+ Cfg.Deriving.Config: configRootOptions :: ConfigRootOptions r f => RootOptions
+ Cfg.Deriving.Config: getOptions :: GetConfigOptions t => KeyOptions
+ Cfg.Deriving.Config: instance (Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Parser.ConfigParser a, GHC.Generics.Generic a, Cfg.Parser.Config.GConfigParser (GHC.Generics.Rep a)) => Cfg.Parser.ConfigParser (Cfg.Deriving.Config.Config a)
+ Cfg.Deriving.Config: instance (Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.ConfigSource a, Cfg.Source.Default.DefaultSource a, GHC.Generics.Generic a, Cfg.Source.Config.GConfigSource (GHC.Generics.Rep a)) => Cfg.Source.ConfigSource (Cfg.Deriving.Config.Config a)
+ Cfg.Deriving.Config: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.Config.Config a)
+ Cfg.Deriving.Config: instance forall a k1 (k2 :: a) (f :: k1). (Cfg.Deriving.KeyModifier.KeyModifier ('Cfg.Options.ConstructorName k2), Cfg.Deriving.KeyModifier.KeyModifier f) => Cfg.Deriving.Config.ConfigRootOptions ('Cfg.Options.ConstructorName k2) f
+ Cfg.Deriving.Config: instance forall a k1 (k2 :: a) (f :: k1). (Cfg.Deriving.KeyModifier.KeyModifier ('Cfg.Options.TypeName k2), Cfg.Deriving.KeyModifier.KeyModifier f) => Cfg.Deriving.Config.ConfigRootOptions ('Cfg.Options.TypeName k2) f
+ Cfg.Deriving.Config: instance forall k (t :: k) a. (Cfg.Deriving.Config.GetConfigOptions t, Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.ConfigSource a, GHC.Generics.Generic a, Cfg.Parser.Config.GConfigParser (GHC.Generics.Rep a)) => Cfg.Parser.ConfigParser (Cfg.Deriving.Config.ConfigOpts t a)
+ Cfg.Deriving.Config: instance forall k (t :: k) a. (Cfg.Deriving.Config.GetConfigOptions t, Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.ConfigSource a, GHC.Generics.Generic a, Cfg.Source.Default.DefaultSource a, Cfg.Source.Config.GConfigSource (GHC.Generics.Rep a)) => Cfg.Source.ConfigSource (Cfg.Deriving.Config.ConfigOpts t a)
+ Cfg.Deriving.Config: instance forall k (t :: k). Cfg.Deriving.KeyModifier.KeyModifier t => Cfg.Deriving.Config.GetConfigOptions t
+ Cfg.Deriving.Config: instance forall k a (t :: k). GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.Config.ConfigOpts t a)
+ Cfg.Deriving.Config: instance forall k1 k2 (r :: k1) (f :: k2) a. (Cfg.Deriving.Config.ConfigRootOptions r f, Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Parser.ConfigParser a, GHC.Generics.Generic a, Cfg.Parser.Config.GConfigParser (GHC.Generics.Rep a)) => Cfg.Parser.ConfigParser (Cfg.Deriving.Config.ConfigRoot r f a)
+ Cfg.Deriving.Config: instance forall k1 k2 (r :: k1) (f :: k2) a. (Cfg.Deriving.Config.ConfigRootOptions r f, Cfg.Deriving.Assert.AssertTopLevelRecord Cfg.Source.ConfigSource a, GHC.Generics.Generic a, Cfg.Source.Default.DefaultSource a, Cfg.Source.Config.GConfigSource (GHC.Generics.Rep a)) => Cfg.Source.ConfigSource (Cfg.Deriving.Config.ConfigRoot r f a)
+ Cfg.Deriving.Config: instance forall k1 k2 a (r :: k1) (f :: k2). GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.Config.ConfigRoot r f a)
+ Cfg.Deriving.Config: newtype Config a
+ Cfg.Deriving.Config: newtype ConfigOpts fieldModifier a
+ Cfg.Deriving.Config: newtype ConfigRoot rootType fieldModifier a
+ Cfg.Deriving.KeyModifier: camelTo :: Char -> String -> String
+ Cfg.Deriving.KeyModifier: camelToText :: Char -> Text -> Text
+ Cfg.Deriving.KeyModifier: class KeyModifier t
+ Cfg.Deriving.KeyModifier: data CamelTo (separator :: Char)
+ Cfg.Deriving.KeyModifier: data Identity
+ Cfg.Deriving.KeyModifier: data LowerFirst
+ Cfg.Deriving.KeyModifier: data StripPrefix (prefix :: Symbol)
+ Cfg.Deriving.KeyModifier: data StripSuffix (suffix :: Symbol)
+ Cfg.Deriving.KeyModifier: data ToLower
+ Cfg.Deriving.KeyModifier: data ToUpper
+ Cfg.Deriving.KeyModifier: data UpperFirst
+ Cfg.Deriving.KeyModifier: getKeyModifier :: KeyModifier t => Text -> Text
+ Cfg.Deriving.KeyModifier: instance (Cfg.Deriving.KeyModifier.KeyModifier a, Cfg.Deriving.KeyModifier.KeyModifier b) => Cfg.Deriving.KeyModifier.KeyModifier (a, b)
+ Cfg.Deriving.KeyModifier: instance (Cfg.Deriving.KeyModifier.KeyModifier a, Cfg.Deriving.KeyModifier.KeyModifier b, Cfg.Deriving.KeyModifier.KeyModifier c) => Cfg.Deriving.KeyModifier.KeyModifier (a, b, c)
+ Cfg.Deriving.KeyModifier: instance (Cfg.Deriving.KeyModifier.KeyModifier a, Cfg.Deriving.KeyModifier.KeyModifier b, Cfg.Deriving.KeyModifier.KeyModifier c, Cfg.Deriving.KeyModifier.KeyModifier d) => Cfg.Deriving.KeyModifier.KeyModifier (a, b, c, d)
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier '()
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier '[]
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier Cfg.Deriving.KeyModifier.Identity
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier Cfg.Deriving.KeyModifier.LowerFirst
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier Cfg.Deriving.KeyModifier.ToLower
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier Cfg.Deriving.KeyModifier.ToUpper
+ Cfg.Deriving.KeyModifier: instance Cfg.Deriving.KeyModifier.KeyModifier Cfg.Deriving.KeyModifier.UpperFirst
+ Cfg.Deriving.KeyModifier: instance GHC.TypeLits.KnownChar separator => Cfg.Deriving.KeyModifier.KeyModifier (Cfg.Deriving.KeyModifier.CamelTo separator)
+ Cfg.Deriving.KeyModifier: instance GHC.TypeLits.KnownSymbol prefix => Cfg.Deriving.KeyModifier.KeyModifier (Cfg.Deriving.KeyModifier.StripPrefix prefix)
+ Cfg.Deriving.KeyModifier: instance GHC.TypeLits.KnownSymbol prefix => Cfg.Deriving.KeyModifier.KeyModifier (Cfg.Deriving.KeyModifier.StripSuffix prefix)
+ Cfg.Deriving.KeyModifier: instance forall a (k :: a). Cfg.Deriving.KeyModifier.KeyModifier k => Cfg.Deriving.KeyModifier.KeyModifier ('Cfg.Options.ConstructorName k)
+ Cfg.Deriving.KeyModifier: instance forall a (k :: a). Cfg.Deriving.KeyModifier.KeyModifier k => Cfg.Deriving.KeyModifier.KeyModifier ('Cfg.Options.TypeName k)
+ Cfg.Deriving.KeyModifier: instance forall a1 (a2 :: a1) (as :: [a1]). (Cfg.Deriving.KeyModifier.KeyModifier a2, Cfg.Deriving.KeyModifier.KeyModifier as) => Cfg.Deriving.KeyModifier.KeyModifier (a2 : as)
+ Cfg.Deriving.KeyModifier: mapFirst :: (Char -> Char) -> Text -> Maybe Text
+ Cfg.Deriving.KeyModifier: type CamelToKebab = CamelTo '-'
+ Cfg.Deriving.KeyModifier: type CamelToSnake = CamelTo '_'
+ Cfg.Deriving.Value: Value :: a -> Value a
+ Cfg.Deriving.Value: [unValue] :: Value a -> a
+ Cfg.Deriving.Value: instance (GHC.Generics.Generic a, Cfg.Parser.Value.GValueParser (GHC.Generics.Rep a)) => Cfg.Parser.ConfigParser (Cfg.Deriving.Value.Value a)
+ Cfg.Deriving.Value: instance (GHC.Generics.Generic a, Cfg.Parser.Value.GValueParser (GHC.Generics.Rep a)) => Cfg.Parser.ValueParser (Cfg.Deriving.Value.Value a)
+ Cfg.Deriving.Value: instance GHC.Generics.Generic a => GHC.Generics.Generic (Cfg.Deriving.Value.Value a)
+ Cfg.Deriving.Value: newtype Value a
+ Cfg.Options: ConstructorName :: a -> RootKey a
+ Cfg.Options: Key :: KeyOptions -> ConfigOptions
+ Cfg.Options: KeyOptions :: (Text -> Text) -> KeyOptions
+ Cfg.Options: Root :: RootOptions -> ConfigOptions
+ Cfg.Options: TypeName :: a -> RootKey a
+ Cfg.Options: [keyOptionsModifier] :: KeyOptions -> Text -> Text
+ Cfg.Options: [rootOptionsModifier] :: RootOptions -> Text -> Text
+ Cfg.Options: [rootOptionsRootKey] :: RootOptions -> RootKey (Text -> Text)
+ Cfg.Options: data KeyOptions
+ Cfg.Options: data RootKey a
+ Cfg.Options: defaultKeyOptions :: KeyOptions
+ Cfg.Options: keyModifier :: ConfigOptions -> Text -> Text
+ Cfg.Parser: ExpectedKeyFoundValue :: Text -> Text -> ConfigParseError
+ Cfg.Parser: ExpectedValueFoundForest :: KeyTree Text Text -> ConfigParseError
+ Cfg.Parser: MissingKey :: Text -> KeyTree Text Text -> ConfigParseError
+ Cfg.Parser: class ConfigParser a
+ Cfg.Parser: instance (Cfg.Parser.ValueParser a, Cfg.Parser.ValueParser b) => Cfg.Parser.ConfigParser (a, b)
+ Cfg.Parser: instance Cfg.Parser.ConfigParser ()
+ Cfg.Parser: instance Cfg.Parser.ConfigParser Data.ByteString.Internal.Type.ByteString
+ Cfg.Parser: instance Cfg.Parser.ConfigParser Data.ByteString.Lazy.Internal.ByteString
+ Cfg.Parser: instance Cfg.Parser.ConfigParser Data.Text.Internal.Lazy.Text
+ Cfg.Parser: instance Cfg.Parser.ConfigParser Data.Text.Internal.Text
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Int.Int16
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Int.Int32
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Int.Int64
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Int.Int8
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Num.Integer.Integer
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Types.Bool
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Types.Char
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Types.Double
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Types.Float
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Types.Int
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Types.Word
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Word.Word16
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Word.Word32
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Word.Word64
+ Cfg.Parser: instance Cfg.Parser.ConfigParser GHC.Word.Word8
+ Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.ConfigParser (GHC.Base.NonEmpty a)
+ Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.ConfigParser (GHC.Maybe.Maybe a)
+ Cfg.Parser: instance Cfg.Parser.ValueParser a => Cfg.Parser.ConfigParser [a]
+ Cfg.Parser: parseConfig :: (ConfigParser a, ValueParser a) => KeyTree Text Text -> Either ConfigParseError a
+ Cfg.Parser.Config: class GConfigParser (f :: Type -> Type)
+ Cfg.Parser.Config: defaultParseConfig :: forall a. (Generic a, GConfigParser (Rep a)) => ConfigOptions -> KeyTree Text Text -> Either ConfigParseError a
+ Cfg.Parser.Config: gParseConfig :: GConfigParser f => ConfigOptions -> KeyTree Text Text -> Either ConfigParseError (f p)
+ Cfg.Parser.Config: instance (Cfg.Parser.Config.GConfigParser a, Cfg.Parser.Config.GConfigParser b) => Cfg.Parser.Config.GConfigParser (a GHC.Generics.:*: b)
+ Cfg.Parser.Config: instance (Cfg.Parser.Config.GConfigParser f, GHC.Generics.Datatype d) => Cfg.Parser.Config.GConfigParser (GHC.Generics.M1 GHC.Generics.D d f)
+ Cfg.Parser.Config: instance (GHC.Generics.Constructor c, Cfg.Parser.Config.GConfigParser f) => Cfg.Parser.Config.GConfigParser (GHC.Generics.M1 GHC.Generics.C c f)
+ Cfg.Parser.Config: instance (GHC.Generics.Selector s, Cfg.Parser.Config.GConfigParser f) => Cfg.Parser.Config.GConfigParser (GHC.Generics.M1 GHC.Generics.S s f)
+ Cfg.Parser.Config: instance Cfg.Parser.ConfigParser a => Cfg.Parser.Config.GConfigParser (GHC.Generics.K1 GHC.Generics.R a)
+ Cfg.Parser.Value: class GValueParser (f :: Type -> Type)
+ Cfg.Parser.Value: defaultValueParser :: forall a. (Generic a, GValueParser (Rep a)) => Parser a
+ Cfg.Parser.Value: gParser :: GValueParser f => Parser (f p)
+ Cfg.Parser.Value: instance (Cfg.Parser.Value.GValueParser a, Cfg.Parser.Value.GValueParser b) => Cfg.Parser.Value.GValueParser (a GHC.Generics.:*: b)
+ Cfg.Parser.Value: instance (Cfg.Parser.Value.GValueParser a, Cfg.Parser.Value.GValueParser b) => Cfg.Parser.Value.GValueParser (a GHC.Generics.:+: b)
+ Cfg.Parser.Value: instance Cfg.Parser.Value.GValueParser GHC.Generics.U1
+ Cfg.Parser.Value: instance Cfg.Parser.Value.GValueParser GHC.Generics.V1
+ Cfg.Parser.Value: instance Cfg.Parser.Value.GValueParser f => Cfg.Parser.Value.GValueParser (GHC.Generics.M1 GHC.Generics.D s f)
+ Cfg.Parser.Value: instance Cfg.Parser.Value.GValueParser f => Cfg.Parser.Value.GValueParser (GHC.Generics.M1 GHC.Generics.S s f)
+ Cfg.Parser.Value: instance Cfg.Parser.ValueParser a => Cfg.Parser.Value.GValueParser (GHC.Generics.K1 GHC.Generics.R a)
+ Cfg.Parser.Value: instance GHC.Generics.Constructor c => Cfg.Parser.Value.GValueParser (GHC.Generics.M1 GHC.Generics.C c GHC.Generics.U1)
+ Cfg.Source: class ConfigSource a
+ Cfg.Source: configSource :: ConfigSource a => KeyTree Text Text
+ Cfg.Source: instance Cfg.Source.ConfigSource ()
+ Cfg.Source: instance Cfg.Source.ConfigSource (Cfg.Deriving.Value.Value a)
+ Cfg.Source: instance Cfg.Source.ConfigSource (Data.Vector.Vector a)
+ Cfg.Source: instance Cfg.Source.ConfigSource (GHC.Base.NonEmpty a)
+ Cfg.Source: instance Cfg.Source.ConfigSource (GHC.Maybe.Maybe a)
+ Cfg.Source: instance Cfg.Source.ConfigSource (a, b)
+ Cfg.Source: instance Cfg.Source.ConfigSource Data.ByteString.Internal.Type.ByteString
+ Cfg.Source: instance Cfg.Source.ConfigSource Data.ByteString.Lazy.Internal.ByteString
+ Cfg.Source: instance Cfg.Source.ConfigSource Data.Text.Internal.Lazy.Text
+ Cfg.Source: instance Cfg.Source.ConfigSource Data.Text.Internal.Text
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Int.Int16
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Int.Int32
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Int.Int64
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Int.Int8
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Num.Integer.Integer
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Types.Bool
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Types.Char
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Types.Double
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Types.Float
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Types.Int
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Types.Word
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Word.Word16
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Word.Word32
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Word.Word64
+ Cfg.Source: instance Cfg.Source.ConfigSource GHC.Word.Word8
+ Cfg.Source: instance Cfg.Source.ConfigSource [a]
+ Cfg.Source.Config: class GConfigSource (a :: Type -> Type)
+ Cfg.Source.Config: defaultConfigSource :: forall a. (DefaultSource a, Generic a, GConfigSource (Rep a)) => ConfigOptions -> KeyTree Text Text
+ Cfg.Source.Config: gConfigSource :: GConfigSource a => (Text -> Maybe Text) -> ConfigOptions -> KeyTree Text Text
+ Cfg.Source.Config: instance (Cfg.Source.Config.GConfigSource a, Cfg.Source.Config.GConfigSource b) => Cfg.Source.Config.GConfigSource (a GHC.Generics.:*: b)
+ Cfg.Source.Config: instance (GHC.Generics.Constructor c, Cfg.Source.Config.GConfigSource f) => Cfg.Source.Config.GConfigSource (GHC.Generics.M1 GHC.Generics.C c f)
+ Cfg.Source.Config: instance (GHC.Generics.Datatype d, Cfg.Source.Config.GConfigSource f) => Cfg.Source.Config.GConfigSource (GHC.Generics.M1 GHC.Generics.D d f)
+ Cfg.Source.Config: instance (GHC.Generics.Selector s, Cfg.Source.Config.GConfigSource f) => Cfg.Source.Config.GConfigSource (GHC.Generics.M1 GHC.Generics.S s f)
+ Cfg.Source.Config: instance Cfg.Source.Config.GConfigSource (a GHC.Generics.:+: b)
+ Cfg.Source.Config: instance Cfg.Source.ConfigSource a => Cfg.Source.Config.GConfigSource (GHC.Generics.K1 GHC.Generics.R a)
+ Cfg.Source.Default: class DefaultSource a
+ Cfg.Source.Default: defaults :: DefaultSource a => Text -> Maybe Text
+ KeyTree: Free :: f (Free f a) -> Free (f :: Type -> Type) a
+ KeyTree: Pure :: a -> Free (f :: Type -> Type) a
+ KeyTree: appendFold :: (Eq k, Eq v) => (a -> v -> v') -> (a -> v') -> (k -> a -> Map k (Free (Map k) v) -> a) -> a -> KeyTree k v -> KeyTree k v'
+ KeyTree: appendTraverse :: (Applicative f, Eq k, Eq v) => (a -> v -> f v') -> (a -> f v') -> (k -> a -> Map k (Free (Map k) v) -> a) -> a -> KeyTree k v -> f (KeyTree k v')
+ KeyTree: data () => Free (f :: Type -> Type) a
+ KeyTree: data () => Map k a
+ KeyTree: foldKeyTree :: (Eq k, Eq v) => (v -> a) -> (k -> Free (Map k) v -> a -> a) -> a -> KeyTree k v -> a
+ KeyTree: mayAppendFold :: (Eq k, Eq v) => (a -> v -> Maybe v') -> (a -> Maybe v') -> (k -> a -> Map k (Free (Map k) v) -> a) -> a -> KeyTree k v -> KeyTree k v'
+ KeyTree: mayAppendTraverse :: (Applicative f, Eq k, Eq v) => (a -> v -> f v') -> (a -> f (Maybe v')) -> (k -> a -> Map k (Free (Map k) v) -> a) -> a -> KeyTree k v -> f (KeyTree k v')
+ KeyTree: type KeyForest key value = Map key (Free (Map key) value)
+ KeyTree: type KeyTree key value = Free (Map key) value
- Cfg: getConfig :: forall a m. (Monad m, RootConfig a, RootParser a) => FetchSource m -> m (Either ConfigParseError a)
+ Cfg: getConfig :: forall a m. (Monad m, ConfigSource a, ConfigParser a) => FetchSource m -> m (Either ConfigParseError a)
- Cfg: getConfigRaw :: Monad m => Tree Text -> (Tree Text -> m (Tree Text)) -> (Tree Text -> Either e a) -> m (Either e a)
+ Cfg: getConfigRaw :: Monad m => KeyTree Text Text -> (KeyTree Text Text -> m (KeyTree Text Text)) -> (KeyTree Text Text -> Either e a) -> m (Either e a)
- Cfg.Deriving.Assert: type AssertTopLevelRecord (constraint :: Type -> Constraint) a = Assert (IsTopLevelRecord (Rep a)) ('Text "🚫 Cannot derive " ':<>: 'ShowType constraint ':<>: 'Text " instance for " ':<>: 'ShowType a ':<>: 'Text " via ConfigRoot" ':$$: 'Text "💡 ConfigRoot must be derived on a top level record type with named fields.")
+ Cfg.Deriving.Assert: type AssertTopLevelRecord (constraint :: Type -> Constraint) a = Assert (IsTopLevelRecord (Rep a)) ('Text "🚫 Cannot derive " ':<>: 'ShowType constraint ':<>: 'Text " instance for " ':<>: 'ShowType a ':$$: ('Text "💡 " ':<>: 'ShowType constraint ':<>: 'Text " must be derived on a top level record type with named fields."))
- Cfg.Env: envSource :: (MonadFail m, MonadIO m) => Tree Text -> m (Tree Text)
+ Cfg.Env: envSource :: (MonadFail m, MonadIO m) => KeyTree Text Text -> m (KeyTree Text Text)
- Cfg.Env: envSourceSep :: forall m. (MonadFail m, MonadIO m) => Text -> Tree Text -> m (Tree Text)
+ Cfg.Env: envSourceSep :: forall m. MonadIO m => Text -> KeyTree Text Text -> m (KeyTree Text Text)
- Cfg.Env: getEnvConfig :: (MonadFail m, MonadIO m, RootConfig a, RootParser a) => m (Either ConfigParseError a)
+ Cfg.Env: getEnvConfig :: forall a m. (MonadFail m, MonadIO m, ConfigSource a, ConfigParser a) => m (Either ConfigParseError a)
- Cfg.Env: getEnvConfigSep :: (MonadFail m, MonadIO m, RootConfig a, RootParser a) => Text -> m (Either ConfigParseError a)
+ Cfg.Env: getEnvConfigSep :: forall a m. (MonadFail m, MonadIO m, ConfigSource a, ConfigParser a) => Text -> m (Either ConfigParseError a)
- Cfg.Env: printDotEnv :: forall a. RootConfig a => FilePath -> IO ()
+ Cfg.Env: printDotEnv :: forall a. ConfigSource a => FilePath -> IO ()
- Cfg.Env: printDotEnv' :: FilePath -> Text -> Tree Text -> IO ()
+ Cfg.Env: printDotEnv' :: FilePath -> Text -> KeyTree Text Text -> IO ()
- Cfg.Env.Keys: getKeys :: Tree Text -> [[Text]]
+ Cfg.Env.Keys: getKeys :: KeyTree Text Text -> [[Text]]
- Cfg.Env.Keys: showEnvKeys :: forall a. RootConfig a => Text -> [Text]
+ Cfg.Env.Keys: showEnvKeys :: forall a. ConfigSource a => Text -> [Text]
- Cfg.Env.Keys: showEnvKeys' :: Text -> Tree Text -> [Text]
+ Cfg.Env.Keys: showEnvKeys' :: Text -> KeyTree Text Text -> [Text]
- Cfg.Options: RootOptions :: (Text -> Text) -> ConfigOptions -> RootOptions
+ Cfg.Options: RootOptions :: RootKey (Text -> Text) -> (Text -> Text) -> RootOptions
- Cfg.Source: type FetchSource m = Tree Text -> m (Tree Text)
+ Cfg.Source: type FetchSource m = KeyTree Text Text -> m (KeyTree Text Text)

Files

cfg.cabal view
@@ -1,7 +1,9 @@ cabal-version: 3.0 name:          cfg-version:       0.0.1.0-synopsis:      Type generated application configuration +version:       0.0.2.0+synopsis:+  Type directed application configuration parsing and accessors+ description:   `cfg` is a library that provides Generic machinery for generating configuration accessors,    and parsers from Haskell types. This package is intended to be used to build out additional @@ -46,6 +48,7 @@     NamedFieldPuns     NumericUnderscores     OverloadedStrings+    PolyKinds     RecordWildCards     ScopedTypeVariables     StandaloneDeriving@@ -53,12 +56,14 @@     TypeApplications     TypeFamilies     TypeOperators+    UndecidableInstances    build-depends:     , base           >=4.18.0 && <4.19     , containers     >=0.6.7  && <0.7+    , free           >=5.1.10 && <5.2     , mtl            >=2.3.1  && <2.4-    , pretty-simple+    , pretty-simple  >=4.1.2  && <4.2     , text           >=2.0.2  && <2.1    default-language:   Haskell2010@@ -76,20 +81,19 @@     Cfg     Cfg.Deriving     Cfg.Deriving.Assert-    Cfg.Deriving.ConfigRoot-    Cfg.Deriving.ConfigValue-    Cfg.Deriving.LabelModifier-    Cfg.Deriving.SubConfig+    Cfg.Deriving.Config+    Cfg.Deriving.KeyModifier+    Cfg.Deriving.Value     Cfg.Env     Cfg.Env.Keys     Cfg.Options     Cfg.Parser-    Cfg.Parser.ConfigParser-    Cfg.Parser.ValueParser+    Cfg.Parser.Config+    Cfg.Parser.Value     Cfg.Source-    Cfg.Source.NestedConfig-    Cfg.Source.RootConfig-    Tree.Append+    Cfg.Source.Config+    Cfg.Source.Default+    KeyTree    hs-source-dirs:  src   ghc-options:     -Wall -fdefer-typed-holes@@ -105,6 +109,8 @@    -- cabal-fmt: expand test -Main   other-modules:+    Cfg.Deriving.KeyModifierSpec+    Cfg.Env.KeysSpec     Cfg.EnvSpec     Cfg.ParserSpec     Cfg.SourceSpec@@ -119,15 +125,12 @@     , hspec-expectations  >=0.8.2 && <0.9     , hspec-hedgehog      >=0.0.1 && <0.1 --- test-suite doctests---   import:           common-opts---   type:             exitcode-stdio-1.0---   hs-source-dirs:   test---   main-is:          doctests.hs---   ghc-options:      -threaded---   build-depends:---     , cfg---     , doctest-parallel---     , pretty-simple------   default-language: Haskell2010+test-suite documentation+  import:         common-opts+  type:           exitcode-stdio-1.0+  hs-source-dirs: doctest+  ghc-options:    -Wall -threaded -rtsopts -with-rtsopts=-N+  main-is:        Main.hs+  build-depends:+    , cfg+    , doctest  >=0.21.1 && <0.22
+ doctest/Main.hs view
@@ -0,0 +1,45 @@+module Main where++import Test.DocTest++main :: IO ()+main = doctest+  [ "-isrc/"+  , "--fast"+  ,  "-XAllowAmbiguousTypes"+  ,  "-XBlockArguments"+  ,  "-XConstraintKinds"+  ,  "-XDataKinds"+  ,  "-XDeriveAnyClass"+  ,  "-XDeriveFunctor"+  ,  "-XDeriveGeneric"+  ,  "-XDerivingStrategies"+  ,  "-XDerivingVia"+  ,  "-XEmptyCase"+  ,  "-XFlexibleContexts"+  ,  "-XFlexibleInstances"+  ,  "-XGADTs"+  ,  "-XGeneralizedNewtypeDeriving"+  ,  "-XImportQualifiedPost"+  ,  "-XInstanceSigs"+  ,  "-XKindSignatures"+  ,  "-XLambdaCase"+  ,  "-XMultiParamTypeClasses"+  ,  "-XMultiWayIf"+  ,  "-XNamedFieldPuns"+  ,  "-XNumericUnderscores"+  ,  "-XOverloadedStrings"+  ,  "-XRecordWildCards"+  ,  "-XScopedTypeVariables"+  ,  "-XStandaloneDeriving"+  ,  "-XTupleSections"+  ,  "-XTypeApplications"+  ,  "-XTypeFamilies"+  ,  "-XTypeOperators"+  ,  "-XUndecidableInstances"+  ,  "-XPolyKinds"+  , "src/Cfg.hs"+  , "src/Cfg/Env/Keys.hs"+  , "src/KeyTree.hs"+  , "src/Cfg/Env.hs"+  ]
src/Cfg.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE DuplicateRecordFields #-}+ -- | --  Module      : Cfg --  Copyright   : © Jonathan Lorimer, 2023@@ -5,540 +7,464 @@ --  Maintainer  : jonathanlorimer@pm.me --  Stability   : stable ----- @since 0.0.1.0 +-- @since 0.0.1.0 -- -- This package provides an api for representing configuration as a haskell -- type. This entails three general considerations: a simplified--- representation our haskell type so that it maps better to existing--- configuration tools, an adapter to translate between the simplified+-- representation of our haskell type so that it maps better to existing+-- configuration formats, an adapter to translate between the simplified -- representation and a concrete configuration "source" (i.e. environment--- variables, yaml files, etc.), and a parser that can translate between the--- abstract representation and the concrete haskell type.+-- variables, yaml files, etc.), and a parser that can recover the structure of+-- the haskell type from the simplified representation. -- -- While this package provides a default source (environment variables), the -- intention is that other packages will provide additional sources.+module Cfg+  ( -- * Concepts -module Cfg (--- * Concepts------ |------ The core concepts in this package are:------    * __A simplified type representation:__ The type chosen to represent our---    underlying haskell type is 'Data.Tree.Tree'. This reflects the---    potentially nested structure of configuration, and makes it easy to nest---    keys and then simply append values as leaf nodes. ------    * __Sources:__ These represent a way to build a simplified representation---    from as Haskell type. Source may seem like an odd name, but other names---    like \"Rep\", or \"Representation\" are taken and overloaded. The tree---    structures created by the typeclasses in "Cfg.Source" are what is used to---    request values from a configuration source.------    * __Parsers:__ Once a request for configuration values has been made to a---    source, and the actual values are appended as leaf nodes on the tree---    representation we require a parser to pull that information out and---    construct a Haskell type. The parser traverses the tree and makes sure---    that it structurally matches our Haskell type, and then it will parse the---    'Data.Text.Text' values at the leaves into actual actual Haskell types.---    The api that corresponds to this part can be found in "Cfg.Parser".------    * __Deriving:__ It is a design principle of this library that the vast---    majority (if not all) functionality should be derivable. For this we use---    "GHC.Generics", and deriving via. You can always hand write instances for---    custom functionality, but there are also a handful of options that can be---    specified using the deriving machinery. Documentation on those options---    can be found in "Cfg.Deriving".------    * __Roots and nesting__: In general there is a distinction between the---    "Root" type for a configuration, and then subtypes that are arbitrarily---    nested under the root type (or other nested configurations). This makes---    some parts of the Generic machinery easier, but also serves some---    practical purposes (i.e. subconfig keys are picked from record field---    names, while the root key is picked from the root type's type name).+    -- |+    --+    -- The core concepts in this package are:+    --+    --    * __A simplified type representation:__ The type chosen to represent our+    --    underlying haskell type is 'KeyTree.KeyTree'. This reflects the+    --    potentially nested structure of configuration, and makes it easy+    --    simply append values as leaf nodes.+    --+    --    * __Sources:__ These represent a way to build a simplified representation+    --    from as Haskell type. Source may seem like an odd name, but other+    --    names like \"Rep\", or \"Representation\" are taken and overloaded.+    --    The tree structures created by the typeclasses in "Cfg.Source" are+    --    used to request values from a configuration source.+    --+    --    * __Parsers:__ Once a request for configuration values has been made to a+    --    source, and the actual values are appended as leaf nodes on the tree+    --    representation we require a parser to pull that information out and+    --    construct a Haskell type. The parser traverses the tree and makes sure+    --    that it structurally matches our Haskell type, and then it will parse the+    --    'Data.Text.Text' values at the leaves into actual Haskell types.+    --    The api that corresponds to this can be found in "Cfg.Parser".+    --+    --    * __Deriving:__ It is a design principle of this library that the+    --    vast majority (if not all) functionality should be derivable. For+    --    this we use "GHC.Generics", and [deriving+    --    via](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/deriving_via.html).+    --    You can always hand write instances for custom functionality, but+    --    there are also a handful of options that can be specified using the+    --    deriving machinery. Documentation on those options can be found in+    --    "Cfg.Deriving". --- * Quickstart guide------ |------ Here we will introduce some sample code that should get you up and running--- quickly. We will also explain some of the internals so you can see how--- things are wired together+    -- * Quickstart guide --- ** Sample configuration------ |------ Let's start out with a couple types that represent some imaginary--- configuration for an imaginary application. You will probably notice that we--- derive the 'RootConfig' class for our top level config @AppConfig@ and we do--- this via the 'ConfigRoot' type. We derive the 'NestedConfig' class for all--- the nested product types via the 'SubConfig' type. Finally, for--- @Environment@, which is not really a configuration, but rather a value that--- can be configured, we derive 'NestedConfig' via 'ConfigValue'.------ This is all probably a bit opaque, to understand the 'RootConfig' and--- 'NestedConfig' class better you can read the "Cfg.Source" module. To--- understand the deriving mechanisms better you can read the "Cfg.Deriving"--- module.------ @--- {-# LANGUAGE DeriveGeneric #-}--- {-# LANGUAGE DerivingVia #-}------ import "Cfg.Deriving" (ConfigValue, ConfigRoot, SubConfig)--- import "Cfg.Source" (RootConfig, NestedConfig)--- import Data.ByteString (ByteString)--- import Data.Text (Text)--- import GHC.Generics------ data Environment = Development | Production---   deriving stock (Generic, Show)---   deriving ('NestedConfig') via ('ConfigValue' Environment)------ data WarpConfig = WarpConfig---   { warpConfigPort :: Int---   , warpConfigTimeout :: Int---   , warpConfigHTTP2Enabled :: Bool---   , warpConfigServerName :: ByteString---   }---   deriving (Generic, Show)---   deriving ('NestedConfig') via ('SubConfig' WarpConfig)------ data RedisConfig = RedisConfig---   { redisConfigHost :: Text---   , redisConfigPort :: Int---   , redisConfigConnectAuth :: Maybe ByteString---   }---   deriving (Generic, Show)---   deriving ('NestedConfig') via ('SubConfig' RedisConfig)------ data AppConfig = AppConfig---   { appConfigWarpSettings :: WarpConfig---   , appConfigRedisSettings :: RedisConfig---   , appConfigEnvironment :: Environment---   }---   deriving stock (Generic, Show)---   deriving ('RootConfig') via ('ConfigRoot' AppConfig)--- @+    -- |+    --+    -- Here we will introduce some sample code that should get you up and running+    -- quickly. We will also explain some of the internals so you can see how+    -- things are wired together --- ** Generated representation------ |------ Below we can see a doctest example that shows the internal \"simplified--- representation\" that this library uses.------ >>> import Text.Pretty.Simple (pPrint) --- >>> import Cfg.Source () -- Pulls in the RootConfig instance for 'toRootConfig'--- >>> pPrint $ toRootConfig @AppConfig--- Node---     { rootLabel = "AppConfig"---     , subForest =---         [ Node---             { rootLabel = "appConfigWarpSettings"---             , subForest =---                 [ Node---                     { rootLabel = "warpConfigPort"---                     , subForest = []---                     }---                 , Node---                     { rootLabel = "warpConfigTimeout"---                     , subForest = []---                     }---                 , Node---                     { rootLabel = "warpConfigHTTP2Enabled"---                     , subForest = []---                     }---                 , Node---                     { rootLabel = "warpConfigServerName"---                     , subForest = []---                     }---                 ]---             }---         , Node---             { rootLabel = "appConfigRedisSettings"---             , subForest =---                 [ Node---                     { rootLabel = "redisConfigHost"---                     , subForest = []---                     }---                 , Node---                     { rootLabel = "redisConfigPort"---                     , subForest = []---                     }---                 , Node---                     { rootLabel = "redisConfigConnectAuth"---                     , subForest = []---                     }---                 ]---             }---         , Node---             { rootLabel = "appConfigEnvironment"---             , subForest = []---             }---         ]---     }+    -- ** Initial configuration --- ** Parsing a representation------ |------ Below we are deriving just the parsers for our example data type from above.--- Just like the above example we use the 'RootConfig', 'SubConfig', and--- 'ConfigValue' types to derive the appropriate parsing classes. This time,--- however, there are 3 classes: 'RootParser' which should be placed on the top--- level configuration record, 'NestedParser' which should be derived for--- nested configuration product type, 'ValueParser' is derived for leaf level--- configuration values (you also need to derive 'NestedParser' for these, but--- there is a default method that just uses the 'ValueParser' so you can derive--- it without any strategy)------ More information on the parsers can be found at "Cfg.Parser".------ In the example below there is a term called @sample@ and this represents a--- tree that may have been retrieved from a source, and should be parsable--- given our type and derived instances.------ @--- {-# LANGUAGE DeriveGeneric #-}--- {-# LANGUAGE DerivingVia #-}------ import "Cfg.Deriving" (ConfigValue(..), SubConfig(..), ConfigRoot(..))--- import "Cfg.Parser" (RootParser(..), ConfigParseError, NestedParser, ValueParser)--- import Data.ByteString (ByteString)--- import Data.Text (Text)--- import GHC.Generics------ data Environment = Development | Production---   deriving stock (Generic, Show)---   deriving ('ValueParser') via ('ConfigValue' Environment)---   deriving 'NestedParser'------ data WarpConfig = WarpConfig---   { warpConfigPort :: Int---   , warpConfigTimeout :: Int---   , warpConfigHTTP2Enabled :: Bool---   , warpConfigServerName :: ByteString---   }---   deriving (Generic, Show)---   deriving ('NestedParser') via ('SubConfig' WarpConfig)------ data RedisConfig = RedisConfig---   { redisConfigHost :: Text---   , redisConfigPort :: Int---   , redisConfigConnectAuth :: Maybe ByteString---   }---   deriving (Generic, Show)---   deriving ('NestedParser') via ('SubConfig' RedisConfig)------ data AppConfig = AppConfig---   { appConfigWarpSettings :: WarpConfig---   , appConfigRedisSettings :: RedisConfig---   , appConfigEnvironment :: Environment---   }---   deriving stock (Generic, Show)---   deriving ('RootParser') via ('ConfigRoot' AppConfig)------ sample :: Tree Text--- sample = Node---     { rootLabel = \"AppConfig\"---     , subForest =---         [ Node---             { rootLabel = "appConfigWarpSettings"---             , subForest =---                 [ Node---                     { rootLabel = "warpConfigPort"---                     , subForest = [ Node "8080" [] ]---                     }---                 , Node---                     { rootLabel = "warpConfigTimeout"---                     , subForest = [ Node "30" [] ]---                     }---                 , Node---                     { rootLabel = "warpConfigHTTP2Enabled"---                     , subForest = [ Node \"True\" [] ]---                     }---                 , Node---                     { rootLabel = "warpConfigServerName"---                     , subForest = [ Node \"MyServer\" [] ]---                     }---                 ]---             }---         , Node---             { rootLabel = "appConfigRedisSettings"---             , subForest =---                 [ Node---                     { rootLabel = "redisConfigHost"---                     , subForest = [ Node "https://localhost" [] ]---                     }---                 , Node---                     { rootLabel = "redisConfigPort"---                     , subForest = [ Node "6379" [] ]---                     }---                 , Node---                     { rootLabel = "redisConfigConnectAuth"---                     , subForest = [ Node "Just password" [] ]---                     }---                 ]---             }---         , Node---             { rootLabel = "appConfigEnvironment"---             , subForest = [ Node \"Development\" [] ]---             }---         ]---     }--- @------ Here is a demonstration of running the parser on the sample tree structure--- shown above.------ >>> import Text.Pretty.Simple (pPrint) --- >>> import Cfg.Parser () -- Pulls in the RootParser instance for 'parseRootConfig'--- >>> pPrint $ parseRootConfig @AppConfig sample--- Right---     ( AppConfig---         { appConfigWarpSettings = WarpConfig---             { warpConfigPort = 8080---             , warpConfigTimeout = 30---             , warpConfigHTTP2Enabled = True---             , warpConfigServerName = "MyServer"---             }---         , appConfigRedisSettings = RedisConfig---             { redisConfigHost = "https://localhost"---             , redisConfigPort = 6379---             , redisConfigConnectAuth = Just "password"---             }---         , appConfigEnvironment = Development---         }---     )+    -- |+    --+    -- Let's start out with a couple types that represent some imaginary+    -- configuration for an imaginary application. This is the most basic+    -- kind of configuration we can have.+    --+    -- You will probably notice that records derive their instances via a+    -- `Config` newtype, while base types (types that represent the actual+    -- configuration values) are derived via a `Value` newtype. Values first+    -- derive a `ValueParser` instance, and then that makes it possible to+    -- derive a `ConfigParser` instance (no deriving via machinery necessary+    -- for that instance).+    --+    -- @+    -- {\-# LANGUAGE DeriveGeneric #-\}+    -- {\-# LANGUAGE DerivingVia #-\}+    --+    -- import "Cfg.Deriving.Config"+    -- import "Cfg.Deriving.Value"+    -- import "Cfg.Parser"+    -- import "Cfg.Source"+    -- import "Cfg.Source.Default"+    -- import Data.ByteString (ByteString)+    -- import GHC.Generics+    --+    -- data Environment = Development | Production+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ValueParser') via ('Value' Environment)+    --   -- Note: This is derivable via ConigParser's default instance, because we provided a ValueParser instance+    --   deriving ('ConfigParser')+    --+    -- data WarpConfig = WarpConfig+    --   { warpConfigPort :: Int+    --   , warpConfigTimeout :: Int+    --   , warpConfigHTTP2Enabled :: Bool+    --   , warpConfigServerName :: ByteString+    --   }+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('Config' WarpConfig)+    --+    -- data RedisConfig = RedisConfig+    --   { redisConfigHost :: Text+    --   , redisConfigPort :: Int+    --   , redisConfigConnectAuth :: Maybe ByteString+    --   }+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('Config' RedisConfig)+    --+    -- data AppConfig = AppConfig+    --   { appConfigWarpSettings :: WarpConfig+    --   , appConfigRedisSettings :: RedisConfig+    --   , appConfigEnvironment :: Environment+    --   }+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('Config' AppConfig)+    -- @+    --+    -- And here is the result of generating the keys for this configuration setup+    --+    -- >>> import Cfg+    -- >>> import Text.Pretty.Simple+    -- >>> import Cfg.Env.Keys+    -- >>> pPrint $ showEnvKeys @AppConfig "_"+    -- [ "appConfigEnvironment"+    -- , "appConfigRedisSettings_redisConfigConnectAuth"+    -- , "appConfigRedisSettings_redisConfigHost"+    -- , "appConfigRedisSettings_redisConfigPort"+    -- , "appConfigWarpSettings_warpConfigHTTP2Enabled"+    -- , "appConfigWarpSettings_warpConfigPort"+    -- , "appConfigWarpSettings_warpConfigServerName"+    -- , "appConfigWarpSettings_warpConfigTimeout"+    -- ] --- ** Manipulating key format------ |------ The last thing we will go over is manipulating the way that we format--- configuration keys. Certain configuration sources have stylistic standards--- that may not be the same as Haskell. Therefore we offer some options for--- configuring their representation. ------ In the example below we will say that we are using environment variables as--- our configuration source. It is pretty standard to have env vars in--- SCREAMING_SNAKE_CASE, therefore we will apply a modifier that does that.------ We will also use a convenience function from "Cfg.Env.Keys" to print out--- the expected shape of the keys after all the formatters have been applied.------ @--- {-# LANGUAGE DeriveGeneric #-}--- {-# LANGUAGE DerivingVia #-}------ import "Cfg.Deriving" (ConfigValue(..), SubConfig(..), ConfigRoot(..))--- import "Cfg.Parser" (RootParser(..), ConfigParseError, NestedParser, ValueParser)--- import "Cfg.Deriving.LabelModifier" (ToUpper)--- import "Cfg.Deriving.ConfigRoot" (ConfigRootOpts(..))--- import "Cfg.Deriving.SubConfig" (SubConfigOpts(..))--- import Data.Text (Text)--- import Data.ByteString (ByteString)--- import GHC.Generics------ data Environment = Development | Production---   deriving stock (Generic, Show)---   deriving ('ValueParser') via ('ConfigValue' Environment)---   deriving 'NestedParser'------ data EnvWarpConfig = EnvWarpConfig---   { envWarpConfigPort :: Int---   , envWarpConfigTimeout :: Int---   , envWarpConfigHTTP2Enabled :: Bool---   , envWarpConfigServerName :: ByteString---   }---   deriving (Generic, Show)---   deriving ('NestedConfig') via ('SubConfigOpts' 'ToUpper' EnvWarpConfig)---   deriving ('NestedParser') via ('SubConfigOpts' 'ToUpper' EnvWarpConfig)------ data EnvRedisConfig = EnvRedisConfig---   { envRedisConfigHost :: Text---   , envRedisConfigPort :: Int---   , envRedisConfigConnectAuth :: Maybe ByteString---   }---   deriving (Generic, Show)---   deriving ('NestedConfig') via ('SubConfigOpts' 'ToUpper' EnvRedisConfig)---   deriving ('NestedParser') via ('SubConfigOpts' 'ToUpper' EnvRedisConfig)------ data EnvAppConfig = EnvAppConfig---   { envAppConfigWarpSettings :: EnvWarpConfig---   , envAppConfigRedisSettings :: EnvRedisConfig---   , envAppConfigEnvironment :: Environment---   }---   deriving stock (Generic, Show)---   deriving ('RootConfig') via ('ConfigRootOpts' 'ToUpper' 'ToUpper' EnvAppConfig)---   deriving ('RootParser') via ('ConfigRootOpts' 'ToUpper' 'ToUpper' EnvAppConfig)--- @------ >>> import Cfg--- >>> import Text.Pretty.Simple--- >>> import Cfg.Env.Keys--- >>> pPrint $ showEnvKeys @EnvAppConfig "_"--- [ "ENVAPPCONFIG_ENVAPPCONFIGWARPSETTINGS_ENVWARPCONFIGPORT"--- , "ENVAPPCONFIG_ENVAPPCONFIGWARPSETTINGS_ENVWARPCONFIGTIMEOUT"--- , "ENVAPPCONFIG_ENVAPPCONFIGWARPSETTINGS_ENVWARPCONFIGHTTP2ENABLED"--- , "ENVAPPCONFIG_ENVAPPCONFIGWARPSETTINGS_ENVWARPCONFIGSERVERNAME"--- , "ENVAPPCONFIG_ENVAPPCONFIGREDISSETTINGS_ENVREDISCONFIGHOST"--- , "ENVAPPCONFIG_ENVAPPCONFIGREDISSETTINGS_ENVREDISCONFIGPORT"--- , "ENVAPPCONFIG_ENVAPPCONFIGREDISSETTINGS_ENVREDISCONFIGCONNECTAUTH"--- , "ENVAPPCONFIG_ENVAPPCONFIGENVIRONMENT"--- ]+    -- ** Basic key modifiers --- * Exports------ |---+    -- |+    --+    -- This is okay, but there are some changes we may want to make. We will go+    -- through a series of tweaks to the example above to format the keys. We+    -- format the keys by providing formatting options through a new newtype+    -- 'ConfigOpts' that accepts a type parameter.+    --+    -- @+    -- data WarpConfig = ...+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('ConfigOpts' 'ToUpper' WarpConfig)+    --+    -- data RedisConfig = ...+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('ConfigOpts' 'ToUpper' WarpConfig)+    --+    -- data AppConfig = ...+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('ConfigOpts' 'ToUpper' AppConfig)+    -- @+    --+    -- Let's print the keys out again (note, we have to add a numbered suffix+    -- to the constructor so we don't get namespace collisions in the doc+    -- tests)+    --+    -- >>> import Cfg+    -- >>> import Text.Pretty.Simple+    -- >>> import Cfg.Env.Keys+    -- >>> pPrint $ showEnvKeys @AppConfig2 "_"+    -- [ "APPCONFIGENVIRONMENT"+    -- , "APPCONFIGREDISSETTINGS_REDISCONFIGCONNECTAUTH"+    -- , "APPCONFIGREDISSETTINGS_REDISCONFIGHOST"+    -- , "APPCONFIGREDISSETTINGS_REDISCONFIGPORT"+    -- , "APPCONFIGWARPSETTINGS_WARPCONFIGHTTP2ENABLED"+    -- , "APPCONFIGWARPSETTINGS_WARPCONFIGPORT"+    -- , "APPCONFIGWARPSETTINGS_WARPCONFIGSERVERNAME"+    -- , "APPCONFIGWARPSETTINGS_WARPCONFIGTIMEOUT"+    -- ] -  getConfigRaw,-  getConfig,-) where+    -- ** Multiple key modifiers +    -- |+    --+    -- This is close, but we probably want to remove the record field suffixes+    -- for our configuration. We can provide more than one formatter through+    -- tuples (up to a cardinality of 4) or a type level list. These formatters+    -- apply in order from left to right.+    --+    -- @+    -- data WarpConfig = WarpConfig+    --   { warpConfigPort :: Int+    --   , warpConfigTimeout :: Int+    --   , warpConfigHTTP2Enabled :: Bool+    --   , warpConfigServerName :: ByteString+    --   }+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('ConfigOpts' ('StripPrefix' "warpConfig", 'ToUpper') WarpConfig)+    --+    -- data RedisConfig = RedisConfig+    --   { redisConfigHost :: Text+    --   , redisConfigPort :: Int+    --   , redisConfigConnectAuth :: Maybe ByteString+    --   }+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser') via ('ConfigOpts' ['StripPrefix' "redisConfig", 'ToUpper'] WarpConfig)+    --+    -- data AppConfig = AppConfig+    --   { appConfigWarpSettings :: WarpConfig+    --   , appConfigRedisSettings :: RedisConfig+    --   , appConfigEnvironment :: Environment+    --   }+    --   deriving (Generic, Show, 'DefaultSource')+    --   deriving ('ConfigSource', 'ConfigParser')+    --    via ('ConfigOpts' ['StripPrefix' "appConfig", 'StripSuffix' \"Settings\", ToUpper] AppConfig)+    -- @+    --+    -- >>> import Cfg+    -- >>> import Text.Pretty.Simple+    -- >>> import Cfg.Env.Keys+    -- >>> pPrint $ showEnvKeys @AppConfig3 "_"+    -- [ "ENVIRONMENT"+    -- , "REDIS_CONNECTAUTH"+    -- , "REDIS_HOST"+    -- , "REDIS_PORT"+    -- , "WARP_HTTP2ENABLED"+    -- , "WARP_PORT"+    -- , "WARP_SERVERNAME"+    -- , "WARP_TIMEOUT"+    -- ] -import Data.Text (Text)-import Data.Tree (Tree (..))-import Cfg.Source (RootConfig(..), FetchSource, NestedConfig)-import Cfg.Parser (RootParser(..), ConfigParseError, NestedParser, ValueParser)-import Cfg.Deriving (ConfigValue(..), ConfigRoot(..))+    -- ** Root key --- Imports for examples-import GHC.Generics+    -- |+    --+    -- This is much better, but we might even want to go a step further and+    -- namespace our config with a rootkey. We can do this by deriving via a+    -- special type on our root config record.+    --+    -- @+    -- data AppConfig = AppConfig+    --   { appConfigWarpSettings :: WarpConfig+    --   , appConfigRedisSettings :: RedisConfig+    --   , appConfigEnvironment :: Environment+    --   }+    -- deriving (Generic, Show, 'DefaultSource')+    -- deriving ('ConfigSource', 'ConfigParser')+    --   via (+    --     'ConfigRoot'+    --       (''TypeName' ['StripSuffix' "Config", 'ToUpper'])+    --       ['StripPrefix' "appConfig", 'StripSuffix' \"Settings\", 'ToUpper']+    --       AppConfig+    --   )+    -- @+    --+    -- The first parameter to 'ConfigRoot' is either 'TypeName' or+    -- 'ConstructorName', this indicates which name will be used for the root+    -- key. You can then provide key formatters to manipulate that name.+    --+    -- >>> import Cfg+    -- >>> import Text.Pretty.Simple+    -- >>> import Cfg.Env.Keys+    -- >>> pPrint $ showEnvKeys @AppConfig4 "_"+    -- [ "APP_ENVIRONMENT"+    -- , "APP_REDIS_CONNECTAUTH"+    -- , "APP_REDIS_HOST"+    -- , "APP_REDIS_PORT"+    -- , "APP_WARP_HTTP2ENABLED"+    -- , "APP_WARP_PORT"+    -- , "APP_WARP_SERVERNAME"+    -- , "APP_WARP_TIMEOUT"+    -- ]++    -- ** Defaults++    -- |+    --+    -- The defaulting machinery is admittedly a bit crude. You must define a+    -- 'DefaultSource' instance for the record that contains the value you want+    -- to default. The reason the defaulting needs to be defined on the record+    -- is that we use the record field key to identify the defaulted value the+    -- value you want to default. The reason the defaulting needs to be defined+    -- on the record is that we use the record field key to identify the+    -- defaulted value.+    --+    -- __There are a bunch of gotchas with defaulting__:+    --+    --    - Since the type of 'defaults' is @Text -> Maybe Text@, the onus is+    --    on the implementor to make sure that this function correctly matches+    --    the record field name.+    --+    --    - The way it is currently implemented we use the 'defaults' function+    --    on the record field /before/ applying key modifiers.+    --+    --    - If there is a mismatch it will fail silently by not defaulting.+    --    This may result in an error when parsing (due to a missing value).+    --+    --    - You can only set defaults via their textual representation, so your+    --    defaults might fail to parse!+    --+    --    - If you declare a default on a field that is supposed to hold nested config this will break, and there is nothing at the type level to prevent you from making this mistake+    --+    -- Considering all of the above, it may be preferable to do some defaulting+    -- on the configuration side (i.e. make sure default environment variables+    -- are set, or provide default configuration files that can be modified).+    --+    -- If you still want to do defaulting on the haskell side here is how:+    --+    -- @+    -- data AppConfig = AppConfig+    --   { appConfigWarpSettings :: WarpConfig+    --   , appConfigRedisSettings :: RedisConfig+    --   , appConfigEnvironment :: Environment+    --   }+    --   deriving (Generic, Show)+    --   deriving ('ConfigSource', 'ConfigParser')+    --    via ('ConfigOpts' ['StripPrefix' "appConfig", 'StripSuffix' \"Settings\", ToUpper] AppConfig)+    --+    -- -- NOTE: If I provide a default for WarpConfig or RedisConfig this will break the configuration machinery+    -- -- so I only match on the field for @Environment@+    --+    -- instance 'DefaultSource' (AppConfig a) where+    --   'defaults' "appConfigEnvironment" = Just "Development"+    --   'defaults' _ = Nothing+    -- @++    -- * Exports+    getConfigRaw+  , getConfig+  )+where++-- Haddock example imports++import Cfg.Deriving+import Cfg.Options+import Cfg.Parser+import Cfg.Parser ()+import Cfg.Source+import Cfg.Source ()+import Cfg.Source.Default import Data.ByteString (ByteString)-import Cfg.Deriving.SubConfig (SubConfig(..))-import Cfg.Deriving.LabelModifier (ToUpper)-import Cfg.Deriving.ConfigRoot (ConfigRootOpts(..))-import Cfg.Deriving.SubConfig (SubConfigOpts(..))+import Data.Text (Text)+import GHC.Generics+import KeyTree  -- | @since 0.0.1.0-getConfigRaw ::-    Monad m =>-    Tree Text ->-    (Tree Text -> m (Tree Text)) ->-    (Tree Text -> Either e a) ->-    m (Either e a)-getConfigRaw keyTree source parser = parser <$> source keyTree+getConfigRaw+  :: (Monad m)+  => KeyTree Text Text+  -> (KeyTree Text Text -> m (KeyTree Text Text))+  -> (KeyTree Text Text -> Either e a)+  -> m (Either e a)+getConfigRaw keyTree source parse = parse <$> source keyTree  -- | @since 0.0.1.0-getConfig :: forall a m . (Monad m, RootConfig a, RootParser a) => FetchSource m -> m (Either ConfigParseError a)-getConfig fetch = parseRootConfig @a <$> fetch (toRootConfig @a)-+getConfig+  :: forall a m+   . (Monad m, ConfigSource a, ConfigParser a)+  => FetchSource m+  -> m (Either ConfigParseError a)+getConfig fetch = parseConfig @a <$> fetch (configSource @a)  ------------------------------------------------------- -- Examples for haddocks ------------------------------------------------------- data Environment = Development | Production-  deriving stock (Generic, Show)-  deriving (NestedConfig) via ConfigValue Environment-  deriving (ValueParser) via ConfigValue Environment-  deriving NestedParser+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ValueParser) via (Value Environment)+  deriving (ConfigParser) -- Note: This is derivable via ConigParser's default instance, because we provided a ValueParser instance +-- Example 1+ data WarpConfig = WarpConfig   { warpConfigPort :: Int   , warpConfigTimeout :: Int   , warpConfigHTTP2Enabled :: Bool   , warpConfigServerName :: ByteString   }-  deriving (Generic, Show)-  deriving (NestedConfig) via (SubConfig WarpConfig)-  deriving (NestedParser) via (SubConfig WarpConfig)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ConfigParser) via (Config WarpConfig)  data RedisConfig = RedisConfig   { redisConfigHost :: Text   , redisConfigPort :: Int   , redisConfigConnectAuth :: Maybe ByteString   }-  deriving (Generic, Show)-  deriving (NestedConfig) via (SubConfig RedisConfig)-  deriving (NestedParser) via (SubConfig RedisConfig)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ConfigParser) via (Config RedisConfig)  data AppConfig = AppConfig   { appConfigWarpSettings :: WarpConfig   , appConfigRedisSettings :: RedisConfig   , appConfigEnvironment :: Environment   }-  deriving stock (Generic, Show)-  deriving (RootConfig) via (ConfigRoot AppConfig)-  deriving (RootParser) via (ConfigRoot AppConfig)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ConfigParser) via (Config AppConfig) -sample :: Tree Text-sample = Node-    { rootLabel = "AppConfig"-    , subForest =-        [ Node-            { rootLabel = "appConfigWarpSettings"-            , subForest =-                [ Node-                    { rootLabel = "warpConfigPort"-                    , subForest = [ Node "8080" [] ]-                    }-                , Node-                    { rootLabel = "warpConfigTimeout"-                    , subForest = [ Node "30" [] ]-                    }-                , Node-                    { rootLabel = "warpConfigHTTP2Enabled"-                    , subForest = [ Node "True" [] ]-                    }-                , Node-                    { rootLabel = "warpConfigServerName"-                    , subForest = [ Node "MyServer" [] ]-                    }-                ]-            }-        , Node-            { rootLabel = "appConfigRedisSettings"-            , subForest =-                [ Node-                    { rootLabel = "redisConfigHost"-                    , subForest = [ Node "https://localhost" [] ]-                    }-                , Node-                    { rootLabel = "redisConfigPort"-                    , subForest = [ Node "6379" [] ]-                    }-                , Node-                    { rootLabel = "redisConfigConnectAuth"-                    , subForest = [ Node "Just password" [] ]-                    }-                ]-            }-        , Node-            { rootLabel = "appConfigEnvironment"-            , subForest = [ Node "Development" [] ]-            }-        ]-    }+-- Example 2+data WarpConfig2 = WarpConfig2+  { warpConfigPort :: Int+  , warpConfigTimeout :: Int+  , warpConfigHTTP2Enabled :: Bool+  , warpConfigServerName :: ByteString+  }+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ConfigParser) via (ConfigOpts ToUpper WarpConfig2) -data EnvWarpConfig = EnvWarpConfig-  { envWarpConfigPort :: Int-  , envWarpConfigTimeout :: Int-  , envWarpConfigHTTP2Enabled :: Bool-  , envWarpConfigServerName :: ByteString+data RedisConfig2 = RedisConfig2+  { redisConfigHost :: Text+  , redisConfigPort :: Int+  , redisConfigConnectAuth :: Maybe ByteString   }-  deriving (Generic, Show)-  deriving (NestedConfig) via (SubConfigOpts ToUpper EnvWarpConfig)-  deriving (NestedParser) via (SubConfigOpts ToUpper EnvWarpConfig)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ConfigParser) via (ConfigOpts ToUpper RedisConfig2) -data EnvRedisConfig = EnvRedisConfig-  { envRedisConfigHost :: Text-  , envRedisConfigPort :: Int-  , envRedisConfigConnectAuth :: Maybe ByteString+data AppConfig2 = AppConfig2+  { appConfigWarpSettings :: WarpConfig2+  , appConfigRedisSettings :: RedisConfig2+  , appConfigEnvironment :: Environment   }-  deriving (Generic, Show)-  deriving (NestedConfig) via (SubConfigOpts ToUpper EnvRedisConfig)-  deriving (NestedParser) via (SubConfigOpts ToUpper EnvRedisConfig)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource, ConfigParser) via (ConfigOpts ToUpper AppConfig2) -data EnvAppConfig = EnvAppConfig-  { envAppConfigWarpSettings :: EnvWarpConfig-  , envAppConfigRedisSettings :: EnvRedisConfig-  , envAppConfigEnvironment :: Environment+-- Example 3+data WarpConfig3 = WarpConfig3+  { warpConfigPort :: Int+  , warpConfigTimeout :: Int+  , warpConfigHTTP2Enabled :: Bool+  , warpConfigServerName :: ByteString   }-  deriving stock (Generic, Show)-  deriving (RootConfig) via (ConfigRootOpts ToUpper ToUpper EnvAppConfig)-  deriving (RootParser) via (ConfigRootOpts ToUpper ToUpper EnvAppConfig)+  deriving (Generic, Show, DefaultSource)+  deriving+    (ConfigSource, ConfigParser)+    via (ConfigOpts (StripPrefix "warpConfig", ToUpper) WarpConfig3)++data RedisConfig3 = RedisConfig3+  { redisConfigHost :: Text+  , redisConfigPort :: Int+  , redisConfigConnectAuth :: Maybe ByteString+  }+  deriving (Generic, Show, DefaultSource)+  deriving+    (ConfigSource, ConfigParser)+    via (ConfigOpts [StripPrefix "redisConfig", ToUpper] RedisConfig3)++data AppConfig3 = AppConfig3+  { appConfigWarpSettings :: WarpConfig3+  , appConfigRedisSettings :: RedisConfig3+  , appConfigEnvironment :: Environment+  }+  deriving (Generic, Show, DefaultSource)+  deriving+    (ConfigSource, ConfigParser)+    via (ConfigOpts [StripPrefix "appConfig", StripSuffix "Settings", ToUpper] AppConfig3)++-- Example 4+data AppConfig4 = AppConfig4+  { appConfigWarpSettings :: WarpConfig3+  , appConfigRedisSettings :: RedisConfig3+  , appConfigEnvironment :: Environment+  }+  deriving (Generic, Show, DefaultSource)+  deriving+    (ConfigSource, ConfigParser)+    via ( ConfigRoot+            ('TypeName [StripSuffix "Config4", ToUpper])+            [StripPrefix "appConfig", StripSuffix "Settings", ToUpper]+            AppConfig4+        )
src/Cfg/Deriving.hs view
@@ -1,12 +1,21 @@+-- |+--  Module      : Cfg.Deriving+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- Convenience module that re-exports all the necessary bits for deriving+-- instances. module Cfg.Deriving-  ( module Cfg.Deriving.ConfigRoot-  , module Cfg.Deriving.ConfigValue-  , module Cfg.Deriving.SubConfig-  , module Cfg.Deriving.LabelModifier+  ( module Cfg.Deriving.Config+  , module Cfg.Deriving.Value+  , module Cfg.Deriving.KeyModifier   ) where -import Cfg.Deriving.ConfigRoot-import Cfg.Deriving.ConfigValue-import Cfg.Deriving.LabelModifier-import Cfg.Deriving.SubConfig+import Cfg.Deriving.Config+import Cfg.Deriving.KeyModifier+import Cfg.Deriving.Value
src/Cfg/Deriving/Assert.hs view
@@ -1,3 +1,14 @@+-- |+--  Module      : Cfg.Deriving.Assert+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module provides type level assertions so that we can constrain the+-- instances user's can create, and give them good error messages module Cfg.Deriving.Assert where  import Data.Kind (Type)@@ -5,25 +16,40 @@ import GHC.Generics import GHC.TypeError (ErrorMessage (..), TypeError) +-- | A type level helper for creating custom error messages based on a predicate+--+-- @since 0.0.2.0 class Assert (pred :: Bool) (msg :: ErrorMessage)+ instance Assert 'True msg-instance TypeError msg ~ '() => Assert 'False msg +instance (TypeError msg ~ '()) => Assert 'False msg++-- | A type level predicate that helps us identify top level product types+--+-- @since 0.0.2.0 type family IsTopLevelRecord f where-    IsTopLevelRecord V1 = 'False-    IsTopLevelRecord U1 = 'False-    IsTopLevelRecord (K1 i c) = 'False-    IsTopLevelRecord (M1 i c f) = IsTopLevelRecord f-    IsTopLevelRecord (f :*: g) = 'True-    IsTopLevelRecord (f :+: g) = 'False+  IsTopLevelRecord V1 = 'False+  IsTopLevelRecord U1 = 'False+  IsTopLevelRecord (K1 i c) = 'False+  IsTopLevelRecord (M1 D c f) = IsTopLevelRecord f+  IsTopLevelRecord (M1 C c f) = IsTopLevelRecord f+  IsTopLevelRecord (M1 S c f) = 'True+  IsTopLevelRecord (f :*: g) = IsTopLevelRecord f+  IsTopLevelRecord (f :+: g) = 'False +-- | A custom error message for non-top-level records+--+-- @since 0.0.2.0 type AssertTopLevelRecord (constraint :: Type -> Constraint) a =-    Assert-        (IsTopLevelRecord (Rep a))-        ( 'Text "🚫 Cannot derive "-            ':<>: 'ShowType constraint-            ':<>: 'Text " instance for "-            ':<>: 'ShowType a-            ':<>: 'Text " via ConfigRoot"-            ':$$: 'Text "💡 ConfigRoot must be derived on a top level record type with named fields."-        )+  Assert+    (IsTopLevelRecord (Rep a))+    ( 'Text "🚫 Cannot derive "+        ':<>: 'ShowType constraint+        ':<>: 'Text " instance for "+        ':<>: 'ShowType a+        ':$$: ( 'Text "💡 "+                  ':<>: 'ShowType constraint+                  ':<>: 'Text " must be derived on a top level record type with named fields."+              )+    )
+ src/Cfg/Deriving/Config.hs view
@@ -0,0 +1,348 @@+-- |+--  Module      : Cfg.Deriving.Config+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module provides types and instances for deriving 'ConfigSource' and+-- 'ConfigParser' instances via generic machinery. These types are also how you+-- modify the key representation for your configuration.+module Cfg.Deriving.Config+  ( -- * Deriving Types+    Config (..)+  , ConfigOpts (..)+  , ConfigRoot (..)++    -- * Internal Typeclasses+  , GetConfigOptions (..)+  , ConfigRootOptions (..)+  )+where++import Cfg.Deriving.Assert (AssertTopLevelRecord)+import Cfg.Deriving.KeyModifier+import Cfg.Options+import Cfg.Parser+import Cfg.Parser.Config+import Cfg.Source+import Cfg.Source.Config+import Cfg.Source.Default+import Data.Coerce+import GHC.Generics++-- $setup+-- >>> import GHC.Generics (Generic (..))+-- >>> import Cfg.Source (ConfigSource(..))+-- >>> import Cfg.Parser (ConfigParser(..))+-- >>> import Text.Pretty.Simple++-- | This newtype is the simplest deriving option. It doesn't allow you to+-- alter key names with a 'Cfg.Deriving.KeyModifier.KeyModifier', it only+-- specifies record fields as keys within the configuration tree hierarchy.+-- Therefore it is not possible to derive this for configuration /values/ (such+-- as product types without named record fields, or sum types), only top level+-- records.+--+-- ===== __Example__+--+-- >>> import GHC.Generics (Generic (..))+-- >>> import Cfg.Source (ConfigSource(..))+-- >>> import Cfg.Parser (ConfigParser(..))+-- >>> import Cfg.Deriving.Config (Config(..))+-- >>> import Cfg.Source.Default (DefaultSource(..))+-- >>> :{+-- data AppConfig = AppConfig+--   { appConfigSetting1 :: Int+--   , appConfigSetting2 :: Bool+--   , appConfigSetting3 :: String+--   }+--   deriving (Generic, Show, DefaultSource)+--   deriving (ConfigSource, ConfigParser) via (Config AppConfig)+-- :}+--+-- >>> pPrint $ configSource @AppConfig+-- Free+--     ( fromList+--         [+--             ( "appConfigSetting1"+--             , Free+--                 ( fromList [] )+--             )+--         ,+--             ( "appConfigSetting2"+--             , Free+--                 ( fromList [] )+--             )+--         ,+--             ( "appConfigSetting3"+--             , Free+--                 ( fromList [] )+--             )+--         ]+--     )+--+-- @since 0.0.2.0+newtype Config a = Config {unConfig :: a}++-- | @since 0.0.2.0+instance (Generic a) => Generic (Config a) where+  type Rep (Config a) = Rep a+  to = Config . to+  from (Config x) = from x++-- | This newtype is identical to 'Config' except that it accepts a type+-- argument which can be used to apply a 'Cfg.Deriving.KeyModifier.KeyModifier'+-- to each record field name when generating keys.+--+-- ===== __Example__+--+-- >>> import GHC.Generics (Generic (..))+-- >>> import Cfg.Source (ConfigSource(..))+-- >>> import Cfg.Parser (ConfigParser(..))+-- >>> import Cfg.Deriving.Config (Config(..))+-- >>> import Cfg.Source.Default (DefaultSource(..))+-- >>> :{+-- data AppConfig = AppConfig+--   { appConfigSetting1 :: Int+--   , appConfigSetting2 :: Bool+--   , appConfigSetting3 :: String+--   }+--   deriving (Generic, Show, DefaultSource)+--   deriving (ConfigSource, ConfigParser)+--      via (ConfigOpts '[StripPrefix "app", CamelToSnake, ToUpper] AppConfig)+-- :}+--+-- >>> pPrint $ configSource @AppConfig+-- Free+--     ( fromList+--         [+--             ( "CONFIG_SETTING1"+--             , Free+--                 ( fromList [] )+--             )+--         ,+--             ( "CONFIG_SETTING2"+--             , Free+--                 ( fromList [] )+--             )+--         ,+--             ( "CONFIG_SETTING3"+--             , Free+--                 ( fromList [] )+--             )+--         ]+--     )+--+-- @since 0.0.2.0+newtype ConfigOpts fieldModifier a = ConfigOpts {unConfigOptions :: a}++-- | @since 0.0.2.0+instance (Generic a) => Generic (ConfigOpts t a) where+  type Rep (ConfigOpts t a) = Rep a+  to = ConfigOpts . to+  from (ConfigOpts x) = from x++-- | This newtype is used to derive instances for your root configuration type+-- (i.e. the top level record for all your configuration). The only additional+-- functionality that it provides is that it lets you specify a root key, which+-- is derived from either the type name or the data constructor name. You+-- choose which name you select by providing either+-- 'Cfg.Options.ConstructorName` or 'Cfg.Options.TypeName' as the first type+-- argument to `ConfigRoot`. These `Cfg.Options.RootKey` types also take a type+-- level argument where you can provide key modifiers, if you don't want to+-- apply any key modifiers you can pass in 'Cfg.Deriving.KeyModifier.Identity'+-- or an empty tuple or an empty type level list.+--+-- ===== __@TypeName@ Example__+--+-- >>> import GHC.Generics (Generic (..))+-- >>> import Cfg.Source (ConfigSource(..))+-- >>> import Cfg.Parser (ConfigParser(..))+-- >>> import Cfg.Deriving.Config (Config(..))+-- >>> import Cfg.Source.Default (DefaultSource(..))+-- >>> import Cfg.Deriving.KeyModifier+-- >>> :{+-- data TypeNameConfig = ConfigConstructor+--   { appConfigSetting1 :: Int+--   , appConfigSetting2 :: Bool+--   , appConfigSetting3 :: String+--   }+--   deriving (Generic, Show, DefaultSource)+--   deriving (ConfigSource, ConfigParser)+--      via ConfigRoot+--        ('TypeName '[StripSuffix "Config", CamelToSnake, ToUpper])+--        '[StripPrefix "app", CamelToSnake, ToUpper]+--        TypeNameConfig+-- :}+--+-- >>> pPrint $ configSource @TypeNameConfig+-- Free+--     ( fromList+--         [+--             ( "TYPE_NAME"+--             , Free+--                 ( fromList+--                     [+--                         ( "CONFIG_SETTING1"+--                         , Free+--                             ( fromList [] )+--                         )+--                     ,+--                         ( "CONFIG_SETTING2"+--                         , Free+--                             ( fromList [] )+--                         )+--                     ,+--                         ( "CONFIG_SETTING3"+--                         , Free+--                             ( fromList [] )+--                         )+--                     ]+--                 )+--             )+--         ]+--     )+--+-- ===== __@ConstructorName@ Example__+--+-- >>> :{+-- data TypeNameConfig = ConfigConstructor+--   { appConfigSetting1 :: Int+--   , appConfigSetting2 :: Bool+--   , appConfigSetting3 :: String+--   }+--   deriving (Generic, Show, DefaultSource)+--   deriving (ConfigSource, ConfigParser)+--      via ConfigRoot+--        ('ConstructorName Identity)+--        '[StripPrefix "app", CamelToSnake, ToUpper]+--        TypeNameConfig+-- :}+--+-- >>> pPrint $ configSource @TypeNameConfig+-- Free+--     ( fromList+--         [+--             ( "ConfigConstructor"+--             , Free+--                 ( fromList+--                     [+--                         ( "CONFIG_SETTING1"+--                         , Free+--                             ( fromList [] )+--                         )+--                     ,+--                         ( "CONFIG_SETTING2"+--                         , Free+--                             ( fromList [] )+--                         )+--                     ,+--                         ( "CONFIG_SETTING3"+--                         , Free+--                             ( fromList [] )+--                         )+--                     ]+--                 )+--             )+--         ]+--     )+--+-- @since 0.0.2.0+newtype ConfigRoot rootType fieldModifier a = ConfigRoot {unConfigRoot :: a}++-- | Typeclass for reifying type level field label modifiers into 'Cfg.Options.KeyOptions'+--+-- @since 0.0.2.0+class (KeyModifier t) => GetConfigOptions t where+  getOptions :: KeyOptions++-- | @since 0.0.2.0+instance (KeyModifier t) => GetConfigOptions t where+  getOptions = KeyOptions (getKeyModifier @t)++-- | @since 0.0.2.0+instance (Generic a) => Generic (ConfigRoot r f a) where+  type Rep (ConfigRoot r f a) = Rep a+  to = ConfigRoot . to+  from (ConfigRoot x) = from x++-- | Typeclass for reifying type level arguments into 'Cfg.Options.RootOptions'+--+-- @since 0.0.2.0+class (KeyModifier r, KeyModifier f) => ConfigRootOptions r f where+  configRootOptions :: RootOptions++-- | @since 0.0.2.0+instance (KeyModifier (TypeName k), KeyModifier f) => ConfigRootOptions (TypeName k) f where+  configRootOptions = RootOptions (TypeName $ getKeyModifier @(TypeName k)) (getKeyModifier @f)++-- | @since 0.0.2.0+instance (KeyModifier (ConstructorName k), KeyModifier f) => ConfigRootOptions (ConstructorName k) f where+  configRootOptions = RootOptions (ConstructorName $ getKeyModifier @(ConstructorName k)) (getKeyModifier @f)++-- Source++-- | @since 0.0.2.0+instance+  (AssertTopLevelRecord ConfigSource a, DefaultSource a, Generic a, GConfigSource (Rep a))+  => ConfigSource (Config a)+  where+  configSource = defaultConfigSource @a defaultConfigOptions++-- | @since 0.0.2.0+instance+  ( GetConfigOptions t+  , AssertTopLevelRecord ConfigSource a+  , Generic a+  , DefaultSource a+  , GConfigSource (Rep a)+  )+  => ConfigSource (ConfigOpts t a)+  where+  configSource = defaultConfigSource @a (Key $ getOptions @t)++-- | @since 0.0.2.0+instance+  ( ConfigRootOptions r f+  , AssertTopLevelRecord ConfigSource a+  , Generic a+  , DefaultSource a+  , GConfigSource (Rep a)+  )+  => ConfigSource (ConfigRoot r f a)+  where+  configSource = defaultConfigSource @a (Root $ configRootOptions @r @f)++-- Parser++-- | @since 0.0.2.0+instance+  (AssertTopLevelRecord ConfigParser a, Generic a, GConfigParser (Rep a))+  => ConfigParser (Config a)+  where+  parseConfig keyTree = coerce `asTypeOf` fmap Config $ defaultParseConfig defaultConfigOptions keyTree++-- | @since 0.0.2.0+instance+  ( GetConfigOptions t+  , AssertTopLevelRecord ConfigSource a+  , Generic a+  , GConfigParser (Rep a)+  )+  => ConfigParser (ConfigOpts t a)+  where+  parseConfig keyTree = coerce `asTypeOf` fmap ConfigOpts $ defaultParseConfig (Key $ getOptions @t) keyTree++-- | @since 0.0.2.0+instance+  ( ConfigRootOptions r f+  , AssertTopLevelRecord ConfigParser a+  , Generic a+  , GConfigParser (Rep a)+  )+  => ConfigParser (ConfigRoot r f a)+  where+  parseConfig keyTree = coerce `asTypeOf` fmap ConfigRoot $ defaultParseConfig (Root $ configRootOptions @r @f) keyTree
− src/Cfg/Deriving/ConfigRoot.hs
@@ -1,52 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}-module Cfg.Deriving.ConfigRoot where--import Cfg.Deriving.Assert (AssertTopLevelRecord)-import Cfg.Deriving.LabelModifier (LabelModifier (..))-import Cfg.Options (ConfigOptions (..), RootOptions (..), defaultRootOptions)-import Cfg.Source (RootConfig (..))-import Cfg.Source.RootConfig (GConfigTree, defaultToRootConfig)-import GHC.Generics-import Cfg.Parser.ConfigParser-import Cfg.Parser (RootParser (..))-import Data.Coerce--newtype ConfigRoot a = ConfigRoot {unConfigRoot :: a}--instance Generic a => Generic (ConfigRoot a) where-    type Rep (ConfigRoot a) = Rep a-    to = ConfigRoot . to-    from (ConfigRoot x) = from x--newtype ConfigRootOpts t t' a = ConfigRootOpts {unConfigRootOpts :: a}--instance Generic a => Generic (ConfigRootOpts t t' a) where-    type Rep (ConfigRootOpts t t' a) = Rep a-    to = ConfigRootOpts . to-    from (ConfigRootOpts x) = from x--class (LabelModifier t, LabelModifier t') => GetConfigRootOptions t t' where-    getConfigRootOptions :: RootOptions--instance (LabelModifier t, LabelModifier t') => GetConfigRootOptions t t' where-    getConfigRootOptions = RootOptions (getLabelModifier @t) (ConfigOptions $ getLabelModifier @t')---- Source-instance (AssertTopLevelRecord RootConfig a, Generic a, GConfigTree (Rep a)) => RootConfig (ConfigRoot a) where-    toRootConfig = defaultToRootConfig @a defaultRootOptions--instance (LabelModifier t, LabelModifier t', AssertTopLevelRecord RootConfig a, Generic a, GConfigTree (Rep a)) => RootConfig (ConfigRootOpts t t' a) where-    toRootConfig = defaultToRootConfig @a (getConfigRootOptions @t @t')---- Parser-instance (AssertTopLevelRecord RootConfig a, Generic a, GRootConfigParser (Rep a)) => RootParser (ConfigRoot a) where-  parseRootConfig tree = coerce `asTypeOf` fmap ConfigRoot $ defaultParseRootConfig defaultRootOptions tree--instance-  ( LabelModifier t-  , LabelModifier t'-  , AssertTopLevelRecord RootConfig a-  , Generic a-  , GRootConfigParser (Rep a)-  ) => RootParser (ConfigRootOpts t t' a) where-    parseRootConfig tree = coerce `asTypeOf` fmap ConfigRootOpts $ defaultParseRootConfig (getConfigRootOptions @t @t') tree
− src/Cfg/Deriving/ConfigValue.hs
@@ -1,19 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}-module Cfg.Deriving.ConfigValue where--import GHC.Generics-import Cfg.Parser.ValueParser-import Cfg.Parser-import Data.Coerce--newtype ConfigValue a = ConfigValue {unConfigValue :: a}--instance Generic a => Generic (ConfigValue a) where-    type Rep (ConfigValue a) = Rep a-    to = ConfigValue . to-    from (ConfigValue x) = from x--instance (Generic a, GValueParser (Rep a)) => ValueParser (ConfigValue a) where-    parser = coerce `asTypeOf` fmap ConfigValue $ defaultValueParser @a--instance (Generic a, GValueParser (Rep a)) => NestedParser (ConfigValue a)
+ src/Cfg/Deriving/KeyModifier.hs view
@@ -0,0 +1,198 @@+-- |+--  Module      : Cfg.Deriving.KeyModifier+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module provides type level tags that can be used to configure string+-- transformations against configuration keys derived from things like record+-- fields.+module Cfg.Deriving.KeyModifier+  ( -- * Key Modifiers+    KeyModifier (..)+  , Identity+  , ToLower+  , ToUpper+  , LowerFirst+  , UpperFirst+  , StripPrefix+  , StripSuffix+  , CamelTo+  , CamelToSnake+  , CamelToKebab++    -- * Helper Functions+  , mapFirst+  , camelTo+  , camelToText+  )+where++import Cfg.Options (RootKey (..))+import Data.Char (isLower, isUpper, toLower, toUpper)+import Data.Data (Proxy (..))+import Data.Functor+import Data.Maybe (fromMaybe)+import Data.Text (Text)+import Data.Text qualified as T+import GHC.TypeLits++-- | Identity transformation, corresponds to 'id', does not change the string.+--+-- @since 0.0.2.0+data Identity++-- | Lower cases all alphabetical characters, corresponds to 'Data.Text.toLower'.+--+-- @since 0.0.2.0+data ToLower++-- | Upper cases all alphabetical characters, corresponds to 'Data.Text.toUpper'.+--+-- @since 0.0.2.0+data ToUpper++-- | Lower cases the first character, corresponds to 'Data.Char.toLower'.+--+-- @since 0.0.2.0+data LowerFirst++-- | Upper cases the first character, corresponds to 'Data.Char.toUpper'.+--+-- @since 0.0.2.0+data UpperFirst++-- | Takes a type level string and removes that from the beginning of the text,+-- corresponds to 'Data.Text.stripPrefix'.+--+-- @since 0.0.2.0+data StripPrefix (prefix :: Symbol)++-- | Takes a type level string and removes that from the end of the text,+-- corresponds to 'Data.Text.stripSuffix.+--+-- @since 0.0.2.0+data StripSuffix (suffix :: Symbol)++-- | Takes a type level character known as the \"separator"\ and will break the+-- camel case string on its \"humps\" and then rejoin the string with the+-- separator.+--+-- @since 0.0.2.0+data CamelTo (separator :: Char)++-- | Specialized version of `CamelTo` where the separator is \"_\". Results in+-- snake cased strings.+--+-- @since 0.0.2.0+type CamelToSnake = CamelTo '_'++-- | Specialized version of `CamelTo` where the separator is \"-\". Results in+-- kebab cased strings.+--+-- @since 0.0.2.0+type CamelToKebab = CamelTo '-'++-- | This typeclass turns a type level \"tag\" into a function from @Text ->+-- Text@. In addition to the instances for the \"tags\", there are also+-- instances for type level lists and tuples up to an arity of 4.+--+-- __important__: For type level lists and tuples the modifiers are applied in+-- order from left to right.+--+-- >>> getKeyModifier @'[ToUpper, ToLower] "Hello World"+-- "hello world"+--+-- >>> getKeyModifier @(ToLower, ToUpper) "Hello World"+-- "HELLO WORLD"+--+-- >>> getKeyModifier @CamelToSnake "iLoveCFGProject"+-- "i_love_cfg_project"+--+-- @since 0.0.2.0+class KeyModifier t where+  getKeyModifier :: Text -> Text++instance (KeyModifier k) => KeyModifier ('TypeName k) where+  getKeyModifier = getKeyModifier @k++instance (KeyModifier k) => KeyModifier ('ConstructorName k) where+  getKeyModifier = getKeyModifier @k++instance KeyModifier Identity where+  getKeyModifier = id++instance KeyModifier '() where+  getKeyModifier = id++instance KeyModifier '[] where+  getKeyModifier = id++instance (KeyModifier a, KeyModifier as) => KeyModifier (a ': as) where+  getKeyModifier = getKeyModifier @as . getKeyModifier @a++instance (KeyModifier a, KeyModifier b) => KeyModifier (a, b) where+  getKeyModifier = getKeyModifier @b . getKeyModifier @a++instance (KeyModifier a, KeyModifier b, KeyModifier c) => KeyModifier (a, b, c) where+  getKeyModifier = getKeyModifier @c . getKeyModifier @b . getKeyModifier @a++instance (KeyModifier a, KeyModifier b, KeyModifier c, KeyModifier d) => KeyModifier (a, b, c, d) where+  getKeyModifier = getKeyModifier @d . getKeyModifier @c . getKeyModifier @b . getKeyModifier @a++instance KeyModifier ToLower where+  getKeyModifier = T.toLower++instance KeyModifier ToUpper where+  getKeyModifier = T.toUpper++instance KeyModifier LowerFirst where+  getKeyModifier t = fromMaybe t $ mapFirst toLower t++instance KeyModifier UpperFirst where+  getKeyModifier t = fromMaybe t $ mapFirst toUpper t++instance (KnownSymbol prefix) => KeyModifier (StripPrefix prefix) where+  getKeyModifier label =+    fromMaybe label . T.stripPrefix (T.pack . symbolVal $ Proxy @prefix) $ label++instance (KnownSymbol prefix) => KeyModifier (StripSuffix prefix) where+  getKeyModifier label =+    fromMaybe label . T.stripSuffix (T.pack . symbolVal $ Proxy @prefix) $ label++instance (KnownChar separator) => KeyModifier (CamelTo separator) where+  getKeyModifier = camelToText (charVal $ Proxy @separator)++-- | Map over the first character of a stream of 'Data.Text.Text'+--+-- @since 0.0.2.0+mapFirst :: (Char -> Char) -> Text -> Maybe Text+mapFirst f text = T.uncons text <&> \(first, rest) -> f first `T.cons` rest++-- | Function for breaking a camel case string on its \"humps\" and re-joining+-- on a provided separator char.+--+-- @since 0.0.2.0+camelTo+  :: Char+  -- ^ Separator character+  -> String+  -- ^ Camel cased string+  -> String+camelTo c = map toLower . go2 . go1+ where+  go1 "" = ""+  go1 (x : u : l : xs) | isUpper u && isLower l = x : c : u : l : go1 xs+  go1 (x : xs) = x : go1 xs+  go2 "" = ""+  go2 (l : u : xs) | isLower l && isUpper u = l : c : u : go2 xs+  go2 (x : xs) = x : go2 xs++-- | "Data.Text.Text" version of 'camelTo'+--+-- @since 0.0.2.0+camelToText :: Char -> Text -> Text+camelToText c = T.pack . camelTo c . T.unpack
− src/Cfg/Deriving/LabelModifier.hs
@@ -1,22 +0,0 @@-module Cfg.Deriving.LabelModifier where--import Data.Text (Text)-import Data.Text qualified as T--data ToLower--data ToUpper--data Ident--class LabelModifier t where-    getLabelModifier :: Text -> Text--instance LabelModifier ToLower where-    getLabelModifier = T.toLower--instance LabelModifier ToUpper where-    getLabelModifier = T.toUpper--instance LabelModifier Ident where-    getLabelModifier = id
− src/Cfg/Deriving/SubConfig.hs
@@ -1,46 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}--module Cfg.Deriving.SubConfig where--import Cfg.Deriving.LabelModifier (LabelModifier (..))-import Cfg.Options (ConfigOptions (..), defaultConfigOptions)-import Cfg.Source (NestedConfig (..))-import Cfg.Source.NestedConfig-import GHC.Generics (Generic (..))-import Cfg.Parser.ConfigParser-import Cfg.Parser-import Data.Coerce--newtype SubConfig a = SubConfig {unSubConfig :: a}--instance Generic a => Generic (SubConfig a) where-    type Rep (SubConfig a) = Rep a-    to = SubConfig . to-    from (SubConfig x) = from x--newtype SubConfigOpts t a = SubConfigOpts {unSubConfigOpts :: a}--instance Generic a => Generic (SubConfigOpts t a) where-    type Rep (SubConfigOpts t a) = Rep a-    to = SubConfigOpts . to-    from (SubConfigOpts x) = from x--class GetConfigOptions t where-    getConfigOptions :: ConfigOptions--instance (LabelModifier t) => GetConfigOptions t where-    getConfigOptions = ConfigOptions (getLabelModifier @t)---- Source-instance (Generic a, GConfigForest (Rep a)) => NestedConfig (SubConfig a) where-    toNestedConfig = defaultToNestedConfig @a defaultConfigOptions--instance (GetConfigOptions t, Generic a, GConfigForest (Rep a)) => NestedConfig (SubConfigOpts t a) where-    toNestedConfig = defaultToNestedConfig @a (getConfigOptions @t)---- Parser-instance (Generic a, GNestedParser (Rep a)) => NestedParser (SubConfig a) where-  parseNestedConfig tree = coerce `asTypeOf` fmap SubConfig $ defaultParseNestedConfig defaultConfigOptions tree--instance (GetConfigOptions t, Generic a, GNestedParser (Rep a)) => NestedParser (SubConfigOpts t a) where-  parseNestedConfig tree = coerce `asTypeOf` fmap SubConfigOpts $ defaultParseNestedConfig (getConfigOptions @t) tree
+ src/Cfg/Deriving/Value.hs view
@@ -0,0 +1,40 @@+-- |+--  Module      : Cfg.Deriving.Value+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module provides a type 'Value' for generating instances for \"leaf\"+-- elements of your configuration tree. These are the elements you actually+-- care about and want to parse out of a configuration source.+module Cfg.Deriving.Value where++import Cfg.Parser+import Cfg.Parser.Value+import Data.Coerce+import GHC.Generics++-- | This newtype is used to derive 'ValueParser' instances for your types+-- using the deriving via mechanism. In general this should be used for sum+-- types, and product types without named fields (i.e. not records). The+-- majority of the types that you would want as values should have instances in+-- "Cfg.Source" and "Cfg.Parser".+--+-- @since 0.0.2.0+newtype Value a = Value {unValue :: a}++-- | @since 0.0.2.0+instance (Generic a) => Generic (Value a) where+  type Rep (Value a) = Rep a+  to = Value . to+  from (Value x) = from x++-- | @since 0.0.2.0+instance (Generic a, GValueParser (Rep a)) => ValueParser (Value a) where+  parser = coerce `asTypeOf` fmap Value $ defaultValueParser @a++-- | @since 0.0.2.0+instance (Generic a, GValueParser (Rep a)) => ConfigParser (Value a)
src/Cfg/Env.hs view
@@ -1,55 +1,154 @@-module Cfg.Env where+-- |+--  Module      : Cfg.Env+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains all the functions for interacting with environment+-- variables as a configuration source.+module Cfg.Env+  ( -- * Retrieval Functions+    envSourceSep+  , envSource+  , getEnvConfigSep+  , getEnvConfig +    -- * Printing Functions+  , printDotEnv'+  , printDotEnv+  )+where++import Cfg+import Cfg.Env.Keys+import Cfg.Parser+import Cfg.Source import Control.Monad.IO.Class (MonadIO, liftIO) import Data.List (intersperse)+import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO (writeFile)-import Data.Tree (Tree)+import KeyTree import System.Environment (lookupEnv)-import Tree.Append (mayAppendLeafA') import Prelude hiding (writeFile)-import Cfg.Parser-import Cfg.Source (RootConfig, toRootConfig)-import Cfg-import Cfg.Env.Keys --- | @since 0.0.1.0+-- | This function folds the tree from root to leaf accumulating the keys along+-- the way. At the leaf we lookup the aggregated key in the environment, if+-- there is a default then we use that for missing keys.+--+-- If you are looking at the source code this is what the functions in the+-- @where@ clause do:+--+--    - @valF@: Gets called on 'Pure' values in the original tree passed in,+--    these indicate defaulted values, so we use the default if looking the+--    value up in the environment failed.+--+--    - @accF@: This operates on the accumulated key, and is responsible for+--    looking up the value in the environment when we hit the @Free M.empty@+--    case.+--+--    - @stepF@: This is the step function for the fold and accumulates the+--    keys as we traverse down the tree.+--+--    - @mkKey@: This function is the same as 'Cfg.Env.Keys.getEnvKey' except+--    that it uses @flip mappend@. The reason for this is that we insert keys+--    into the accumulator as we traverse down so they end up in reversed+--    order, then we @foldr@ over them so we just need to make sure that we are+--    placing the elements at the end of the list on the left hand side of the+--    aggregate key.+--+-- @since 0.0.1.0 envSourceSep   :: forall m-   . (MonadFail m, MonadIO m)+   . (MonadIO m)   => Text-  -> Tree Text-  -> m (Tree Text)-envSourceSep sep = mayAppendLeafA' getLeafFromEnv []+  -- ^ Separator+  -> KeyTree Text Text+  -- ^ Configuration source+  -> m (KeyTree Text Text)+  -- ^ Configuration tree with values filled in+envSourceSep sep = mayAppendTraverse valF accF stepF []  where-  getLeafFromEnv :: [Text] -> m (Maybe Text)-  getLeafFromEnv keys = do-    let-      key = foldr (flip mappend) "" $ intersperse sep keys+  valF :: [Text] -> Text -> m Text+  valF keys def = fromMaybe def <$> accF keys -    liftIO $ (fmap T.pack) <$> (lookupEnv $ T.unpack key)+  accF :: [Text] -> m (Maybe Text)+  accF = fmap (fmap T.pack) . liftIO . lookupEnv . T.unpack . mkKey --- | @since 0.0.1.0-envSource :: (MonadFail m, MonadIO m) => Tree Text -> m (Tree Text)+  stepF :: Text -> [Text] -> KeyForest Text Text -> [Text]+  stepF key acc _ = key : acc++  mkKey :: [Text] -> Text+  mkKey keys = foldr (flip mappend) "" $ intersperse sep keys++-- | This function is the same as 'envSourceSep' but with the separator specialized to \"_\".+--+-- >>> import System.Environment+-- >>> import Data.Map qualified as M+-- >>> setEnv "A_B" "Functor"+-- >>> setEnv "A_C" "Applicative"+-- >>> setEnv "A_D" "Monad"+-- >>> envSource $ Free $ M.fromList [("A", Free $ M.fromList [("B", Free M.empty), ("C", Free M.empty), ("D", Free M.empty)])]+-- Free (fromList [("A",Free (fromList [("B",Pure "Functor"),("C",Pure "Applicative"),("D",Pure "Monad")]))])+--+-- @since 0.0.1.0+envSource :: (MonadFail m, MonadIO m) => KeyTree Text Text -> m (KeyTree Text Text) envSource = envSourceSep "_" --- | @since 0.0.1.0-printDotEnv' :: FilePath -> Text -> Tree Text -> IO ()+-- | This function can be used to print a dotenv style file with all the+-- aggregate keys, none of the values will be filled in.+--+-- Useful for testing what your expected environment variables should look+-- like, and generating an env var file template.+--+-- @since 0.0.1.0+printDotEnv'+  :: FilePath+  -- ^ Destination filepath+  -> Text+  -- ^ Separator+  -> KeyTree Text Text+  -- ^ Source representation+  -> IO () printDotEnv' path sep = writeFile path . foldMap (\line -> "export " <> line <> "=\n") . showEnvKeys' sep --- | @since 0.0.1.0-getEnvConfigSep :: (MonadFail m, MonadIO m, RootConfig a, RootParser a) => Text -> m (Either ConfigParseError a)+-- | Requires a type annotation for your configuration type (with a+-- 'ConfigSource' and 'ConfigParser' instance), and a separator, and will go+-- out and fetch the values from environment variables then return your type+-- parsed from those values.+--+-- @getEnvConfigSep \@AppConfig "_"@+--+-- @since 0.0.1.0+getEnvConfigSep+  :: forall a m+   . (MonadFail m, MonadIO m, ConfigSource a, ConfigParser a)+  => Text+  -- ^ Separator+  -> m (Either ConfigParseError a) getEnvConfigSep sep = getConfig $ envSourceSep sep --- | @since 0.0.1.0-getEnvConfig :: (MonadFail m, MonadIO m, RootConfig a, RootParser a) => m (Either ConfigParseError a)+-- | The same as 'getEnvConfigSep' but with the separator hard coded to \"_\"+--+-- @since 0.0.1.0+getEnvConfig+  :: forall a m. (MonadFail m, MonadIO m, ConfigSource a, ConfigParser a) => m (Either ConfigParseError a) getEnvConfig = getConfig $ envSource --- | @since 0.0.1.0-printDotEnv :: forall a. (RootConfig a) => FilePath -> IO ()+-- | The same as 'printDotEnv'' but with the separator hard coded to \"_\" and+-- it uses a type application to generate the configuration source tree+-- representation.+--+-- @printDotEnv \@AppConfig ".env"@+--+-- @since 0.0.1.0+printDotEnv :: forall a. (ConfigSource a) => FilePath -> IO () printDotEnv path =   writeFile path     . foldMap (\line -> "export " <> line <> "=\n")     . showEnvKeys' "_"-    $ toRootConfig @a+    $ configSource @a
src/Cfg/Env/Keys.hs view
@@ -1,27 +1,113 @@+-- |+--  Module      : Cfg.Env.Keys+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module provides helper functions for manipulating the keys on our+-- internal tree representation of a configuration; 'Cfg.KeyTree.KeyTree'.+--+-- These helper functions are generally oriented towards using environment+-- variables as your configuration source. module Cfg.Env.Keys where -import Data.Text (Text)-import Data.Tree (Tree, foldTree)-import Cfg.Source (RootConfig (..))+import Cfg.Source (ConfigSource (..))+import Data.Foldable import Data.List (intersperse)+import Data.Map.Strict (empty)+import Data.Text (Text)+import KeyTree --- | @since 0.0.1.0-getEnvKey :: Text -> [Text] -> Text-getEnvKey sep = foldr mappend "" . intersperse sep+-- | This function takes a separator and a list of keys and joins them from the+-- end of the list to the beginning, interspersed with the provided separator.+--+-- >>> getEnvKey "_" ["A", "B", "C"]+-- "A_B_C"+--+-- @since 0.0.1.0+getEnvKey+  :: Text+  -- ^ Separator+  -> [Text]+  -- ^ List of keys+  -> Text+getEnvKey sep = fold . intersperse sep --- | @since 0.0.1.0-getKeys :: Tree Text -> [[Text]]-getKeys = foldTree f+-- | Folds a 'Cfg.KeyTree.KeyTree' from leaf to root, into distinct key paths.+-- This is necessary for the way that hierarchical structures are represented+-- in environment variables (i.e. \"KEYA_SUBKEYA\", \"KEYA_SUBKEYB\").+--+-- Here is a visual representation of how the keys would get folded+--+-- @+--       A+--      / \\+--     B   C+--+--  [ [ "A", "B" ]+--  , [ "A", "C" ]+--  ]+-- @+-- >>> import KeyTree+-- >>> import Data.Map qualified as M+-- >>> getKeys $ Free $ M.singleton "A" $ Free (M.fromList [("B", Free M.empty), ("C", Free M.empty)])+-- [["A","B"],["A","C"]]+--+-- @since 0.0.1.0+getKeys :: KeyTree Text Text -> [[Text]]+getKeys = foldKeyTree valF stepF []  where-  f :: Text -> [[[Text]]] -> [[Text]]-  f label [] = [[label]]-  f label xs = concat $ fmap (label :) <$> xs+  stepF :: Text -> Free (Map Text) Text -> [[Text]] -> [[Text]]+  stepF key (Pure _) acc = [key] : acc+  stepF key t@(Free m) acc =+    if m == empty+      then [key] : acc+      else fmap (key :) (getKeys t) <> acc --- | @since 0.0.1.0-showEnvKeys' :: Text -> Tree Text -> [Text]-showEnvKeys' sep tree = getEnvKey sep <$> getKeys tree+  valF :: Text -> [[Text]]+  valF _ = [] --- | @since 0.0.1.0-showEnvKeys :: forall a. (RootConfig a) => Text -> [Text]-showEnvKeys sep = getEnvKey sep <$> (getKeys $ toRootConfig @a)+-- | Gets all the keys from a configuration tree, and flattens the hierarchy so that+-- each key is prefixed with its path through the tree.+--+-- Accepts separator to individuate the key prefixes.+--+-- >>> import KeyTree+-- >>> import Data.Map qualified as M+-- >>> showEnvKeys' "-" $ Free $ M.singleton "A" $ Free (M.fromList [("B", Free M.empty), ("C", Free M.empty)])+-- ["A-B","A-C"]+--+-- @since 0.0.1.0+showEnvKeys'+  :: Text+  -- ^ Separator+  -> KeyTree Text Text+  -- ^ Configuration tree+  -> [Text]+showEnvKeys' sep tree = getEnvKey sep <$> getKeys tree +-- | Same as 'showEnvKeys'' but the 'KeyTree.KeyTree' is generated via a 'configSource'+--+-- >>> import GHC.Generics (Generic (..))+-- >>> import Cfg.Source (ConfigSource(..))+-- >>> import Cfg.Parser (ConfigParser(..))+-- >>> import Cfg.Deriving.Config (Config(..))+-- >>> import Cfg.Source.Default (DefaultSource(..))+-- >>> :{+-- data Sub = Sub { c :: Int, d :: Bool }+--   deriving (Generic, Show, DefaultSource)+--   deriving (ConfigSource, ConfigParser) via Config Sub+-- data TypeCon = DataCon { a :: Sub }+--   deriving (Generic, Show, DefaultSource)+--   deriving (ConfigSource, ConfigParser) via Config TypeCon+-- :}+--+-- >>> showEnvKeys @TypeCon "_"+-- ["a_c","a_d"]+--+-- @since 0.0.1.0+showEnvKeys :: forall a. (ConfigSource a) => Text -> [Text]+showEnvKeys sep = getEnvKey sep <$> (getKeys $ configSource @a)
src/Cfg/Options.hs view
@@ -1,17 +1,87 @@-module Cfg.Options where+-- |+--  Module      : Cfg.Options+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains the options types that we pass to our generic+-- functions. Mostly these are used for registering text transformations on+-- keys, but another interesting use case is choosing between the type+-- constructor and the data constructor for deriving a name for root keys.+module Cfg.Options+  ( -- * Option Types+    KeyOptions (..)+  , RootKey (..)+  , RootOptions (..)+  , ConfigOptions (..) +    -- * Helper Functions+  , defaultKeyOptions+  , defaultRootOptions+  , defaultConfigOptions+  , keyModifier+  )+where+ import Data.Text (Text) +-- | Options that pertain to record field accessors.+--+-- @since 0.0.1.0+data KeyOptions = KeyOptions+  { keyOptionsModifier :: Text -> Text+  }++-- | Default key options, does no transformation to record field accessors.+--+-- @since 0.0.2.0+defaultKeyOptions :: KeyOptions+defaultKeyOptions = KeyOptions id++-- | Type that represents a decision between using the type constructor name or+-- the data constructor name as the root key.+--+-- This type is polymorphic so that we can use it to contain a term level text+-- transformation for root keys, as well as be used at the type level+-- parameterized by a type that defines the key modifiers to use.+--+-- @since 0.0.2.0+data RootKey a = ConstructorName a | TypeName a++-- | Options for manipulating a root key+--+-- @since 0.0.2.0 data RootOptions = RootOptions-  { rootOptionsLabelModifier :: Text -> Text-  , rootOptionsFieldOptions :: ConfigOptions+  { rootOptionsRootKey :: RootKey (Text -> Text)+  , rootOptionsModifier :: Text -> Text   } +-- | Default root key option, uses the type constructor name for the root key+-- and applies no transformations to the root key or keys derived from record+-- fields.+--+-- @since 0.0.2.0 defaultRootOptions :: RootOptions-defaultRootOptions = RootOptions id (ConfigOptions id)+defaultRootOptions = RootOptions (TypeName id) id -data ConfigOptions = ConfigOptions-  {configOptionsLabelModifier :: Text -> Text}+-- | Represents all possible kinds of configuration options.+--+-- @since 0.0.2.0+data ConfigOptions = Root RootOptions | Key KeyOptions +-- | Defaults to regular 'KeyOptions' (not 'RootOptions')+--+-- @since 0.0.2.0 defaultConfigOptions :: ConfigOptions-defaultConfigOptions = ConfigOptions id+defaultConfigOptions = Key defaultKeyOptions++-- | Helper function that allows us to generically extract the record field+-- modifiers from either a 'RootOptions' or a 'KeyOptions' record.+--+-- @since 0.0.2.0+keyModifier :: ConfigOptions -> (Text -> Text)+keyModifier (Root options) = rootOptionsModifier options+keyModifier (Key options) = keyOptionsModifier options
src/Cfg/Parser.hs view
@@ -1,143 +1,223 @@ {-# LANGUAGE DefaultSignatures #-} -module Cfg.Parser where+-- |+--  Module      : Cfg.Parser+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains the type classes for parsing a configuration type from+-- a source, as well as instances for most basic Haskell types. One important+-- interaction to note is that we use a default instance for 'ConfigParser'+-- that dispatches to a 'ValueParser' instances. This is how we distinguish+-- between a \"parser\" that just navigates the tree representation of our+-- configuration and a parser that actually converts from text to our Haskell+-- type.+module Cfg.Parser+  ( -- * Parser Typeclasses+    ConfigParser (..)+  , ValueParser (..) +    -- * Parser Types+  , Parser+  , ConfigParseError (..)+  )+where+ import Control.Error (note) import Data.ByteString qualified as BS import Data.ByteString.Lazy qualified as BL-import Data.Functor (void, ($>))+import Data.Functor (void) import Data.Int import Data.List.NonEmpty (NonEmpty, fromList)+import Data.Map.Strict qualified as M import Data.Text (Text) import Data.Text qualified as T import Data.Text.Encoding (encodeUtf8) import Data.Text.Lazy qualified as TL-import Data.Tree (Tree (..)) import Data.Void (Void) import Data.Word import GHC.Generics (Generic)-import Text.Megaparsec (Parsec, anySingle, between, empty, option, parseMaybe, sepBy, sepBy1, some, takeRest, try, (<|>))+import KeyTree+import Text.Megaparsec+  ( Parsec+  , anySingle+  , between+  , empty+  , option+  , parseMaybe+  , sepBy+  , sepBy1+  , some+  , takeRest+  , try+  , (<|>)+  ) import Text.Megaparsec.Char (char, digitChar, space1, string, string') import Text.Megaparsec.Char.Lexer qualified as L +-- | Type alias for our megaparsec parser+--+-- @since 0.0.1.0 type Parser = Parsec Void Text +-- | Type errors we can encounter when parsing+--+-- @since 0.0.2.0 data ConfigParseError-  = UnmatchedFields [Tree Text]-  | MismatchedRootKey Text Text-  | MismatchedKeyAndField Text (Text, Text)-  | MissingKeys [Text]-  | MissingValue Text-  | UnexpectedKeys Text [Tree Text]-  | ValueParseError Text+  = -- | We encountered a 'Data.Map.Map' that was missing a key.+    MissingKey+      Text+      -- ^ The record field name that was missing.+      (KeyTree Text Text)+      -- ^ The subtree that was missing an entry.+  | -- | Expected to find a subtree aka a 'Free' with a map in it, but instead+    -- we found a 'Pure'.+    ExpectedKeyFoundValue+      Text+      -- ^ The key that was missing+      Text+      -- ^ The value that was found+  | -- | Expected to find a 'Pure' with a value but instead found a subtree+    ExpectedValueFoundForest+      (KeyTree Text Text)+      -- ^ The subtree that was found instead+  | -- | Ran a 'parser' and was unable to parse value+    ValueParseError+      Text+      -- ^ The parser error   deriving (Eq, Show, Generic) -class RootParser a where-  parseRootConfig :: Tree Text -> Either ConfigParseError a+-- | This is the instance that allows us to parse a result from our+-- configuration source after we have retrieved it.+--+-- @since 0.0.2.0+class ConfigParser a where+  -- | Takes in the tree representation of our configuration and parses out our Haskell type+  --+  --  The default instance allows us to wrap a 'ValueParser' in a+  --  'ConfigParser', this allows us to use a uniform typeclass for parsing,+  --  but at the same time distinguish between traversing the key structure and+  --  actually parsing the textual value.+  parseConfig :: KeyTree Text Text -> Either ConfigParseError a+  default parseConfig :: (ValueParser a) => KeyTree Text Text -> Either ConfigParseError a+  parseConfig (Pure val) = note (ValueParseError val) $ parseMaybe parser val+  parseConfig kt = Left $ ExpectedValueFoundForest kt -class NestedParser a where-  parseNestedConfig :: Tree Text -> Either ConfigParseError a-  default parseNestedConfig :: (ValueParser a) => Tree Text -> Either ConfigParseError a-  parseNestedConfig (Node val []) = note (ValueParseError val) $ parseMaybe parser val-  parseNestedConfig (Node label xs) = Left $ UnexpectedKeys label xs+-- | This is a text parser that we use to parse the eventual values we get from a+-- configuration.+--+-- @since 0.0.2.0+class ValueParser a where+  parser :: Parser a +-- | Lexer parser helper.+--+-- @since 0.0.2.0 sp :: Parsec Void Text () sp = L.space space1 empty empty -class ValueParser a where-  parser :: Parser a- -- | @since 0.0.1.0 instance ValueParser () where   parser = string "()" >> pure ()  -- | @since 0.0.1.0-instance NestedParser ()+instance ConfigParser ()  -- | @since 0.0.1.0 instance ValueParser Bool where   parser = try (string' "true" >> pure True) <|> (string' "false" >> pure False)  -- | @since 0.0.1.0-instance NestedParser Bool+instance ConfigParser Bool  -- | @since 0.0.1.0 instance ValueParser Char where   parser = anySingle  -- | @since 0.0.1.0-instance NestedParser Char+instance ConfigParser Char  -- | @since 0.0.1.0 instance ValueParser TL.Text where   parser = TL.fromStrict <$> takeRest  -- | @since 0.0.1.0-instance NestedParser TL.Text+instance ConfigParser TL.Text  -- | @since 0.0.1.0 instance ValueParser BL.ByteString where   parser = BL.fromStrict . encodeUtf8 <$> takeRest  -- | @since 0.0.1.0-instance NestedParser BL.ByteString+instance ConfigParser BL.ByteString  -- | @since 0.0.1.0 instance ValueParser BS.ByteString where   parser = encodeUtf8 <$> takeRest  -- | @since 0.0.1.0-instance NestedParser BS.ByteString+instance ConfigParser BS.ByteString --- @since 0.0.1.0+-- | @since 0.0.1.0 instance ValueParser Text where   parser = takeRest  -- | @since 0.0.1.0-instance NestedParser Text+instance ConfigParser Text  -- | @since 0.0.1.0-instance ValueParser a => ValueParser [a] where+instance (ValueParser a) => ValueParser [a] where   parser = between (L.symbol sp "[") (L.symbol sp "]") $ parser @a `sepBy` (L.symbol sp ",")  -- | @since 0.0.1.0-instance ValueParser a => NestedParser [a]+instance (ValueParser a) => ConfigParser [a]  -- | @since 0.0.1.0-instance ValueParser a => ValueParser (NonEmpty a) where+instance (ValueParser a) => ValueParser (NonEmpty a) where   parser = between (L.symbol sp "[") (L.symbol sp "]") $ fromList <$> parser @a `sepBy1` (L.symbol sp ",")  -- | @since 0.0.1.0-instance ValueParser a => NestedParser (NonEmpty a)+instance (ValueParser a) => ConfigParser (NonEmpty a)  -- | @since 0.0.1.0-instance ValueParser a => ValueParser (Maybe a) where-  parser =-    (try (string "Nothing") $> Nothing)-      <|> (L.symbol sp "Just" >> (Just <$> parser @a))--instance ValueParser a => NestedParser (Maybe a)+instance (ValueParser a) => ConfigParser (Maybe a) where+  parseConfig (Free m) =+    if m == M.empty+      then Right Nothing+      else Left $ ExpectedValueFoundForest (Free m)+  parseConfig (Pure v) = Just <$> note (ValueParseError v) (parseMaybe (parser @a) v) --- Numeric Types+-- Numeric parser helpers -rd :: Read a => Text -> a+-- | @since 0.0.2.0+rd :: (Read a) => Text -> a rd = read . T.unpack +-- | @since 0.0.2.0 plus :: Parser Text plus = char '+' >> number +-- | @since 0.0.2.0 minus :: Parser Text minus = liftA2 (T.cons) (char '-') number +-- | @since 0.0.2.0 number :: Parser Text number = T.pack <$> some digitChar +-- | @since 0.0.2.0 decimal :: Parser Text decimal = option "" $ (T.cons) <$> char '.' <*> number +-- | @since 0.0.2.0 integral :: (Read a) => Parser a integral = rd <$> (try plus <|> try minus <|> number) +-- | @since 0.0.2.0 fractional :: (Read a) => Parser a fractional = fmap rd $ liftA2 (<>) integral decimal @@ -145,79 +225,92 @@ instance ValueParser Double where   parser = fractional -instance NestedParser Double+-- | @since 0.0.1.0+instance ConfigParser Double  -- | @since 0.0.1.0 instance ValueParser Float where   parser = fractional -instance NestedParser Float+-- | @since 0.0.1.0+instance ConfigParser Float --- @since 0.0.1.0+-- | @since 0.0.1.0 instance ValueParser Int where   parser = integral -instance NestedParser Int+-- | @since 0.0.1.0+instance ConfigParser Int  -- | @since 0.0.1.0 instance ValueParser Int8 where   parser = integral -instance NestedParser Int8+-- | @since 0.0.1.0+instance ConfigParser Int8  -- | @since 0.0.1.0 instance ValueParser Int16 where   parser = integral -instance NestedParser Int16+-- | @since 0.0.1.0+instance ConfigParser Int16  -- | @since 0.0.1.0 instance ValueParser Int32 where   parser = integral -instance NestedParser Int32+-- | @since 0.0.1.0+instance ConfigParser Int32  -- | @since 0.0.1.0 instance ValueParser Int64 where   parser = integral -instance NestedParser Int64+-- | @since 0.0.1.0+instance ConfigParser Int64  -- | @since 0.0.1.0 instance ValueParser Integer where   parser = integral -instance NestedParser Integer+-- | @since 0.0.1.0+instance ConfigParser Integer  -- | @since 0.0.1.0 instance ValueParser Word where   parser = rd <$> number -instance NestedParser Word+-- | @since 0.0.1.0+instance ConfigParser Word  -- | @since 0.0.1.0 instance ValueParser Word8 where   parser = rd <$> number -instance NestedParser Word8+-- | @since 0.0.1.0+instance ConfigParser Word8  -- | @since 0.0.1.0 instance ValueParser Word16 where   parser = rd <$> number -instance NestedParser Word16+-- | @since 0.0.1.0+instance ConfigParser Word16  -- | @since 0.0.1.0 instance ValueParser Word32 where   parser = rd <$> number -instance NestedParser Word32+-- | @since 0.0.1.0+instance ConfigParser Word32  -- | @since 0.0.1.0 instance ValueParser Word64 where   parser = rd <$> number -instance NestedParser Word64+-- | @since 0.0.1.0+instance ConfigParser Word64  -- | @since 0.0.1.0 instance (ValueParser a, ValueParser b) => ValueParser (a, b) where@@ -228,4 +321,4 @@     pure (a, b)  -- | @since 0.0.1.0-instance (ValueParser a, ValueParser b) => NestedParser (a, b)+instance (ValueParser a, ValueParser b) => ConfigParser (a, b)
+ src/Cfg/Parser/Config.hs view
@@ -0,0 +1,135 @@+-- |+--  Module      : Cfg.Parser.Config+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains the generic machinery for building parsers from the+-- structure of a type. The majority of the work here is threading a+-- 'Cfg.Parser.Value.ValueParser' through the 'KeyTree.KeyTree' structure until+-- a 'Pure' value is hit, and then dispatching the correct parser.+module Cfg.Parser.Config+  ( -- * Default Parser Function+    defaultParseConfig++    -- * Generic Machinery+  , GConfigParser (..)+  )+where++import Cfg.Options+import Cfg.Parser (ConfigParseError (..), ConfigParser (..))+import Data.Kind (Type)+import Data.Map qualified as M+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Generics+import KeyTree++-- | This function is the workhorse of the generic machinery, however the user+-- should never have to invoke it directly. Instead, one of the newtypes from+-- 'Cfg.Deriving.Config' should call into this function in the definition of a+-- 'Cfg.Parser.ConfigParser' instance. The deriving via type should pull out+-- the 'ConfigOptions' from type level information.+--+-- @since 0.0.1.0+defaultParseConfig+  :: forall a+   . (Generic a, GConfigParser (Rep a))+  => ConfigOptions+  -> KeyTree Text Text+  -> Either ConfigParseError a+defaultParseConfig opts tree = fmap to $ gParseConfig opts tree++-- | This class is the generic version of 'ConfigParser'. It recurses on the+-- generic structure of a type, building up a return type for the parser.+--+-- @since 0.0.2.0+class GConfigParser (f :: Type -> Type) where+  gParseConfig :: ConfigOptions -> KeyTree Text Text -> Either ConfigParseError (f p)++-- | This is the \"base case\", since "GHC.Generics" don't recurse the generic+-- represetation multiple levels, @a@ is just a plain type. Therefore we call+-- 'parseConfig' on it. @a@ may be another nested record, in which case+-- 'gParseConfig' will probably get called again, but for the generic+-- representation of a sub-tree. Or it will find the default instance for+-- 'ConfigParser' (indicating that we have reached a leaf) and dispatch to a+-- value parser through 'parseConfig'.+--+-- @since 0.0.2.0+instance (ConfigParser a) => GConfigParser (K1 R a) where+  gParseConfig _ kt = K1 <$> parseConfig kt++-- | This is the type constructor case, if we are dealing with a+-- 'Cfg.Deriving.Config.ConfigRoot' instance, then we have lookup the \"root+-- key\", but in all other cases we just keep recursing.+--+-- @since 0.0.2.0+instance (GConfigParser f, Datatype d) => GConfigParser (M1 D d f) where+  gParseConfig opts t@(Free keyForest) =+    case opts of+      Root (RootOptions{rootOptionsRootKey = TypeName modifier}) ->+        let+          key = modifier . T.pack $ datatypeName @d undefined+        in+          case M.lookup key keyForest of+            Just subTree -> M1 <$> gParseConfig opts subTree+            Nothing -> Left $ MissingKey key t+      _ -> M1 <$> gParseConfig opts t+  gParseConfig opts (Pure value) = Left $ ExpectedKeyFoundValue key value+   where+    key = keyModifier opts . T.pack $ datatypeName @d undefined++-- | This is the data constructor case, if we are dealing with a+-- 'Cfg.Deriving.Config.ConfigRoot' instance, then we have lookup the \"root+-- key\", but in all other cases we just keep recursing.+--+-- @since 0.0.2.0+instance (Constructor c, GConfigParser f) => GConfigParser (M1 C c f) where+  gParseConfig opts t@(Free keyForest) =+    case opts of+      Root (RootOptions{rootOptionsRootKey = ConstructorName modifier}) ->+        let+          key = modifier . T.pack $ conName @c undefined+        in+          case M.lookup key keyForest of+            Just subTree -> M1 <$> gParseConfig opts subTree+            Nothing -> Left $ MissingKey key t+      _ -> M1 <$> gParseConfig opts t+  gParseConfig opts (Pure value) = Left $ ExpectedKeyFoundValue key value+   where+    key = keyModifier opts . T.pack $ conName @c undefined++-- | This is the most important case, we need to look up the subconfig by key+-- (just the record field with all key modifiers applied), and then recursively+-- parse the sub tree.+--+-- @since 0.0.2.0+instance (Selector s, GConfigParser f) => GConfigParser (M1 S s f) where+  gParseConfig opts (Pure value) = Left $ ExpectedKeyFoundValue key value+   where+    key = keyModifier opts . T.pack $ selName @s undefined+  gParseConfig opts t@(Free keyForest) =+    case M.lookup selectorName keyForest of+      Nothing -> Left $ MissingKey selectorName t+      Just subTree -> M1 <$> gParseConfig opts subTree+   where+    selectorName = keyModifier opts . T.pack $ selName @s undefined++-- | This is the product case, we just distribute the parsers over the+-- different product fields.+--+-- Notably, there is no sum type case. We could potentially add that in the future,+-- allowing users to specify different cases of configuration. But right now+-- that seems like it would be more confusing than helpful, so we just give a+-- type error by eliding the instance.+--+-- @since 0.0.2.0+instance (GConfigParser a, GConfigParser b) => GConfigParser (a :*: b) where+  gParseConfig opts xs = do+    a <- gParseConfig opts xs+    b <- gParseConfig opts xs+    pure $ a :*: b
− src/Cfg/Parser/ConfigParser.hs
@@ -1,94 +0,0 @@-{-# LANGUAGE UndecidableInstances #-}-module Cfg.Parser.ConfigParser where--import Cfg.Options (ConfigOptions (..), RootOptions (..))-import Cfg.Parser (ConfigParseError (..), NestedParser (..))-import Data.Kind (Type)-import Data.Text (Text)-import Data.Text qualified as T-import Data.Tree (Tree (..))-import GHC.Generics-import Data.List--defaultParseRootConfig ::-    forall a.-    (Generic a, GRootConfigParser (Rep a)) =>-    RootOptions ->-    Tree Text ->-    Either ConfigParseError a-defaultParseRootConfig opts tree = fmap to $ gParseRootConfig opts tree--class GRootConfigParser (f :: Type -> Type) where-    gParseRootConfig :: RootOptions -> Tree Text -> Either ConfigParseError (f p)--instance GRootConfigParser f => GRootConfigParser (M1 D s f) where-    gParseRootConfig opts tree = M1 <$> gParseRootConfig opts tree--instance (Selector s, GNestedParser f) => GRootConfigParser (M1 S s f) where-    gParseRootConfig opts tree = M1 <$> gParseNestedConfig (rootOptionsFieldOptions opts) tree--instance (Constructor c, GRootConfigParser f) => GRootConfigParser (M1 C c f) where-    gParseRootConfig opts t@(Node label _) =-        if label == (rootOptionsLabelModifier opts . T.pack $ conName m)-            then M1 <$> gParseRootConfig opts t-            else Left $ MismatchedRootKey label (rootOptionsLabelModifier opts . T.pack $ conName m)-      where-        m :: t c f a-        m = undefined--instance (GFieldParser (a :*: b)) => GRootConfigParser (a :*: b) where-    gParseRootConfig opts (Node _ forest) = gParseFields (rootOptionsFieldOptions opts) forest--class FieldParser a where-    parseFields :: [Tree Text] -> Either ConfigParseError a--class GFieldParser (f :: Type -> Type) where-    gParseFields :: ConfigOptions -> [Tree Text] -> Either ConfigParseError (f p)--instance (Selector s, GNestedParser f) => GFieldParser (M1 S s f) where-    gParseFields opts xs = case find ((==) (configOptionsLabelModifier opts . T.pack $ selName @s undefined) . rootLabel) xs of-                              Nothing -> Left $ MissingKeys [configOptionsLabelModifier opts . T.pack $ selName @s undefined]-                              Just t -> M1 <$> gParseNestedConfig opts t--instance (GFieldParser a, GFieldParser b) => GFieldParser (a :*: b) where-    gParseFields opts xs = do-        a <- gParseFields opts xs-        b <- gParseFields opts xs-        pure $ a :*: b--defaultParseNestedConfig ::-    forall a.-    (Generic a, GNestedParser (Rep a)) =>-    ConfigOptions ->-    Tree Text ->-    Either ConfigParseError a-defaultParseNestedConfig opts tree = fmap to $ gParseNestedConfig opts tree--class GNestedParser (f :: Type -> Type) where-    gParseNestedConfig :: ConfigOptions -> Tree Text -> Either ConfigParseError (f p)--instance NestedParser a => GNestedParser (K1 R a) where-    gParseNestedConfig _ (Node label []) = Left $ MissingValue label-    gParseNestedConfig _ (Node _ [val]) = K1 <$> parseNestedConfig val-    gParseNestedConfig _ tree = K1 <$> parseNestedConfig tree--instance (GNestedParser f) => GNestedParser (M1 D c f) where-    gParseNestedConfig opts t = M1 <$> gParseNestedConfig opts t--instance (Constructor c, GNestedParser f) => GNestedParser (M1 C c f) where-    gParseNestedConfig opts t = M1 <$> gParseNestedConfig opts t--instance (Selector s, GNestedParser f) => GNestedParser (M1 S s f) where-    gParseNestedConfig opts t@(Node label _) =-        if label == modifiedSelectorName-            then Left $ MismatchedKeyAndField label (T.pack $ selName m, modifiedSelectorName)-            else M1 <$> gParseNestedConfig opts t-      where-        m :: t s f a-        m = undefined--        modifiedSelectorName :: Text-        modifiedSelectorName = configOptionsLabelModifier opts . T.pack $ selName m--instance (GFieldParser (a :*: b)) => GNestedParser (a :*: b) where-    gParseNestedConfig opts (Node _ forest) = gParseFields opts forest
+ src/Cfg/Parser/Value.hs view
@@ -0,0 +1,74 @@+-- |+--  Module      : Cfg.Parser.Value+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains the generic machinery for value parsers. The main use+-- case for deriving 'Cfg.Parser.ValueParser' generically is for sum types, as+-- instances for most common types are provided in "Cfg.Parser".+module Cfg.Parser.Value where++import Cfg.Parser+import Data.Kind (Type)+import Data.Text qualified as T+import GHC.Generics+import Text.Megaparsec+import Text.Megaparsec.Char (string)++-- | This is the function that hooks into the generic machinery. It is called+-- by the deriving mechanism in "Cfg.Deriving.Value".+--+-- @since 0.0.2.0+defaultValueParser+  :: forall a+   . (Generic a, GValueParser (Rep a))+  => Parser a+defaultValueParser = fmap to $ gParser @(Rep a)++-- | This is a generic version of 'Cfg.Parser.ValueParser'+--+-- @since 0.0.2.0+class GValueParser (f :: Type -> Type) where+  gParser :: Parser (f p)++-- | @since 0.0.2.0+instance GValueParser V1 where+  gParser = undefined++-- | @since 0.0.2.0+instance GValueParser U1 where+  gParser = string "()" >> pure U1++-- | @since 0.0.2.0+instance (ValueParser a) => GValueParser (K1 R a) where+  gParser = K1 <$> parser @a++-- | @since 0.0.2.0+instance (GValueParser f) => GValueParser (M1 D s f) where+  gParser = M1 <$> gParser @f++-- | @since 0.0.2.0+instance (Constructor c) => GValueParser (M1 C c U1) where+  gParser = M1 U1 <$ string (T.pack $ conName @c undefined)++-- | @since 0.0.2.0+instance (GValueParser f) => GValueParser (M1 S s f) where+  gParser = M1 <$> gParser @f++-- | This is the main instance, which distributs a value parser over a sum type+-- using retry and alternative.+--+-- @since 0.0.2.0+instance (GValueParser a, GValueParser b) => GValueParser (a :+: b) where+  gParser = L1 <$> (try $ gParser @a) <|> R1 <$> (gParser @b)++-- | This is also an important instance for product types with unnamed fields+-- that are intended to be parsed as values (not nested configurations).+--+-- @since 0.0.2.0+instance (GValueParser a, GValueParser b) => GValueParser (a :*: b) where+  gParser = liftA2 (:*:) (gParser @a) (gParser @b)
− src/Cfg/Parser/ValueParser.hs
@@ -1,41 +0,0 @@-module Cfg.Parser.ValueParser where--import Cfg.Parser-import GHC.Generics-import Data.Kind (Type)-import Text.Megaparsec-import Text.Megaparsec.Char (string)-import qualified Data.Text as T--defaultValueParser ::-    forall a.-    (Generic a, GValueParser (Rep a)) =>-    Parser a-defaultValueParser = fmap to $ gParser @(Rep a)--class GValueParser (f :: Type -> Type) where-    gParser :: Parser (f p)--instance GValueParser V1 where-    gParser = undefined--instance GValueParser U1 where-    gParser = string "()" >> pure U1--instance ValueParser a => GValueParser (K1 R a) where-    gParser = K1 <$> parser @a--instance GValueParser f => GValueParser (M1 D s f) where-    gParser = M1 <$> gParser @f--instance (Constructor c) => GValueParser (M1 C c U1) where-    gParser = M1 U1 <$ string (T.pack $ conName @c undefined)  --instance (GValueParser f) => GValueParser (M1 S s f) where-    gParser = M1 <$> gParser @f--instance (GValueParser a, GValueParser b) => GValueParser (a :*: b) where-    gParser = liftA2 (:*:) (gParser @a) (gParser @b)--instance (GValueParser a, GValueParser b) => GValueParser (a :+: b) where-    gParser = L1 <$> (try $ gParser @a) <|> R1 <$> (gParser @b)
src/Cfg/Source.hs view
@@ -1,102 +1,128 @@-module Cfg.Source where+-- |+--  Module      : Cfg.Source+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains the type classes for generating a 'KeyTree.KeyTree'+-- representation of a configuration source. The primary purpose is to generate+-- a \"serialized\" version of our Haskell type that can be easily mapped to+-- configuration sources.+module Cfg.Source+  ( FetchSource+  , ConfigSource (..)+  )+where -import Cfg.Deriving.ConfigValue (ConfigValue)+import Cfg.Deriving.Value (Value) import Data.ByteString qualified as BS import Data.ByteString.Lazy qualified as BL-import Data.Int-import Data.List.NonEmpty+import Data.Int (Int16, Int32, Int64, Int8)+import Data.List.NonEmpty (NonEmpty)+import Data.Map.Strict (empty) import Data.Text (Text) import Data.Text.Lazy qualified as TL-import Data.Tree (Tree)-import Data.Vector-import Data.Word---- | @since 0.0.1.0-type FetchSource m = Tree Text -> m (Tree Text)+import Data.Vector (Vector)+import Data.Word (Word16, Word32, Word64, Word8)+import KeyTree (Free (..), KeyTree) --- | @since 0.0.1.0-class RootConfig a where-  toRootConfig :: Tree Text+-- | This type alias represents a function that fetches values from an external+-- configuration source and inserts them into the leaves of our+-- 'KeyTree.KeyTree'+--+-- @since 0.0.1.0+type FetchSource m = KeyTree Text Text -> m (KeyTree Text Text) --- | @since 0.0.1.0-class NestedConfig a where-  toNestedConfig :: [Tree Text]+-- | This is the instance that allows us to construct a tree representation of+-- type @a@ based on its structure.+--+-- @since 0.0.1.0+class ConfigSource a where+  -- | Since the structure of @a@ is statically known this 'KeyTree.KeyTree'+  -- can be thought of as a constant, no runtime computation required!+  configSource :: KeyTree Text Text --- | @since 0.0.1.0-instance NestedConfig (ConfigValue a) where-  toNestedConfig = []+-- | Base case for 'ConfigSource', inserts an empty map that indicates we are+-- \"expecting a value here\"+--+-- @since 0.0.1.0+instance ConfigSource (Value a) where+  configSource = Free empty  -- | @since 0.0.1.0-deriving via (ConfigValue ()) instance NestedConfig ()+deriving via (Value ()) instance ConfigSource () --- | @since 0.0.1.0-deriving via (ConfigValue Bool) instance NestedConfig Bool+-- | @since 0.0.1.1+deriving via (Value Bool) instance ConfigSource Bool  -- | @since 0.0.1.0-deriving via (ConfigValue Char) instance NestedConfig Char+deriving via (Value Char) instance ConfigSource Char  -- | @since 0.0.1.0-deriving via (ConfigValue TL.Text) instance NestedConfig TL.Text+deriving via (Value TL.Text) instance ConfigSource TL.Text  -- | @since 0.0.1.0-deriving via (ConfigValue BL.ByteString) instance NestedConfig BL.ByteString+deriving via (Value BL.ByteString) instance ConfigSource BL.ByteString  -- | @since 0.0.1.0-deriving via (ConfigValue BS.ByteString) instance NestedConfig BS.ByteString+deriving via (Value BS.ByteString) instance ConfigSource BS.ByteString  -- | @since 0.0.1.0-deriving via (ConfigValue Text) instance NestedConfig Text+deriving via (Value Text) instance ConfigSource Text  -- | @since 0.0.1.0-deriving via (ConfigValue [a]) instance NestedConfig [a]+deriving via (Value [a]) instance ConfigSource [a]  -- | @since 0.0.1.0-deriving via (ConfigValue (NonEmpty a)) instance NestedConfig (NonEmpty a)+deriving via (Value (NonEmpty a)) instance ConfigSource (NonEmpty a)  -- | @since 0.0.1.0-deriving via (ConfigValue (Vector a)) instance NestedConfig (Vector a)+deriving via (Value (Vector a)) instance ConfigSource (Vector a)  -- | @since 0.0.1.0-deriving via (ConfigValue (Maybe a)) instance NestedConfig (Maybe a)+deriving via (Value (Maybe a)) instance ConfigSource (Maybe a)  -- | @since 0.0.1.0-deriving via (ConfigValue Double) instance NestedConfig Double+deriving via (Value Double) instance ConfigSource Double  -- | @since 0.0.1.0-deriving via (ConfigValue Float) instance NestedConfig Float+deriving via (Value Float) instance ConfigSource Float  -- | @since 0.0.1.0-deriving via (ConfigValue Int) instance NestedConfig Int+deriving via (Value Int) instance ConfigSource Int  -- | @since 0.0.1.0-deriving via (ConfigValue Int8) instance NestedConfig Int8+deriving via (Value Int8) instance ConfigSource Int8  -- | @since 0.0.1.0-deriving via (ConfigValue Int16) instance NestedConfig Int16+deriving via (Value Int16) instance ConfigSource Int16  -- | @since 0.0.1.0-deriving via (ConfigValue Int32) instance NestedConfig Int32+deriving via (Value Int32) instance ConfigSource Int32  -- | @since 0.0.1.0-deriving via (ConfigValue Int64) instance NestedConfig Int64+deriving via (Value Int64) instance ConfigSource Int64  -- | @since 0.0.1.0-deriving via (ConfigValue Integer) instance NestedConfig Integer+deriving via (Value Integer) instance ConfigSource Integer  -- | @since 0.0.1.0-deriving via (ConfigValue Word) instance NestedConfig Word+deriving via (Value Word) instance ConfigSource Word  -- | @since 0.0.1.0-deriving via (ConfigValue Word8) instance NestedConfig Word8+deriving via (Value Word8) instance ConfigSource Word8  -- | @since 0.0.1.0-deriving via (ConfigValue Word16) instance NestedConfig Word16+deriving via (Value Word16) instance ConfigSource Word16  -- | @since 0.0.1.0-deriving via (ConfigValue Word32) instance NestedConfig Word32+deriving via (Value Word32) instance ConfigSource Word32  -- | @since 0.0.1.0-deriving via (ConfigValue Word64) instance NestedConfig Word64+deriving via (Value Word64) instance ConfigSource Word64  -- | @since 0.0.1.0-deriving via (ConfigValue (a, b)) instance NestedConfig (a, b)+deriving via (Value (a, b)) instance ConfigSource (a, b)
+ src/Cfg/Source/Config.hs view
@@ -0,0 +1,161 @@+-- |+--  Module      : Cfg.Source.Config+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains the generic machinery for generating a tree+-- representation of your configuration, this tree representation is intended+-- to be used with various sources. The tree structure should match the+-- structure of your (potentially) nested record type.+--+-- It is important to note that defaults are injected here, and not in the+-- parser stage. We use a 'DefaultSource' instance to inject 'Pure' values at+-- the leaves that can be used if the source fetcher doesn't return a value for+-- that key. In the case that there is no default a 'Free Data.Map.empty' is+-- placed to represent a required value.+module Cfg.Source.Config+  ( -- * Default Source Generator+    defaultConfigSource++    -- * Generic Machinery+  , GConfigSource (..)+  )+where++import Cfg.Options+import Cfg.Source+import Cfg.Source.Default+import Data.Kind (Type)+import Data.Map.Strict (empty, singleton)+import Data.Text (Text)+import Data.Text qualified as T+import GHC.Generics+import KeyTree++-- | This function is used by the deriving via machinery to dispatch to the+-- generic machinery, the user should never have to invoke it directly. This+-- takes in a 'ConfigOptions' which are retrieved from the deriving via+-- newtypes, and also threads a 'DefaultSource' instance through so that we can+-- dispatch to 'defaults' in the 'K1' case.+--+-- @since 0.0.2.0+defaultConfigSource+  :: forall a. (DefaultSource a, Generic a, GConfigSource (Rep a)) => ConfigOptions -> KeyTree Text Text+defaultConfigSource opts = gConfigSource @(Rep a) (defaults @a) opts++-- | This class is the generic version of 'ConfigSource. It recurses on the+-- generic structure of a type, building up 'KeyTree' representation.+--+-- @since 0.0.2.0+class GConfigSource (a :: Type -> Type) where+  gConfigSource :: (Text -> Maybe Text) -> ConfigOptions -> KeyTree Text Text++-- | This is the \"base case\", since "GHC.Generics" don't recurse the generic+-- represetation multiple levels, @a@ is just a plain type. Therefore we call+-- 'configSource' on it. @a@ may be another nested record, in which case+-- 'Cfg.Parser.Config.gParseConfig' will probably get called again, but for the+-- generic representation of a sub-tree. It will do this until it finds a+-- 'ConfigSource' instance for 'Cfg.Deriving.Value.Value' which will just add a+-- 'Free Data.Map.empty' (indicating a hole to be filled when we fetch the+-- configuration).+--+-- @since 0.0.2.0+instance (ConfigSource a) => GConfigSource (K1 R a) where+  gConfigSource _ _ = configSource @a++-- | This instance is important because it does the work of pulling off the+-- field selector name, and creating a sub-tree under that key by calling+-- 'gConfigSource' recursively. If there is a default for that selector then no+-- sub-tree is created, instead we insert a \"placeholder\" value tagged by+-- 'Pure' to represent that it is the end of the tree.+--+-- We detect if a default exists by calling 'defaults' from the 'DefaultSource'+-- instance on the selector, @defaults@ is of type @Text -> Maybe Text@, so the+-- @Nothing@ case indicates no default.+--+-- @since 0.0.2.0+instance (Selector s, GConfigSource f) => GConfigSource (M1 S s f) where+  gConfigSource def opts =+    if selName @s undefined == ""+      then -- TODO: Would be nice if we could turn this into a compile time error.+        error "Can only create a tree for named product types i.e. Records with named fields"+      else case def selectorName of+        Nothing -> Free $ singleton key value+        Just val -> Free $ singleton key (Pure val)+   where+    selectorName :: Text+    selectorName = T.pack $ selName @s undefined++    key :: Text+    key = keyModifier opts $ selectorName++    value :: KeyTree Text Text+    value = gConfigSource @f def opts++-- | This is the data constructor case, if we are dealing with a+-- 'Cfg.Deriving.Config.ConfigRoot' instance, then we have to create an extra layer with the \"root+-- key\" as a key and then a subtree (calculated by recursively calling 'gConfigSource') as the value.+--+-- @since 0.0.2.0+instance (Constructor c, GConfigSource f) => GConfigSource (M1 C c f) where+  gConfigSource def opts =+    case opts of+      Root (RootOptions{rootOptionsRootKey = ConstructorName modifier}) ->+        if conIsRecord @c undefined+          then Free $ singleton (modifier key) (gConfigSource @f def opts)+          else -- TODO: Would be nice if we could turn this into a compile time error.+            error "Can only create a tree for named product types i.e. Records with named fields"+      _ -> (gConfigSource @f def opts)+   where+    key :: Text+    key = T.pack $ conName @c undefined++-- | This is the type constructor case, if we are dealing with a+-- 'Cfg.Deriving.Config.ConfigRoot' instance, then we have to create an extra layer with the \"root+-- key\" as a key and then a subtree (calculated by recursively calling 'gConfigSource') as the value.+--+-- @since 0.0.2.0+instance (Datatype d, GConfigSource f) => GConfigSource (M1 D d f) where+  gConfigSource def opts =+    case opts of+      Root (RootOptions{rootOptionsRootKey = TypeName modifier}) ->+        Free $ singleton (modifier key) (gConfigSource @f def opts)+      _ -> (gConfigSource @f def opts)+   where+    key :: Text+    key = T.pack $ datatypeName @d undefined++-- | This instance handles product types and is pretty important. We need to+-- check that recursive calls to 'gConfigSource' generate sub-trees, and then+-- we merge the sub-trees.+--+-- You may wonder what happens if there is a 'Pure' value in one of the record+-- fields, well that would be represented like so:+--+-- @+-- Free $ M.singleton fieldName (Pure value)+-- @+--+-- since we need to account for the key corresponding to the record field. So+-- we really should never hit a case were a recursive call to 'gConfigSource'+-- yields a raw 'Pure'.+--+-- @since 0.0.2.0+instance (GConfigSource a, GConfigSource b) => GConfigSource (a :*: b) where+  gConfigSource def opts =+    case (gConfigSource @a def opts, gConfigSource @b def opts) of+      (Free m, Free m') -> Free $ m <> m'+      -- TODO: Would be nice if we could turn this into a compile time error.+      _ -> error "expected product types to generate subtrees (i.e. not contain Pure values)"++-- | Sum types should represent base values, so @Free M.empty@ is the right+-- thing to do here, although we should probably never hit this case, since sum+-- types should be nested under record fields as base values.+--+-- @since 0.0.2.0+instance GConfigSource (a :+: b) where+  gConfigSource _ _ = Free empty
+ src/Cfg/Source/Default.hs view
@@ -0,0 +1,28 @@+-- |+--  Module      : Cfg.Source.Default+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains code related to defaulting.+module Cfg.Source.Default where++import Data.Text (Text)++-- TODO: Figure out a way to make this error when there is a nested record++-- | This class lets us represent defaults as a function from record field+-- names to 'Maybe' values. @Nothing@ represents the absence of a default, and+-- the default implementation is to always return @Nothing@.+--+-- @since 0.0.2.0+class DefaultSource a where+  defaults+    :: Text+    -- ^ Record field label+    -> Maybe Text+    -- ^ Serialized default+  defaults = const Nothing
− src/Cfg/Source/NestedConfig.hs
@@ -1,58 +0,0 @@-module Cfg.Source.NestedConfig where--import Cfg.Options (ConfigOptions (..))-import Cfg.Source (NestedConfig (..))-import Data.Kind (Type)-import Data.Text (Text)-import Data.Text qualified as T-import Data.Tree (Tree (..))-import GHC.Generics--defaultToNestedConfig :: forall a. (Generic a, GConfigForest (Rep a)) => ConfigOptions -> [Tree Text]-defaultToNestedConfig opts = gToForest @(Rep a) opts--{- | Generic typeclass machinery for inducting on the structure- of the type, such that we can thread `Display` instances through- the structure of the type. The primary use case is for implementing- `RecordInstance`, which does this "threading" for record fields. This- machinery does, crucially, depend on child types (i.e. the type of a- record field) having a `Display` instance.-- @since 0.0.1.0--}-class GConfigForest (a :: Type -> Type) where-    gToForest :: ConfigOptions -> [Tree Text]--instance GConfigForest V1 where-    gToForest _ = []--instance GConfigForest U1 where-    gToForest _ = []--instance NestedConfig a => GConfigForest (K1 R a) where-    gToForest _ = toNestedConfig @a--instance GConfigForest f => GConfigForest (M1 D s f) where-    gToForest opts = gToForest @f opts --instance (Constructor c, GConfigForest f) => GConfigForest (M1 C c f) where-    gToForest opts = gToForest @f opts--instance (Selector s, GConfigForest f) => GConfigForest (M1 S s f) where-    gToForest opts =-        if selName m == ""-            then error "Can only create a tree for named product types i.e. Records with named fields"-            else-                [ Node-                    (configOptionsLabelModifier opts $ T.pack (selName m))-                    (gToForest @f opts)-                ]-      where-        m :: t s f a-        m = undefined--instance (GConfigForest a, GConfigForest b) => GConfigForest (a :*: b) where-    gToForest opts = gToForest @a opts <> gToForest @b opts--instance GConfigForest (a :+: b) where-    gToForest _ = []
− src/Cfg/Source/RootConfig.hs
@@ -1,37 +0,0 @@-module Cfg.Source.RootConfig where--import Cfg.Options (RootOptions (..))-import Cfg.Source.NestedConfig-import Data.Kind (Type)-import Data.Text (Text)-import Data.Text qualified as T-import Data.Tree (Tree (..))-import GHC.Generics--defaultToRootConfig :: forall a. (Generic a, GConfigTree (Rep a)) => RootOptions -> Tree Text-defaultToRootConfig opts = gToTree @(Rep a) opts--class GConfigTree (a :: Type -> Type) where-    gToTree :: RootOptions -> Tree Text---- TODO: Investigate whether this is required or not. Theoretically don't need this?------ instance RootConfig a => GConfigTree (K1 R a) where---   gToTree opts _ = toRootConfig opts (Proxy @a)--instance GConfigTree f => GConfigTree (M1 S s f) where-    gToTree opts = gToTree @f opts--instance GConfigTree f => GConfigTree (M1 D s f) where-    gToTree opts = gToTree @f opts--instance (Constructor c, GConfigForest f) => GConfigTree (M1 C c f) where-    gToTree opts-        | conIsRecord m =-            Node-                (rootOptionsLabelModifier opts . T.pack $ conName m)-                (gToForest @f $ rootOptionsFieldOptions opts)-        | otherwise = error "Can only create a tree for named product types i.e. Records with named fields"-      where-        m :: t c f a-        m = undefined
+ src/KeyTree.hs view
@@ -0,0 +1,161 @@+-- |+--  Module      : KeyTree+--  Copyright   : © Jonathan Lorimer, 2023+--  License     : MIT+--  Maintainer  : jonathanlorimer@pm.me+--  Stability   : stable+--+-- @since 0.0.2.0+--+-- This module contains types for our internal tree representation of types and+-- configurations. It also contains some helper functions for working with+-- these trees. This should make it easier to implement different source+-- providers.+module KeyTree+  ( -- * Types+    KeyTree+  , KeyForest++    -- * Helper Functions+  , foldKeyTree+  , appendFold+  , mayAppendFold+  , appendTraverse+  , mayAppendTraverse++    -- * Type Re-exports+  , Map+  , Free (..)+  )+where++import Control.Monad.Free+import Data.Functor ((<&>))+import Data.Map.Strict++-- | Type alias for our internal tree structure. If this was written directly+-- as a sum type it would look like this:+--+-- @Pure value | Free (Map key (KeyTree key value))@+--+-- @since 0.0.2.0+type KeyTree key value = Free (Map key) value++-- | Type alias for a subtree+--+-- @since 0.0.2.0+type KeyForest key value = Map key (Free (Map key) value)++-- | Right fold on a 'KeyTree'. Uses 'Data.Map.foldrWithKey' under the hood.+--+-- @since 0.0.2.0+foldKeyTree+  :: (Eq k, Eq v)+  => (v -> a)+  -- ^ Function to run on 'Pure' values+  -> (k -> Free (Map k) v -> a -> a)+  -- ^ Step function for fold+  -> a+  -- ^ Initial accumulator+  -> KeyTree k v+  -- ^ KeyTree to fold+  -> a+foldKeyTree _ stepF acc (Free m) = foldrWithKey stepF acc m+foldKeyTree valF _ _ (Pure val) = valF val++-- | A fold that appends a value at the leaf of the 'KeyTree', identifies what to+-- insert at the leaf by running a function on an accumulated value.+--+-- @since 0.0.2.0+appendFold+  :: (Eq k, Eq v)+  => (a -> v -> v')+  -- ^ Function to run on existing 'Pure' leaves @a@ is the accumulator @v@ is the value in @Pure@.+  -> (a -> v')+  -- ^ Function from accumulator @a@ to a value @v@. This happens when an empty subtree is found.+  -> (k -> a -> Map k (Free (Map k) v) -> a)+  -- ^ Step function for fold with key @k@ as an argument.+  -> a+  -- ^ Accumulator.+  -> KeyTree k v+  -- ^ KeyTree to append values to.+  -> KeyTree k v'+appendFold valF accF stepF acc (Free m) =+  if m == empty+    then Pure $ accF acc+    else Free $ mapWithKey (\k a -> appendFold valF accF stepF (stepF k acc m) a) m+appendFold valF _ _ acc (Pure v) = Pure $ valF acc v++-- | A fold that appends a value at the leaf of the 'KeyTree' (like+-- 'appendFold'), but the function on the accumulator can return a 'Maybe'. In+-- the @Nothing@ case we just append a 'Free' full of an empty 'Data.Map.Map'.+--+-- @since 0.0.2.0+mayAppendFold+  :: (Eq k, Eq v)+  => (a -> v -> Maybe v')+  -- ^ Function to run on existing 'Pure' leaves.+  -> (a -> Maybe v')+  -- ^ Function to run when an empty node is found.+  -> (k -> a -> Map k (Free (Map k) v) -> a)+  -- ^ Step function for fold with key @k@ as an argument.+  -> a+  -- ^ Accumulator.+  -> KeyTree k v+  -- ^ Tree to be folded.+  -> KeyTree k v'+mayAppendFold valF accF stepF acc (Free m) =+  if m == empty+    then case accF acc of+      Nothing -> Free empty+      Just v -> Pure v+    else Free $ mapWithKey (\k a -> mayAppendFold valF accF stepF (stepF k acc m) a) m+mayAppendFold valF _ _ acc (Pure v) =+  case valF acc v of+    Nothing -> Free empty+    Just v' -> Pure v'++-- | Like 'appendFold' but with functions that return a value wrapped in an+-- 'Applicative' effect @f@. The function is suffixed with \"Traverse\" because+-- we use the 'sequenceA' to wrap the entire tree in a single 'Applicative'+-- effect.+--+-- @since 0.0.2.0+appendTraverse+  :: (Applicative f, Eq k, Eq v)+  => (a -> v -> f v')+  -- ^ Function to run on existing 'Pure' leaves.+  -> (a -> f v')+  -- ^ Function to run when an empty node is found.+  -> (k -> a -> Map k (Free (Map k) v) -> a)+  -- ^ Step function for fold with key @k@ as an argument.+  -> a+  -- ^ Accumulator.+  -> KeyTree k v+  -- ^ Tree to be folded.+  -> f (KeyTree k v')+appendTraverse valF accF stepF acc = sequenceA . appendFold valF accF stepF acc++-- | Similar to 'appendTraverse' except the 'Applicative' effect can optionally+-- return a result. The function is manually implemented rather than using+-- 'Traversable' methods so that we don't have to require 'Traversable' on @f@.+-- This allows consumers to use effects like 'IO' that don't have a+-- 'Traversable' instance.+--+-- @since 0.0.2.0+mayAppendTraverse+  :: (Applicative f, Eq k, Eq v)+  => (a -> v -> f v')+  -> (a -> f (Maybe v'))+  -> (k -> a -> Map k (Free (Map k) v) -> a)+  -> a+  -> KeyTree k v+  -> f (KeyTree k v')+mayAppendTraverse valF accF stepF acc (Free m) =+  if m == empty+    then+      accF acc <&> \case+        Nothing -> Free empty+        Just v -> Pure v+    else Free <$> traverseWithKey (\k v -> mayAppendTraverse valF accF stepF (stepF k acc m) v) m+mayAppendTraverse valF _ _ acc (Pure v) = Pure <$> valF acc v
− src/Tree/Append.hs
@@ -1,41 +0,0 @@-module Tree.Append where--import Data.Functor-import Data.Tree (Tree (..))---- TODO: Come up with better names--appendLeaf :: (a -> b) -> ([a] -> b) -> [a] -> Tree a -> Tree b-appendLeaf f g acc (Node label []) = Node (f label) [Node (g (label : acc)) []]-appendLeaf f g acc (Node label xs) = Node (f label) $ fmap (appendLeaf f g (label : acc)) xs--mayAppendLeaf :: (a -> b) -> ([a] -> Maybe b) -> [a] -> Tree a -> Tree b-mayAppendLeaf f g acc (Node label []) =-  case g (label : acc) of-    Just leaf -> Node (f label) [Node leaf []]-    Nothing -> Node (f label) []-mayAppendLeaf f g acc (Node label xs) = Node (f label) $ fmap (mayAppendLeaf f g (label : acc)) xs--appendLeafA :: (Applicative f) => ([a] -> f a) -> [a] -> Tree a -> Tree (f a)-appendLeafA f acc = appendLeaf pure f acc--mayAppendLeafA :: (Applicative f) => ([a] -> Maybe (f a)) -> [a] -> Tree a -> Tree (f a)-mayAppendLeafA f acc = mayAppendLeaf pure f acc--mayAppendLeafA' :: (Applicative f) => ([a] -> f (Maybe a)) -> [a] -> Tree a -> f (Tree a)-mayAppendLeafA' f acc (Node label []) =-  f (label : acc)-    <&> \case-      Just leaf -> Node label [Node leaf []]-      Nothing -> Node label []-mayAppendLeafA' f acc (Node label xs) =-  let-    trees = traverse (mayAppendLeafA' f (label : acc)) xs-  in-    Node label <$> trees--travAppendLeafA :: (Applicative f) => ([a] -> f a) -> [a] -> Tree a -> f (Tree a)-travAppendLeafA f acc = sequenceA . appendLeaf pure f acc--travMayAppendLeafA :: (Traversable f, Applicative f) => ([a] -> f (Maybe a)) -> [a] -> Tree a -> f (Tree a)-travMayAppendLeafA f acc = sequenceA . mayAppendLeaf pure (sequenceA . f) acc
+ test/Cfg/Deriving/KeyModifierSpec.hs view
@@ -0,0 +1,54 @@+module Cfg.Deriving.KeyModifierSpec where++import Cfg.Deriving.KeyModifier+import Test.Hspec++spec :: Spec+spec = do+  describe "basic modifiers" $ do+    it "ToLower" $ do+      let+        text = "ThisShouldBeLower1234"+      getKeyModifier @ToLower text `shouldBe` "thisshouldbelower1234"+    it "ToUpper" $ do+      let+        text = "ThisShouldBeUpper1234"+      getKeyModifier @ToUpper text `shouldBe` "THISSHOULDBEUPPER1234"+    it "LowerFirst" $ do+      let+        text = "ThisShouldBeLower1234"+      getKeyModifier @LowerFirst text `shouldBe` "thisShouldBeLower1234"+    it "UpperFirst" $ do+      let+        text = "thisShouldBeLower1234"+      getKeyModifier @UpperFirst text `shouldBe` "ThisShouldBeLower1234"+    it "StripPrefix" $ do+      let+        text = "someConvalutedRecordNameRecordValue"+      getKeyModifier @(StripPrefix "someConvalutedRecordName") text+        `shouldBe` "RecordValue"+    it "StripSuffix" $ do+      let+        text = "superCoolApplicationConfig"+      getKeyModifier @(StripSuffix "Config") text+        `shouldBe` "superCoolApplication"+    it "CamelToSnake" $ do+      let+        text = "superCoolApplicationConfig"+      getKeyModifier @CamelToSnake text+        `shouldBe` "super_cool_application_config"+    it "CamelToKebab" $ do+      let+        text = "superCoolApplicationConfig"+      getKeyModifier @CamelToKebab text+        `shouldBe` "super-cool-application-config"+    it "should work as a list, applied in left to right order" $ do+      let+        text = "superCoolApplicationConfig"+      getKeyModifier @[StripSuffix "Config", CamelToSnake, ToUpper] text+        `shouldBe` "SUPER_COOL_APPLICATION"+    it "should work as a tuple, applied in left to right order" $ do+      let+        text = "superCoolApplicationConfig"+      getKeyModifier @(StripSuffix "Config", CamelToSnake, ToUpper) text+        `shouldBe` "SUPER_COOL_APPLICATION"
+ test/Cfg/Env/KeysSpec.hs view
@@ -0,0 +1,99 @@+module Cfg.Env.KeysSpec where++import Cfg.Deriving+import Cfg.Deriving.KeyModifier+import Cfg.Env.Keys+import Cfg.Options+import Cfg.Source (ConfigSource)+import Cfg.Source.Default+import Data.Map.Strict (empty, fromList, singleton)+import Data.Text (Text)+import GHC.Generics+import KeyTree+import Test.Hspec++spec :: Spec+spec = do+  describe "getKeys" $ do+    it "should collect all tree keys" $ do+      let+        tree :: KeyTree Text Text =+          Free $+            fromList+              [ ("A", Pure "Case1")+              ,+                ( "B"+                , Free $+                    fromList+                      [ ("1", Pure "Hello World")+                      , ("2", Pure "27")+                      , ("3", Pure "True")+                      , ("4", Free empty)+                      ]+                )+              , ("C", Pure "18")+              ,+                ( "D"+                , Free $+                    singleton "E" $+                      Free $+                        singleton "F" $+                          Free $+                            singleton "G" $+                              Free $+                                singleton "H" $+                                  Free $+                                    singleton "I" $+                                      Free $+                                        singleton "J" $+                                          Free $+                                            singleton "K" $+                                              Pure "[1,2,3,4]"+                )+              ]+      let+        expected =+          [ ["A"]+          , ["B", "1"]+          , ["B", "2"]+          , ["B", "3"]+          , ["B", "4"]+          , ["C"]+          , ["D", "E", "F", "G", "H", "I", "J", "K"]+          ]+      getKeys tree `shouldBe` expected+  describe "showEnvKeys" $ do+    it "should concatenate all tree keys with separator" $ do+      let+        expected =+          [ "ROOT_DATA_CON_OPTS__KEY_OPTS1"+          , "ROOT_DATA_CON_OPTS__KEY_OPTS2__SUB_KEY_OPTS1"+          , "ROOT_DATA_CON_OPTS__KEY_OPTS2__SUB_KEY_OPTS2"+          , "ROOT_DATA_CON_OPTS__KEY_OPTS2__SUB_KEY_OPTS3"+          , "ROOT_DATA_CON_OPTS__KEY_OPTS3"+          , "ROOT_DATA_CON_OPTS__KEY_OPTS4"+          ]+      showEnvKeys @(RootTyConOpts Text) "__" `shouldBe` expected++data SumTypeConfig = Case1 | Case2+  deriving stock (Generic, Show)+  deriving (ConfigSource) via Value SumTypeConfig++data SubTyConOpts = SubDataConOpts+  { subKeyOpts1 :: Text+  , subKeyOpts2 :: Int+  , subKeyOpts3 :: Maybe Bool+  }+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource) via (ConfigOpts [CamelToSnake, ToUpper] SubTyConOpts)++data RootTyConOpts a = RootDataConOpts+  { keyOpts1 :: SumTypeConfig+  , keyOpts2 :: SubTyConOpts+  , keyOpts3 :: Int+  , keyOpts4 :: a+  }+  deriving (Generic, Show, DefaultSource)+  deriving+    (ConfigSource)+    via (ConfigRoot ('ConstructorName [CamelToSnake, ToUpper]) [CamelToSnake, ToUpper] (RootTyConOpts a))
test/Cfg/EnvSpec.hs view
@@ -1,92 +1,73 @@ module Cfg.EnvSpec where -import Cfg.Env (envSource)-import Control.Monad.Identity+import Cfg.Env+import Data.Map.Strict (empty, fromList, singleton) import Data.Text (Text)-import Data.Tree (Tree (..))-import System.Environment (setEnv)+import KeyTree+import System.Environment (setEnv, unsetEnv) import Test.Hspec-import Tree.Append+import Test.Hspec.Core.Spec (sequential)  spec :: Spec-spec = do-  describe "appendLeaf" $ do-    it "should append the prefix of node keys as a leaf" $ do-      let-        tree :: Tree Text = Node "A" [Node "B" [], Node "C" [], Node "D" []]+spec = around+  ( \runTest -> do+      setEnv "A_B" "Functor"+      setEnv "A_C" "Applicative"+      setEnv "A_D" "Monad"+      runTest ()+      unsetEnv "A_B"+      unsetEnv "A_C"+      unsetEnv "A_D"+  )+  $ sequential+  $ describe "envSource"+  $ do+    it "should get variables from environment" $ do       let-        expected =-          Node-            "A"-            [ Node "B" [Node "AB" []]-            , Node "C" [Node "AC" []]-            , Node "D" [Node "AD" []]-            ]-      appendLeaf id (foldr (\x acc -> acc <> x) "") [] tree `shouldBe` expected+        tree :: KeyTree Text Text =+          Free $+            singleton "A" $+              Free $+                fromList+                  [ ("B", Free empty)+                  , ("C", Free empty)+                  , ("D", Free empty)+                  ] -  describe "appendLeafA" $ do-    it "should append the prefix of node keys as a leaf with Identity" $ do       let-        tree :: Tree Text = Node "A" [Node "B" [], Node "C" [], Node "D" []]-      let         expected =-          Node-            (Identity "A")-            [ Node (Identity "B") [Node (Identity "AB") []]-            , Node (Identity "C") [Node (Identity "AC") []]-            , Node (Identity "D") [Node (Identity "AD") []]-            ]-      appendLeafA (pure . foldr (\x acc -> acc <> x) "") [] tree `shouldBe` expected--  describe "travAppendLeafA" $ do-    it "should append the prefix of node keys as a leaf with Just" $ do-      let-        tree :: Tree Text = Node "A" [Node "B" [], Node "C" [], Node "D" []]-      let-        expected =-          Just $-            Node-              "A"-              [ Node "B" [Node "AB" []]-              , Node "C" [Node "AC" []]-              , Node "D" [Node "AD" []]-              ]-      travAppendLeafA (pure . foldr (\x acc -> acc <> x) "") [] tree `shouldBe` expected--    it "should be Nothing when the leaves are Nothing" $ do-      let-        tree :: Tree Text = Node "A" [Node "B" [], Node "C" [], Node "D" []]-      travAppendLeafA (const Nothing) [] tree `shouldBe` Nothing--  describe "envSource" $ do-    it "Should get environment variables that are in the environment" $ do-      let-        tree :: Tree Text = Node "A" [Node "B" [], Node "C" [], Node "D" []]-      let-        expected =-          Just $-            Node-              "A"-              [ Node "B" [Node "AB" []]-              , Node "C" [Node "AC" []]-              , Node "D" [Node "AD" []]-              ]-      travAppendLeafA (pure . foldr (\x acc -> acc <> x) "") [] tree `shouldBe` expected--    it "should be Nothing when the leaves are Nothing" $ do-      setEnv "A_B" "Functor"-      setEnv "A_C" "Applicative"-      setEnv "A_D" "Monad"+          Free $+            singleton "A" $+              Free $+                fromList+                  [ ("B", Pure "Functor")+                  , ("C", Pure "Applicative")+                  , ("D", Pure "Monad")+                  ]+      result <- envSource tree+      result `shouldBe` expected+    it "should respect defaults" $ do+      unsetEnv "A_C"       let-        tree :: Tree Text = Node "A" [Node "B" [], Node "C" [], Node "D" []]+        tree :: KeyTree Text Text =+          Free $+            singleton "A" $+              Free $+                fromList+                  [ ("B", Pure "Traversable")+                  , ("C", Pure "Applicative")+                  , ("D", Free empty)+                  ]        let         expected =-          Node-            "A"-            [ Node "B" [Node "Functor" []]-            , Node "C" [Node "Applicative" []]-            , Node "D" [Node "Monad" []]-            ]+          Free $+            singleton "A" $+              Free $+                fromList+                  [ ("B", Pure "Functor")+                  , ("C", Pure "Applicative")+                  , ("D", Pure "Monad")+                  ]       result <- envSource tree       result `shouldBe` expected
test/Cfg/ParserSpec.hs view
@@ -1,26 +1,27 @@ module Cfg.ParserSpec where -import Cfg.Deriving.ConfigRoot (ConfigRoot (..))-import Cfg.Deriving.ConfigValue-import Cfg.Deriving.SubConfig (SubConfig (..))+import Cfg.Deriving.Config+import Cfg.Deriving.Value import Cfg.Parser+import Data.Map.Strict (empty, fromList) import Data.Text (Text)-import Data.Tree (Tree (..)) import GHC.Generics (Generic (..))+import KeyTree import Test.Hspec  data SumTypeConfig = Case1 | Case2   deriving stock (Generic, Show, Eq)-  deriving (ValueParser) via (ConfigValue SumTypeConfig)-  deriving (NestedParser)+  deriving (ValueParser) via (Value SumTypeConfig)+  deriving (ConfigParser)  data SubTyCon = SubDataCon   { subKey1 :: Text   , subKey2 :: Int   , subKey3 :: Maybe Bool+  , subKey4 :: Maybe Bool   }   deriving (Generic, Show, Eq)-  deriving (NestedParser) via (SubConfig SubTyCon)+  deriving (ConfigParser) via (Config SubTyCon)  data RootTyCon a = RootDataCon   { key1 :: SumTypeConfig@@ -29,28 +30,32 @@   , key4 :: a   }   deriving stock (Generic, Show, Eq)-  deriving (RootParser) via (ConfigRoot (RootTyCon a))+  deriving (ConfigParser) via (Config (RootTyCon a))  spec :: Spec spec = do-  describe "toRootConfig" $ do+  describe "toConfig" $ do     it "should parse a type from the sample config" $ do       let-        subConfig = SubDataCon "Hello World" 27 (Just True)+        subConfig = SubDataCon "Hello World" 27 (Just True) Nothing       let         expected :: RootTyCon [Int] = RootDataCon Case1 subConfig 18 [1, 2, 3, 4]       let         underTest =-          Node-            "RootDataCon"-            [ Node "key1" [Node "Case1" []]-            , Node-                "key2"-                [ Node "subKey1" [Node "Hello World" []]-                , Node "subKey2" [Node "27" []]-                , Node "subKey3" [Node "Just True" []]-                ]-            , Node "key3" [Node "18" []]-            , Node "key4" [Node "[1,2,3,4]" []]-            ]-      parseRootConfig underTest `shouldBe` (Right expected)+          Free $+            fromList+              [ ("key1", Pure "Case1")+              ,+                ( "key2"+                , Free $+                    fromList+                      [ ("subKey1", Pure "Hello World")+                      , ("subKey2", Pure "27")+                      , ("subKey3", Pure "True")+                      , ("subKey4", Free empty)+                      ]+                )+              , ("key3", Pure "18")+              , ("key4", Pure "[1,2,3,4]")+              ]+      parseConfig underTest `shouldBe` (Right expected)
test/Cfg/SourceSpec.hs view
@@ -1,62 +1,94 @@ module Cfg.SourceSpec where -import Cfg.Deriving.ConfigRoot-import Cfg.Deriving.ConfigValue-import Cfg.Deriving.LabelModifier-import Cfg.Deriving.SubConfig+import Cfg.Deriving.Config+import Cfg.Deriving.KeyModifier+import Cfg.Deriving.Value+import Cfg.Options+import Cfg.Parser import Cfg.Source+import Cfg.Source.Default+import Data.Map.Strict (empty, fromList, singleton) import Data.Text (Text)-import Data.Tree (Tree (..)) import GHC.Generics (Generic (..))+import KeyTree import Test.Hspec  spec :: Spec spec = do-  describe "toRootConfig" $ do+  describe "configSource" $ do     it "should create a tree from the sample config" $ do       let         expected =-          Node-            "RootDataCon"-            [ Node "key1" []-            , Node-                "key2"-                [ Node "subKey1" []-                , Node "subKey2" []-                , Node "subKey3" []-                ]-            , Node "key3" []-            , Node "key4" []-            ]-      toRootConfig @(RootTyCon Text) `shouldBe` expected+          Free $+            fromList+              [ ("key1", Free empty)+              ,+                ( "key2"+                , Free $+                    fromList+                      [ ("subKey1", Free empty)+                      , ("subKey2", Free empty)+                      , ("subKey3", Free empty)+                      ]+                )+              , ("key3", Free empty)+              , ("key4", Free empty)+              ]+      configSource @(RootTyCon Text) `shouldBe` expected     it "should create a tree with modified options from sample config" $ do       let         expected =-          Node-            "ROOTDATACONOPTS"-            [ Node "keyopts1" []-            , Node-                "keyopts2"-                [ Node "SUBKEY1" []-                , Node "SUBKEY2" []-                , Node "SUBKEY3" []-                ]-            , Node "keyopts3" []-            , Node "keyopts4" []-            ]-      toRootConfig @(RootTyConOpts Text) `shouldBe` expected+          Free $+            singleton "ROOTTYCONOPTS" $+              Free $+                fromList+                  [ ("keyopts1", Free empty)+                  ,+                    ( "keyopts2"+                    , Free $+                        fromList+                          [ ("SUBKEYOPTS1", Free empty)+                          , ("SUBKEYOPTS2", Free empty)+                          , ("SUBKEYOPTS3", Free empty)+                          ]+                    )+                  , ("keyopts3", Free empty)+                  , ("keyopts4", Free empty)+                  ]+      configSource @(RootTyConOpts Text) `shouldBe` expected+    it "should create a tree with modified options from sample config AND respect defaults" $ do+      let+        expected =+          Free $+            singleton "ROOTTYCONOPTSDEF" $+              Free $+                fromList+                  [ ("keyoptsdef1", Pure "Case1")+                  ,+                    ( "keyoptsdef2"+                    , Free $+                        fromList+                          [ ("SUBKEYOPTS1", Free empty)+                          , ("SUBKEYOPTS2", Free empty)+                          , ("SUBKEYOPTS3", Free empty)+                          ]+                    )+                  , ("keyoptsdef3", Free empty)+                  , ("keyoptsdef4", Free empty)+                  ]+      configSource @(RootTyConOptsDef Text) `shouldBe` expected  data SumTypeConfig = Case1 | Case2-  deriving stock (Generic, Show)-  deriving (NestedConfig) via ConfigValue SumTypeConfig+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource) via Value SumTypeConfig  data SubTyCon = SubDataCon   { subKey1 :: Text   , subKey2 :: Int   , subKey3 :: Maybe Bool   }-  deriving (Generic, Show)-  deriving (NestedConfig) via (SubConfig SubTyCon)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource) via (Config SubTyCon)  data RootTyCon a = RootDataCon   { key1 :: SumTypeConfig@@ -64,16 +96,16 @@   , key3 :: Int   , key4 :: a   }-  deriving stock (Generic, Show)-  deriving (RootConfig) via (ConfigRoot (RootTyCon a))+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource) via (Config (RootTyCon a))  data SubTyConOpts = SubDataConOpts   { subKeyOpts1 :: Text   , subKeyOpts2 :: Int   , subKeyOpts3 :: Maybe Bool   }-  deriving (Generic, Show)-  deriving (NestedConfig) via (SubConfigOpts ToUpper SubTyCon)+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource) via (ConfigOpts ToUpper SubTyConOpts)  data RootTyConOpts a = RootDataConOpts   { keyOpts1 :: SumTypeConfig@@ -81,5 +113,18 @@   , keyOpts3 :: Int   , keyOpts4 :: a   }-  deriving stock (Generic, Show)-  deriving (RootConfig) via (ConfigRootOpts ToUpper ToLower (RootTyConOpts a))+  deriving (Generic, Show, DefaultSource)+  deriving (ConfigSource) via (ConfigRoot ('TypeName ToUpper) ToLower (RootTyConOpts a))++data RootTyConOptsDef a = RootTyConOptsDef+  { keyOptsDef1 :: SumTypeConfig+  , keyOptsDef2 :: SubTyConOpts+  , keyOptsDef3 :: Int+  , keyOptsDef4 :: a+  }+  deriving (Generic, Show)+  deriving (ConfigSource) via (ConfigRoot ('TypeName ToUpper) ToLower (RootTyConOptsDef a))++instance DefaultSource (RootTyConOptsDef a) where+  defaults "keyOptsDef1" = Just "Case1"+  defaults _ = Nothing
test/Main.hs view
@@ -11,12 +11,14 @@  main :: IO () main = do-    mText <- lookupEnv "TEST_CONCURRENCY"-    let maxResources :: Int-        maxResources = fromMaybe 8 (mText >>= readMaybe)-    let cfg =-            TR.defaultConfig-                { configConcurrentJobs = Just maxResources-                , configFormatter = Just specdoc-                }-    hspecWith cfg (parallel Spec.spec)+  mText <- lookupEnv "TEST_CONCURRENCY"+  let+    maxResources :: Int+    maxResources = fromMaybe 8 (mText >>= readMaybe)+  let+    cfg =+      TR.defaultConfig+        { configConcurrentJobs = Just maxResources+        , configFormatter = Just specdoc+        }+  hspecWith cfg (parallel Spec.spec)