diff --git a/app/Main.hs b/app/Main.hs index 9afa44a..b3ed9ca 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -11,6 +11,7 @@ import Tempered.Options import Tempered.Parser import Tempered.Template +-- | Run tempered on cmdline args. main :: IO () main = do envVars <- getEnvVars @@ -20,6 +21,7 @@ main = do where renderOutput = traverse_ (interpTemplate >=> liftIO . putStr) +-- | Combine local and global environment variables getEnvVars :: IO EnvVars getEnvVars = do cwd <- getCurrentDirectory diff --git a/src/Tempered/Options.hs b/src/Tempered/Options.hs index 6bfbdad..a886158 100644 --- a/src/Tempered/Options.hs +++ b/src/Tempered/Options.hs @@ -18,25 +18,29 @@ import System.Exit type EnvVars = [(String, String)] type EnvMap = M.Map String String +-- | Given a directory tries to find env.yaml recursively upwards; +-- parses `EnvMap` from the file if found. getProjectOptions :: FilePath -> IO EnvMap getProjectOptions path = do mProjSettingsFile <- findProjSettings path mOptions <- traverse optionsFromFilename mProjSettingsFile return $ fromMaybe mempty mOptions --- Retrieve an options object from a yaml file +-- Retrieve an EnvMap from a yaml file optionsFromFilename :: FilePath -> IO EnvMap optionsFromFilename = Y.decodeFileEither >=> \case Left err -> die . prettyPrintParseException $ err Right options -> return options +-- Try to find an 'env.yaml' file. findProjSettings :: FilePath -> IO (Maybe FilePath) findProjSettings fpath = do absPath <- makeAbsolute fpath let searchPaths = ( "env.yaml") <$> recurseUp absPath listToMaybe <$> filterM doesFileExist searchPaths +-- Get all parent directories of a directory path. recurseUp :: FilePath -> [FilePath] recurseUp = unfoldr go where diff --git a/src/Tempered/Parser.hs b/src/Tempered/Parser.hs index 052de9b..ca2b80b 100644 --- a/src/Tempered/Parser.hs +++ b/src/Tempered/Parser.hs @@ -17,18 +17,22 @@ infix 0 ?> (?>) :: String -> ParsecT s u m a -> ParsecT s u m a (?>) = flip () +-- | Parse a template from a file. templateFromFile :: FilePath -> IO (Either ParseError (Template Command)) templateFromFile fname = do file <- readFile fname return $ parseTemplate fname file +-- | Parse a template from a string with a given filename for errors. parseTemplate :: FilePath -> String -> Either ParseError (Template Command) parseTemplate = runP templateP () +-- | Fail if parsing errors occurred, otherwise return the template. handleTemplateError :: Either ParseError (Template a) -> IO (Template a) handleTemplateError (Left err) = print err >> exitFailure handleTemplateError (Right temp) = return temp +-- | Template Parser templateP :: Parser (Template Command) templateP = "template" ?> do optional (try shebangP) @@ -39,10 +43,12 @@ templateP = "template" ?> do cmd = Right <$> commandP txt = Left <$> many1 (notFollowedBy (string "{{") *> anyChar) +-- | Shebang Parser shebangP :: Parser String shebangP = "shebang" ?> liftA2 (++) (lookAhead (string "#!") *> string "#!") (manyTill anyChar (char '\n')) +-- | Command Parser commandP :: Parser Command commandP = "command" ?> do _ <- string "{{" diff --git a/src/Tempered/Template.hs b/src/Tempered/Template.hs index 86d46bf..08a4ae0 100644 --- a/src/Tempered/Template.hs +++ b/src/Tempered/Template.hs @@ -13,19 +13,23 @@ import Data.Foldable import Tempered.Options +-- | Represents values interspersed with text. data Template a = Template [Either String a] deriving Show +-- | Given an execution environment render a template into a string. interpTemplate :: (MonadReader EnvVars m, MonadIO m) => Template Command -> m String interpTemplate (Template elems) = fold <$> mapM toText elems where toText = either return interpCommand +-- | Represents a command to be run by the system. data Command = Command String deriving Show +-- | Run a command in an environment returning the result. interpCommand :: (MonadReader EnvVars m, MonadIO m) => Command -> m String interpCommand (Command cmd) = do envVars <- ask