File: Progress.hs

package info (click to toggle)
haskell-unixutils 1.22-3
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 132 kB
  • sloc: haskell: 973; makefile: 2
file content (378 lines) | stat: -rw-r--r-- 12,120 bytes parent folder | download
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