{-# LANGUAGE ScopedTypeVariables #-} -- |Run shell commands with various types of progress reporting. -- -- Author: David Fox module System.Unix.OldProgress {-# DEPRECATED "Use System.Unix.Progress" #-} ( systemTask, -- [Style] -> String -> IO TimeDiff otherTask, -- [Style] -> IO a -> IO a Style (Start, Finish, Error, Output, Echo, Elapsed, Verbosity, Indent), readStyle, -- String -> Maybe Style Output (Indented, Dots, Done, Quiet), msg, -- [Style] -> String -> IO () msgLn, -- [Style] -> String -> IO () -- * Accessors output, -- [Style] -> Maybe Output verbosity, -- [Style] -> Int -- * Style Set modification setStyles, -- [Style] -> [Style] -> [Style] setStyle, -- Style -> [Style] -> [Style] addStyles, -- [Style] -> [Style] -> [Style] addStyle, -- Style -> [Style] -> [Style] removeStyle, -- Style -> [Style] -> [Style] -- * Utilities stripDist, -- FilePath -> FilePath showElapsed, -- String -> IO a -> IO a System.Time.TimeDiff, System.Time.noTimeDiff, fixedTimeDiffToString ) where import Control.Exception import Data.List import System.Exit import System.IO import System.Process import System.Time data Output = Indented | -- ^ Print all the command's output with each line -- indented using (by default) the string ' > '. Dots | -- ^ Print a dot for every 1024 characters the command -- outputs Done | -- ^ Print an ellipsis (...) when the command starts -- and then "done." when it finishes. Quiet -- ^ Print nothing. instance Show Output where show Indented = "Indented" show Dots = "Dots" show Done = "Done" show Quiet = "Quiet " data Style = Start String | -- ^ Message printed before the execution begins Finish String | -- ^ Message printed on successful termination Error String | -- ^ Message printed on failure Output Output | -- ^ Type of output to generate during execution Echo Bool | -- ^ If true, echo the shell command before beginning Elapsed Bool | -- ^ If true print the elapsed time on termination Verbosity Int | -- ^ Set the verbosity level. This value can be queried -- using the verbosity function, but is not otherwise used -- by the -- functions in this module. Indent String -- ^ Set the indentation string for the generated output. instance Show Style where show (Start s) = "Start " ++ show s show (Finish s) = "Finish " ++ show s show (Error s) = "Error " ++ show s show (Output output) = "Output " ++ show output show (Echo flag) = "Echo " ++ show flag show (Elapsed flag) = "Elapsed " ++ show flag show (Verbosity n) = "Verbosity " ++ show n show (Indent s) = "Verbosity " ++ show s styleClass (Start _) = "Start" styleClass (Finish _) = "Finish" styleClass (Error _) = "Error" styleClass (Output _) = "Progress" styleClass (Echo _) = "Echo" styleClass (Elapsed _) = "Elapsed" styleClass (Verbosity _) = "Verbosity" styleClass (Indent _) = "Indent" -- This definition of equivalence is used to add or replace a style -- parameter - for example, supply a Start message if none is present. instance Eq Style where a == b = styleClass a == styleClass b -- |Create a task that sends its output to a handle and then can be -- terminated using an IO operation that returns an exit status. -- Throws an error if the command fails. systemTask :: [Style] -> String -> IO TimeDiff systemTask style command = do start <- getClockTime putIndent style startMessage style taskStart style (_, _, outputHandle, processHandle) <- runInteractiveCommand ("{ " ++ command ++ "; } 1>&2") text <- progressOutput (maybe Indented id (output style)) outputHandle; result <- waitForProcess processHandle finish <- getClockTime let elapsed = diffClockTimes finish start case result of ExitSuccess -> finishMessage style elapsed ExitFailure _ -> errorMessage style text return elapsed where taskStart (Echo True : etc) = do hPutStrLn stderr ("\n -> " ++ command); taskStart etc taskStart (_ : etc) = taskStart etc taskStart [] = return () otherTask :: [Style] -> IO a -> IO a otherTask style task = do start <- getClockTime putIndent style startMessage style taskStart style result <- try task hPutStr stderr "..." finish <- getClockTime let elapsed = diffClockTimes finish start case result of Left (e :: SomeException) -> do errorMessage style (show e) error (show e) Right a -> do finishMessage style elapsed return a where taskStart (_ : etc) = taskStart etc taskStart [] = return () -- FIXME: these two should break up the text into lines and prepend -- the indentation to each. msg :: [Style] -> String -> IO () msg style text = do putIndent style hPutStr stderr text msgLn :: [Style] -> String -> IO () msgLn style text = do putIndent style hPutStrLn stderr text putIndent :: [Style] -> IO () putIndent style = hPutStr stderr (indent style) startMessage :: [Style] -> IO () startMessage (Start message : etc) = do hPutStr stderr message; startMessage etc startMessage (_ : etc) = startMessage etc startMessage [] = return () progressOutput :: Output -> Handle -> IO String progressOutput Dots handle = do hPutStr stderr "..." doText 0 "" where doText count text = do eof <- hIsEOF handle case eof of False -> do line <- hGetLine handle let count' = count + length line + 1 let text' = text ++ line ++ "\n" let (n, m) = quotRem count' 1024 hPutStr stderr (replicate n '.') doText m text' True -> do -- hPutStr stderr "done." return text progressOutput Done handle = do hPutStr stderr "..." doText "" where doText text = do eof <- hIsEOF handle case eof of False -> do line <- hGetLine handle let text' = text ++ line ++ "\n" doText text' True -> do -- hPutStr stderr "done." return text progressOutput Indented handle = do hPutStrLn stderr "" doText where doText = do eof <- hIsEOF handle case eof of True -> return "" False -> do line <- hGetLine handle -- Not collecting text here since it gets output. -- This is a judgement call. -- let text' = text ++ line ++ "\n" hPutStrLn stdout (prefix ++ line) hFlush stdout doText prefix = " > " progressOutput Quiet handle = do doText "" where doText text = do eof <- hIsEOF handle case eof of False -> do line <- hGetLine handle let text' = text ++ line ++ "\n" doText text' True -> return text finishMessage :: [Style] -> TimeDiff -> IO () finishMessage (Elapsed True : etc) elapsed = do hPutStr stderr (" (Elapsed: " ++ fixedTimeDiffToString elapsed ++ ")") finishMessage etc elapsed finishMessage (Finish message : etc) elapsed = do hPutStr stderr message; finishMessage etc elapsed finishMessage (_ : etc) elapsed = finishMessage etc elapsed finishMessage [] _ = do hPutStrLn stderr ""; return () errorMessage :: [Style] -> String -> IO () errorMessage (Error message : _) text = do hPutStrLn stderr text error message errorMessage (_ : etc) text = errorMessage etc text errorMessage [] text = errorMessage [Error "failed"] text -- |Remove styles by class removeStyle :: Style -> [Style] -> [Style] removeStyle (Start _) style = filter (not . isStart) style removeStyle (Finish _) style = filter (not . isFinish) style removeStyle (Error _) style = filter (not. isError) style removeStyle (Output _) style = filter (not . isOutput) style removeStyle (Echo _) style = filter (not . isEcho) style removeStyle old style = filter (/= old) style -- |Add styles, replacing old ones if present setStyles :: [Style] -> [Style] -> [Style] setStyles [] style = style setStyles (x:xs) style = setStyles xs (x : (removeStyle x style)) -- |Singleton case of setStyles setStyle :: Style -> [Style] -> [Style] setStyle new style = setStyles [new] style -- |Singleton case of addStyles addStyle :: Style -> [Style] -> [Style] addStyle x@(Start _) style = case filter isStart style of [] -> x : style; _ -> style addStyle x@(Finish _) style = case filter isFinish style of [] -> x : style; _ -> style addStyle x@(Error _) style = case filter isError style of [] -> x : style; _ -> style addStyle x@(Output _) style = case filter isOutput style of [] -> x : style; _ -> style addStyle x@(Echo _) style = case filter isEcho style of [] -> x : style; _ -> style addStyle x style = if elem x style then style else x : style isStart (Start _) = True isStart _ = False isFinish (Finish _) = True isFinish _ = False isError (Error _) = True isError _ = False isOutput (Output _) = True isOutput _ = False isEcho (Echo _) = True isEcho _ = False output :: [Style] -> Maybe Output output (Output x : _) = Just x output (_ : xs) = output xs output [] = Nothing -- |Add styles only if not present addStyles :: [Style] -> [Style] -> [Style] addStyles styles style = foldr addStyle style styles stripDist :: FilePath -> FilePath stripDist path = maybe path (\ n -> "..." ++ drop (n + 7) path) (isSublistOf "/dists/" path) verbosity :: [Style] -> Int verbosity [] = 0 verbosity (Verbosity n : _) = n verbosity (_ : etc) = verbosity etc indent :: [Style] -> String indent [] = "" indent (Indent s : _) = s indent (_ : etc) = indent etc readStyle :: String -> Maybe Style -- FIXME: implement this readStyle text = case (mapSnd tail . break (== '=')) text of ("Start", message) -> Just $ Start message ("Finish", message) -> Just $ Finish message ("Error", message) -> Just $ Error message ("Output", "Indented") -> Just $ Output Indented ("Output", "Dots") -> Just $ Output Dots ("Output", "Done") -> Just $ Output Done ("Output", "Quiet") -> Just $ Output Quiet ("Echo", flag) -> Just $ Echo (readFlag flag) ("Elapsed", flag) -> Just $ Elapsed (readFlag flag) ("Verbosity", value) -> Just $ Verbosity (read value) ("Indent", prefix) -> Just $ Indent prefix _ -> Nothing where readFlag "yes" = True readFlag "no" = False readFlag "true" = True readFlag "false" = True readFlag text = error ("Unrecognized bool: " ++ text) -- |The timeDiffToString function returns the empty string for -- the zero time diff, this is not the behavior I need. fixedTimeDiffToString :: TimeDiff -> [Char] fixedTimeDiffToString diff = case timeDiffToString diff of "" -> "0 sec" s -> s showElapsed :: String -> IO a -> IO a showElapsed label f = do (result, time) <- elapsed f ePut (label ++ fixedTimeDiffToString time) return result elapsed :: IO a -> IO (a, TimeDiff) elapsed f = do start <- getClockTime result <- f finish <- getClockTime return (result, diffClockTimes finish start) isSublistOf :: Eq a => [a] -> [a] -> Maybe Int isSublistOf sub lst = maybe Nothing (\ s -> Just (length s - length sub)) (find (isSuffixOf sub) (inits lst)) mapSnd :: (b -> c) -> (a, b) -> (a, c) mapSnd f (a, b) = (a, f b) ePut :: String -> IO () ePut s = hPutStrLn stderr s