1 -- | Utilities for running processes and timing them.
7 import Control
.Exception
(throwIO
)
8 import Control
.Monad
(when)
9 import Data
.ByteString
.Lazy
(ByteString
)
10 import qualified Data
.ByteString
.Lazy
as ByteString
11 import Data
.Text
(Text
)
12 import qualified Data
.Text
as T
13 import qualified Data
.Text
.IO as T
14 import qualified Data
.Text
.Lazy
as T
(toStrict
)
15 import qualified Data
.Text
.Lazy
.Encoding
as T
(decodeUtf8
)
16 import System
.Directory
(withCurrentDirectory
)
17 import System
.Exit
(ExitCode (ExitFailure
, ExitSuccess
))
18 import System
.Process
.Typed
(ExitCodeException
(..), proc
, readProcess
, runProcess
)
20 import ANSI
(SGR
(BrightBlue
, BrightGreen
, BrightRed
, Reset
), setSGR
)
21 import Cli
(Opts
(..), Verbosity
(..))
22 import ClockUtil
(diffAbsoluteTime
, formatDiffTime
, getAbsoluteTime
)
24 -- | Like `timed`, but runs the command in a given directory.
27 -- ^ @cabal-validate@ options.
29 -- ^ Path to run the command in.
31 -- ^ The command to run.
33 -- ^ Arguments to pass to the command.
35 timedWithCwd opts cdPath command args
=
36 withCurrentDirectory cdPath
(timed opts command args
)
38 -- | Run a command, displaying timing information after it finishes.
40 -- This prints out the command to be executed before it's run, handles hiding
41 -- or showing output (according to the value of `verbose`), and throws an
42 -- `ExitCodeException` if the command fails.
45 -- ^ @cabal-validate@ options.
47 -- ^ The command to run.
49 -- ^ Arguments to pass to the command.
51 timed opts command args
= do
52 let prettyCommand
= displayCommand command args
53 process
= proc command args
55 startTime
' <- getAbsoluteTime
57 -- TODO: Replace `$HOME` or `opts.cwd` for brevity?
64 (exitCode
, rawStdout
, rawStderr
) <-
65 if verbosity opts
> Quiet
67 exitCode
<- runProcess process
68 pure
(exitCode
, ByteString
.empty, ByteString
.empty)
69 else readProcess process
71 endTime
<- getAbsoluteTime
73 let duration
= diffAbsoluteTime endTime startTime
'
74 totalDuration
= diffAbsoluteTime endTime
(startTime opts
)
76 output
= decodeStrip rawStdout
<> "\n" <> decodeStrip rawStderr
78 outputLines
= T
.lines output
79 hiddenLines
= length outputLines
- linesLimit
80 tailLines
= drop hiddenLines outputLines
84 -- Output is captured when `--quiet` is used, so only print it here
85 -- if `--quiet` _isn't_ used.
86 when (verbosity opts
> Quiet
) $ do
88 then T
.putStrLn output
92 <> T
.pack
(show hiddenLines
)
93 <> " lines hidden, use `--verbose` to show)\n"
95 <> T
.unlines tailLines
100 <> formatDiffTime duration
103 <> "\nTotal time so far: "
104 <> formatDiffTime totalDuration
106 ExitFailure exitCode
' -> do
107 when (verbosity opts
<= Info
) $ do
112 <> "Failed with exit code "
115 <> formatDiffTime duration
118 <> "\nTotal time so far: "
119 <> formatDiffTime totalDuration
124 { eceExitCode
= exitCode
125 , eceProcessConfig
= process
126 , eceStdout
= rawStdout
127 , eceStderr
= rawStderr
130 -- | Decode `ByteString` output from a command and strip whitespace at the
132 decodeStrip
:: ByteString
-> Text
133 decodeStrip
= T
.strip
. T
.toStrict
. T
.decodeUtf8
135 -- | Escape a shell command to display it to a user.
137 -- TODO: Shell escaping
138 displayCommand
:: String -> [String] -> String
139 displayCommand command args
= command
<> " " <> unwords args