-- $Id: GenUtil.hs,v 1.52 2007/05/25 23:54:08 john Exp $ -- arch-tag: 835e46b7-8ffd-40a0-aaf9-326b7e347760 -- Copyright (c) 2002 John Meacham (john@foo.net) -- -- Permission is hereby granted, free of charge, to any person obtaining a -- copy of this software and associated documentation files (the -- "Software"), to deal in the Software without restriction, including -- without limitation the rights to use, copy, modify, merge, publish, -- distribute, sublicense, and/or sell copies of the Software, and to -- permit persons to whom the Software is furnished to do so, subject to -- the following conditions: -- -- The above copyright notice and this permission notice shall be included -- in all copies or substantial portions of the Software. -- -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -- OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ---------------------------------------- -- | This is a collection of random useful utility functions written in pure -- Haskell 98. In general, it trys to conform to the naming scheme put forth -- the haskell prelude and fill in the obvious omissions, as well as provide -- useful routines in general. To ensure maximum portability, no instances are -- exported so it may be added to any project without conflicts. ---------------------------------------- module GenUtil( -- * Functions -- ** Error reporting putErr,putErrLn,putErrDie, -- ** Simple deconstruction fromLeft,fromRight,fsts,snds,splitEither,rights,lefts, isLeft,isRight, fst3,snd3,thd3, -- ** System routines exitSuccess, System.exitFailure, epoch, lookupEnv,endOfTime, -- ** Random routines repMaybe, liftT2, liftT3, liftT4, snub, snubFst, snubUnder, smerge, sortFst, groupFst, foldl', fmapLeft,fmapRight,isDisjoint,isConjoint, groupUnder, sortUnder, minimumUnder, maximumUnder, sortGroupUnder, sortGroupUnderF, sortGroupUnderFG, sameLength, naturals, -- ** Monad routines perhapsM, repeatM, repeatM_, replicateM, replicateM_, maybeToMonad, toMonadM, ioM, ioMp, foldlM, foldlM_, foldl1M, foldl1M_, maybeM, -- ** Text Routines -- *** Quoting shellQuote, simpleQuote, simpleUnquote, -- *** Layout indentLines, buildTableLL, buildTableRL, buildTable, trimBlankLines, paragraph, paragraphBreak, expandTabs, chunkText, -- *** Scrambling rot13, -- ** Random intercalate, powerSet, randomPermute, randomPermuteIO, chunk, rtup, triple, fromEither, mapFst, mapSnd, mapFsts, mapSnds, tr, readHex, overlaps, showDuration, readM, readsM, split, tokens, count, hasRepeatUnder, -- ** Option handling getArgContents, parseOpt, getOptContents, doTime, getPrefix, rspan, rbreak, rdropWhile, rtakeWhile, rbdropWhile, concatMapM, on, mapMsnd, mapMfst, -- * Classes UniqueProducer(..) ) where import Char(isAlphaNum, isSpace, toLower, ord, chr) import List import Monad import qualified IO import qualified System import Random(StdGen, newStdGen, Random(randomR)) import Time import CPUTime {-# SPECIALIZE snub :: [String] -> [String] #-} {-# SPECIALIZE snub :: [Int] -> [Int] #-} {-# RULES "snub/snub" forall x . snub (snub x) = snub x #-} {-# RULES "snub/nub" forall x . snub (nub x) = snub x #-} {-# RULES "nub/snub" forall x . nub (snub x) = snub x #-} {-# RULES "snub/sort" forall x . snub (sort x) = snub x #-} {-# RULES "sort/snub" forall x . sort (snub x) = snub x #-} {-# RULES "snub/[]" snub [] = [] #-} {-# RULES "snub/[x]" forall x . snub [x] = [x] #-} -- | sorted nub of list, much more efficient than nub, but doesnt preserve ordering. snub :: Ord a => [a] -> [a] snub = map head . group . sort -- | sorted nub of list of tuples, based solely on the first element of each tuple. snubFst :: Ord a => [(a,b)] -> [(a,b)] snubFst = map head . groupBy (\(x,_) (y,_) -> x == y) . sortBy (\(x,_) (y,_) -> compare x y) -- | sorted nub of list based on function of values snubUnder :: Ord b => (a -> b) -> [a] -> [a] snubUnder f = map head . groupUnder f . sortUnder f -- | sort list of tuples, based on first element of each tuple. sortFst :: Ord a => [(a,b)] -> [(a,b)] sortFst = sortBy (\(x,_) (y,_) -> compare x y) -- | group list of tuples, based only on equality of the first element of each tuple. groupFst :: Eq a => [(a,b)] -> [[(a,b)]] groupFst = groupBy (\(x,_) (y,_) -> x == y) concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b] concatMapM f xs = do res <- mapM f xs return $ concat res on :: (a -> a -> b) -> (c -> a) -> c -> c -> b (*) `on` f = \x y -> f x * f y mapMsnd :: Monad m => (b -> m c) -> [(a,b)] -> m [(a,c)] mapMsnd f xs = do let g (a,b) = do c <- f b return (a,c) mapM g xs mapMfst :: Monad m => (b -> m c) -> [(b,a)] -> m [(c,a)] mapMfst f xs = do let g (a,b) = do c <- f a return (c,b) mapM g xs rspan :: (a -> Bool) -> [a] -> ([a], [a]) rspan fn xs = f xs [] where f [] rs = ([],reverse rs) f (x:xs) rs | fn x = f xs (x:rs) | otherwise = (reverse rs ++ x:za,zb) where (za,zb) = f xs [] rbreak :: (a -> Bool) -> [a] -> ([a], [a]) rbreak fn xs = rspan (not . fn) xs rdropWhile :: (a -> Bool) -> [a] -> [a] rdropWhile fn xs = f xs [] where f [] _ = [] f (x:xs) rs | fn x = f xs (x:rs) | otherwise = reverse rs ++ x:(f xs []) rtakeWhile :: (a -> Bool) -> [a] -> [a] rtakeWhile fn xs = f xs [] where f [] rs = reverse rs f (x:xs) rs | fn x = f xs (x:rs) | otherwise = f xs [] rbdropWhile :: (a -> Bool) -> [a] -> [a] rbdropWhile fn xs = rdropWhile fn (dropWhile fn xs) -- | group a list based on a function of the values. groupUnder :: Eq b => (a -> b) -> [a] -> [[a]] groupUnder f = groupBy (\x y -> f x == f y) -- | sort a list based on a function of the values. sortUnder :: Ord b => (a -> b) -> [a] -> [a] sortUnder f = sortBy (\x y -> f x `compare` f y) -- | merge sorted lists in linear time smerge :: Ord a => [a] -> [a] -> [a] smerge (x:xs) (y:ys) | x == y = x:smerge xs ys | x < y = x:smerge xs (y:ys) | otherwise = y:smerge (x:xs) ys smerge [] ys = ys smerge xs [] = xs sortGroupUnder :: Ord a => (b -> a) -> [b] -> [[b]] sortGroupUnder f = groupUnder f . sortUnder f sortGroupUnderF :: Ord a => (b -> a) -> [b] -> [(a,[b])] sortGroupUnderF f xs = [ (f x, xs) | xs@(x:_) <- sortGroupUnder f xs] sortGroupUnderFG :: Ord b => (a -> b) -> (a -> c) -> [a] -> [(b,[c])] sortGroupUnderFG f g xs = [ (f x, map g xs) | xs@(x:_) <- sortGroupUnder f xs] minimumUnder :: Ord b => (a -> b) -> [a] -> a minimumUnder _ [] = error "minimumUnder: empty list" minimumUnder _ [x] = x minimumUnder f (x:xs) = g (f x) x xs where g _ x [] = x g fb b (x:xs) | fx < fb = g fx x xs | otherwise = g fb b xs where fx = f x maximumUnder :: Ord b => (a -> b) -> [a] -> a maximumUnder _ [] = error "maximumUnder: empty list" maximumUnder _ [x] = x maximumUnder f (x:xs) = g (f x) x xs where g _ x [] = x g fb b (x:xs) | fx > fb = g fx x xs | otherwise = g fb b xs where fx = f x -- | Flushes stdout and writes string to standard error putErr :: String -> IO () putErr s = IO.hFlush IO.stdout >> IO.hPutStr IO.stderr s -- | Flush stdout and write string and newline to standard error putErrLn :: String -> IO () putErrLn s = IO.hFlush IO.stdout >> IO.hPutStrLn IO.stderr s -- | Flush stdout, write string and newline to standard error, -- then exit program with failure. putErrDie :: String -> IO a putErrDie s = putErrLn s >> System.exitFailure -- | exit program successfully. 'exitFailure' is -- also exported from System. exitSuccess :: IO a exitSuccess = System.exitWith System.ExitSuccess {-# INLINE fromRight #-} fromRight :: Either a b -> b fromRight (Right x) = x fromRight _ = error "fromRight" {-# INLINE fromLeft #-} fromLeft :: Either a b -> a fromLeft (Left x) = x fromLeft _ = error "fromLeft" -- | recursivly apply function to value until it returns Nothing repMaybe :: (a -> Maybe a) -> a -> a repMaybe f e = case f e of Just e' -> repMaybe f e' Nothing -> e {-# INLINE liftT2 #-} {-# INLINE liftT3 #-} {-# INLINE liftT4 #-} liftT4 (f1,f2,f3,f4) (v1,v2,v3,v4) = (f1 v1, f2 v2, f3 v3, f4 v4) liftT3 (f,g,h) (x,y,z) = (f x, g y, h z) -- | apply functions to values inside a tupele. 'liftT3' and 'liftT4' also exist. liftT2 :: (a -> b, c -> d) -> (a,c) -> (b,d) liftT2 (f,g) (x,y) = (f x, g y) -- | class for monads which can generate -- unique values. class Monad m => UniqueProducer m where -- | produce a new unique value newUniq :: m Int rtup a b = (b,a) triple a b c = (a,b,c) fst3 (a,_,_) = a snd3 (_,b,_) = b thd3 (_,_,c) = c -- | the standard unix epoch epoch :: ClockTime epoch = toClockTime $ CalendarTime { ctYear = 1970, ctMonth = January, ctDay = 0, ctHour = 0, ctMin = 0, ctSec = 0, ctTZ = 0, ctPicosec = 0, ctWDay = undefined, ctYDay = undefined, ctTZName = undefined, ctIsDST = undefined} -- | an arbitrary time in the future endOfTime :: ClockTime endOfTime = toClockTime $ CalendarTime { ctYear = 2020, ctMonth = January, ctDay = 0, ctHour = 0, ctMin = 0, ctSec = 0, ctTZ = 0, ctPicosec = 0, ctWDay = undefined, ctYDay = undefined, ctTZName = undefined, ctIsDST = undefined} {-# INLINE fsts #-} -- | take the fst of every element of a list fsts :: [(a,b)] -> [a] fsts = map fst {-# INLINE snds #-} -- | take the snd of every element of a list snds :: [(a,b)] -> [b] snds = map snd {-# INLINE repeatM #-} {-# SPECIALIZE repeatM :: IO a -> IO [a] #-} repeatM :: Monad m => m a -> m [a] repeatM x = sequence $ repeat x {-# INLINE repeatM_ #-} {-# SPECIALIZE repeatM_ :: IO a -> IO () #-} repeatM_ :: Monad m => m a -> m () repeatM_ x = sequence_ $ repeat x {-# RULES "replicateM/0" replicateM 0 = const (return []) #-} {-# RULES "replicateM_/0" replicateM_ 0 = const (return ()) #-} {-# INLINE replicateM #-} {-# SPECIALIZE replicateM :: Int -> IO a -> IO [a] #-} replicateM :: Monad m => Int -> m a -> m [a] replicateM n x = sequence $ replicate n x {-# INLINE replicateM_ #-} {-# SPECIALIZE replicateM_ :: Int -> IO a -> IO () #-} replicateM_ :: Monad m => Int -> m a -> m () replicateM_ n x = sequence_ $ replicate n x -- | convert a maybe to an arbitrary failable monad maybeToMonad :: Monad m => Maybe a -> m a maybeToMonad (Just x) = return x maybeToMonad Nothing = fail "Nothing" -- | convert a maybe to an arbitrary failable monad maybeM :: Monad m => String -> Maybe a -> m a maybeM _ (Just x) = return x maybeM s Nothing = fail s toMonadM :: Monad m => m (Maybe a) -> m a toMonadM action = join $ liftM maybeToMonad action foldlM :: Monad m => (a -> b -> m a) -> a -> [b] -> m a foldlM f v (x:xs) = (f v x) >>= \a -> foldlM f a xs foldlM _ v [] = return v foldl1M :: Monad m => (a -> a -> m a) -> [a] -> m a foldl1M f (x:xs) = foldlM f x xs foldl1M _ _ = error "foldl1M" foldlM_ :: Monad m => (a -> b -> m a) -> a -> [b] -> m () foldlM_ f v xs = foldlM f v xs >> return () foldl1M_ ::Monad m => (a -> a -> m a) -> [a] -> m () foldl1M_ f xs = foldl1M f xs >> return () -- | partition a list of eithers. splitEither :: [Either a b] -> ([a],[b]) splitEither (r:rs) = case splitEither rs of (xs,ys) -> case r of Left x -> (x:xs,ys) Right y -> (xs,y:ys) splitEither [] = ([],[]) isLeft Left {} = True isLeft _ = False isRight Right {} = True isRight _ = False perhapsM :: Monad m => Bool -> a -> m a perhapsM True a = return a perhapsM False _ = fail "perhapsM" sameLength (_:xs) (_:ys) = sameLength xs ys sameLength [] [] = True sameLength _ _ = False fromEither :: Either a a -> a fromEither (Left x) = x fromEither (Right x) = x {-# INLINE mapFst #-} {-# INLINE mapSnd #-} mapFst :: (a -> b) -> (a,c) -> (b,c) mapFst f (x,y) = (f x, y) mapSnd :: (a -> b) -> (c,a) -> (c,b) mapSnd g (x,y) = ( x,g y) {-# INLINE mapFsts #-} {-# INLINE mapSnds #-} mapFsts :: (a -> b) -> [(a,c)] -> [(b,c)] mapFsts f xs = [(f x, y) | (x,y) <- xs] mapSnds :: (a -> b) -> [(c,a)] -> [(c,b)] mapSnds g xs = [(x, g y) | (x,y) <- xs] {-# INLINE rights #-} -- | take just the rights rights :: [Either a b] -> [b] rights xs = [x | Right x <- xs] {-# INLINE lefts #-} -- | take just the lefts lefts :: [Either a b] -> [a] lefts xs = [x | Left x <- xs] -- | Trasform IO errors into the failing of an arbitrary monad. ioM :: Monad m => IO a -> IO (m a) ioM action = catch (fmap return action) (\e -> return (fail (show e))) -- | Trasform IO errors into the mzero of an arbitrary member of MonadPlus. ioMp :: MonadPlus m => IO a -> IO (m a) ioMp action = catch (fmap return action) (\_ -> return mzero) -- | reformat a string to not be wider than a given width, breaking it up -- between words. paragraph :: Int -> String -> String paragraph maxn xs = drop 1 (f maxn (words xs)) where f n (x:xs) | lx < n = (' ':x) ++ f (n - lx) xs where lx = length x + 1 f _ (x:xs) = '\n': (x ++ f (maxn - length x) xs) f _ [] = "\n" chunk :: Int -> [a] -> [[a]] chunk 0 _ = repeat [] chunk _ [] = [] chunk mw s = case splitAt mw s of (a,[]) -> [a] (a,b) -> a : chunk mw b chunkText :: Int -> String -> String chunkText mw s = concatMap (unlines . chunk mw) $ lines s rot13Char :: Char -> Char rot13Char c | c >= 'a' && c <= 'm' || c >= 'A' && c <= 'M' = chr $ ord c + 13 | c >= 'n' && c <= 'z' || c >= 'N' && c <= 'Z' = chr $ ord c - 13 | otherwise = c rot13 :: String -> String rot13 = map rot13Char {- paragraphBreak :: Int -> String -> String paragraphBreak maxn xs = unlines (map ( unlines . map (unlines . chunk maxn) . lines . f maxn ) $ lines xs) where f _ "" = "" f n xs | length ss > 0 = if length ss + r rs > n then '\n':f maxn rs else ss where (ss,rs) = span isSpace xs f n xs = ns ++ f (n - length ns) rs where (ns,rs) = span (not . isSpace) xs r xs = length $ fst $ span (not . isSpace) xs -} paragraphBreak :: Int -> String -> String paragraphBreak maxn xs = unlines $ (map f) $ lines xs where f s | length s <= maxn = s f s | isSpace (head b) = a ++ "\n" ++ f (dropWhile isSpace b) | all (not . isSpace) a = a ++ "\n" ++ f b | otherwise = reverse (dropWhile isSpace sa) ++ "\n" ++ f (reverse ea ++ b) where (ea, sa) = span (not . isSpace) $ reverse a (a,b) = splitAt maxn s expandTabs' :: Int -> Int -> String -> String expandTabs' 0 _ s = filter (/= '\t') s expandTabs' sz off ('\t':s) = replicate len ' ' ++ expandTabs' sz (off + len) s where len = (sz - (off `mod` sz)) expandTabs' sz _ ('\n':s) = '\n': expandTabs' sz 0 s expandTabs' sz off (c:cs) = c: expandTabs' sz (off + 1) cs expandTabs' _ _ "" = "" -- | expand tabs into spaces in a string assuming tabs are every 8 spaces and we are starting at column 0. expandTabs :: String -> String expandTabs s = expandTabs' 8 0 s -- | Translate characters to other characters in a string, if the second argument is empty, -- delete the characters in the first argument, else map each character to the -- cooresponding one in the second argument, cycling the second argument if -- necessary. tr :: String -> String -> String -> String tr as "" s = filter (`notElem` as) s tr as bs s = map (f as bs) s where f (a:_) (b:_) c | a == c = b f (_:as) (_:bs) c = f as bs c f [] _ c = c f as' [] c = f as' bs c --f _ _ _ = error "invalid tr" -- | quote strings rc style. single quotes protect any characters between -- them, to get an actual single quote double it up. Inverse of 'simpleUnquote' simpleQuote :: [String] -> String simpleQuote ss = unwords (map f ss) where f s | any isBad s || null s = "'" ++ dquote s ++ "'" f s = s dquote s = concatMap (\c -> if c == '\'' then "''" else [c]) s isBad c = isSpace c || c == '\'' -- | inverse of 'simpleQuote' simpleUnquote :: String -> [String] simpleUnquote s = f (dropWhile isSpace s) where f [] = [] f ('\'':xs) = case quote' "" xs of (x,y) -> x:f (dropWhile isSpace y) f xs = case span (not . isSpace) xs of (x,y) -> x:f (dropWhile isSpace y) quote' a ('\'':'\'':xs) = quote' ('\'':a) xs quote' a ('\'':xs) = (reverse a, xs) quote' a (x:xs) = quote' (x:a) xs quote' a [] = (reverse a, "") -- | quote a set of strings as would be appropriate to pass them as -- arguments to a sh style shell shellQuote :: [String] -> String shellQuote ss = unwords (map f ss) where f s | any (not . isGood) s || null s = "'" ++ dquote s ++ "'" f s = s dquote s = concatMap (\c -> if c == '\'' then "'\\''" else [c]) s isGood c = isAlphaNum c || c `elem` "@/.-_" -- | looks up an enviornment variable and returns it in an arbitrary Monad rather -- than raising an exception if the variable is not set. lookupEnv :: Monad m => String -> IO (m String) lookupEnv s = catch (fmap return $ System.getEnv s) (\e -> if IO.isDoesNotExistError e then return (fail (show e)) else ioError e) {-# SPECIALIZE fmapLeft :: (a -> c) -> [(Either a b)] -> [(Either c b)] #-} fmapLeft :: Functor f => (a -> c) -> f (Either a b) -> f (Either c b) fmapLeft fn = fmap f where f (Left x) = Left (fn x) f (Right x) = Right x {-# SPECIALIZE fmapRight :: (b -> c) -> [(Either a b)] -> [(Either a c)] #-} fmapRight :: Functor f => (b -> c) -> f (Either a b) -> f (Either a c) fmapRight fn = fmap f where f (Left x) = Left x f (Right x) = Right (fn x) {-# SPECIALIZE isDisjoint :: [String] -> [String] -> Bool #-} {-# SPECIALIZE isConjoint :: [String] -> [String] -> Bool #-} {-# SPECIALIZE isDisjoint :: [Int] -> [Int] -> Bool #-} {-# SPECIALIZE isConjoint :: [Int] -> [Int] -> Bool #-} -- | set operations on lists. (slow!) isDisjoint, isConjoint :: Eq a => [a] -> [a] -> Bool isConjoint xs ys = or [x == y | x <- xs, y <- ys] isDisjoint xs ys = not (isConjoint xs ys) -- | 'concat' composed with 'List.intersperse'. Can be used similarly to join in perl. intercalate :: [a] -> [[a]] -> [a] intercalate x xss = concat (intersperse x xss) -- | place spaces before each line in string. indentLines :: Int -> String -> String indentLines n s = unlines $ map (replicate n ' ' ++)$ lines s -- | trim blank lines at beginning and end of string trimBlankLines :: String -> String trimBlankLines cs = unlines $ rbdropWhile (all isSpace) (lines cs) buildTableRL :: [(String,String)] -> [String] buildTableRL ps = map f ps where f (x,"") = x f (x,y) = replicate (bs - length x) ' ' ++ x ++ replicate 4 ' ' ++ y bs = maximum (map (length . fst) [ p | p@(_,_:_) <- ps ]) buildTableLL :: [(String,String)] -> [String] buildTableLL ps = map f ps where f (x,y) = x ++ replicate (bs - length x) ' ' ++ replicate 4 ' ' ++ y bs = maximum (map (length . fst) ps) {-# INLINE foldl' #-} -- | strict version of 'foldl' foldl' :: (a -> b -> a) -> a -> [b] -> a foldl' _ a [] = a foldl' f a (x:xs) = (foldl' f $! f a x) xs -- | count elements of list that have a given property count :: (a -> Bool) -> [a] -> Int count f xs = g 0 xs where g n [] = n g n (x:xs) | f x = let x = n + 1 in x `seq` g x xs | otherwise = g n xs -- | randomly permute a list, using the standard random number generator. randomPermuteIO :: [a] -> IO [a] randomPermuteIO xs = newStdGen >>= \g -> return (randomPermute g xs) -- | randomly permute a list given a RNG randomPermute :: StdGen -> [a] -> [a] randomPermute _ [] = [] randomPermute gen xs = (head tl) : randomPermute gen' (hd ++ tail tl) where (idx, gen') = randomR (0,length xs - 1) gen (hd, tl) = splitAt idx xs hasRepeatUnder f xs = any (not . null . tail) $ sortGroupUnder f xs -- | compute the power set of a list powerSet :: [a] -> [[a]] powerSet [] = [[]] powerSet (x:xs) = xss /\/ map (x:) xss where xss = powerSet xs -- | interleave two lists lazily, alternating elements from them. This can also be -- used instead of concatination to avoid space leaks in certain situations. (/\/) :: [a] -> [a] -> [a] [] /\/ ys = ys (x:xs) /\/ ys = x : (ys /\/ xs) readHexChar a | a >= '0' && a <= '9' = return $ ord a - ord '0' readHexChar a | z >= 'a' && z <= 'f' = return $ 10 + ord z - ord 'a' where z = toLower a readHexChar x = fail $ "not hex char: " ++ [x] readHex :: Monad m => String -> m Int readHex [] = fail "empty string" readHex cs = mapM readHexChar cs >>= \cs' -> return (rh $ reverse cs') where rh (c:cs) = c + 16 * (rh cs) rh [] = 0 {-# SPECIALIZE overlaps :: (Int,Int) -> (Int,Int) -> Bool #-} -- | determine if two closed intervals overlap at all. overlaps :: Ord a => (a,a) -> (a,a) -> Bool (a,_) `overlaps` (_,y) | y < a = False (_,b) `overlaps` (x,_) | b < x = False _ `overlaps` _ = True -- | translate a number of seconds to a string representing the duration expressed. showDuration :: Integral a => a -> String showDuration x = st "d" dayI ++ st "h" hourI ++ st "m" minI ++ show secI ++ "s" where (dayI, hourI) = divMod hourI' 24 (hourI', minI) = divMod minI' 60 (minI',secI) = divMod x 60 st _ 0 = "" st c n = show n ++ c -- | behave like while(<>) in perl, go through the argument list, reading the -- concation of each file name mentioned or stdin if '-' is on it. If no -- arguments are given, read stdin. getArgContents :: IO String getArgContents = do as <- System.getArgs let f "-" = getContents f fn = readFile fn cs <- mapM f as if null as then getContents else return $ concat cs -- | Combination of parseOpt and getArgContents. getOptContents :: String -> IO (String,[Char],[(Char,String)]) getOptContents args = do as <- System.getArgs (as,o1,o2) <- parseOpt args as let f "-" = getContents f fn = readFile fn cs <- mapM f as s <- if null as then getContents else return $ concat cs return (s,o1,o2) -- | Process options with an option string like the standard C getopt function call. parseOpt :: Monad m => String -- ^ Argument string, list of valid options with : after ones which accept an argument -> [String] -- ^ Arguments -> m ([String],[Char],[(Char,String)]) -- ^ (non-options,flags,options with arguments) parseOpt ps as = f ([],[],[]) as where (args,oargs) = g ps [] [] where g (':':_) _ _ = error "getOpt: Invalid option string" g (c:':':ps) x y = g ps x (c:y) g (c:ps) x y = g ps (c:x) y g [] x y = (x,y) f cs [] = return cs f (xs,ys,zs) ("--":rs) = return (xs ++ rs, ys, zs) f cs (('-':as@(_:_)):rs) = z cs as where z (xs,ys,zs) (c:cs) | c `elem` args = z (xs,c:ys,zs) cs | c `elem` oargs = case cs of [] -> case rs of (x:rs) -> f (xs,ys,(c,x):zs) rs [] -> fail $ "Option requires argument: " ++ [c] x -> f (xs,ys,(c,x):zs) rs | otherwise = fail $ "Invalid option: " ++ [c] z cs [] = f cs rs f (xs,ys,zs) (r:rs) = f (xs ++ [r], ys, zs) rs readM :: (Monad m, Read a) => String -> m a readM cs = case [x | (x,t) <- reads cs, ("","") <- lex t] of [x] -> return x [] -> fail "readM: no parse" _ -> fail "readM: ambiguous parse" readsM :: (Monad m, Read a) => String -> m (a,String) readsM cs = case readsPrec 0 cs of [(x,s)] -> return (x,s) _ -> fail "cannot readsM" -- | Splits a list into components delimited by separators, where the -- predicate returns True for a separator element. The resulting -- components do not contain the separators. Two adjacent separators -- result in an empty component in the output. eg. -- -- > split (=='a') "aabbaca" -- > ["", "", "bb", "c", ""] -- split :: (a -> Bool) -> [a] -> [[a]] split p s = case rest of [] -> [chunk] _:rest -> chunk : split p rest where (chunk, rest) = break p s -- | Like 'split', except that sequences of adjacent separators are -- treated as a single separator. eg. -- -- > tokens (=='a') "aabbaca" -- > ["bb","c"] tokens :: (a -> Bool) -> [a] -> [[a]] tokens p = filter (not.null) . split p buildTable :: [String] -> [(String,[String])] -> String buildTable ts rs = bt [ x:xs | (x,xs) <- ("",ts):rs ] where bt ts = unlines (map f ts) where f xs = intercalate " " [ es n s | s <- xs | n <- cw ] cw = [ maximum (map length xs) | xs <- transpose ts] es n s = replicate (n - length s) ' ' ++ s -- | time task doTime :: String -> IO a -> IO a doTime str action = do start <- getCPUTime x <- action end <- getCPUTime putStrLn $ "Timing: " ++ str ++ " " ++ show ((end - start) `div` cpuTimePrecision) return x getPrefix :: Monad m => String -> String -> m String getPrefix a b = f a b where f [] ss = return ss f _ [] = fail "getPrefix: value too short" f (p:ps) (s:ss) | p == s = f ps ss | otherwise = fail $ "getPrefix: " ++ a ++ " " ++ b {-# INLINE naturals #-} naturals :: [Int] naturals = [0..]