{-# LANGUAGE RecordWildCards #-} module System.Console.AsciiProgress ( ProgressBar(..) , Options(..) , Stats(..) , isComplete , newProgressBar , complete , tick , tickN , tickNI , getProgressStrIO , getProgressStats , getProgressStr -- Re-exports: , Default(..) , module System.Console.Regions ) where import Control.Applicative ((<$>)) import Control.Concurrent (modifyMVar_, readChan, readMVar, writeChan) import Control.Concurrent.Async (Async (..), async, poll, wait) import Data.Default (Default (..)) import Data.Maybe (fromMaybe, isJust) import System.Console.AsciiProgress.Internal import System.Console.Regions data ProgressBar = ProgressBar { pgInfo :: ProgressBarInfo , pgFuture :: Async () , pgRegion :: ConsoleRegion } -- | -- Creates a new progress bar with the given @Options@. Multiple progress bars -- may be created. This package depends on `concurrent-output`, so it's -- -- necessary that progress-bar usage is wrapped with a call to -- 'displayConsoleRegions'. -- -- > import Control.Concurrent (threadDelay) -- > import Control.Monad (unless) -- > import System.Console.AsciiProgress -- > -- > main :: IO () -- > main = displayConsoleRegions $ do -- > pg <- newProgressBar def { pgWidth = 100 -- > , pgOnCompletion = Just "Done :percent after :elapsed seconds" -- > } -- > loop pg -- > where -- > loop pg = do -- > b <- isComplete pg -- > unless b $ do -- > threadDelay $ 200 * 1000 -- > tick pg -- > loop pg newProgressBar :: Options -> IO ProgressBar newProgressBar opts = do region <- openConsoleRegion Linear info <- newProgressBarInfo opts -- Display initial progress-bar pgStr <- pgGetProgressStr opts opts <$> getInfoStats info setConsoleRegion region pgStr future <- async $ start info region return $ ProgressBar info future region where start info@ProgressBarInfo{..} region = do c <- readMVar pgCompleted unlessDone c $ do n <- readChan pgChannel _ <- handleMessage info region n unlessDone (c + n) $ start info region where unlessDone c action | c < pgTotal opts = action unlessDone _ _ = do let fmt = fromMaybe (pgFormat opts) (pgOnCompletion opts) onCompletion <- pgGetProgressStr opts opts { pgFormat = fmt } <$> getInfoStats info setConsoleRegion region onCompletion handleMessage info region n = do -- Update the completed tick count modifyMVar_ (pgCompleted info) (\c -> return (c + n)) -- Find and update the current and first tick times: stats <- getInfoStats info let progressStr = pgGetProgressStr opts opts stats setConsoleRegion region progressStr -- | -- Tick the progress bar tick :: ProgressBar -> IO () tick pg = tickN pg 1 -- | -- Tick the progress bar N times tickN :: ProgressBar -> Int -> IO () tickN (ProgressBar info _ _) = writeChan (pgChannel info) . fromIntegral -- | -- Tick the progress bar N times tickNI :: ProgressBar -> Integer -> IO () tickNI (ProgressBar info _ _) = writeChan (pgChannel info) -- | -- Returns if the progress bar rendering thread has exited (it has done enough -- ticks) isComplete :: ProgressBar -> IO Bool isComplete (ProgressBar _ future _) = isJust <$> poll future -- | -- Forces a 'ProgressBar' to finish complete :: ProgressBar -> IO () complete pg@(ProgressBar info future _) = do let total = pgTotal (pgOptions info) tickNI pg total wait future -- | -- Gets the progress bar current @Stats @object getProgressStats :: ProgressBar -> IO Stats getProgressStats (ProgressBar info _ _) = getInfoStats info -- | -- Like @getProgressStr@ but works on the @ProgressBar@ object and uses the IO -- monad. getProgressStrIO :: ProgressBar -> IO String getProgressStrIO (ProgressBar info _ _) = getProgressStr (pgOptions info) <$> getInfoStats info