{-# LANGUAGE DeriveDataTypeable, RankNTypes, ScopedTypeVariables, BangPatterns #-}
module Utils.Misc (
-- * Environment
envIsSet
, getEnvMaybe
-- * List operations
, subsetOf
, noDuplicates
, equivClasses
-- * Control
, whileTrue
-- * Hashing
, stringSHA256
-- * Set operations
, setAny
-- * Map operations
, invertMap
) where
import Data.List
import System.Environment
import System.IO.Unsafe
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as S
import Data.Map ( Map )
import qualified Data.Map as M
import Data.Digest.Pure.SHA (bytestringDigest, sha256)
import Blaze.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Char8 as C8
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Base64 as B64 (encode)
import qualified Blaze.ByteString.Builder.Char.Utf8 as Utf8 (fromString)
-- | @noDuplicates xs@ returns @True@ if the list @xs@ contains no duplicates
noDuplicates :: (Ord a) => [a] -> Bool
noDuplicates xs = all ((==1).length) . group . sort $ xs
-- | @getEnvMaybe k@ returns @Just v@ if @k=v@ is in the environment and @Nothing@ otherwise
getEnvMaybe :: String -> Maybe String
getEnvMaybe k = unsafePerformIO $ do
l <- getEnvironment
return $ lookup k l
-- | @envIsSet k@ returns @True@ if there is a v such @k=v@ is in the environment and @False@ otherwise.
envIsSet :: String -> Bool
envIsSet k = isJust $ getEnvMaybe k
-- | @subsetOf xs ys@ return @True@ if @set xs@ is a subset of @set ys@
subsetOf :: Ord a => [a] -> [a] -> Bool
subsetOf xs ys = (S.fromList xs) `S.isSubsetOf` (S.fromList ys)
-- | Inverts a bijective Map.
invertMap :: (Ord k, Ord v) => Map k v -> Map v k
invertMap = M.fromList . map (uncurry (flip (,))) . M.toList
-- | @whileTrue m@ iterates m until it returns @False@.
-- Returns the number of iterations @m@ was run. @0@
-- means @m@ never returned @True@.
whileTrue :: Monad m => m Bool -> m Int
whileTrue m = go 0
where go (!n) = m >>= \b -> if b then go (n+1) else return n
-- | Compute the equality classes given wrto a partial function.
equivClasses :: (Ord a, Ord b) => [(a, b)] -> M.Map b (S.Set a)
equivClasses =
foldl' insertEdge M.empty
where
insertEdge m (from,to) = M.insertWith' S.union to (S.singleton from) m
-- | The SHA-256 hash of a string in base64 notation.
stringSHA256 :: String -> String
stringSHA256 =
C8.unpack . urlEncodeBase64 . C8.concat . L.toChunks
. bytestringDigest . sha256 . toLazyByteString . Utf8.fromString
where
urlEncodeBase64 = C8.init . C8.map replace . B64.encode
replace '/' = '_'
replace '+' = '-'
replace c = c
setAny :: (a -> Bool) -> Set a -> Bool
setAny f = S.foldr (\x b -> f x || b) False