module Util where import Prelude hiding (head) import qualified Control.Concurrent as Concurrent import qualified Control.Concurrent.MVar as MVar import qualified Control.Exception as Exception import Control.Monad import qualified Data.Char as Char import qualified Data.Function as Function import qualified Data.IntSet as IntSet import qualified Data.List as List import qualified Data.List.Split as Split import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Text as T import qualified Data.Text.IO as Text.IO import qualified System.Directory as Directory import qualified System.Exit as Exit import System.FilePath (()) import qualified System.IO as IO import qualified System.IO.Error as IO.Error import qualified System.Process as Process -- | Copy paste from FastTags.Tag.haskellOpChar. -- -- From the haskell report: -- > varsym → ( symbol⟨:⟩ {symbol} )⟨reservedop | dashes⟩ -- > symbol → ascSymbol | uniSymbol⟨special | _ | " | '⟩ -- > uniSymbol → any Unicode symbol or punctuation haskellOpChar :: Char -> Bool haskellOpChar c = IntSet.member (Char.ord c) opChars || isSymbolCharacterCategory (Char.generalCategory c) where opChars :: IntSet.IntSet opChars = IntSet.fromList $ map Char.ord "-!#$%&*+./<=>?@^|~:\\" isSymbolCharacterCategory :: Char.GeneralCategory -> Bool isSymbolCharacterCategory cat = Set.member cat symbolCategories where symbolCategories :: Set.Set Char.GeneralCategory symbolCategories = Set.fromList [ Char.ConnectorPunctuation , Char.DashPunctuation , Char.OtherPunctuation , Char.MathSymbol , Char.CurrencySymbol , Char.ModifierSymbol , Char.OtherSymbol ] -- * list -- | List initial and final element, if any. unsnoc :: [a] -> Maybe ([a], a) unsnoc [] = Nothing unsnoc (x:xs) = Just $ go x xs where go x [] = ([], x) go x (x':xs) = let (pre, post) = go x' xs in (x:pre, post) -- | Concat a list with 'sep' in between. join :: [a] -> [[a]] -> [a] join = List.intercalate -- | Split 'xs' on 'sep', dropping 'sep' from the result. split :: (Eq a) => [a] -> [a] -> [[a]] split = Split.splitOn -- | Split where the function matches. splitWith :: (a -> Bool) -> [a] -> [[a]] splitWith f xs = map reverse (go f xs []) where go _ [] collect = [collect] go f (x:xs) collect | f x = collect : go f xs [x] | otherwise = go f xs (x:collect) head :: [a] -> Maybe a head [] = Nothing head (x:_) = Just x sortOn :: (Ord k) => (a -> k) -> [a] -> [a] sortOn key = List.sortBy (compare `Function.on` key) groupOn :: (Eq k) => (a -> k) -> [a] -> [[a]] groupOn key = List.groupBy ((==) `Function.on` key) minimumOn :: Ord k => (a -> k) -> [a] -> Maybe a minimumOn _ [] = Nothing minimumOn key xs = Just (List.foldl1' f xs) where f low x = if key x < key low then x else low -- | Like 'List.partition', but partition by two functions consecutively. partition2 :: (a -> Bool) -> (a -> Bool) -> [a] -> ([a], [a], [a]) partition2 f1 f2 xs = (as, bs, xs3) where (as, xs2) = List.partition f1 xs (bs, xs3) = List.partition f2 xs2 zipPrev :: [a] -> [(a, a)] zipPrev xs = zip xs (drop 1 xs) -- | Modify a list at the first place the predicate matches. modifyAt :: (a -> Bool) -> (a -> a) -> [a] -> Maybe [a] modifyAt match modify = go where go [] = Nothing go (x:xs) | match x = Just (modify x : xs) | otherwise = (x:) <$> go xs multimap :: Ord k => [(k, a)] -> Map.Map k [a] multimap = Map.fromAscList . map (\gs -> (fst (List.head gs), map snd gs)) . groupOn fst . sortOn fst partitionOn :: (a -> Maybe b) -> [a] -> ([b], [a]) partitionOn f = go where go [] = ([], []) go (x:xs) = case f x of Just b -> (b:bs, as) Nothing -> (bs, x:as) where (bs, as) = go xs -- * control ifM :: Monad m => m Bool -> m a -> m a -> m a ifM cond consequent alternative = do b <- cond if b then consequent else alternative partitionM :: (Monad m) => (a -> m Bool) -> [a] -> m ([a], [a]) partitionM f = go [] [] where go ts fs [] = return (ts, fs) go ts fs (x:xs) = ifM (f x) (go (x:ts) fs xs) (go ts (x:fs) xs) anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool anyM _ [] = return False anyM f (x:xs) = ifM (f x) (return True) (anyM f xs) -- | If @op@ raised ENOENT, return Nothing. catchENOENT :: IO a -> IO (Maybe a) catchENOENT op = Exception.handleJust (guard . IO.Error.isDoesNotExistError) (const (return Nothing)) (fmap Just op) -- * file listDir :: FilePath -> IO [FilePath] listDir dir = fmap (map add . filter (not . (`elem` [".", ".."]))) (Directory.getDirectoryContents dir) where add = if dir == "." then id else (dir ) -- | Similar to System.Process.readProcessWithExitCode but return Text instead -- of String. readProcessWithExitCode :: FilePath -> [String] -> IO (Exit.ExitCode, T.Text, T.Text) readProcessWithExitCode cmd args = do (_, Just outh, Just errh, pid) <- Process.createProcess (Process.proc cmd args) { Process.std_out = Process.CreatePipe , Process.std_err = Process.CreatePipe } outMVar <- MVar.newEmptyMVar errMVar <- MVar.newEmptyMVar void $ Concurrent.forkIO $ MVar.putMVar outMVar =<< Text.IO.hGetContents outh void $ Concurrent.forkIO $ MVar.putMVar errMVar =<< Text.IO.hGetContents errh out <- MVar.takeMVar outMVar err <- MVar.takeMVar errMVar IO.hClose outh IO.hClose errh ex <- Process.waitForProcess pid return (ex, out, err)