-- ----------------------------------------------------------------------------- -- Copyright 2002, Simon Marlow. -- Copyright 2006, Bjorn Bringert. -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions are -- met: -- -- * Redistributions of source code must retain the above copyright notice, -- this list of conditions and the following disclaimer. -- -- * Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- * Neither the name of the copyright holder(s) nor the names of -- contributors may be used to endorse or promote products derived from -- this software without specific prior written permission. -- -- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- ----------------------------------------------------------------------------- module Util where import Control.Exception as Exception import Control.Concurrent import Control.Monad import Data.Array.IO import Data.Char import Data.List import Data.Ratio (numerator) import Foreign.C.Error (getErrno, eNOENT, eNOTDIR) import Network.Socket as Socket import System.IO import System.Exit import System.Locale import System.Posix import System.Time ----------------------------------------------------------------------------- -- Utils -- ToDo: deHex is supposed to remove the '%'-encoding deHex :: String -> String deHex s = s hPutStrCrLf :: Handle -> String -> IO () hPutStrCrLf h s = hPutStr h s >> hPutChar h '\r' >> hPutChar h '\n' die :: String -> IO () die err = do hPutStrLn stderr err exitFailure ----------------------------------------------------------------------------- -- String utils readM :: (Read a, Monad m) => String -> m a readM s = readSM reads s readSM :: Monad m => ReadS a -> String -> m a readSM f s = case f s of [] -> fail $ "No parse of " ++ show s [(x,[])] -> return x [(_,_)] -> fail $ "Junk at end of " ++ show s _ -> fail $ "Ambiguous parse of " ++ show s lookupLC :: String -> [(String,a)] -> Maybe a lookupLC s xs = lookup (map toLower s) [(map toLower n,v) | (n,v) <- xs] concatS :: [ShowS] -> ShowS concatS = foldr (.) id unlinesS :: [ShowS] -> ShowS unlinesS = concatS . map (. showChar '\n') ----------------------------------------------------------------------------- -- List utils -- Split a list at some delimiter. splitBy :: (a -> Bool) -> [a] -> [[a]] splitBy _ [] = [[]] splitBy f xs = first : case rest of _:ys -> splitBy f ys [] -> [] where (first, rest) = break f xs glue :: [a] -> [[a]] -> [a] glue g = concat . intersperse g splits :: [a] -> [([a],[a])] splits xs = zip (inits xs) (tails xs) dropPrefix :: Eq a => [a] -> [a] -> [a] dropPrefix xs pref | pref `isPrefixOf` xs = drop (length pref) xs | otherwise = xs dropSuffix :: Eq a => [a] -> [a] -> [a] dropSuffix xs suf = reverse (reverse xs `dropPrefix` reverse suf) ----------------------------------------------------------------------------- -- File path utils splitPath :: FilePath -> [String] splitPath = splitBy (=='/') joinPath :: [String] -> FilePath joinPath = glue "/" -- Get the directory component of a path -- FIXME: is this good enough? dirname :: FilePath -> FilePath dirname = reverse . dropWhile (/= '/') . reverse -- Get the filename component of a path -- FIXME: probably System.FilePath should be used here. basename :: FilePath -> FilePath basename = reverse . takeWhile (/= '/') . reverse ----------------------------------------------------------------------------- -- Monad utils firstJustM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b) firstJustM _ [] = return Nothing firstJustM f (x:xs) = f x >>= maybe (firstJustM f xs) (return . Just) ----------------------------------------------------------------------------- -- Parsec utils ----------------------------------------------------------------------------- -- Time utils formatTimeSensibly :: CalendarTime -> String formatTimeSensibly time = formatCalendarTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT" time epochTimeToClockTime :: EpochTime -> ClockTime epochTimeToClockTime epoch_time = TOD (numToInteger epoch_time) 0 where numToInteger = numerator . toRational ----------------------------------------------------------------------------- -- concurrency utilities -- block forever wait :: IO a wait = newEmptyMVar >>= takeMVar ----------------------------------------------------------------------------- -- networking utils accept :: Socket -- Listening Socket -> IO (Handle,SockAddr) -- StdIO Handle for read/write accept sock = do (sock', addr) <- Socket.accept sock hndle <- socketToHandle sock' ReadWriteMode return (hndle,addr) ----------------------------------------------------------------------------- -- file utils statFile :: String -> IO (Maybe FileStatus) statFile = stat_ getFileStatus statSymLink :: String -> IO (Maybe FileStatus) statSymLink = stat_ getSymbolicLinkStatus stat_ :: (FilePath -> IO FileStatus) -> String -> IO (Maybe FileStatus) stat_ f filename = do maybe_stat <- tryJust ioErrors (f filename) case maybe_stat of Left e -> do errno <- getErrno if errno == eNOENT || errno == eNOTDIR then return Nothing else ioError e Right stat -> return (Just stat) isSymLink :: FilePath -> IO Bool isSymLink = liftM (maybe False isSymbolicLink) . statSymLink ----------------------------------------------------------------------------- -- I/O utils bufsize :: Int bufsize = 4 * 1024 -- squirt data from 'rd' into 'wr' as fast as possible. We use a 4k -- single buffer. squirt :: Handle -> Handle -> IO () squirt rd wr = do arr <- newArray_ (0, bufsize-1) let loop = do r <- hGetArray rd arr bufsize if (r == 0) then return () else if (r < bufsize) then hPutArray wr arr r else hPutArray wr arr bufsize >> loop loop -- | Read the given number of bytes from a Handle hGetChars :: Handle -> Int -> IO String hGetChars _ 0 = return "" hGetChars h n = do arr <- newArray_ (0, n-1) r <- hGetArray h arr n when (r < n) $ fail $ "" -- FIXME: input encoding? liftM (map (toEnum . fromEnum)) $ getElems arr ----------------------------------------------------------------------------- -- Exception utils -- | Catch IO Errors for which a given predicate is true. catchSomeIOErrors :: (IOError -> Bool) -> IO a -> (IOError -> IO a) -> IO a catchSomeIOErrors p = catchJust p' where p' (IOException e) | p e = Just e p' _ = Nothing