packages feed

zre-0.1.1.0: src/Network/ZRE/Config.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Network.ZRE.Config where

import System.Environment
import System.Directory
import System.FilePath.Posix
import qualified Data.ByteString.Char8 as B

import Network.ZRE.Types

import Data.Default (def)
import qualified Data.Either
import qualified Data.Foldable

import Options.Applicative
import Network.ZRE.Options

import qualified Data.Text
import qualified Data.Attoparsec.Text

trueStr :: Data.Attoparsec.Text.Parser Bool
trueStr = pure True <$> (
    Data.Foldable.asum
  . map Data.Attoparsec.Text.string $ [ "true", "t", "yes", "y" ]
  )

falseStr :: Data.Attoparsec.Text.Parser Bool
falseStr =  pure False <$> (
    Data.Foldable.asum
  . map Data.Attoparsec.Text.string $ [ "false" , "f" , "no" , "n" ]
  )

iniFileToArgs :: [String] -> String -> [String]
iniFileToArgs sections file =
    concatMap (\(k, v) -> ["--" ++ k] ++ (if v /= "" then [v] else []))
  . map (Data.Text.unpack <$>)
  . map (\(k, v) -> if Data.Either.isRight $ Data.Attoparsec.Text.parseOnly (trueStr) v then (k, "") else (k, v)) -- fix --flag true -> --flag
  . filter (\(_, v) -> case Data.Attoparsec.Text.parseOnly (trueStr <|> falseStr) v of
      Left _e -> True
      Right b -> b)
  . map (Data.Text.pack <$>)
  . map (\x ->
          let t = takeWhile (flip elem $ '-':['a'..'z'])
          in (t x, dropWhile (== ' ') $ drop 1 $ dropWhile (/= '=') x)
        )
  . concatMap (\(_section, fields) -> fields)
  . filter (\(section, _fields) -> section `elem` sections)
  . groupBySections
  . filter (\(x:_xs) -> x /= '#') -- comments
  . filter (/="") -- empty
  $ lines file

-- transform [ "[zre]", "debug = false" "gossip=localhost:31337" "[zrecat]" "bufsize = 300"
-- to
-- [("zre", ["debug=false", "gossip=localhost:31337"]), ("zrecat", ["bufsize=300"])]
groupBySections :: [String] -> [(String, [String])]
groupBySections lines' = go lines'
  where
    go [] = []
    go ((x:xs):ls) | x == '[' = (takeWhile (flip elem $ '-':['a'..'z']) xs, keyVals ls):go ls
    go (_l:ls)     | otherwise = go ls
    keyVals [] = []
    keyVals ls = takeWhile (\(x:_) -> '[' /= x) ls

-- | Override config value from new iff it differs to default value
--
-- This could be done with `gzipWithT` and Generics
overrideNonDefault :: ZRECfg -> ZRECfg -> ZRECfg
overrideNonDefault orig new = ZRECfg {
    zreNamed         = ovr (zreNamed orig)         (zreNamed new)         (zreNamed def)
  , zreQuietPeriod   = ovr (zreQuietPeriod orig)   (zreQuietPeriod new)   (zreQuietPeriod def)
  , zreQuietPingRate = ovr (zreQuietPingRate orig) (zreQuietPingRate new) (zreQuietPingRate def)
  , zreDeadPeriod    = ovr (zreDeadPeriod orig)    (zreDeadPeriod new)    (zreDeadPeriod def)
  , zreBeaconPeriod  = ovr (zreBeaconPeriod orig)  (zreBeaconPeriod new)  (zreBeaconPeriod def)
  , zreInterfaces    = ovr (zreInterfaces orig)    (zreInterfaces new)    (zreInterfaces def)
  , zreMCast         = ovr (zreMCast orig)         (zreMCast new)         (zreMCast def)
  , zreZGossip       = ovr (zreZGossip orig)       (zreZGossip new)       (zreZGossip def)
  , zreDbg           = ovr (zreDbg orig)           (zreDbg new)           (zreDbg def)
  }
  where
    ovr :: (Eq a) => a -> a -> a -> a
    ovr _o n d | n /= d = n
    ovr o _n _d | otherwise = o

parseZRECfg :: String -> FilePath -> IO (Either String ZRECfg)
parseZRECfg exeName fpath = do
  isFile <- doesFileExist fpath
  case isFile of
    False -> pure $ Left "No such file"
    True -> do
      f <- readFile fpath
      let cfg = execParserPure defaultPrefs opts (iniFileToArgs ["zre", exeName] f)
      case cfg of
        -- we always fail when one of the configs fails to parse
        Failure e -> error $ fst $ renderFailure e ""
        Success cfg' -> return $ Right $ cfg'
        CompletionInvoked _ -> error "No completion"
  where
    opts = info (parseOptions <**> helper)
      ( fullDesc
     <> progDesc "ZRE"
     <> header "zre tools" )

-- The order is
-- * path of @ZRECFG@ env iff set
-- * @/etc/zre.conf@
-- * @~/.zre.conf@
-- * @default@
--
-- This also tries to parse subsection for zre programs according to their
-- name and construct correct command line for these so we can do
--
-- @[zrecat]@
-- @bufsize = 1024@
--
-- If @ZRENAME@ env var is set, it overrides name field in the result config.
envZRECfg :: String -> IO (ZRECfg)
envZRECfg exeName = do
  menv  <- lookupEnv "ZRECFG"
  mname <- lookupEnv "ZRENAME"

  hom <- getHomeDirectory

  cfg <- asumOneConfig [
      maybe (pure $ Left "No ZRECFG env") (parseZRECfg exeName) menv
    , parseZRECfg exeName "/etc/zre.conf"
    , parseZRECfg exeName $ hom </> ".zre.conf"
    , return $ Right def
    ]
  return $ maybeUpdateName cfg mname
  where
    maybeUpdateName cfg mname = maybe cfg (\x -> cfg { zreNamed = B.pack x}) mname
    asumOneConfig [] = error "Can't happen"
    asumOneConfig (x:xs) = x >>= \y -> case y of
      Left _e -> asumOneConfig xs
      Right cfg -> return $ cfg