1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
|
-- |Run shell commands with various types of progress reporting.
--
-- Author: David Fox
module 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 -> 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
|