module IdeSession.Util (
showExWithClass
, accessorName
, lookup'
, envWithPathOverride
, writeFileAtomic
, setupEnv
, relInclToOpts
, parseProgressMessage
, ignoreDoesNotExist
, interruptible
, Diff(..)
, applyMapDiff
, swizzleStdout
, swizzleStderr
, redirectStderr
, captureOutput
) where
import Control.Applicative ((<$>))
import Control.Monad (void, forM_, mplus)
import Crypto.Classes (blockLength, initialCtx, updateCtx, finalize)
import Crypto.Types (BitLength)
import Data.Accessor (Accessor, accessor)
import Data.Binary (Binary(..))
import Data.Char (isSpace)
import Data.Digest.Pure.MD5 (MD5Digest, MD5Context)
import Data.List (intercalate)
import Data.Maybe (fromMaybe)
import Data.Tagged (Tagged, untag)
import Data.Text (Text)
import Data.Typeable (typeOf)
import Foreign.C.Types (CFile)
import Foreign.Ptr (Ptr, castPtr, nullPtr)
import GHC.Generics (Generic)
import GHC.IO (unsafeUnmask)
import System.Directory (createDirectoryIfMissing, removeFile, renameFile)
import System.Environment (getEnvironment)
import System.FilePath (splitFileName, (<.>), (</>))
import System.FilePath (splitSearchPath, searchPathSeparator)
import System.IO
import System.IO.Error (isDoesNotExistError)
import System.IO.Temp (withSystemTempFile)
import System.Posix (Fd)
import System.Posix.Env (setEnv, unsetEnv)
import System.Posix.IO
import System.Posix.Types (CPid(..))
import Text.Show.Pretty
import qualified Control.Exception as Ex
import qualified Data.Attoparsec.Text as Att
import qualified Data.Binary as Bin
import qualified Data.Binary.Builder.Internal as Bin (writeN)
import qualified Data.Binary.Get.Internal as Bin (readNWith)
import qualified Data.Binary.Put as Bin (putBuilder)
import qualified Data.ByteString as BSS
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as Text
import qualified Data.Text.Foreign as Text
import qualified System.Posix.Files as Files
import IdeSession.Strict.Container
import qualified IdeSession.Strict.Map as StrictMap
foreign import ccall "fflush" fflush :: Ptr CFile -> IO ()
showExWithClass :: Ex.SomeException -> String
showExWithClass (Ex.SomeException ex) = show (typeOf ex) ++ ": " ++ show ex
accessorName :: String -> Maybe String
accessorName ('_' : str) = Just str
accessorName _ = Nothing
lookup' :: Eq a => a -> Accessor [(a, b)] (Maybe b)
lookup' key =
accessor (lookup key) $ \mVal list ->
case mVal of
Nothing -> delete key list
Just val -> override key val list
where
override :: Eq a => a -> b -> [(a, b)] -> [(a, b)]
override a b [] = [(a, b)]
override a b ((a', b') : xs)
| a == a' = (a, b) : xs
| otherwise = (a', b') : override a b xs
delete :: Eq a => a -> [(a, b)] -> [(a, b)]
delete _ [] = []
delete a ((a', b') : xs)
| a == a' = xs
| otherwise = (a', b') : delete a xs
envWithPathOverride :: [FilePath] -> IO (Maybe [(String, String)])
envWithPathOverride [] = return Nothing
envWithPathOverride extraPathDirs = do
env <- getEnvironment
let path = fromMaybe "" (lookup "PATH" env)
path' = intercalate [searchPathSeparator]
(extraPathDirs ++ splitSearchPath path)
env' = ("PATH", path') : filter (\(var, _) -> var /= "PATH") env
return (Just env')
writeFileAtomic :: FilePath -> BSL.ByteString -> IO MD5Digest
writeFileAtomic targetPath content = do
let (targetDir, targetFile) = splitFileName targetPath
createDirectoryIfMissing True targetDir
Ex.bracketOnError
(openBinaryTempFile targetDir $ targetFile <.> "tmp")
(\(tmpPath, handle) -> hClose handle >> removeFile tmpPath)
(\(tmpPath, handle) -> do
let bits :: Tagged MD5Digest BitLength ; bits = blockLength
hash <- go handle initialCtx $ makeBlocks (untag bits `div` 8) content
hClose handle
renameFile tmpPath targetPath
return hash)
where
go :: Handle -> MD5Context -> [BSS.ByteString] -> IO MD5Digest
go _ _ [] = error "Bug in makeBlocks"
go h ctx [bs] = BSS.hPut h bs >> return (finalize ctx bs)
go h ctx (bs:bss) = BSS.hPut h bs >> go h (updateCtx ctx bs) bss
makeBlocks :: Int -> BSL.ByteString -> [BSS.ByteString]
makeBlocks n = go . BSL.toChunks
where
go [] = [BSS.empty]
go (bs:bss)
| BSS.length bs >= n =
let l = BSS.length bs (BSS.length bs `rem` n)
(bsInit, bsTail) = BSS.splitAt l bs
in bsInit : go (bsTail : bss)
| otherwise =
case bss of
[] -> [bs]
(bs':bss') -> go (BSS.append bs bs' : bss')
setupEnv :: [(String, String)] -> [(String, Maybe String)] -> IO ()
setupEnv initEnv overrides = do
curEnv <- getEnvironment
forM_ curEnv $ \(var, _val) -> unsetEnv var
forM_ initEnv $ \(var, val) -> setEnv var val True
forM_ overrides $ \(var, mVal) ->
case mVal of
Just val -> setEnv var val True
Nothing -> unsetEnv var
relInclToOpts :: FilePath -> [FilePath] -> [String]
relInclToOpts sourcesDir relIncl =
["-i"]
++ map (\path -> "-i" ++ sourcesDir </> path) relIncl
parseProgressMessage :: Text -> Either String (Int, Int, Text)
parseProgressMessage = Att.parseOnly parser
where
parser :: Att.Parser (Int, Int, Text)
parser = do
_ <- Att.char '[' ; Att.skipSpace
step <- Att.decimal ; Att.skipSpace
_ <- Att.string (Text.pack "of") ; Att.skipSpace
numS <- Att.decimal ; Att.skipSpace
_ <- Att.char ']' ; Att.skipSpace
rest <- parseCompiling `mplus` Att.takeText
return (step, numS, rest)
parseCompiling :: Att.Parser Text
parseCompiling = do
compiling <- Att.string (Text.pack "Compiling") ; Att.skipSpace
_ <- parseTH ; Att.skipSpace
modName <- Att.takeTill isSpace
return $ Text.concat [compiling, Text.pack " ", modName]
parseTH :: Att.Parser ()
parseTH = Att.option () $ void $ Att.string (Text.pack "[TH]")
ignoreDoesNotExist :: IO () -> IO ()
ignoreDoesNotExist = Ex.handle $ \e ->
if isDoesNotExistError e then return ()
else Ex.throwIO e
interruptible :: IO a -> IO a
interruptible act = do
st <- Ex.getMaskingState
case st of
Ex.Unmasked -> act
Ex.MaskedInterruptible -> unsafeUnmask act
Ex.MaskedUninterruptible -> act
data Diff a = Keep | Remove | Insert a
deriving (Show, Functor, Generic)
instance Binary a => Binary (Diff a) where
put Keep = Bin.putWord8 0
put Remove = Bin.putWord8 1
put (Insert a) = Bin.putWord8 2 >> Bin.put a
get = do
header <- Bin.getWord8
case header of
0 -> return Keep
1 -> return Remove
2 -> Insert <$> Bin.get
_ -> fail "Diff.get: invalid header"
instance PrettyVal a => PrettyVal (Diff a)
applyMapDiff :: forall k v. Ord k
=> Strict (Map k) (Diff v)
-> Strict (Map k) v -> Strict (Map k) v
applyMapDiff diff = foldr (.) id (map aux $ StrictMap.toList diff)
where
aux :: (k, Diff v) -> Strict (Map k) v -> Strict (Map k) v
aux (_, Keep) = id
aux (k, Remove) = StrictMap.delete k
aux (k, Insert x) = StrictMap.insert k x
swizzleStdout :: Fd -> IO a -> IO a
swizzleStdout = swizzleHandle (stdout, stdOutput)
swizzleStderr :: Fd -> IO a -> IO a
swizzleStderr = swizzleHandle (stderr, stdError)
swizzleHandle :: (Handle, Fd) -> Fd -> IO a -> IO a
swizzleHandle (targetHandle, targetFd) fd act =
Ex.bracket swizzle unswizzle (\_ -> act)
where
swizzle :: IO Fd
swizzle = do
hFlush targetHandle
fflush nullPtr
backup <- dup targetFd
_ <- dupTo fd targetFd
return backup
unswizzle :: Fd -> IO ()
unswizzle backup = do
hFlush targetHandle
fflush nullPtr
_ <- dupTo backup targetFd
closeFd backup
redirectStderr :: FilePath -> IO a -> IO a
redirectStderr fp act = do
Ex.bracket (openFd fp WriteOnly (Just mode) defaultFileFlags)
closeFd $ \errorLogFd ->
swizzleStderr errorLogFd $
act
where
mode = Files.unionFileModes Files.ownerReadMode Files.ownerWriteMode
captureOutput :: IO a -> IO (String, a)
captureOutput act = do
withSystemTempFile "suppressed" $ \fp handle -> do
fd <- handleToFd handle
a <- swizzleStdout fd . swizzleStderr fd $ act
closeFd fd
suppressed <- readFile fp
return (suppressed, a)
#if !MIN_VERSION_text(1,2,1)
instance Binary Text where
get = do units <- Bin.get
Bin.readNWith (units * 2) $ \ptr ->
Text.fromPtr (castPtr ptr) (fromIntegral units)
put t = do put (Text.lengthWord16 t)
Bin.putBuilder $
Bin.writeN (Text.lengthWord16 t * 2)
(\p -> Text.unsafeCopyToPtr t (castPtr p))
#endif
deriving instance Binary CPid