extra 1.6.20 → 1.6.21
raw patch · 16 files changed
+159/−120 lines, 16 filesdep +quickcheck-instancesPVP ok
version bump matches the API change (PVP)
Dependencies added: quickcheck-instances
API changes (from Hackage documentation)
+ Data.List.NonEmpty.Extra: nubOrd :: Ord a => NonEmpty a -> NonEmpty a
+ Data.List.NonEmpty.Extra: nubOrdBy :: (a -> a -> Ordering) -> NonEmpty a -> NonEmpty a
+ Data.List.NonEmpty.Extra: nubOrdOn :: Ord b => (a -> b) -> NonEmpty a -> NonEmpty a
Files
- CHANGES.txt +3/−0
- Generate.hs +8/−3
- extra.cabal +3/−2
- src/Control/Concurrent/Extra.hs +13/−13
- src/Control/Exception/Extra.hs +10/−10
- src/Control/Monad/Extra.hs +21/−21
- src/Data/List/Extra.hs +4/−1
- src/Data/List/NonEmpty/Extra.hs +21/−1
- src/Extra.hs +1/−1
- src/System/Directory/Extra.hs +7/−7
- src/System/IO/Extra.hs +15/−15
- src/System/Process/Extra.hs +1/−1
- src/System/Time/Extra.hs +14/−14
- test/TestCustom.hs +2/−2
- test/TestGen.hs +32/−25
- test/TestUtil.hs +4/−4
CHANGES.txt view
@@ -1,5 +1,8 @@ Changelog for Extra +1.6.21, released 2020-03-02+ #54, deprecate nubOn since its O(n^2). Use nubOrdOn+ #53, add some nub functions to NonEmpty 1.6.20, released 2020-02-16 Add firstM, secondM 1.6.19, released 2020-02-11
Generate.hs view
@@ -25,7 +25,7 @@ words $ replace "," " " $ drop1 $ dropWhile (/= '(') $ unlines $ filter (\x -> not $ any (`isPrefixOf` trim x) ["--","#"]) $ lines src let tests = mapMaybe (stripPrefix "-- > ") $ lines src- return (mod, funcs, tests)+ pure (mod, funcs, tests) writeFileBinaryChanged "src/Extra.hs" $ unlines $ ["-- GENERATED CODE - DO NOT MODIFY" ,"-- See Generate.hs for details of how to generate"@@ -49,6 +49,9 @@ ,"{-# LANGUAGE ExtendedDefaultRules, ScopedTypeVariables, ViewPatterns #-}" ,"module TestGen(tests) where" ,"import TestUtil"+ ,"import qualified Data.List"+ ,"import qualified Data.List.NonEmpty.Extra"+ ,"import Test.QuickCheck.Instances.Semigroup ()" ,"default(Maybe Bool,Int,Double,Maybe (Maybe Bool),Maybe (Maybe Char))" ,"tests :: IO ()" ,"tests = do"] ++@@ -62,12 +65,14 @@ writeFileBinaryChanged :: FilePath -> String -> IO () writeFileBinaryChanged file x = do evaluate $ length x -- ensure we don't write out files with _|_ in them- old <- ifM (doesFileExist file) (Just <$> readFileBinary' file) (return Nothing)+ old <- ifM (doesFileExist file) (Just <$> readFileBinary' file) (pure Nothing) when (Just x /= old) $ writeFileBinary file x hidden :: String -> [String]-hidden "Data.List.NonEmpty.Extra" = ["cons", "snoc", "sortOn", "union", "unionBy"]+hidden "Data.List.NonEmpty.Extra" = [ "cons", "snoc", "sortOn", "union", "unionBy"+ , "nubOrd", "nubOrdBy", "nubOrdOn"+ ] hidden _ = [] notHidden :: String -> String -> Bool
extra.cabal view
@@ -1,7 +1,7 @@ cabal-version: >= 1.18 build-type: Simple name: extra-version: 1.6.20+version: 1.6.21 license: BSD3 license-file: LICENSE category: Development@@ -73,7 +73,8 @@ directory, filepath, extra,- QuickCheck >= 2.10+ QuickCheck >= 2.10,+ quickcheck-instances >= 0.3.17 if !os(windows) build-depends: unix hs-source-dirs: test
src/Control/Concurrent/Extra.hs view
@@ -46,23 +46,23 @@ -- If the function raises an exception, the same exception will be reraised each time. -- -- > let x ||| y = do t1 <- onceFork x; t2 <- onceFork y; t1; t2--- > \(x :: IO Int) -> void (once x) == return ()+-- > \(x :: IO Int) -> void (once x) == pure () -- > \(x :: IO Int) -> join (once x) == x -- > \(x :: IO Int) -> (do y <- once x; y; y) == x -- > \(x :: IO Int) -> (do y <- once x; y ||| y) == x once :: IO a -> IO (IO a) once act = do var <- newVar OncePending- let run = either throwIO return- return $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of- OnceDone x -> return (v, unmask $ run x)- OnceRunning x -> return (v, unmask $ run =<< waitBarrier x)+ let run = either throwIO pure+ pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of+ OnceDone x -> pure (v, unmask $ run x)+ OnceRunning x -> pure (v, unmask $ run =<< waitBarrier x) OncePending -> do b <- newBarrier- return $ (OnceRunning b,) $ do+ pure $ (OnceRunning b,) $ do res <- try_ $ unmask act signalBarrier b res- modifyVar_ var $ \_ -> return $ OnceDone res+ modifyVar_ var $ \_ -> pure $ OnceDone res run res data Once a = OncePending | OnceRunning (Barrier a) | OnceDone a@@ -76,7 +76,7 @@ onceFork act = do bar <- newBarrier forkFinally act $ signalBarrier bar- return $ eitherM throwIO return $ waitBarrier bar+ pure $ eitherM throwIO pure $ waitBarrier bar ---------------------------------------------------------------------@@ -115,7 +115,7 @@ withLockTry (Lock m) act = bracket (tryTakeMVar m) (\v -> when (isJust v) $ putMVar m ())- (\v -> if isJust v then fmap Just act else return Nothing)+ (\v -> if isJust v then fmap Just act else pure Nothing) ---------------------------------------------------------------------@@ -150,7 +150,7 @@ -- | Write a value to become the new value of 'Var'. writeVar :: Var a -> a -> IO ()-writeVar v x = modifyVar_ v $ const $ return x+writeVar v x = modifyVar_ v $ const $ pure x -- | Modify a 'Var' producing a new value and a return result. modifyVar :: Var a -> (a -> IO (a, b)) -> IO b@@ -195,7 +195,7 @@ signalBarrier :: Partial => Barrier a -> a -> IO () signalBarrier (Barrier var) v = mask_ $ -- use mask so never in an inconsistent state join $ modifyVar var $ \x -> case x of- Left bar -> return (Right v, putMVar bar ())+ Left bar -> pure (Right v, putMVar bar ()) Right res -> error "Control.Concurrent.Extra.signalBarrier, attempt to signal a barrier that has already been signaled" @@ -204,12 +204,12 @@ waitBarrier (Barrier var) = do x <- readVar var case x of- Right res -> return res+ Right res -> pure res Left bar -> do readMVar bar x <- readVar var case x of- Right res -> return res+ Right res -> pure res Left bar -> error "Control.Concurrent.Extra, internal invariant violated in Barrier"
src/Control/Exception/Extra.hs view
@@ -35,16 +35,16 @@ -- | Fully evaluate an input String. If the String contains embedded exceptions it will produce @\<Exception\>@. ----- > stringException "test" == return "test"--- > stringException ("test" ++ undefined) == return "test<Exception>"--- > stringException ("test" ++ undefined ++ "hello") == return "test<Exception>"--- > stringException ['t','e','s','t',undefined] == return "test<Exception>"+-- > stringException "test" == pure "test"+-- > stringException ("test" ++ undefined) == pure "test<Exception>"+-- > stringException ("test" ++ undefined ++ "hello") == pure "test<Exception>"+-- > stringException ['t','e','s','t',undefined] == pure "test<Exception>" stringException :: String -> IO String stringException x = do r <- try_ $ evaluate $ list [] (\x xs -> x `seq` x:xs) x case r of- Left e -> return "<Exception>"- Right [] -> return []+ Left e -> pure "<Exception>"+ Right [] -> pure [] Right (x:xs) -> (x:) <$> stringException xs @@ -66,7 +66,7 @@ -- | Ignore any exceptions thrown by the action. -- -- > ignore (print 1) == print 1--- > ignore (fail "die") == return ()+-- > ignore (fail "die") == pure () ignore :: IO () -> IO () ignore = void . try_ @@ -74,7 +74,7 @@ -- | An 'IO' action that when evaluated calls 'error', in the 'IO' monad. -- Note that while 'fail' in 'IO' raises an 'IOException', this function raises an 'ErrorCall' exception with a call stack. ----- > catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello"+-- > catch (errorIO "Hello") (\(ErrorCall x) -> pure x) == pure "Hello" -- > seq (errorIO "foo") (print 1) == print 1 errorIO :: Partial => String -> IO a errorIO x = withFrozenCallStack $ evaluate $ error x@@ -103,7 +103,7 @@ res <- tryBool p x case res of Left _ -> retryBool p (i-1) x- Right v -> return v+ Right v -> pure v -- | A version of 'catch' without the 'Exception' context, restricted to 'SomeException',@@ -135,7 +135,7 @@ -- As an example: -- -- @--- readFileExists x == catchBool isDoesNotExistError (readFile \"myfile\") (const $ return \"\")+-- readFileExists x == catchBool isDoesNotExistError (readFile \"myfile\") (const $ pure \"\") -- @ catchBool :: Exception e => (e -> Bool) -> IO a -> (e -> IO a) -> IO a catchBool f a b = catchJust (bool f) a b
src/Control/Monad/Extra.hs view
@@ -30,7 +30,7 @@ -- | Perform some operation on 'Just', given the field inside the 'Just'. ----- > whenJust Nothing print == return ()+-- > whenJust Nothing print == pure () -- > whenJust (Just 1) print == print 1 whenJust :: Applicative m => Maybe a -> (a -> m ()) -> m () whenJust mg f = maybe (pure ()) f mg@@ -38,14 +38,14 @@ -- | Like 'whenJust', but where the test can be monadic. whenJustM :: Monad m => m (Maybe a) -> (a -> m ()) -> m () -- Can't reuse whenMaybe on GHC 7.8 or lower because Monad does not imply Applicative-whenJustM mg f = maybeM (return ()) f mg+whenJustM mg f = maybeM (pure ()) f mg -- | Like 'when', but return either 'Nothing' if the predicate was 'False', -- of 'Just' with the result of the computation. -- -- > whenMaybe True (print 1) == fmap Just (print 1)--- > whenMaybe False (print 1) == return Nothing+-- > whenMaybe False (print 1) == pure Nothing whenMaybe :: Applicative m => Bool -> m a -> m (Maybe a) whenMaybe b x = if b then Just <$> x else pure Nothing @@ -54,7 +54,7 @@ -- Can't reuse whenMaybe on GHC 7.8 or lower because Monad does not imply Applicative whenMaybeM mb x = do b <- mb- if b then liftM Just x else return Nothing+ if b then liftM Just x else pure Nothing -- | The identity function which requires the inner argument to be @()@. Useful for functions@@ -89,7 +89,7 @@ -- | Like 'fold1M' but discards the result. fold1M_ :: (Partial, Monad m) => (a -> a -> m a) -> [a] -> m ()-fold1M_ f xs = fold1M f xs >> return ()+fold1M_ f xs = fold1M f xs >> pure () -- Data.List for Monad@@ -99,18 +99,18 @@ -- > partitionM (Just . even) [1,2,3] == Just ([2], [1,3]) -- > partitionM (const Nothing) [1,2,3] == Nothing partitionM :: Monad m => (a -> m Bool) -> [a] -> m ([a], [a])-partitionM f [] = return ([], [])+partitionM f [] = pure ([], []) partitionM f (x:xs) = do res <- f x (as,bs) <- partitionM f xs- return ([x | res]++as, [x | not res]++bs)+ pure ([x | res]++as, [x | not res]++bs) -- | A version of 'concatMap' that works with a monadic predicate. concatMapM :: Monad m => (a -> m [b]) -> [a] -> m [b] {-# INLINE concatMapM #-}-concatMapM op = foldr f (return [])- where f x xs = do x <- op x; if null x then xs else do xs <- xs; return $ x++xs+concatMapM op = foldr f (pure [])+ where f x xs = do x <- op x; if null x then xs else do xs <- xs; pure $ x++xs -- | Like 'concatMapM', but has its arguments flipped, so can be used -- instead of the common @fmap concat $ forM@ pattern.@@ -124,8 +124,8 @@ -- | A version of 'mapMaybe' that works with a monadic predicate. mapMaybeM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b] {-# INLINE mapMaybeM #-}-mapMaybeM op = foldr f (return [])- where f x xs = do x <- op x; case x of Nothing -> xs; Just x -> do xs <- xs; return $ x:xs+mapMaybeM op = foldr f (pure [])+ where f x xs = do x <- op x; case x of Nothing -> xs; Just x -> do xs <- xs; pure $ x:xs -- Looping @@ -145,7 +145,7 @@ res <- act x case res of Left x -> loopM act x- Right v -> return v+ Right v -> pure v -- | Keep running an operation until it becomes 'False'. As an example: --@@ -164,11 +164,11 @@ -- | Like 'when', but where the test can be monadic. whenM :: Monad m => m Bool -> m () -> m ()-whenM b t = ifM b t (return ())+whenM b t = ifM b t (pure ()) -- | Like 'unless', but where the test can be monadic. unlessM :: Monad m => m Bool -> m () -> m ()-unlessM b f = ifM b (return ()) f+unlessM b f = ifM b (pure ()) f -- | Like @if@, but where the test can be monadic. ifM :: Monad m => m Bool -> m a -> m a -> m a@@ -186,7 +186,7 @@ -- > Just False ||^ Just True == Just True -- > Just False ||^ Just False == Just False (||^) :: Monad m => m Bool -> m Bool -> m Bool-(||^) a b = ifM a (return True) b+(||^) a b = ifM a (pure True) b -- | The lazy '&&' operator lifted to a monad. If the first -- argument evaluates to 'False' the second argument will not@@ -196,7 +196,7 @@ -- > Just True &&^ Just True == Just True -- > Just True &&^ Just False == Just False (&&^) :: Monad m => m Bool -> m Bool -> m Bool-(&&^) a b = ifM a b (return False)+(&&^) a b = ifM a b (pure False) -- | A version of 'any' lifted to a monad. Retains the short-circuiting behaviour. --@@ -204,7 +204,7 @@ -- > anyM Just [False,False,undefined] == undefined -- > \(f :: Int -> Maybe Bool) xs -> anyM f xs == orM (map f xs) anyM :: Monad m => (a -> m Bool) -> [a] -> m Bool-anyM p = foldr ((||^) . p) (return False)+anyM p = foldr ((||^) . p) (pure False) -- | A version of 'all' lifted to a monad. Retains the short-circuiting behaviour. --@@ -212,7 +212,7 @@ -- > allM Just [True,True ,undefined] == undefined -- > \(f :: Int -> Maybe Bool) xs -> anyM f xs == orM (map f xs) allM :: Monad m => (a -> m Bool) -> [a] -> m Bool-allM p = foldr ((&&^) . p) (return True)+allM p = foldr ((&&^) . p) (pure True) -- | A version of 'or' lifted to a monad. Retains the short-circuiting behaviour. --@@ -238,9 +238,9 @@ -- > findM (Just . isUpper) "test" == Just Nothing -- > findM (Just . const True) ["x",undefined] == Just (Just "x") findM :: Monad m => (a -> m Bool) -> [a] -> m (Maybe a)-findM p = foldr (\x -> ifM (p x) (return $ Just x)) (return Nothing)+findM p = foldr (\x -> ifM (p x) (pure $ Just x)) (pure Nothing) -- | Like 'findM', but also allows you to compute some additional information in the predicate. firstJustM :: Monad m => (a -> m (Maybe b)) -> [a] -> m (Maybe b)-firstJustM p [] = return Nothing-firstJustM p (x:xs) = maybeM (firstJustM p xs) (return . Just) (p x)+firstJustM p [] = pure Nothing+firstJustM p (x:xs) = maybeM (firstJustM p xs) (pure . Just) (p x)
src/Data/List/Extra.hs view
@@ -377,10 +377,13 @@ where (.*.) `on2` f = \x -> let fx = f x in \y -> fx .*. f y --- | A version of 'nub' where the equality is done on some extracted value.+-- | /DEPRECATED/ Use 'nubOrdOn', since this function is _O(n^2)_.+--+-- A version of 'nub' where the equality is done on some extracted value. -- @nubOn f@ is equivalent to @nubBy ((==) `on` f)@, but has the -- performance advantage of only evaluating @f@ once for each element in the -- input list.+{-# DEPRECATED nubOn "Use nubOrdOn, since this function is O(n^2)" #-} nubOn :: Eq b => (a -> b) -> [a] -> [a] nubOn f = map snd . nubBy ((==) `on` fst) . map (\x -> let y = f x in y `seq` (y, x))
src/Data/List/NonEmpty/Extra.hs view
@@ -7,11 +7,12 @@ (|:), (|>), snoc, appendl, appendr, sortOn, union, unionBy,+ nubOrd, nubOrdBy, nubOrdOn, maximum1, minimum1, maximumBy1, minimumBy1, maximumOn1, minimumOn1 ) where import Data.Function-import qualified Data.List as List+import qualified Data.List.Extra as List import Data.List.NonEmpty #if __GLASGOW_HASKELL__ <= 802@@ -61,6 +62,25 @@ -- > (1 :| [3, 5, 3]) `union` (4 :| [5, 3, 5, 2]) == 1 :| [3, 5, 3, 4, 2] union :: Eq a => NonEmpty a -> NonEmpty a -> NonEmpty a union = unionBy (==)++-- | @nubOrd@ for 'NonEmpty'. Behaves the same as 'Data.List.Extra.nubOrd'.+--+-- > Data.List.NonEmpty.Extra.nubOrd (1 :| [2, 3, 3, 4, 1, 2]) == 1 :| [2, 3, 4]+-- > \xs -> Data.List.NonEmpty.Extra.nubOrd xs == Data.List.NonEmpty.Extra.nub xs+nubOrd :: Ord a => NonEmpty a -> NonEmpty a+nubOrd = nubOrdBy compare++-- | @nubOrdBy@ for 'NonEmpty'. Behaves the same as 'Data.List.Extra.nubOrdBy'.+--+-- > Data.List.NonEmpty.Extra.nubOrdBy (compare `on` Data.List.length) ("a" :| ["test","of","this"]) == "a" :| ["test","of"]+nubOrdBy :: (a -> a -> Ordering) -> NonEmpty a -> NonEmpty a+nubOrdBy cmp = fromList . List.nubOrdBy cmp . toList++-- | @nubOrdOn@ for 'NonEmpty'. Behaves the same as 'Data.List.Extra.nubOrdOn'.+--+-- > Data.List.NonEmpty.Extra.nubOrdOn Data.List.length ("a" :| ["test","of","this"]) == "a" :| ["test","of"]+nubOrdOn :: Ord b => (a -> b) -> NonEmpty a -> NonEmpty a+nubOrdOn f = fromList . List.nubOrdOn f . toList -- | The non-overloaded version of 'union'. unionBy :: (a -> a -> Bool) -> NonEmpty a -> NonEmpty a -> NonEmpty a
src/Extra.hs view
@@ -59,7 +59,7 @@ import Data.Either.Extra import Data.IORef.Extra import Data.List.Extra-import Data.List.NonEmpty.Extra hiding (cons, snoc, sortOn, union, unionBy)+import Data.List.NonEmpty.Extra hiding (cons, snoc, sortOn, union, unionBy, nubOrd, nubOrdBy, nubOrdOn) import Data.Tuple.Extra import Data.Version.Extra import Numeric.Extra
src/System/Directory/Extra.hs view
@@ -52,12 +52,12 @@ -- -- > withTempDir $ \dir -> do writeFile (dir </> "test.txt") ""; (== [dir </> "test.txt"]) <$> listContents dir -- > let touch = mapM_ $ \x -> createDirectoryIfMissing True (takeDirectory x) >> writeFile x ""--- > let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; return $ map (drop (length dir + 1)) res == bs+-- > let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; pure $ map (drop (length dir + 1)) res == bs -- > listTest listContents ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","foo","zoo"] listContents :: FilePath -> IO [FilePath] listContents dir = do xs <- getDirectoryContents dir- return $ sort [dir </> x | x <- xs, not $ all (== '.') x]+ pure $ sort [dir </> x | x <- xs, not $ all (== '.') x] -- | Like 'listContents', but only returns the directories in a directory, not the files.@@ -81,21 +81,21 @@ -- -- > listTest listFilesRecursive ["bar.txt","zoo","foo" </> "baz.txt"] ["bar.txt","zoo","foo" </> "baz.txt"] listFilesRecursive :: FilePath -> IO [FilePath]-listFilesRecursive = listFilesInside (const $ return True)+listFilesRecursive = listFilesInside (const $ pure True) -- | Like 'listFilesRecursive', but with a predicate to decide where to recurse into. -- Typically directories starting with @.@ would be ignored. The initial argument directory -- will have the test applied to it. ----- > listTest (listFilesInside $ return . not . isPrefixOf "." . takeFileName)+-- > listTest (listFilesInside $ pure . not . isPrefixOf "." . takeFileName) -- > ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]--- > listTest (listFilesInside $ const $ return False) ["bar.txt"] []+-- > listTest (listFilesInside $ const $ pure False) ["bar.txt"] [] listFilesInside :: (FilePath -> IO Bool) -> FilePath -> IO [FilePath]-listFilesInside test dir = ifM (notM $ test $ dropTrailingPathSeparator dir) (return []) $ do+listFilesInside test dir = ifM (notM $ test $ dropTrailingPathSeparator dir) (pure []) $ do (dirs,files) <- partitionM doesDirectoryExist =<< listContents dir rest <- concatMapM (listFilesInside test) dirs- return $ files ++ rest+ pure $ files ++ rest -- | Create a directory with permissions so that only the current user can view it.
src/System/IO/Extra.hs view
@@ -66,7 +66,7 @@ hGetContents' h = do s <- hGetContents h void $ evaluate $ length s- return s+ pure s -- | A strict version of 'readFile'. When the string is produced, the entire -- file will have been read into memory and the file handle will have been closed.@@ -112,7 +112,7 @@ -- | Capture the 'stdout' and 'stderr' of a computation. ----- > captureOutput (print 1) == return ("1\n",())+-- > captureOutput (print 1) == pure ("1\n",()) captureOutput :: IO a -> IO (String, a) captureOutput act = withTempFile $ \file -> withFile file ReadWriteMode $ \h -> do@@ -120,7 +120,7 @@ hClose h act out <- readFile' file- return (out, res)+ pure (out, res) where clone out h act = do buf <- hGetBuffering out@@ -166,13 +166,13 @@ newTempFileWithin tmpdir = do file <- create del <- once $ ignore $ removeFile file- return (file, del)+ pure (file, del) where create = do val <- tempUnique (file, h) <- retryBool (\(_ :: IOError) -> True) 5 $ openTempFile tmpdir $ "extra-file-" ++ show val ++ "-" hClose h- return file+ pure file -- | Create a temporary file in the temporary directory. The file will be deleted@@ -180,9 +180,9 @@ -- The 'FilePath' will not have any file extension, will exist, and will be zero bytes long. -- If you require a file with a specific name, use 'withTempDir'. ----- > withTempFile doesFileExist == return True--- > (doesFileExist =<< withTempFile return) == return False--- > withTempFile readFile' == return ""+-- > withTempFile doesFileExist == pure True+-- > (doesFileExist =<< withTempFile pure) == pure False+-- > withTempFile readFile' == pure "" withTempFile :: (FilePath -> IO a) -> IO a withTempFile act = do (file, del) <- newTempFile@@ -200,22 +200,22 @@ newTempDirWithin tmpdir = do dir <- retryBool (\(_ :: IOError) -> True) 5 $ create tmpdir del <- once $ ignore $ removeDirectoryRecursive dir- return (dir, del)+ pure (dir, del) where create tmpdir = do v <- tempUnique let dir = tmpdir </> "extra-dir-" ++ show v catchBool isAlreadyExistsError- (createDirectoryPrivate dir >> return dir) $+ (createDirectoryPrivate dir >> pure dir) $ \_ -> create tmpdir -- | Create a temporary directory inside the system temporary directory. -- The directory will be deleted after the action completes. ----- > withTempDir doesDirectoryExist == return True--- > (doesDirectoryExist =<< withTempDir return) == return False--- > withTempDir listFiles == return []+-- > withTempDir doesDirectoryExist == pure True+-- > (doesDirectoryExist =<< withTempDir pure) == pure False+-- > withTempDir listFiles == pure [] withTempDir :: (FilePath -> IO a) -> IO a withTempDir act = do (dir,del) <- newTempDir@@ -235,8 +235,8 @@ r1 <- hGetBuf h1 b1 bufsz r2 <- hGetBuf h2 b2 bufsz if r1 == 0- then return $ r2 == 0- else return (r1 == r2) &&^ bufeq b1 b2 r1 &&^ eq b1 b2+ then pure $ r2 == 0+ else pure (r1 == r2) &&^ bufeq b1 b2 r1 &&^ eq b1 b2 bufeq b1 b2 s = (==0) <$> memcmp b1 b2 (fromIntegral s) withb = allocaBytesAligned bufsz 4096 bufsz = 64*1024
src/System/Process/Extra.hs view
@@ -40,4 +40,4 @@ (res,out) <- systemOutput x when (res /= ExitSuccess) $ error $ "Failed when running system command: " ++ x- return out+ pure out
src/System/Time/Extra.hs view
@@ -26,18 +26,18 @@ -- | Sleep for a number of seconds. ----- > fmap (round . fst) (duration $ sleep 1) == return 1+-- > fmap (round . fst) (duration $ sleep 1) == pure 1 sleep :: Seconds -> IO () sleep = loopM $ \s -> -- important to handle both overflow and underflow vs Int if s < 0 then- return $ Right ()+ pure $ Right () else if s > 2000 then do threadDelay 2000000000 -- 2000 * 1e6- return $ Left $ s - 2000+ pure $ Left $ s - 2000 else do threadDelay $ ceiling $ s * 1000000- return $ Right ()+ pure $ Right () -- An internal type that is thrown as a dynamic exception to@@ -52,19 +52,19 @@ -- overflows the bounds of an 'Int'. In addition, the bug that negative -- timeouts run for ever has been fixed. ----- > timeout (-3) (print 1) == return Nothing+-- > timeout (-3) (print 1) == pure Nothing -- > timeout 0.1 (print 1) == fmap Just (print 1)--- > do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; return $ t < 1--- > timeout 0.1 (sleep 2 >> print 1) == return Nothing+-- > do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; pure $ t < 1+-- > timeout 0.1 (sleep 2 >> print 1) == pure Nothing timeout :: Seconds -> IO a -> IO (Maybe a) -- Copied from GHC with a few tweaks. timeout n f- | n <= 0 = return Nothing+ | n <= 0 = pure Nothing | otherwise = do pid <- myThreadId ex <- fmap Timeout newUnique handleBool (== ex)- (const $ return Nothing)+ (const $ pure Nothing) (bracket (forkIOWithUnmask $ \unmask -> unmask $ sleep n >> throwTo pid ex) killThread (\_ -> fmap Just f))@@ -90,13 +90,13 @@ -- | Call once to start, then call repeatedly to get the elapsed time since the first call. -- The time is guaranteed to be monotonic. This function is robust to system time changes. ----- > do f <- offsetTime; xs <- replicateM 10 f; return $ xs == sort xs+-- > do f <- offsetTime; xs <- replicateM 10 f; pure $ xs == sort xs offsetTime :: IO (IO Seconds) offsetTime = do start <- time- return $ do+ pure $ do end <- time- return $ 1e-9 * fromIntegral (toNanoSecs $ end - start)+ pure $ 1e-9 * fromIntegral (toNanoSecs $ end - start) where time = getTime Monotonic {-# DEPRECATED offsetTimeIncrease "Use 'offsetTime' instead, which is guaranteed to always increase." #-}@@ -107,10 +107,10 @@ -- | Record how long a computation takes in 'Seconds'. ----- > do (a,_) <- duration $ sleep 1; return $ a >= 1 && a <= 1.5+-- > do (a,_) <- duration $ sleep 1; pure $ a >= 1 && a <= 1.5 duration :: IO a -> IO (Seconds, a) duration act = do time <- offsetTime res <- act time <- time- return (time, res)+ pure (time, res)
test/TestCustom.hs view
@@ -16,14 +16,14 @@ testRaw "withTempFile" $ do xs <- replicateM 4 $ onceFork $ do- replicateM_ 100 $ withTempFile (const $ return ())+ replicateM_ 100 $ withTempFile (const $ pure ()) putChar '.' sequence_ xs putStrLn "done" testRaw "withTempDir" $ do xs <- replicateM 4 $ onceFork $ do- replicateM_ 100 $ withTempDir (const $ return ())+ replicateM_ 100 $ withTempDir (const $ pure ()) putChar '.' sequence_ xs putStrLn "done"
test/TestGen.hs view
@@ -4,30 +4,33 @@ {-# LANGUAGE ExtendedDefaultRules, ScopedTypeVariables, ViewPatterns #-} module TestGen(tests) where import TestUtil+import qualified Data.List+import qualified Data.List.NonEmpty.Extra+import Test.QuickCheck.Instances.Semigroup () default(Maybe Bool,Int,Double,Maybe (Maybe Bool),Maybe (Maybe Char)) tests :: IO () tests = do let x ||| y = do t1 <- onceFork x; t2 <- onceFork y; t1; t2- testGen "\\(x :: IO Int) -> void (once x) == return ()" $ \(x :: IO Int) -> void (once x) == return ()+ testGen "\\(x :: IO Int) -> void (once x) == pure ()" $ \(x :: IO Int) -> void (once x) == pure () testGen "\\(x :: IO Int) -> join (once x) == x" $ \(x :: IO Int) -> join (once x) == x testGen "\\(x :: IO Int) -> (do y <- once x; y; y) == x" $ \(x :: IO Int) -> (do y <- once x; y; y) == x testGen "\\(x :: IO Int) -> (do y <- once x; y ||| y) == x" $ \(x :: IO Int) -> (do y <- once x; y ||| y) == x testGen "\\(x :: IO Int) -> join (onceFork x) == x" $ \(x :: IO Int) -> join (onceFork x) == x testGen "\\(x :: IO Int) -> (do a <- onceFork x; a; a) == x" $ \(x :: IO Int) -> (do a <- onceFork x; a; a) == x- testGen "stringException \"test\" == return \"test\"" $ stringException "test" == return "test"- testGen "stringException (\"test\" ++ undefined) == return \"test<Exception>\"" $ stringException ("test" ++ undefined) == return "test<Exception>"- testGen "stringException (\"test\" ++ undefined ++ \"hello\") == return \"test<Exception>\"" $ stringException ("test" ++ undefined ++ "hello") == return "test<Exception>"- testGen "stringException ['t','e','s','t',undefined] == return \"test<Exception>\"" $ stringException ['t','e','s','t',undefined] == return "test<Exception>"+ testGen "stringException \"test\" == pure \"test\"" $ stringException "test" == pure "test"+ testGen "stringException (\"test\" ++ undefined) == pure \"test<Exception>\"" $ stringException ("test" ++ undefined) == pure "test<Exception>"+ testGen "stringException (\"test\" ++ undefined ++ \"hello\") == pure \"test<Exception>\"" $ stringException ("test" ++ undefined ++ "hello") == pure "test<Exception>"+ testGen "stringException ['t','e','s','t',undefined] == pure \"test<Exception>\"" $ stringException ['t','e','s','t',undefined] == pure "test<Exception>" testGen "ignore (print 1) == print 1" $ ignore (print 1) == print 1- testGen "ignore (fail \"die\") == return ()" $ ignore (fail "die") == return ()- testGen "catch (errorIO \"Hello\") (\\(ErrorCall x) -> return x) == return \"Hello\"" $ catch (errorIO "Hello") (\(ErrorCall x) -> return x) == return "Hello"+ testGen "ignore (fail \"die\") == pure ()" $ ignore (fail "die") == pure ()+ testGen "catch (errorIO \"Hello\") (\\(ErrorCall x) -> pure x) == pure \"Hello\"" $ catch (errorIO "Hello") (\(ErrorCall x) -> pure x) == pure "Hello" testGen "seq (errorIO \"foo\") (print 1) == print 1" $ seq (errorIO "foo") (print 1) == print 1 testGen "retry 1 (print \"x\") == print \"x\"" $ retry 1 (print "x") == print "x" testGen "retry 3 (fail \"die\") == fail \"die\"" $ retry 3 (fail "die") == fail "die"- testGen "whenJust Nothing print == return ()" $ whenJust Nothing print == return ()+ testGen "whenJust Nothing print == pure ()" $ whenJust Nothing print == pure () testGen "whenJust (Just 1) print == print 1" $ whenJust (Just 1) print == print 1 testGen "whenMaybe True (print 1) == fmap Just (print 1)" $ whenMaybe True (print 1) == fmap Just (print 1)- testGen "whenMaybe False (print 1) == return Nothing" $ whenMaybe False (print 1) == return Nothing+ testGen "whenMaybe False (print 1) == pure Nothing" $ whenMaybe False (print 1) == pure Nothing testGen "\\(x :: Maybe ()) -> unit x == x" $ \(x :: Maybe ()) -> unit x == x testGen "fold1M (\\x y -> Just x) [] == undefined" $ erroneous $ fold1M (\x y -> Just x) [] testGen "fold1M (\\x y -> Just $ x + y) [1, 2, 3] == Just 6" $ fold1M (\x y -> Just $ x + y) [1, 2, 3] == Just 6@@ -243,6 +246,10 @@ testGen "appendl (1 :| [2,3]) [4,5] == 1 :| [2,3,4,5]" $ appendl (1 :| [2,3]) [4,5] == 1 :| [2,3,4,5] testGen "appendr [1,2,3] (4 :| [5]) == 1 :| [2,3,4,5]" $ appendr [1,2,3] (4 :| [5]) == 1 :| [2,3,4,5] testGen "(1 :| [3, 5, 3]) `union` (4 :| [5, 3, 5, 2]) == 1 :| [3, 5, 3, 4, 2]" $ (1 :| [3, 5, 3]) `union` (4 :| [5, 3, 5, 2]) == 1 :| [3, 5, 3, 4, 2]+ testGen "Data.List.NonEmpty.Extra.nubOrd (1 :| [2, 3, 3, 4, 1, 2]) == 1 :| [2, 3, 4]" $ Data.List.NonEmpty.Extra.nubOrd (1 :| [2, 3, 3, 4, 1, 2]) == 1 :| [2, 3, 4]+ testGen "\\xs -> Data.List.NonEmpty.Extra.nubOrd xs == Data.List.NonEmpty.Extra.nub xs" $ \xs -> Data.List.NonEmpty.Extra.nubOrd xs == Data.List.NonEmpty.Extra.nub xs+ testGen "Data.List.NonEmpty.Extra.nubOrdBy (compare `on` Data.List.length) (\"a\" :| [\"test\",\"of\",\"this\"]) == \"a\" :| [\"test\",\"of\"]" $ Data.List.NonEmpty.Extra.nubOrdBy (compare `on` Data.List.length) ("a" :| ["test","of","this"]) == "a" :| ["test","of"]+ testGen "Data.List.NonEmpty.Extra.nubOrdOn Data.List.length (\"a\" :| [\"test\",\"of\",\"this\"]) == \"a\" :| [\"test\",\"of\"]" $ Data.List.NonEmpty.Extra.nubOrdOn Data.List.length ("a" :| ["test","of","this"]) == "a" :| ["test","of"] testGen "first succ (1,\"test\") == (2,\"test\")" $ first succ (1,"test") == (2,"test") testGen "second reverse (1,\"test\") == (1,\"tset\")" $ second reverse (1,"test") == (1,"tset") testGen "firstM (\\x -> [x-1, x+1]) (1,\"test\") == [(0,\"test\"),(2,\"test\")]" $ firstM (\x -> [x-1, x+1]) (1,"test") == [(0,"test"),(2,"test")]@@ -259,38 +266,38 @@ testGen "withTempDir $ \\dir -> do writeFile (dir </> \"foo.txt\") \"\"; withCurrentDirectory dir $ doesFileExist \"foo.txt\"" $ withTempDir $ \dir -> do writeFile (dir </> "foo.txt") ""; withCurrentDirectory dir $ doesFileExist "foo.txt" testGen "withTempDir $ \\dir -> do writeFile (dir </> \"test.txt\") \"\"; (== [dir </> \"test.txt\"]) <$> listContents dir" $ withTempDir $ \dir -> do writeFile (dir </> "test.txt") ""; (== [dir </> "test.txt"]) <$> listContents dir let touch = mapM_ $ \x -> createDirectoryIfMissing True (takeDirectory x) >> writeFile x ""- let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; return $ map (drop (length dir + 1)) res == bs+ let listTest op as bs = withTempDir $ \dir -> do touch $ map (dir </>) as; res <- op dir; pure $ map (drop (length dir + 1)) res == bs testGen "listTest listContents [\"bar.txt\",\"foo/baz.txt\",\"zoo\"] [\"bar.txt\",\"foo\",\"zoo\"]" $ listTest listContents ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","foo","zoo"] testGen "listTest listDirectories [\"bar.txt\",\"foo/baz.txt\",\"zoo\"] [\"foo\"]" $ listTest listDirectories ["bar.txt","foo/baz.txt","zoo"] ["foo"] testGen "listTest listFiles [\"bar.txt\",\"foo/baz.txt\",\"zoo\"] [\"bar.txt\",\"zoo\"]" $ listTest listFiles ["bar.txt","foo/baz.txt","zoo"] ["bar.txt","zoo"] testGen "listTest listFilesRecursive [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"] [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"]" $ listTest listFilesRecursive ["bar.txt","zoo","foo" </> "baz.txt"] ["bar.txt","zoo","foo" </> "baz.txt"]- testGen "listTest (listFilesInside $ return . not . isPrefixOf \".\" . takeFileName) [\"bar.txt\",\"foo\" </> \"baz.txt\",\".foo\" </> \"baz2.txt\", \"zoo\"] [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"]" $ listTest (listFilesInside $ return . not . isPrefixOf "." . takeFileName) ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]- testGen "listTest (listFilesInside $ const $ return False) [\"bar.txt\"] []" $ listTest (listFilesInside $ const $ return False) ["bar.txt"] []+ testGen "listTest (listFilesInside $ pure . not . isPrefixOf \".\" . takeFileName) [\"bar.txt\",\"foo\" </> \"baz.txt\",\".foo\" </> \"baz2.txt\", \"zoo\"] [\"bar.txt\",\"zoo\",\"foo\" </> \"baz.txt\"]" $ listTest (listFilesInside $ pure . not . isPrefixOf "." . takeFileName) ["bar.txt","foo" </> "baz.txt",".foo" </> "baz2.txt", "zoo"] ["bar.txt","zoo","foo" </> "baz.txt"]+ testGen "listTest (listFilesInside $ const $ pure False) [\"bar.txt\"] []" $ listTest (listFilesInside $ const $ pure False) ["bar.txt"] [] testGen "isWindows == (os == \"mingw32\")" $ isWindows == (os == "mingw32") testGen "\\(filter isHexDigit -> s) -> fmap (== s) $ withTempFile $ \\file -> do writeFile file s; readFile' file" $ \(filter isHexDigit -> s) -> fmap (== s) $ withTempFile $ \file -> do writeFile file s; readFile' file testGen "\\s -> withTempFile $ \\file -> do writeFileUTF8 file s; fmap (== s) $ readFileUTF8' file" $ \s -> withTempFile $ \file -> do writeFileUTF8 file s; fmap (== s) $ readFileUTF8' file testGen "\\(ASCIIString s) -> withTempFile $ \\file -> do writeFileBinary file s; fmap (== s) $ readFileBinary' file" $ \(ASCIIString s) -> withTempFile $ \file -> do writeFileBinary file s; fmap (== s) $ readFileBinary' file- testGen "captureOutput (print 1) == return (\"1\\n\",())" $ captureOutput (print 1) == return ("1\n",())- testGen "withTempFile doesFileExist == return True" $ withTempFile doesFileExist == return True- testGen "(doesFileExist =<< withTempFile return) == return False" $ (doesFileExist =<< withTempFile return) == return False- testGen "withTempFile readFile' == return \"\"" $ withTempFile readFile' == return ""- testGen "withTempDir doesDirectoryExist == return True" $ withTempDir doesDirectoryExist == return True- testGen "(doesDirectoryExist =<< withTempDir return) == return False" $ (doesDirectoryExist =<< withTempDir return) == return False- testGen "withTempDir listFiles == return []" $ withTempDir listFiles == return []+ testGen "captureOutput (print 1) == pure (\"1\\n\",())" $ captureOutput (print 1) == pure ("1\n",())+ testGen "withTempFile doesFileExist == pure True" $ withTempFile doesFileExist == pure True+ testGen "(doesFileExist =<< withTempFile pure) == pure False" $ (doesFileExist =<< withTempFile pure) == pure False+ testGen "withTempFile readFile' == pure \"\"" $ withTempFile readFile' == pure ""+ testGen "withTempDir doesDirectoryExist == pure True" $ withTempDir doesDirectoryExist == pure True+ testGen "(doesDirectoryExist =<< withTempDir pure) == pure False" $ (doesDirectoryExist =<< withTempDir pure) == pure False+ testGen "withTempDir listFiles == pure []" $ withTempDir listFiles == pure [] testGen "fileEq \"does_not_exist1\" \"does_not_exist2\" == undefined" $ erroneousIO $ fileEq "does_not_exist1" "does_not_exist2" testGen "fileEq \"does_not_exist\" \"does_not_exist\" == undefined" $ erroneousIO $ fileEq "does_not_exist" "does_not_exist" testGen "withTempFile $ \\f1 -> fileEq \"does_not_exist\" f1 == undefined" $ erroneousIO $ withTempFile $ \f1 -> fileEq "does_not_exist" f1 testGen "withTempFile $ \\f1 -> withTempFile $ \\f2 -> fileEq f1 f2" $ withTempFile $ \f1 -> withTempFile $ \f2 -> fileEq f1 f2 testGen "withTempFile $ \\f1 -> withTempFile $ \\f2 -> writeFile f1 \"a\" >> writeFile f2 \"a\" >> fileEq f1 f2" $ withTempFile $ \f1 -> withTempFile $ \f2 -> writeFile f1 "a" >> writeFile f2 "a" >> fileEq f1 f2 testGen "withTempFile $ \\f1 -> withTempFile $ \\f2 -> writeFile f1 \"a\" >> writeFile f2 \"b\" >> notM (fileEq f1 f2)" $ withTempFile $ \f1 -> withTempFile $ \f2 -> writeFile f1 "a" >> writeFile f2 "b" >> notM (fileEq f1 f2)- testGen "fmap (round . fst) (duration $ sleep 1) == return 1" $ fmap (round . fst) (duration $ sleep 1) == return 1- testGen "timeout (-3) (print 1) == return Nothing" $ timeout (-3) (print 1) == return Nothing+ testGen "fmap (round . fst) (duration $ sleep 1) == pure 1" $ fmap (round . fst) (duration $ sleep 1) == pure 1+ testGen "timeout (-3) (print 1) == pure Nothing" $ timeout (-3) (print 1) == pure Nothing testGen "timeout 0.1 (print 1) == fmap Just (print 1)" $ timeout 0.1 (print 1) == fmap Just (print 1)- testGen "do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; return $ t < 1" $ do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; return $ t < 1- testGen "timeout 0.1 (sleep 2 >> print 1) == return Nothing" $ timeout 0.1 (sleep 2 >> print 1) == return Nothing+ testGen "do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; pure $ t < 1" $ do (t, _) <- duration $ timeout 0.1 $ sleep 1000; print t; pure $ t < 1+ testGen "timeout 0.1 (sleep 2 >> print 1) == pure Nothing" $ timeout 0.1 (sleep 2 >> print 1) == pure Nothing testGen "showDuration 3.435 == \"3.44s\"" $ showDuration 3.435 == "3.44s" testGen "showDuration 623.8 == \"10m24s\"" $ showDuration 623.8 == "10m24s" testGen "showDuration 62003.8 == \"17h13m\"" $ showDuration 62003.8 == "17h13m" testGen "showDuration 1e8 == \"27777h47m\"" $ showDuration 1e8 == "27777h47m"- testGen "do f <- offsetTime; xs <- replicateM 10 f; return $ xs == sort xs" $ do f <- offsetTime; xs <- replicateM 10 f; return $ xs == sort xs- testGen "do (a,_) <- duration $ sleep 1; return $ a >= 1 && a <= 1.5" $ do (a,_) <- duration $ sleep 1; return $ a >= 1 && a <= 1.5+ testGen "do f <- offsetTime; xs <- replicateM 10 f; pure $ xs == sort xs" $ do f <- offsetTime; xs <- replicateM 10 f; pure $ xs == sort xs+ testGen "do (a,_) <- duration $ sleep 1; pure $ a >= 1 && a <= 1.5" $ do (a,_) <- duration $ sleep 1; pure $ a >= 1 && a <= 1.5
test/TestUtil.hs view
@@ -46,7 +46,7 @@ testGen msg prop = testRaw msg $ do r <- quickCheckResult prop case r of- Success{} -> return ()+ Success{} -> pure () _ -> errorIO "Test failed" testRaw :: String -> IO () -> IO ()@@ -87,7 +87,7 @@ a == b = unsafePerformIO $ do a <- try_ $ captureOutput a b <- try_ $ captureOutput b- if a == b then return True else+ if a == b then pure True else error $ show ("IO values not equal", a, b) instance Show (IO a) where@@ -96,10 +96,10 @@ instance Arbitrary a => Arbitrary (IO a) where arbitrary = do (prnt :: Maybe Int, thrw :: Maybe Int, res) <- arbitrary- return $ do+ pure $ do whenJust prnt print whenJust thrw (fail . show)- return res+ pure res instance Eq SomeException where a == b = show a == show b