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 {
programs :: [Program]
}
deriving (Eq, Show)
instance FromJSON Config where
parseJSON (Object obj) = Config <$> mapM (\(key, value) ->
parseJSON value >>= \program -> return program { name = key }
) (toList obj)
parseJSON value = typeMismatch "Config" value
emptyConfig :: Config
emptyConfig = Config []
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
nearestConfigFile :: MonadIO m => FilePath -> m (Maybe FilePath)
nearestConfigFile dir = findM (liftIO . doesFileExist) $ map (</> defaultFileName) parents
where
parents = takeWhile (\dir -> dir /= takeDrive dir) (iterate takeDirectory dir)
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}}"