module Omnifmt.Config (
Config(..),
emptyConfig, readConfig, nearestConfigFile, defaultFileName, programFor, unsafeProgramFor,
supported,
Program(..),
emptyProgram, substitute, usesInputVariable, usesOutputVariable, inputVariableName,
outputVariableName,
) where
import Control.Arrow (second)
import Control.Monad.Except
import Control.Monad.Extra
import Control.Monad.Logger
import Data.Aeson.Types
import Data.HashMap.Lazy (toList)
import Data.List (find)
import Data.Maybe (fromJust, isJust)
import Data.Text (Text, cons, isInfixOf, pack, replace, snoc)
import Data.Yaml (prettyPrintParseException)
import Data.Yaml.Include (decodeFileEither)
import System.Directory.Extra
import System.FilePath
data Config = Config {
source :: Maybe FilePath,
programs :: [Program]
}
deriving (Eq, Show)
instance FromJSON Config where
parseJSON (Object obj) = Config Nothing <$> mapM (\(key, value) ->
parseJSON value >>= \program -> return program { name = key }
) (toList obj)
parseJSON value = typeMismatch "Config" value
emptyConfig :: Config
emptyConfig = Config Nothing []
readConfig :: (MonadIO m, MonadLogger m) => FilePath -> m (Maybe Config)
readConfig filePath = liftIO (decodeFileEither filePath) >>= \ethr -> case ethr of
Left error -> do
logDebugN . pack $ filePath ++ ": error\n" ++ prettyPrintParseException error
return Nothing
Right config -> return $ Just config { source = Just filePath }
nearestConfigFile :: MonadIO m => FilePath -> m (Maybe FilePath)
nearestConfigFile dir = liftIO (canonicalizePath dir) >>= \dir' -> findM (liftIO . doesFileExist) $ map (</> defaultFileName) (parents dir')
where
parents = reverse . scanl1 combine . splitDirectories
defaultFileName :: FilePath
defaultFileName = ".omnifmt.yaml"
programFor :: Config -> Text -> Maybe Program
programFor config ext = find (\program -> ext `elem` extensions program) (programs config)
unsafeProgramFor :: Config -> Text -> Program
unsafeProgramFor config = fromJust . programFor config
supported :: Config -> Text -> Bool
supported config = isJust . programFor config
data Program = Program {
name :: Text,
extensions :: [Text],
command :: Text
}
deriving (Eq, Show)
instance FromJSON Program where
parseJSON (Object obj) = Program "" <$> obj .: "extensions" <*> obj .: "command"
parseJSON value = typeMismatch "Program" value
emptyProgram :: Program
emptyProgram = Program "" [] "false"
substitute :: Text -> [(Text, Text)] -> Text
substitute = foldr (uncurry replace . second (quote . escape))
where
quote = cons '"' . (`snoc` '"')
escape = replace (pack "\"") (pack "\\\"") . replace (pack "\\") (pack "\\\\")
usesInputVariable :: Text -> Bool
usesInputVariable = isInfixOf inputVariableName
usesOutputVariable :: Text -> Bool
usesOutputVariable = isInfixOf outputVariableName
inputVariableName :: Text
inputVariableName = "{{input}}"
outputVariableName :: Text
outputVariableName = "{{output}}"