hie-bios-0.20.0: src/HIE/Bios/Config/YAML.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
-- | Datatypes for parsing @hie.yaml@ files
module HIE.Bios.Config.YAML
( CradleConfigYAML(..)
, CradleComponent(..)
, MultiSubComponent(..)
, CabalConfig(..)
, CabalComponent(..)
, StackConfig(..)
, StackComponent(..)
, DirectConfig(..)
, BiosConfig(..)
, NoneConfig(..)
, OtherConfig(..)
, OneOrManyComponents(..)
, Callable(..)
) where
import Control.Applicative ((<|>))
import Data.Aeson
#if MIN_VERSION_aeson(2,0,0)
import Data.Aeson.KeyMap (keys,lookup)
#else
import Data.HashMap.Strict (lookup)
import qualified Data.HashMap.Strict as Map
import qualified Data.Text as T
#endif
import Data.Aeson.Types (Parser, typeMismatch)
import qualified Data.Char as C (toLower)
import Data.List ((\\))
import GHC.Generics (Generic)
import Prelude hiding (lookup)
#if !MIN_VERSION_aeson(2,0,0)
-- | Backwards compatible type-def for Key
-- This used to be just a Text, but since aeson >= 2
-- this is an opaque datatype.
type Key = T.Text
-- | Backwards compatible type-def for KeyMap
-- This used to be just a HashMap, but since aeson >= 2
-- this is an opaque datatype.
type KeyMap v = Map.HashMap T.Text v
keys :: KeyMap v -> [Key]
keys = Map.keys
#endif
checkObjectKeys :: [Key] -> Object -> Parser ()
checkObjectKeys allowedKeys obj =
let extraKeys = keys obj \\ allowedKeys
in case extraKeys of
[] -> pure ()
_ -> fail $ mconcat [ "Unexpected keys "
, show extraKeys
, ", keys allowed: "
, show allowedKeys
]
data CradleConfigYAML a
= CradleConfigYAML { cradle :: CradleComponent a
, dependencies :: Maybe [FilePath]
} deriving (Generic, FromJSON)
data CradleComponent a
= Multi [MultiSubComponent a]
| Cabal CabalConfig
| Stack StackConfig
| Direct DirectConfig
| Bios BiosConfig
| None NoneConfig
| Other (OtherConfig a)
deriving (Generic)
instance FromJSON a => FromJSON (CradleComponent a) where
parseJSON = let opts = defaultOptions { constructorTagModifier = fmap C.toLower
, sumEncoding = ObjectWithSingleField
}
in genericParseJSON opts
data NoneConfig = NoneConfig
data OtherConfig a
= OtherConfig { otherConfig :: a
, originalYamlValue :: Value
}
instance FromJSON a => FromJSON (OtherConfig a) where
parseJSON v = OtherConfig
<$> parseJSON v
<*> pure v
instance FromJSON NoneConfig where
parseJSON Null = pure NoneConfig
parseJSON v = typeMismatch "NoneConfig" v
data MultiSubComponent a
= MultiSubComponent { path :: FilePath
, config :: CradleConfigYAML a
} deriving (Generic, FromJSON)
data CabalConfig
= CabalConfig { cabalProject :: Maybe FilePath
, cabalComponentsToLoad :: Maybe [String]
, cabalComponents :: OneOrManyComponents CabalComponent
}
instance FromJSON CabalConfig where
parseJSON v@(Array _) = CabalConfig Nothing Nothing . ManyComponents <$> parseJSON v
parseJSON v@(Object obj) = (checkObjectKeys ["cabalProject", "component", "components", "componentsToLoad"] obj)
*> (CabalConfig
<$> obj .:? "cabalProject"
<*> obj .:? "componentsToLoad"
<*> parseJSON v)
parseJSON Null = pure $ CabalConfig Nothing Nothing NoComponent
parseJSON v = typeMismatch "CabalConfig" v
data CabalComponent
= CabalComponent { cabalPath :: FilePath
, cabalComponent :: String
, cabalComponentProject :: Maybe FilePath
}
instance FromJSON CabalComponent where
parseJSON =
let parseCabalComponent obj = checkObjectKeys ["path", "component", "cabalProject"] obj
*> (CabalComponent
<$> obj .: "path"
<*> obj .: "component"
<*> obj .:? "cabalProject"
)
in withObject "CabalComponent" parseCabalComponent
data StackConfig
= StackConfig { stackYaml :: Maybe FilePath
, stackComponentsToLoad :: Maybe [String]
, stackComponents :: OneOrManyComponents StackComponent
}
data StackComponent
= StackComponent { stackPath :: FilePath
, stackComponent :: String
, stackComponentYAML :: Maybe FilePath
}
instance FromJSON StackConfig where
parseJSON v@(Array _) = StackConfig Nothing Nothing . ManyComponents <$> parseJSON v
parseJSON v@(Object obj) = (checkObjectKeys ["component", "components", "stackYaml", "componentsToLoad"] obj)
*> (StackConfig
<$> obj .:? "stackYaml"
<*> obj .:? "componentsToLoad"
<*> parseJSON v
)
parseJSON Null = pure $ StackConfig Nothing Nothing NoComponent
parseJSON v = typeMismatch "StackConfig" v
instance FromJSON StackComponent where
parseJSON =
let parseStackComponent obj = (checkObjectKeys ["path", "component", "stackYaml"] obj)
*> (StackComponent
<$> obj .: "path"
<*> obj .: "component"
<*> obj .:? "stackYaml")
in withObject "StackComponent" parseStackComponent
data OneOrManyComponents component
= SingleComponent String
| ManyComponents [component]
| NoComponent
instance FromJSON a => FromJSON (OneOrManyComponents a) where
parseJSON (Object obj)
| Just v <- lookup "component" obj = SingleComponent <$> parseJSON v
| Just v <- lookup "components" obj = ManyComponents <$> parseJSON v
| otherwise = pure NoComponent
parseJSON Null = pure NoComponent
parseJSON v = typeMismatch "OneOrManyComponents" v
data DirectConfig
= DirectConfig { arguments :: [String] }
deriving (Generic, FromJSON)
data BiosConfig =
BiosConfig { callable :: Callable
, depsCallable :: Maybe Callable
, ghcPath :: Maybe FilePath
}
instance FromJSON BiosConfig where
parseJSON = withObject "BiosConfig" parseBiosConfig
data Callable
= Program FilePath
| Shell String
parseBiosConfig :: Object -> Parser BiosConfig
parseBiosConfig obj =
let parseCallable o = (Program <$> o .: "program") <|> (Shell <$> o .: "shell")
parseDepsCallable o = (Just . Program <$> o .: "dependency-program")
<|> (Just . Shell <$> o .: "dependency-shell")
<|> (pure Nothing)
parse o = BiosConfig <$> parseCallable o
<*> parseDepsCallable o
<*> (o .:? "with-ghc")
check = checkObjectKeys ["program", "shell", "dependency-program", "dependency-shell", "with-ghc"]
in check obj *> parse obj