packages feed

nptools-0.2.2: timer.hs

{-# LANGUAGE PatternGuards #-}
import Prelude hiding (min,and)
import System.Environment (getArgs)
import Control.Concurrent (threadDelay)
import System.IO (hFlush, stdout)
import Control.Monad (forM_, when)
import Data.Char (isDigit)

-- one day this would be standard (the Applicative version)
void :: IO a -> IO ()
void x = x >> return ()

setupTitle :: String -> IO ()
setupTitle title = do
  putStr "\ESC]2;"
  putStr title
  putStr "\a"
  hFlush stdout

redrawLn :: String -> IO ()
redrawLn str = do
  putStr "\r\ESC[K"
  putStr str
  hFlush stdout

type Microseconds = Int
type Seconds = Int
type Minutes = Int

minute, minutes :: Minutes -> Microseconds
second, seconds :: Seconds -> Microseconds
seconds = (*1000000)
minutes = (*60) . seconds
second  = seconds
minute  = minutes

sleep_ :: Microseconds -> IO ()
sleep_ = void . threadDelay

countDown :: Int -> (Int -> IO ()) -> IO ()
countDown i = forM_ (reverse [1..i])

(<|) :: a -> (a -> b) -> b
(<|) x f = f x

timerDisplay :: String -> IO ()
timerDisplay s = do setupTitle $ "Timer: " ++ s
                    redrawLn $ s

showTime :: Minutes -> Seconds -> String
showTime min sec = f min "minutes" `and` f sec "seconds" where
  f x s | x == 0     = ""
        | otherwise  = show x ++ " " ++ s
  "" `and` x   = x
  x  `and` ""  = x
  x  `and` y   = x ++ " and " ++ y

remainingTime :: Minutes -> Seconds -> IO ()
remainingTime min sec
  = timerDisplay $ "Remaining time: "++showTime min sec++"..."

timer :: Int -> Int -> IO ()
timer min sec = do
  countDown sec     $ \i ->
    remainingTime min i >> sleep_ (1<|second)

  countDown (min-1) $ \i ->
    remainingTime (i+1) 0 >> sleep_ (1<|minute)

  when (min>=1) $
    countDown 60 $ \i ->
      remainingTime 0 i >> sleep_ (1<|second)

  timerDisplay "Time done!\07"

  forM_ [1 :: Int ..] $ \i -> do
    sleep_ (1<|minute)
    timerDisplay $ "Time passed since " ++ showTime i 0 ++ "!\07"

main :: IO ()
main = do
  args <- getArgs
  case args of
    [dur] | (min,sec') <- break (==':') dur,
            sec <- drop 1 sec',
            all isDigit min,
            all isDigit sec ->
         timer (read min) (if null sec then 0 else read sec)
    _ -> error "Duration in minutes (or minutes:seconds) expected as argument"