    {-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, TypeFamilies #-}

    -- | A module for parsing and using config files in a Shake build system. Config files
    --   consist of variable bindings, for example:
    --
    -- > # This is my Config file
    -- > HEADERS_DIR = /path/to/dir
    -- > CFLAGS = -g -I${HEADERS_DIR}
    -- > CFLAGS = $CFLAGS -O2
    -- > include extra/file.cfg
    --
    --   This defines the variable @HEADERS_DIR@ (equal to @\/path\/to\/dir@), and
    --   @CFLAGS@ (equal to @-g -I\/path\/to\/dir -O2@), and also includes the configuration
    --   statements in the file @extra/file.cfg@. The full lexical syntax for configuration
    --   files is defined here: <https://ninja-build.org/manual.html#_lexical_syntax>.
    --   The use of Ninja file syntax is due to convenience and the desire to reuse an
    --    externally-defined specification (but the choice of configuration language is mostly arbitrary).
    --
    --   To use the configuration file either use 'readConfigFile' to parse the configuration file
    --   and use the values directly, or 'usingConfigFile' and 'getConfig' to track the configuration
    --   values, so they become build dependencies.
    module Development.Shake.Config(
        readConfigFile, readConfigFileWithEnv,
        usingConfigFile, usingConfig,
        getConfig, getConfigKeys
        ) where

    import Development.Shake
    import Development.Shake.Classes
    import qualified Development.Ninja.Parse as Ninja
    import qualified Development.Ninja.Env as Ninja
    import qualified Data.HashMap.Strict as Map
    import qualified Data.ByteString.UTF8 as UTF8
    import Control.Applicative
    import Data.Tuple.Extra
    import Data.List
    import Prelude

    -- | Read a config file, returning a list of the variables and their bindings.
    --   Config files use the Ninja lexical syntax:
    --   <https://ninja-build.org/manual.html#_lexical_syntax>
    readConfigFile :: FilePath -> IO (Map.HashMap String String)
    readConfigFile = readConfigFileWithEnv []

    -- | Read a config file with an initial environment, returning a list of the variables and their bindings.
    --   Config files use the Ninja lexical syntax:
    --   <https://ninja-build.org/manual.html#_lexical_syntax>
    readConfigFileWithEnv :: [(String, String)] -> FilePath -> IO (Map.HashMap String String)
    readConfigFileWithEnv vars file = do
        env <- Ninja.newEnv
        mapM_ (uncurry (Ninja.addEnv env) . (UTF8.fromString *** UTF8.fromString)) vars
        Ninja.parse file env
        mp <- Ninja.fromEnv env
        return $ Map.fromList $ map (UTF8.toString *** UTF8.toString) $ Map.toList mp

    newtype Config = Config String deriving (Show,Typeable,Eq,Hashable,Binary,NFData)

    newtype ConfigKeys = ConfigKeys () deriving (Show,Typeable,Eq,Hashable,Binary,NFData)

    type instance RuleResult Config = Maybe String
    type instance RuleResult ConfigKeys = [String]

    -- | Specify the file to use with 'getConfig'.
    usingConfigFile :: FilePath -> Rules ()
    usingConfigFile file = do
        mp <- newCache $ \() -> do
            need [file]
            liftIO $ readConfigFile file
        addOracle $ \(Config x) -> Map.lookup x <$> mp ()
        addOracle $ \(ConfigKeys ()) -> sort . Map.keys <$> mp ()
        return ()

    -- | Specify the values to use with 'getConfig', generally prefer
    --   'usingConfigFile' unless you also need access to the values
    --   of variables outside 'Action'.
    usingConfig :: Map.HashMap String String -> Rules ()
    usingConfig mp = do
        addOracle $ \(Config x) -> return $ Map.lookup x mp
        addOracle $ \(ConfigKeys ()) -> return $ sort $ Map.keys mp
        return ()

    -- | Obtain the value of a configuration variable, returns 'Nothing' to indicate the variable
    --   has no binding. Any build system using 'getConfig' /must/ call either 'usingConfigFile'
    --   or 'usingConfig'. The 'getConfig' function will introduce a dependency on the configuration
    --   variable (but not the whole configuration file), and if the configuration variable changes, the rule will be rerun.
    --   As an example:
    --
    -- @
    -- 'usingConfigFile' \"myconfiguration.cfg\"
    -- \"*.o\" '%>' \\out -> do
    --     cflags <- 'getConfig' \"CFLAGS\"
    --     'cmd' \"gcc\" [out '-<.>' \"c\"] (fromMaybe \"\" cflags)
    -- @
    getConfig :: String -> Action (Maybe String)
    getConfig = askOracle . Config

    -- | Obtain the configuration keys.
    --   Any build system using 'getConfigKeys' /must/ call either 'usingConfigFile' or 'usingConfig'.
    --   The 'getConfigKeys' function will introduce a dependency on the configuration keys
    --   (but not the whole configuration file), and if the configuration keys change, the rule will be rerun.
    --   Usually use as part of an action.
    --   As an example:
    --
    -- @
    -- 'usingConfigFile' \"myconfiguration.cfg\"
    -- 'action' $ need =<< getConfigKeys
    -- @
    getConfigKeys :: Action [String]
    getConfigKeys = askOracle $ ConfigKeys ()