{-| Module : Work Description : Work system for PP ('.pp-work/' directory) Copyright : (c) 2017 Patrick Champion License : see LICENSE file Maintainer : chlablak@gmail.com Stability : provisional Portability : portable -} {-# LANGUAGE OverloadedStrings #-} module Work ( initialize , reuse , path ) where import Control.Monad import Data.Binary import Data.Hash import Data.Maybe import qualified Data.Text as T import Data.Yaml ((.:), (.=)) import qualified Data.Yaml as Y import qualified Log import System.Directory import System.IO -- |Work's directory wDir :: FilePath -> FilePath wDir p = ".pp-work/" ++ p wDir2 a b = wDir a ++ "/" ++ b wFile = wDir "work.yaml" -- |Change working directory path :: FilePath -> Log.Logger path p = do e <- Log.io $ doesDirectoryExist p if e then do Log.io $ setCurrentDirectory p Log.info $ "working directory set to: " ++ p else Log.err $ "path doesn't exist: " ++ p -- |Initialize module if necessary initialize :: Log.Logger initialize = do cwd <- Log.io getCurrentDirectory Log.info $ "current working directory: " ++ cwd e <- exists $ wDir "" unless e $ do Log.info $ "create directory: " ++ wDir "" Log.io $ createDirectory $ wDir "" Log.io $ writeFile wFile "" -- |Reuse previous computation, if any reuse :: (Binary a) => Bool -> FilePath -> String -> Log.LoggerIO a -> Log.LoggerIO a reuse w p k c = do Log.pushTag "work" r <- if w then do m <- modified p k if m then do Log.info $ show (p, k) ++ " modified, use computation and save" computeAndSave p k c else do Log.info $ show (p, k) ++ " not modified" e <- exists' p k if e then do Log.info "use previous work" load p k else do Log.info "no previous work, use computation and save" computeAndSave p k c else do Log.info "disabled, use computation" c Log.popTag return r where computeAndSave p k c = do v <- c save p k v register p k return v -- |Save a binary file save :: (Binary a) => FilePath -> String -> a -> Log.Logger save p k v = do (n, _) <- getHash p Log.io $ createDirectoryIfMissing False (wDir n) Log.io $ encodeFile (wDir2 n k) v -- |Load a binary file load :: (Binary a) => FilePath -> String -> Log.LoggerIO a load p k = do (n, _) <- getHash p Log.io $ decodeFile $ wDir2 n k -- |Check if a file exists exists :: FilePath -> Log.LoggerIO Bool exists p = Log.io $ doesPathExist p exists' p k = do (n, _) <- getHash p exists $ wDir2 n k -- |Register a file state register :: FilePath -> String -> Log.Logger register p k = do (n, v) <- getHash p c <- getConfig setConfig $ case filter (\f -> fileid f == n) (files c) of [] -> Config $ ConfigFile p n [ConfigHash k v] : files c [f] -> Config $ ConfigFile p n (ConfigHash k v : filter (\h -> hashname h /= k) (filehash f)) : filter (\f -> fileid f /= n) (files c) -- |Check if a file has changed modified :: FilePath -> String -> Log.LoggerIO Bool modified p k = do (n, v) <- getHash p c <- getConfig case filter (\f -> fileid f == n) (files c) of [] -> return True [f] -> case filter (\h -> hashname h == k) (filehash f) of [] -> return True [h] -> return $ hashvalue h /= v -- |Get the hash of a file getHash :: FilePath -> Log.LoggerIO (String, String) getHash p = do f <- Log.io $ readFile p return (hash' p, hash' f) where hash' = show . asWord64 . hash -- |Working file structure newtype Config = Config { files :: [ConfigFile] } deriving (Eq, Show) data ConfigFile = ConfigFile { filepath :: FilePath , fileid :: String , filehash :: [ConfigHash] } deriving (Eq, Show) data ConfigHash = ConfigHash { hashname :: String , hashvalue :: String } deriving (Eq, Show) instance Y.FromJSON Config where parseJSON (Y.Object v) = Config <$> v .: "files" parseJSON _ = return $ Config [] instance Y.ToJSON Config where toJSON v = Y.object ["files" .= files v] instance Y.FromJSON ConfigFile where parseJSON (Y.Object v) = ConfigFile <$> v .: "path" <*> v .: "id" <*> v .: "hash" instance Y.ToJSON ConfigFile where toJSON v = Y.object ["path" .= filepath v, "id" .= fileid v, "hash" .= filehash v] instance Y.FromJSON ConfigHash where parseJSON (Y.Object v) = ConfigHash <$> v .: "name" <*> v .: "value" instance Y.ToJSON ConfigHash where toJSON v = Y.object ["name" .= hashname v, "value" .= hashvalue v] -- |Get the config getConfig :: Log.LoggerIO Config getConfig = do c <- Log.io $ Y.decodeFile wFile return $ fromMaybe (Config []) c -- |Set the config setConfig :: Config -> Log.Logger setConfig c = Log.io $ Y.encodeFile wFile c