1 -- | Utilities for running processes and timing them.
7 import Control
.Exception
(throwIO
)
8 import Control
.Monad
(unless)
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
(..))
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
) <-
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 unless (verbose opts
) $ do
86 then T
.putStrLn output
90 <> T
.pack
(show hiddenLines
)
91 <> " lines hidden, use `--verbose` to show)\n"
93 <> T
.unlines tailLines
98 <> formatDiffTime duration
101 <> "\nTotal time so far: "
102 <> formatDiffTime totalDuration
104 ExitFailure exitCode
' -> do
105 unless (verbose opts
) $ do
110 <> "Failed with exit code "
113 <> formatDiffTime duration
116 <> "\nTotal time so far: "
117 <> formatDiffTime totalDuration
122 { eceExitCode
= exitCode
123 , eceProcessConfig
= process
124 , eceStdout
= rawStdout
125 , eceStderr
= rawStderr
128 -- | Decode `ByteString` output from a command and strip whitespace at the
130 decodeStrip
:: ByteString
-> Text
131 decodeStrip
= T
.strip
. T
.toStrict
. T
.decodeUtf8
133 -- | Escape a shell command to display it to a user.
135 -- TODO: Shell escaping
136 displayCommand
:: String -> [String] -> String
137 displayCommand command args
= command
<> " " <> unwords args