packages feed

gdo-0.1.5: Main.hs

import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
import Control.Monad.Trans.Except
import Control.Monad.Trans.Reader
import Data.List
import Data.Maybe
import Data.Monoid
import System.Console.GetOpt
import System.Directory
import System.Environment
import System.Exit
import System.FilePath
import System.IO.Error

import Lib

data Flag = Changed | Created | Help
          deriving (Show,Read,Eq)

clOption :: [OptDescr Flag]
clOption = [ Option "" ["if"] (ReqArg read "MODE")
             "Redo mode, valid modes are: Changed, Created"
           , Option "h" ["help"] (NoArg Help)
             "Print usage info"
           ]

variableInfo :: String
variableInfo =
  unlines $
  "Environment variables:" :
  map ('\t':)
  [ "GDO_DEFAULTS : List of paths to directories containing default do files"
  ]

usageHeader :: String
usageHeader =
  unlines
  [ "gdo" ]

printUsage :: IO ()
printUsage = do
  putStrErr $ usageInfo usageHeader clOption
  putStrLn ""
  putStrLn variableInfo

getCliOpts :: (MonadIO m) => ExceptT ExitCode m ([Flag],[FilePath])
getCliOpts = do
  args <- liftIO getArgs
  case getOpt RequireOrder clOption args of
    (_,_,_:_) -> do liftIO printUsage
                    throwE (ExitFailure 1)
    (opts,deps,_) -> return (opts,deps)

optsToMode :: [Flag] -> Maybe Flag
optsToMode flags =
  getFirst . mconcat . map (First . ($flags)) $
  [ find (==Help)
  , listToMaybe . reverse
  ]

withTarget :: (MonadIO m) =>
              FilePath -> (FilePath -> m a) -> m a
withTarget target fun = do
  dir <- liftIO getCurrentDirectory
  let (targetDir,file) = splitFileName target
  liftIO $ setCurrentDirectory targetDir
  result <- fun file
  liftIO $ setCurrentDirectory dir
  return result
         
main :: IO ()
main = do
  invocationDir <-
    fromMaybeM
    (setInvocationDir >> getCurrentDirectory)
    getInvocationDir
  defaultDirs <- getDefaultDirs
  either exitWith (const exitSuccess) =<<
    (flip runReaderT invocationDir . runExceptT)
    (do (mMode,deps) <- getCliOpts
        case optsToMode mMode of
         Just Changed -> do
           buildTarget <- getBuildTarget
           mapM_ (redoIfChanged defaultDirs buildTarget) deps
         Just Created -> do
           buildTarget <- getBuildTarget
           mapM_ (redoIfCreated defaultDirs buildTarget) deps
         Just Help -> liftIO printUsage
         Nothing -> mapM_ (`withTarget` redo defaultDirs) deps
        return ()
    )

type Redo m = ExceptT ExitCode (ReaderT FilePath m)

redo :: (MonadIO m) =>
        [FilePath] -> FilePath -> Redo m ()
redo mDefaultsDir target = do
  invocationDir <- lift $ ask
  targetRelativePath <- makeRelative invocationDir <$>
    liftIO (makeAbsolute target)
  targetDoesExist <- liftIO $ doesFileExist target
  isUpToDate <- upToDate mDefaultsDir target
  unless (isUpToDate && targetDoesExist) $ do
    void . liftIO . tryJust (guard . isDoesNotExistError) $
      purgeRecordedDependencies target
    maybe (do targetExists <- liftIO $ doesFileExist target
              unless targetExists $ do
                (liftIO . putStrLnErr)
                  ("Neither target nor do file found for "++show target)
                throwE (ExitFailure 1))
          (\recipe ->
            do liftIO $ putStrLnErr $ "redo "++show targetRelativePath
               redoIfCreated mDefaultsDir target (target++".do")
               redoIfChanged mDefaultsDir target recipe
               buildResult <- liftIO $ executeRecipe target recipe
               when (buildResult /= ExitSuccess)
                 (do liftIO $ purgeRecordedDependencies target
                     throwE buildResult)
               liftIO $ putStrLnErr $ "redone "++show targetRelativePath)
      =<< getRecipeFile mDefaultsDir target

fromMaybeM :: (Monad m) => m a -> m (Maybe a) -> m a
fromMaybeM fallback action = do
  mValue <- action
  case mValue of
    Just value -> return value
    Nothing -> fallback

redoIfCreated :: (MonadIO m) =>
                 [FilePath]     -- ^ directory path of defaults
              -> FilePath       -- ^ build target
              -> FilePath       -- ^ path of the dependency
              -> Redo m ()
redoIfCreated mDefaultsDir buildTarget dep = do
  depExists <- liftIO $ doesFileExist dep
  if depExists
    then redoIfChanged mDefaultsDir buildTarget dep
    else liftIO $ recordMissingDependency buildTarget dep

redoIfChanged :: MonadIO m =>
                 [FilePath]     -- ^ directory of defaults
              -> FilePath       -- ^ build target
              -> FilePath       -- ^ path of the dependency
              -> Redo m ()
redoIfChanged mDefaultsDir buildTarget dep = do
  (`withTarget` redo mDefaultsDir) dep
  liftIO $ recordDependency buildTarget dep

getBuildTarget :: (MonadIO m) => ExceptT ExitCode m FilePath
getBuildTarget = do
  mTarget <- liftIO $ lookupEnv "REDO_TARGET"
  case mTarget of
    Nothing -> do putStrLnErr "Could not detect build target"
                  throwE $ ExitFailure 1
    Just target -> return target