{-# OPTIONS_GHC -Wall #-} {-# LANGUAGE NamedFieldPuns, RecordWildCards #-} module Makedo.Target ( -- * your main function redoMain, listing -- * creating targets , Target(..), RedoArgs(..) , TargetLike(..) , ExactTarget(..), WildcardTarget(..) , ShowPC(..) -- * within target actions , redoIfChange, redo, stampAll , runVerbosely ) where import HSH import System.Directory import System.Environment import System.Exit import System.FilePath import System.Process import System.IO -- | A makedo script is basically just a list of 'Target' -- -- If a target matches the input file name, its action is run data Target = Target { match :: FilePath -> Bool , description :: (String, String) -- ^ (name, blurb) for human consumption only , action :: RedoArgs -> IO () } data RedoArgs = RedoArgs { rwrite :: FilePath -- ^ temporary file managed by redo; write your output here -- or to stdout; it will be renamed to 'rname' if the rule -- is successful , rname :: FilePath -- ^ desired filename , rext :: String } -- | Useful for targets that take parameters which you want to -- convert into path components class ShowPC a where showPC :: a -> String runVerbosely :: ShellCommand a => a -> IO () runVerbosely cmd = run cmd >>= hPutStr stderr -- ---------------------------------------------------------------------- -- | Targets that use an exact filename match data ExactTarget = ExactTarget { ePath :: FilePath , eBlurb :: String , eAction :: RedoArgs -> IO () } data WildcardTarget = WildcardTarget { wPrefix :: FilePath , wExtension :: String -- ^ as recognised by System.FilePath , wBlurb :: String , wAction :: RedoArgs -> IO () } class TargetLike t where target :: t -> Target instance TargetLike Target where target = id instance TargetLike ExactTarget where target (ExactTarget {..}) = Target (== ePath) (ePath, eBlurb) eAction instance TargetLike WildcardTarget where target (WildcardTarget {..}) = Target (matchDirExt wPrefix wExtension) (wPrefix "*" <.> wExtension, wBlurb) wAction matchDirExt :: FilePath -> String -> FilePath -> Bool matchDirExt d x f = takeDirectory f == d && takeExtensions f == ("" <.> x) -- NB <.> is clever about not duping -- ---------------------------------------------------------------------- gussyUp :: [Target] -> [Target] gussyUp ts = target (listing ts) : target (listing2 ts) : addAll ts -- | Special target that prints out the targets you can run -- Note that makedo automatically includes this listing :: [Target] -> ExactTarget listing ts = ExactTarget "targets" "list known targets" $ const $ do listingBody (filter described ts) hPutStrLn stderr $ "Try `redo " ++ ePath (listing2 ts) ++ "` for a full list" where described = not . null . snd . description -- | Special target that prints out the targets you can run -- Note that makedo automatically includes this listing2 :: [Target] -> ExactTarget listing2 ts = ExactTarget "targets2" "list all known targets" $ const $ listingBody ts listingBody :: [Target] -> IO () listingBody ts = hPutStr stderr . unlines . map (showd . description) $ ts where maxl = maximum $ map (length . fst . description) ts padRight x = x ++ replicate (maxl - length x) ' ' showd (d,blurb) = padRight d ++ " " ++ blurb addAll :: [Target] -> [Target] addAll ts = if any (\t -> fst (description t) == "all") ts then ts else allT : ts where allT = target $ ExactTarget "all" "help text" $ const $ do hPutStrLn stderr "run 'redo targets' to see your options" exitFailure -- | Your main function should probably just invoke this and do nothing -- else redoMain :: [Target] -> IO () redoMain ts = do args <- getArgs case args of [x1, x2, x3] -> fromRedo (gussyUp ts) (RedoArgs { rwrite = x3, rname = x1, rext = x2 }) _ -> fail ("Was expecting exactly 3 arguments\n" ++ "This script should only be run by redo") fromRedo :: [Target] -> RedoArgs -> IO () fromRedo ts args@(RedoArgs {rname}) = case filter (flip match rname) ts of [] -> fail ("No matches for " ++ rname) ms -> mapM_ (flip action args) ms -- Not using HSH for this because of -- -- > Traceback (most recent call last): -- > File "/usr/local/redo/redo", line 60, in -- > jwack.setup(j) -- > File "/usr/local/redo-0.06/jwack.py", line 95, in setup -- > raise ValueError('broken --jobserver-fds from make; prefix your Makefile rule with a "+"') -- > ValueError: broken --jobserver-fds from make; prefix your Makefile rule with a "+" redoIfChange :: [String] -> IO () redoIfChange args = runProcess "redo-ifchange" args Nothing Nothing Nothing Nothing Nothing >>= waitForProcess >> return () redo :: [String] -> IO () redo args = runProcess "redo" args Nothing Nothing Nothing Nothing Nothing >>= waitForProcess >> return () stampAll :: FilePath -> [String] -> IO () stampAll d exts_ = do cs <- getDirectoryContents d runIO $ ("cat", [ d c | c <- cs, takeExtensions c `elem` exts ]) -|- "redo-stamp" where exts = map ("" <.>) exts_