achille-0.0.0: src/Achille/Recipe.hs
{-# LANGUAGE RecordWildCards #-}
-- | Core recipes including reading files, saving them.
module Achille.Recipe
( Recipe
, liftIO
, getInput
, getCurrentDir
, readText
, readBS
, saveFile
, saveFileAs
, copyFile
, copyFileAs
, copy
, write
, task
, debug
, callCommand
, runCommandWith
, toTimestamped
) where
import Control.Monad.IO.Class (liftIO)
import Data.Binary (Binary, encodeFile)
import Data.Functor (void)
import Data.Text (Text, pack)
import Data.Text.Encoding (decodeUtf8)
import Data.ByteString (ByteString)
import System.FilePath ((</>))
import Achille.Config
import Achille.Writable (Writable)
import Achille.Timestamped
import qualified Achille.Writable as Writable
import Achille.Internal as Internal
import Achille.Internal.IO (AchilleIO)
import qualified Achille.Internal.IO as AchilleIO
data Color = Red | Blue
color :: Color -> String -> String
color c x = "\x1b[" <> start <> x <> "\x1b[0m"
where start = case c of
Red -> "31m"
Blue -> "34m"
-------------------------------------
-- Recipe building blocks (uncached)
-------------------------------------
-- | Recipe returning its input.
getInput :: Applicative m
=> Recipe m a a
getInput = nonCached $ pure . inputValue
-- | Recipe for retrieving the current directory
getCurrentDir :: Applicative m
=> Recipe m a FilePath
getCurrentDir = nonCached (pure . Internal.currentDir)
-- | Recipe retrieving the contents of the input file as text
readText :: AchilleIO m
=> Recipe m FilePath Text
readText = nonCached \Context{..} ->
decodeUtf8 <$> AchilleIO.readFile (inputDir </> currentDir </> inputValue)
-- | Recipe retrieving the contents of the input file as a bytestring.
readBS :: AchilleIO m => Recipe m FilePath ByteString
readBS = nonCached \Context{..} ->
AchilleIO.readFile (inputDir </> currentDir </> inputValue)
-- | Recipe for saving a value to the location given as input.
-- Returns the input filepath as is.
saveFile :: (AchilleIO m, Writable m a)
=> a -> Recipe m FilePath FilePath
saveFile = saveFileAs id
-- | Recipe for saving a value to a file, using the path modifier applied to the input filepath.
-- Returns the path of the output file.
saveFileAs :: (AchilleIO m, Writable m a)
=> (FilePath -> FilePath) -> a -> Recipe m FilePath FilePath
saveFileAs mod x = flip write x =<< mod <$> getInput
-- | Recipe for copying an input file to the same location in the output dir.
copyFile :: AchilleIO m
=> Recipe m FilePath FilePath
copyFile = copyFileAs id
-- | Recipe for copying an input file to the output dir, using the path modifier.
copyFileAs :: AchilleIO m
=> (FilePath -> FilePath) -> Recipe m FilePath FilePath
copyFileAs mod = getInput >>= \from -> copy from (mod from)
-- | Recipe for copying a file to a given destination.
-- Returns the output filepath.
copy :: AchilleIO m
=> FilePath -> FilePath -> Recipe m a FilePath
copy from to = nonCached \Context{..} -> do
AchilleIO.copyFile (inputDir </> currentDir </> from)
(outputDir </> currentDir </> to)
AchilleIO.log (color Blue $ (currentDir </> from) <> " → " <> (currentDir </> to))
pure to
-- | Recipe for writing to a an output file.
-- Returns the output filepath.
write :: (AchilleIO m, Writable m b)
=> FilePath -> b -> Recipe m a FilePath
write to x = nonCached \Context{..} -> do
Writable.write (outputDir </> currentDir </> to) x
AchilleIO.log (color Blue $ "writing " <> (currentDir </> to))
pure to
-- | Make a recipe out of a task. The input will simply be discarded.
task :: Task m b -> Recipe m a b
task (Recipe r) = Recipe (r . void)
------------------------------
-- Lifted IO
------------------------------
-- | Recipe for printing a value to the console.
debug :: AchilleIO m
=> Show b => b -> Recipe m a ()
debug = nonCached . const . AchilleIO.log . show
-- | Recipe for running a shell command in a new process.
callCommand :: AchilleIO m
=> String -> Recipe m a ()
callCommand = nonCached . const . AchilleIO.callCommand
-- | Recipe for running a shell command in a new process.
-- The command is defined with an helper function which depends on an input filepath
-- and the same filepath with a modifier applied.
--
-- Examples:
--
-- > cp :: Recipe IO FilePath FilePath
-- > cp = runCommandWith id (\a b -> "cp " <> a <> " " <> b)
runCommandWith :: AchilleIO m
=> (FilePath -> FilePath)
-> (FilePath -> FilePath -> String)
-> Recipe m FilePath FilePath
runCommandWith mod cmd = nonCached \Context{..} ->
let p' = mod inputValue
in AchilleIO.callCommand (cmd (inputDir </> currentDir </> inputValue)
(outputDir </> currentDir </> p'))
>> pure p'
-- | Recipe that will retrieve the datetime contained in a filepath.
toTimestamped :: Monad m => FilePath -> Recipe m a (Timestamped FilePath)
toTimestamped = pure . timestamped