happstack-util-0.3.1: src/Happstack/Util/Common.hs
-----------------------------------------------------------------------------
-- |
-- Module : Happstack.Util.Common
-- Copyright : (c) Happstack.com, 2009; (c) HAppS.org, 2005
-- License : BSD3
--
--
-- Various helper routines.
-----------------------------------------------------------------------------
module Happstack.Util.Common where
import System.Log.Logger
import Control.Concurrent
import Control.Monad
import qualified Data.ByteString.Char8 as P
import Data.Char
import Data.Int
import System.IO
import System.Exit
import System.IO.Error
import System.Process
import System.IO.Unsafe
import System.Time
import Control.Arrow (first,second)
type Seconds = Int
type EpochSeconds = Int64
epochSeconds :: CalendarTime -> EpochSeconds
epochSeconds ct = let TOD sec _ = toClockTime ct in fromIntegral sec
eSecsToCalTime :: EpochSeconds -> IO CalendarTime
eSecsToCalTime s = toCalendarTime (TOD (fromIntegral s) 0)
epochPico :: CalendarTime -> Integer
epochPico ct = fromIntegral (epochSeconds ct) * 1000
----reliable getline and putline
logMC :: Priority -> String -> IO ()
logMC = logM "Happstack.Util.Common"
-- | Put a line into a handle followed by "\r\n" and echo to stdout
hPutLine :: Handle -> String -> IO ()
hPutLine handle line = do
hPutStr handle line
hPutStr handle "\r\n"
hFlush handle
logMC DEBUG line
return ()
-- | Get a line from the handle and echo to stdout
hGetLn :: Handle -> IO String
hGetLn handle = do
let hGetLn' = do
c <- hGetChar handle
case c of
'\n' -> return []
'\r' -> do c2 <- hGetChar handle
if c2 == '\n' then return [] else getRest c
_ -> getRest c
getRest c = fmap (c:) hGetLn'
line <- hGetLn'
logMC DEBUG line
return line
unBracket, ltrim, rtrim, trim :: String -> String
-- | Removes the whitespace surrounding a string as well
-- as the first and last character.
-- @unBracket " (asdf) " = "asdf"@
unBracket = tail . init . trim
-- | Drops the whitespace at the start of the string
ltrim = dropWhile isSpace
-- | Drops the whitespace at the end of the string
rtrim = reverse.ltrim.reverse
-- | Trims the beginning and ending whitespace of a string
trim=ltrim.rtrim
-- | Repeadly splits a list by the provided separator and collects the results
splitList :: Eq a => a -> [a] -> [[a]]
splitList _ [] = []
splitList sep list = h:splitList sep t
where (h,t)=split (==sep) list
-- | Repeatedly splits a list and collects the results
splitListBy :: (a -> Bool) -> [a] -> [[a]]
splitListBy _ [] = []
splitListBy f list = h:splitListBy f t
where (h,t)=split f list
-- | Split is like break, but the matching element is dropped.
split :: (a -> Bool) -> [a] -> ([a], [a])
split f s = (left,right)
where
(left,right')=break f s
right = if null right' then [] else tail right'
-- | Read file with a default value if the file does not exist.
mbReadFile :: a -> (String -> a) -> FilePath -> IO a
mbReadFile noth just path =
(do text <- readFile path;return $ just text)
`catch` \err -> if isDoesNotExistError err then return noth else ioError err
mapFst :: (a -> b) -> [(a,x)] -> [(b,x)]
mapFst = map . first
mapSnd :: (a -> b) -> [(x,a)] -> [(x,b)]
mapSnd = map . second
-- | applies the list of functions to the provided argument
revmap :: a -> [a -> b] -> [b]
revmap item = map (\f->f item)
-- | @comp f a b@ compares @a@ and @b@ after apply
-- @f@.
comp :: Ord t => (a -> t) -> a -> a -> Ordering
comp f e1 e2 = f e1 `compare` f e2
-- | Run an external command. Upon failure print status
-- to stderr.
runCommand :: String -> [String] -> IO ()
runCommand cmd args = do
(_, outP, errP, pid) <- runInteractiveProcess cmd args Nothing Nothing
let pGetContents h = do mv <- newEmptyMVar
let put [] = putMVar mv []
put xs = last xs `seq` putMVar mv xs
forkIO (hGetContents h >>= put)
takeMVar mv
os <- pGetContents outP
es <- pGetContents errP
ec <- waitForProcess pid
case ec of
ExitSuccess -> return ()
ExitFailure e ->
do hPutStrLn stderr ("Running process "++unwords (cmd:args)++" FAILED ("++show e++")")
hPutStrLn stderr os
hPutStrLn stderr es
hPutStrLn stderr "Raising error..."
fail "Running external command failed"
-- | Unsafe tracing, outputs the message and the value to stderr.
debug :: Show a => String -> a -> a
debug msg s =
seq (unsafePerformIO (hPutStr stderr ("DEBUG: "++msg++"\n") >>
hPutStr stderr (show s++"\n"))) s
{-# NOINLINE debugM #-}
-- | Unsafe tracing messages inside a monad.
debugM :: Monad m => String -> m ()
debugM msg = unsafePerformIO (P.hPutStr stderr (P.pack (msg++"\n")) >> hFlush stderr) `seq` return ()
-- | Read in any monad.
readM :: (Monad m, Read t) => String -> m t
readM s = case reads s of
[(v,"")] -> return v
_ -> fail "readM: parse error"
-- | Convert Maybe into an another monad. This is a simple injection that calls
-- fail when given a Nothing.
maybeM :: Monad m => Maybe a -> m a
maybeM (Just x) = return x
maybeM _ = fail "maybeM: Nothing"
-- | Lifts a bool into a MonadPlus, with False mapped to the mzero.
boolM :: (MonadPlus m) => Bool -> m Bool
boolM False = mzero
boolM True = return True
-- | @notMb a b@ returns @Just a@ if @b@ is @Nothing@ and @Nothing@ if
-- @b@ is @Just _@.
notMb :: a-> Maybe a-> Maybe a
notMb v1 v2 = maybe (Just v1) (const Nothing) v2
-- | Takes a list of delays, in seconds, and an action to execute
-- repeatedly. The action is then executed repeatedly in a separate thread
-- until the list has been consumed. The first action takes place immediately.
periodic :: [Int] -> IO () -> IO ThreadId
periodic ts = forkIO . periodic' ts
-- a little something to fix the types of ^
infixr 8 .^
(.^) :: Int->Int->Int
a .^ b = a ^ b
-- | Similar to 'periodic' but runs in the same thread
periodic' :: [Int] -> IO a -> IO a
periodic' [] x = x
periodic' (t:ts) x = x >> threadDelay ((10 .^ 6)*t) >> periodic' ts x