cfg-0.0.2.0: src/Cfg/Env/Keys.hs
-- |
-- 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 Cfg.Source (ConfigSource (..))
import Data.Foldable
import Data.List (intersperse)
import Data.Map.Strict (empty)
import Data.Text (Text)
import KeyTree
-- | 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
-- | 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
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
valF :: Text -> [[Text]]
valF _ = []
-- | 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)