{-# LANGUAGE ScopedTypeVariables, ConstraintKinds, GeneralizedNewtypeDeriving, ViewPatterns #-}
module General.Extra(
getProcessorCount,
findGcc,
whenLeft,
randomElem,
wrapQuote, showBracket,
withs, forNothingM,
maximum', maximumBy',
unconcat,
fastAt,
zipExact, zipWithExact,
isAsyncException,
showDurationSecs,
usingLineBuffering,
doesFileExist_, doesDirectoryExist_,
usingNumCapabilities,
removeFile_, createDirectoryRecursive,
catchIO, tryIO, handleIO, handleSynchronous,
Located, Partial, callStackTop, callStackFull, withFrozenCallStack, callStackFromException,
Ver(..), makeVer,
QTypeRep(..),
NoShow(..)
) where
import Control.Exception.Extra
import Data.Char
import Data.List.Extra
import System.Environment
import Development.Shake.FilePath
import Control.DeepSeq
import General.Cleanup
import Data.Typeable
import System.IO.Error
import System.IO.Extra
import System.Time.Extra
import System.IO.Unsafe
import System.Info.Extra
import System.Random
import System.Directory
import System.Exit
import Numeric.Extra
import Foreign.Storable
import Control.Concurrent.Extra
import Data.Maybe
import Data.Hashable
import Data.Primitive.Array
import Control.Monad
import Control.Monad.ST
import GHC.Conc(getNumProcessors)
import GHC.Stack
maximumBy' :: (a -> a -> Ordering) -> [a] -> a
maximumBy' cmp = foldl1' $ \x y -> if cmp x y == GT then x else y
maximum' :: Ord a => [a] -> a
maximum' = maximumBy' compare
newtype NoShow a = NoShow a
instance Show (NoShow a) where show _ = "NoShow"
unconcat :: [[a]] -> [b] -> [[b]]
unconcat [] _ = []
unconcat (a:as) bs = b1 : unconcat as b2
where (b1,b2) = splitAt (length a) bs
wrapQuote :: String -> String
wrapQuote xs | any isSpace xs = "\"" ++ concatMap (\x -> if x == '\"' then "\"\"" else [x]) xs ++ "\""
| otherwise = xs
wrapBracket :: String -> String
wrapBracket xs | any isSpace xs = "(" ++ xs ++ ")"
| otherwise = xs
showBracket :: Show a => a -> String
showBracket = wrapBracket . show
fastAt :: [a] -> (Int -> Maybe a)
fastAt xs = \i -> if i < 0 || i >= n then Nothing else Just $ indexArray arr i
where
n = length xs
arr = runST $ do
let n = length xs
arr <- newArray n undefined
forM_ (zipFrom 0 xs) $ \(i,x) ->
writeArray arr i x
unsafeFreezeArray arr
zipWithExact :: Partial => (a -> b -> c) -> [a] -> [b] -> [c]
zipWithExact f = g
where
g [] [] = []
g (a:as) (b:bs) = f a b : g as bs
g _ _ = error "zipWithExacts: unequal lengths"
zipExact :: Partial => [a] -> [b] -> [(a,b)]
zipExact = zipWithExact (,)
{-# NOINLINE getProcessorCount #-}
getProcessorCount :: IO Int
getProcessorCount = let res = unsafePerformIO act in pure res
where
act =
if rtsSupportsBoundThreads then
fromIntegral <$> getNumProcessors
else do
env <- lookupEnv "NUMBER_OF_PROCESSORS"
case env of
Just s | [(i,"")] <- reads s -> pure i
_ -> do
src <- readFile' "/proc/cpuinfo" `catchIO` \_ -> pure ""
pure $! max 1 $ length [() | x <- lines src, "processor" `isPrefixOf` x]
findGcc :: IO (Bool, Maybe FilePath)
findGcc = do
v <- findExecutable "gcc"
case v of
Nothing | isWindows -> do
ghc <- findExecutable "ghc"
case ghc of
Just ghc -> do
let gcc = takeDirectory (takeDirectory ghc) </> "mingw/bin/gcc.exe"
b <- doesFileExist_ gcc
pure $ if b then (True, Just $ takeDirectory gcc) else (False, Nothing)
_ -> pure (False, Nothing)
_ -> pure (isJust v, Nothing)
randomElem :: [a] -> IO a
randomElem xs = do
when (null xs) $ fail "General.Extra.randomElem called with empty list, can't pick a random element"
i <- randomRIO (0, length xs - 1)
pure $ xs !! i
usingLineBuffering :: Cleanup -> IO ()
usingLineBuffering cleanup = do
out <- hGetBuffering stdout
err <- hGetBuffering stderr
when (out /= LineBuffering || err /= LineBuffering) $ do
register cleanup $ hSetBuffering stdout out >> hSetBuffering stderr err
hSetBuffering stdout LineBuffering >> hSetBuffering stderr LineBuffering
showDurationSecs :: Seconds -> String
showDurationSecs = replace ".00s" "s" . showDuration . intToDouble . round
withs :: [(a -> r) -> r] -> ([a] -> r) -> r
withs [] act = act []
withs (f:fs) act = f $ \a -> withs fs $ \as -> act $ a:as
forNothingM :: Monad m => [a] -> (a -> m (Maybe b)) -> m (Maybe [b])
forNothingM [] f = pure $ Just []
forNothingM (x:xs) f = do
v <- f x
case v of
Nothing -> pure Nothing
Just v -> liftM (v:) `liftM` forNothingM xs f
usingNumCapabilities :: Cleanup -> Int -> IO ()
usingNumCapabilities cleanup new = when rtsSupportsBoundThreads $ do
old <- getNumCapabilities
when (old /= new) $ do
register cleanup $ setNumCapabilities old
setNumCapabilities new
isAsyncException :: SomeException -> Bool
isAsyncException e
| Just (_ :: AsyncException) <- fromException e = True
| Just (_ :: ExitCode) <- fromException e = True
| otherwise = False
catchIO :: IO a -> (IOException -> IO a) -> IO a
catchIO = catch
tryIO :: IO a -> IO (Either IOException a)
tryIO = try
handleIO :: (IOException -> IO a) -> IO a -> IO a
handleIO = handle
handleSynchronous :: (SomeException -> IO a) -> IO a -> IO a
handleSynchronous = handleBool (not . isAsyncException)
doesFileExist_ :: FilePath -> IO Bool
doesFileExist_ x = doesFileExist x `catchIO` \_ -> pure False
doesDirectoryExist_ :: FilePath -> IO Bool
doesDirectoryExist_ x = doesDirectoryExist x `catchIO` \_ -> pure False
removeFile_ :: FilePath -> IO ()
removeFile_ x =
removeFile x `catchIO` \e ->
when (isPermissionError e) $ handleIO (\_ -> pure ()) $ do
perms <- getPermissions x
setPermissions x perms{readable = True, searchable = True, writable = True}
removeFile x
createDirectoryRecursive :: FilePath -> IO ()
createDirectoryRecursive dir = do
x <- tryIO $ doesDirectoryExist dir
when (x /= Right True) $ createDirectoryIfMissing True dir
whenLeft :: Applicative m => Either a b -> (a -> m ()) -> m ()
whenLeft x f = either f (const $ pure ()) x
type Located = Partial
callStackTop :: Partial => String
callStackTop = withFrozenCallStack $ headDef "unknown location" callStackFull
callStackFull :: Partial => [String]
callStackFromException :: SomeException -> ([String], SomeException)
parseCallStack = reverse . map trimStart . drop1 . lines
callStackFull = parseCallStack $ prettyCallStack $ popCallStack callStack
callStackFromException (fromException -> Just (ErrorCallWithLocation msg loc)) = (parseCallStack loc, toException $ ErrorCall msg)
callStackFromException e = ([], e)
newtype Ver = Ver Int
deriving (Show,Eq,Storable)
makeVer :: String -> Ver
makeVer = Ver . hash
newtype QTypeRep = QTypeRep {fromQTypeRep :: TypeRep}
deriving (Eq,Hashable,NFData)
instance Show QTypeRep where
show (QTypeRep x) = f x
where
f x = ['(' | xs /= []] ++ (unwords $ g c : map f xs) ++ [')' | xs /= []]
where (c, xs) = splitTyConApp x
g x = tyConModule x ++ "." ++ tyConName x