-- |  Configuration (file) handling.
module LiBro.Config where

import Data.Default
import Data.Ini.Config
import Data.Text
import qualified Data.Text.IO as TIO
import System.IO

-- |  Configuration of storage details.
data StorageConfig = Storage
  { StorageConfig -> FilePath
directory     :: FilePath
  , StorageConfig -> FilePath
personFile    :: String
  , StorageConfig -> FilePath
tasksFile     :: String
  , StorageConfig -> FilePath
trackingFile  :: String
  } deriving (StorageConfig -> StorageConfig -> Bool
(StorageConfig -> StorageConfig -> Bool)
-> (StorageConfig -> StorageConfig -> Bool) -> Eq StorageConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: StorageConfig -> StorageConfig -> Bool
== :: StorageConfig -> StorageConfig -> Bool
$c/= :: StorageConfig -> StorageConfig -> Bool
/= :: StorageConfig -> StorageConfig -> Bool
Eq, Int -> StorageConfig -> ShowS
[StorageConfig] -> ShowS
StorageConfig -> FilePath
(Int -> StorageConfig -> ShowS)
-> (StorageConfig -> FilePath)
-> ([StorageConfig] -> ShowS)
-> Show StorageConfig
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> StorageConfig -> ShowS
showsPrec :: Int -> StorageConfig -> ShowS
$cshow :: StorageConfig -> FilePath
show :: StorageConfig -> FilePath
$cshowList :: [StorageConfig] -> ShowS
showList :: [StorageConfig] -> ShowS
Show)

instance Default StorageConfig where
  def :: StorageConfig
def = FilePath -> FilePath -> FilePath -> FilePath -> StorageConfig
Storage FilePath
"data-storage" FilePath
"persons.xlsx" FilePath
"tasks.xlsx" FilePath
"tracking.xlsx"

-- |  Global settings.
data Config = Config
  { Config -> StorageConfig
storage :: StorageConfig
  } deriving (Config -> Config -> Bool
(Config -> Config -> Bool)
-> (Config -> Config -> Bool) -> Eq Config
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Config -> Config -> Bool
== :: Config -> Config -> Bool
$c/= :: Config -> Config -> Bool
/= :: Config -> Config -> Bool
Eq, Int -> Config -> ShowS
[Config] -> ShowS
Config -> FilePath
(Int -> Config -> ShowS)
-> (Config -> FilePath) -> ([Config] -> ShowS) -> Show Config
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Config -> ShowS
showsPrec :: Int -> Config -> ShowS
$cshow :: Config -> FilePath
show :: Config -> FilePath
$cshowList :: [Config] -> ShowS
showList :: [Config] -> ShowS
Show)

instance Default Config where
  def :: Config
def = StorageConfig -> Config
Config StorageConfig
forall a. Default a => a
def

-- |  Parses a 'Config' value from a given 'Text'
--    or gives a parsing error message.
parseConfig :: Text -> Either String Config
parseConfig :: Text -> Either FilePath Config
parseConfig = (Text -> IniParser Config -> Either FilePath Config)
-> IniParser Config -> Text -> Either FilePath Config
forall a b c. (a -> b -> c) -> b -> a -> c
flip Text -> IniParser Config -> Either FilePath Config
forall a. Text -> IniParser a -> Either FilePath a
parseIniFile (IniParser Config -> Text -> Either FilePath Config)
-> IniParser Config -> Text -> Either FilePath Config
forall a b. (a -> b) -> a -> b
$ do
  StorageConfig
st <- Text -> SectionParser StorageConfig -> IniParser StorageConfig
forall a. Text -> SectionParser a -> IniParser a
section Text
"storage" (SectionParser StorageConfig -> IniParser StorageConfig)
-> SectionParser StorageConfig -> IniParser StorageConfig
forall a b. (a -> b) -> a -> b
$
    FilePath -> FilePath -> FilePath -> FilePath -> StorageConfig
Storage (FilePath -> FilePath -> FilePath -> FilePath -> StorageConfig)
-> SectionParser FilePath
-> SectionParser
     (FilePath -> FilePath -> FilePath -> StorageConfig)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text
-> (Text -> Either FilePath FilePath) -> SectionParser FilePath
forall a. Text -> (Text -> Either FilePath a) -> SectionParser a
fieldOf Text
"directory"     Text -> Either FilePath FilePath
forall a. IsString a => Text -> Either FilePath a
string
            SectionParser (FilePath -> FilePath -> FilePath -> StorageConfig)
-> SectionParser FilePath
-> SectionParser (FilePath -> FilePath -> StorageConfig)
forall a b.
SectionParser (a -> b) -> SectionParser a -> SectionParser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text
-> (Text -> Either FilePath FilePath) -> SectionParser FilePath
forall a. Text -> (Text -> Either FilePath a) -> SectionParser a
fieldOf Text
"person-file"   Text -> Either FilePath FilePath
forall a. IsString a => Text -> Either FilePath a
string
            SectionParser (FilePath -> FilePath -> StorageConfig)
-> SectionParser FilePath
-> SectionParser (FilePath -> StorageConfig)
forall a b.
SectionParser (a -> b) -> SectionParser a -> SectionParser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text
-> (Text -> Either FilePath FilePath) -> SectionParser FilePath
forall a. Text -> (Text -> Either FilePath a) -> SectionParser a
fieldOf Text
"tasks-file"    Text -> Either FilePath FilePath
forall a. IsString a => Text -> Either FilePath a
string
            SectionParser (FilePath -> StorageConfig)
-> SectionParser FilePath -> SectionParser StorageConfig
forall a b.
SectionParser (a -> b) -> SectionParser a -> SectionParser b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text
-> (Text -> Either FilePath FilePath) -> SectionParser FilePath
forall a. Text -> (Text -> Either FilePath a) -> SectionParser a
fieldOf Text
"tracking-file" Text -> Either FilePath FilePath
forall a. IsString a => Text -> Either FilePath a
string
  Config -> IniParser Config
forall a. a -> IniParser a
forall (m :: * -> *) a. Monad m => a -> m a
return (Config -> IniParser Config) -> Config -> IniParser Config
forall a b. (a -> b) -> a -> b
$ StorageConfig -> Config
Config StorageConfig
st

-- |  Reads a 'Config' value from @config.ini@.
--    Prints parsing error messages to @STDERR@ when failing.
readConfig :: IO (Maybe Config)
readConfig :: IO (Maybe Config)
readConfig = FilePath -> IO (Maybe Config)
readConfigFrom FilePath
"config.ini"

-- |  Reads a 'Config' value from the given file path.
--    Prints parsing error messages to @STDERR@ when failing.
readConfigFrom :: FilePath -> IO (Maybe Config)
readConfigFrom :: FilePath -> IO (Maybe Config)
readConfigFrom FilePath
fp = do
  Text
content <- FilePath -> IO Text
TIO.readFile FilePath
fp
  case Text -> Either FilePath Config
parseConfig Text
content of
    Right Config
config  -> Maybe Config -> IO (Maybe Config)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Config -> Maybe Config
forall a. a -> Maybe a
Just Config
config)
    Left FilePath
errorMsg -> do
      Handle -> FilePath -> IO ()
hPutStrLn Handle
stderr (FilePath -> IO ()) -> FilePath -> IO ()
forall a b. (a -> b) -> a -> b
$ FilePath
"Error parsing '" FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
fp FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
":\n" FilePath -> ShowS
forall a. [a] -> [a] -> [a]
++ FilePath
errorMsg
      Maybe Config -> IO (Maybe Config)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe Config
forall a. Maybe a
Nothing