xrefcheck-0.1.0.0: src/Xrefcheck/Progress.hs
{- SPDX-FileCopyrightText: 2019 Serokell <https://serokell.io>
-
- SPDX-License-Identifier: MPL-2.0
-}
-- | Printing progress bars.
module Xrefcheck.Progress
( -- * Progress
Progress (..)
, initProgress
, incProgress
, incProgressErrors
, showProgress
-- * Printing
, Rewrite
, allowRewrite
, putTextRewrite
) where
import Data.Ratio ((%))
import System.Console.Pretty (Color (..), Style (..), color, style)
import Time (ms, threadDelay)
-----------------------------------------------------------
-- Progress
-----------------------------------------------------------
-- | Processing progress of any thing.
data Progress a = Progress
{ pCurrent :: a
-- ^ How much has been completed.
, pTotal :: a
-- ^ Overall amount of work.
, pErrors :: !a
-- ^ How many of the completed work finished with an error.
} deriving (Show)
-- | Initialise null progress.
initProgress :: Num a => a -> Progress a
initProgress a = Progress{ pTotal = a, pCurrent = 0, pErrors = 0 }
-- | Increase progress amount.
incProgress :: (Num a, Show a) => Progress a -> Progress a
incProgress Progress{..} = Progress{ pCurrent = pCurrent + 1, .. }
-- | Increase errors amount.
incProgressErrors :: (Num a, Show a) => Progress a -> Progress a
incProgressErrors Progress{..} = Progress{ pErrors = pErrors + 1, .. }
-- | Visualise progress bar.
showProgress :: Text -> Int -> Color -> Progress Int -> Text
showProgress name width col Progress{..} = mconcat
[ color col (name <> ": [")
, toText bar
, color col "]"
, status
]
where
done = floor $ (pCurrent % pTotal) * fromIntegral width
errs = ceiling $ (pErrors % pTotal) * fromIntegral width
done' = max 0 $ done - errs
remained' = width - errs - done'
bar | pTotal == 0 = replicate width '-'
| otherwise = mconcat
[ color Red $ replicate errs '■'
, color col $ replicate done' '■'
, color col $ replicate remained' ' '
, " "
]
status
| pTotal == 0 = ""
| pErrors == 0 = style Faint $ color White "✓"
| otherwise = color Red "!"
-----------------------------------------------------------
-- Rewritable output
-----------------------------------------------------------
-- | Rewrites state.
data RewriteCtx = RewriteCtx
{ rMaxPrintedSize :: IORef Int
}
-- | Passing this object allows returning caret and replace text in line.
-- Only functions which has this thing can do that because being
-- interleaved with 'putTextLn' printing caret symbol produced garbage.
data Rewrite
= Rewrite RewriteCtx
-- ^ Default value.
| RewriteDisabled
-- ^ Do not print anything which will be rewritten.
-- Useful when terminal does not interpret caret returns as expected.
-- | Provide context for rewrite operations.
allowRewrite :: (MonadIO m, MonadMask m) => Bool -> (Rewrite -> m a) -> m a
allowRewrite enabled action =
bracket prepare erase action
where
prepare
| enabled = do
rMaxPrintedSize <- newIORef 0
return $ Rewrite RewriteCtx{..}
| otherwise =
pure RewriteDisabled
erase (Rewrite RewriteCtx{..}) = liftIO $ do
maxPrintedSize <- readIORef rMaxPrintedSize
hPutStr stderr $ '\r' : replicate maxPrintedSize ' ' ++ "\r"
-- prevent our output to interleave with further outputs
threadDelay (ms 100)
erase RewriteDisabled = pass
-- | Return caret and print the given text.
putTextRewrite :: MonadIO m => Rewrite -> Text -> m ()
putTextRewrite (Rewrite RewriteCtx{..}) msg = do
liftIO $ hPutStr stderr ('\r' : toString msg)
atomicModifyIORef' rMaxPrintedSize $ \maxPrinted ->
(max maxPrinted (length msg), ())
putTextRewrite RewriteDisabled _ = pass