packages feed

gitson 0.5.0 → 0.5.1

raw patch · 5 files changed

+112/−131 lines, 5 filesdep −HUnitdep −QuickCheckdep −hastachedep ~Globdep ~criteriondep ~doctestPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies removed: HUnit, QuickCheck, hastache, statistics

Dependency ranges changed: Glob, criterion, doctest, hspec

API changes (from Hackage documentation)

- Gitson: documentIdFromName :: MonadIO i => FilePath -> String -> i (Maybe Int)
+ Gitson: documentIdFromName :: (MonadIO i, Functor i) => FilePath -> String -> i (Maybe Int)
- Gitson: documentNameFromId :: MonadIO i => FilePath -> Int -> i (Maybe String)
+ Gitson: documentNameFromId :: (MonadIO i, Functor i) => FilePath -> Int -> i (Maybe String)
- Gitson: listCollections :: MonadIO i => i [FilePath]
+ Gitson: listCollections :: (MonadIO i, Functor i) => i [FilePath]
- Gitson: listDocumentKeys :: MonadIO i => FilePath -> i [FileName]
+ Gitson: listDocumentKeys :: (MonadIO i, Functor i) => FilePath -> i [FileName]
- Gitson: listEntries :: (MonadIO i, FromJSON a) => FilePath -> i [a]
+ Gitson: listEntries :: (MonadIO i, Functor i, FromJSON a) => FilePath -> i [a]
- Gitson: readDocument :: (MonadIO i, FromJSON a) => FilePath -> FileName -> i (Maybe a)
+ Gitson: readDocument :: (MonadIO i, Functor i, FromJSON a) => FilePath -> FileName -> i (Maybe a)
- Gitson: readDocumentById :: (MonadIO i, FromJSON a) => FilePath -> Int -> i (Maybe a)
+ Gitson: readDocumentById :: (MonadIO i, Functor i, FromJSON a) => FilePath -> Int -> i (Maybe a)
- Gitson: readDocumentByName :: (MonadIO i, FromJSON a) => FilePath -> String -> i (Maybe a)
+ Gitson: readDocumentByName :: (MonadIO i, Functor i, FromJSON a) => FilePath -> String -> i (Maybe a)
- Gitson: saveDocument :: (MonadIO i, ToJSON a) => FilePath -> FileName -> a -> TransactionWriter i ()
+ Gitson: saveDocument :: (MonadIO i, Functor i, ToJSON a) => FilePath -> FileName -> a -> TransactionWriter i ()
- Gitson: saveDocumentById :: (MonadIO i, ToJSON a) => FilePath -> Int -> a -> TransactionWriter i ()
+ Gitson: saveDocumentById :: (MonadIO i, Functor i, ToJSON a) => FilePath -> Int -> a -> TransactionWriter i ()
- Gitson: saveDocumentByName :: (MonadIO i, ToJSON a) => FilePath -> String -> a -> TransactionWriter i ()
+ Gitson: saveDocumentByName :: (MonadIO i, Functor i, ToJSON a) => FilePath -> String -> a -> TransactionWriter i ()
- Gitson: saveNextDocument :: (MonadIO i, ToJSON a) => FilePath -> FileName -> a -> TransactionWriter i ()
+ Gitson: saveNextDocument :: (MonadIO i, Functor i, ToJSON a) => FilePath -> FileName -> a -> TransactionWriter i ()
- Gitson: transaction :: (MonadIO i, MonadBaseControl IO i) => FilePath -> TransactionWriter i () -> i ()
+ Gitson: transaction :: (MonadIO i, Functor i, MonadBaseControl IO i) => FilePath -> TransactionWriter i () -> i ()

Files

README.md view
@@ -1,4 +1,4 @@-# gitson [![Hackage](https://img.shields.io/hackage/v/gitson.svg?style=flat)](https://hackage.haskell.org/package/gitson) [![Build Status](https://img.shields.io/travis/myfreeweb/gitson.svg?style=flat)](https://travis-ci.org/myfreeweb/gitson) [![Coverage Status](https://img.shields.io/coveralls/myfreeweb/gitson.svg?style=flat)](https://coveralls.io/r/myfreeweb/gitson) [![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg?style=flat)](https://www.tldrlegal.com/l/apache2)+# gitson [![Hackage](https://img.shields.io/hackage/v/gitson.svg?style=flat)](https://hackage.haskell.org/package/gitson) [![Build Status](https://img.shields.io/travis/myfreeweb/gitson.svg?style=flat)](https://travis-ci.org/myfreeweb/gitson) [![Coverage Status](https://img.shields.io/coveralls/myfreeweb/gitson.svg?style=flat)](https://coveralls.io/r/myfreeweb/gitson) [![ISC License](https://img.shields.io/badge/license-ISC-red.svg?style=flat)](https://tldrlegal.com/license/-isc-license)  A simple document store library for Git + JSON, based on [Aeson]. Uses command line git, at least for now.@@ -104,16 +104,5 @@  ## License -Copyright 2014 Greg V <floatboth@me.com>--Licensed under the Apache License, Version 2.0 (the "License");-you may not use this file except in compliance with the License.-You may obtain a copy of the License at--    http://www.apache.org/licenses/LICENSE-2.0--Unless required by applicable law or agreed to in writing, software-distributed under the License is distributed on an "AS IS" BASIS,-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.-See the License for the specific language governing permissions and-limitations under the License.+Copyright 2014-2015 Greg V <greg@unrelenting.technology>+Available under the ISC license, see the `COPYING` file
gitson.cabal view
@@ -1,5 +1,5 @@ name:            gitson-version:         0.5.0+version:         0.5.1 synopsis:        A document store library for Git + JSON. description:     A simple document store library for Git + JSON, based on Aeson. Uses command line git. Transactions use flock, so it's safe even across completely separate programs! category:        Database, JSON, Git@@ -7,14 +7,14 @@ author:          Greg V copyright:       2014-2015 Greg V <greg@unrelenting.technology> maintainer:      greg@unrelenting.technology-license:         Apache-2.0+license:         OtherLicense license-file:    COPYING build-type:      Simple cabal-version:   >= 1.18 extra-source-files:     README.md tested-with:-    GHC == 7.8.2+    GHC == 7.8.3  source-repository head     type: git@@ -45,8 +45,8 @@ test-suite examples     build-depends:         base >= 4.0.0.0 && < 5-      , Glob == 0.*-      , doctest >= 0.8+      , Glob+      , doctest     default-language: Haskell2010     ghc-options: -threaded -Wall -Werror     hs-source-dirs: test-suite@@ -61,9 +61,7 @@       , process       , directory       , aeson-      , hspec == 1.*-        , HUnit-        , QuickCheck+      , hspec     default-language: Haskell2010     ghc-options: -threaded -Wall -Werror -fhpc     hs-source-dirs: test-suite@@ -80,9 +78,7 @@       , random       , gitson       , aeson-      , criterion == 0.6.*-        , hastache < 0.6-        , statistics < 0.11+      , criterion     default-language: Haskell2010     hs-source-dirs: benchmark     main-is: Bench.hs
library/Gitson.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE Safe, FlexibleContexts #-}+{-# LANGUAGE Safe, FlexibleContexts, UnicodeSyntax #-}  -- | Gitson is a simple document store library for Git + JSON. module Gitson (@@ -21,13 +21,14 @@  import           System.Directory import           System.Lock.FLock-import           Control.Applicative ((<$>))+import           Control.Applicative import           Control.Exception (try, IOException) import           Control.Error.Util (hush) import           Control.Monad.Trans.Writer import           Control.Monad.Trans.Control import           Control.Monad.IO.Class-import           Data.Maybe (fromMaybe, catMaybes)+import           Control.Monad (liftM)+import           Data.Maybe (fromMaybe, mapMaybe) import           Data.List (find, isSuffixOf) import           Text.Printf (printf) import qualified Data.ByteString.Lazy as BL@@ -39,133 +40,126 @@  type IdAndName = (Int, String) type FileName = String-type Finder = ([(IdAndName, FileName)] -> Maybe (IdAndName, FileName))+type Finder = [(IdAndName, FileName)] → Maybe (IdAndName, FileName) -splitFindDocument :: (MonadIO i) => FilePath -> Finder -> i (Maybe (IdAndName, FileName))-splitFindDocument collection finder = listDocumentKeys collection >>=-  return . finder . catMaybes . map (\x -> intoFunctor (maybeReadIntString x) x)+splitFindDocument ∷ (MonadIO i, Functor i) ⇒ FilePath → Finder → i (Maybe (IdAndName, FileName))+splitFindDocument collection finder = +  finder . mapMaybe (\x → intoFunctor (maybeReadIntString x) x) <$> listDocumentKeys collection -documentFullKey :: (MonadIO i) => FilePath -> Finder -> i (Maybe FileName)-documentFullKey collection finder = splitFindDocument collection finder >>= return . (snd <$>)+documentFullKey ∷ (MonadIO i, Functor i) ⇒ FilePath → Finder → i (Maybe FileName)+documentFullKey collection finder = (snd <$>) <$> splitFindDocument collection finder -findById :: Int -> Finder+findById ∷ Int → Finder findById i = find $ (== i) . fst . fst -findByName :: String -> Finder-findByName n = find $ (isSuffixOf n) . snd . fst+findByName ∷ String → Finder+findByName n = find $ isSuffixOf n . snd . fst  -- | Creates a git repository under a given path.-createRepo :: FilePath -> IO ()+createRepo ∷ FilePath → IO () createRepo path = do   createDirectoryIfMissing True path   insideDirectory path $ shell "git" ["init"]  -- | Executes a blocking transaction on a repository, committing the results to git.-transaction :: (MonadIO i, MonadBaseControl IO i) => FilePath -> TransactionWriter i () -> i ()+transaction ∷ (MonadIO i, Functor i, MonadBaseControl IO i) ⇒ FilePath → TransactionWriter i () → i () transaction repoPath action =   insideDirectory repoPath $ do     liftIO $ writeFile lockPath ""     withLock lockPath Exclusive Block $ do-      writeActions <- execWriterT action+      writeActions ← execWriterT action       shell "git" ["stash"] -- it's totally ok to do this without changes       liftIO $ sequence_ writeActions       shell "git" ["add", "--all"]       shell "git" ["commit", "-m", "Gitson transaction"]       shell "git" ["stash", "pop"] -combineKey :: IdAndName -> FileName-combineKey (n, s) = zeroPad n ++ "-" ++ s-  where zeroPad :: Int -> String-        zeroPad x = printf "%06d" x+combineKey ∷ IdAndName → FileName+combineKey (n, s) = printf "%06d-%s" n s -writeDocument :: ToJSON a => FilePath -> FileName -> a -> IO ()+writeDocument ∷ ToJSON a ⇒ FilePath → FileName → a → IO () writeDocument collection key content = BL.writeFile (documentPath collection key) (encode content)  -- | Adds a write action to a transaction.-saveDocument :: (MonadIO i, ToJSON a) => FilePath -> FileName -> a -> TransactionWriter i ()-saveDocument collection key content = do+saveDocument ∷ (MonadIO i, Functor i, ToJSON a) ⇒ FilePath → FileName → a → TransactionWriter i ()+saveDocument collection key content =   tell [createDirectoryIfMissing True collection,         writeDocument collection key content]  -- | Adds a write action to a transaction. -- The key will start with a numeric id, incremented from the last id in the collection.-saveNextDocument :: (MonadIO i, ToJSON a) => FilePath -> FileName -> a -> TransactionWriter i ()-saveNextDocument collection key content = do+saveNextDocument ∷ (MonadIO i, Functor i, ToJSON a) ⇒ FilePath → FileName → a → TransactionWriter i ()+saveNextDocument collection key content =   tell [createDirectoryIfMissing True collection,         listDocumentKeys collection >>=         return . nextKeyId >>=-        \nextId -> writeDocument collection (combineKey (nextId, key)) content]+        \nextId → writeDocument collection (combineKey (nextId, key)) content]  -- | Adds a write action to a transaction. -- Will update the document with the given numeric id.-saveDocumentById :: (MonadIO i, ToJSON a) => FilePath -> Int -> a -> TransactionWriter i ()-saveDocumentById collection i content = do+saveDocumentById ∷ (MonadIO i, Functor i, ToJSON a) ⇒ FilePath → Int → a → TransactionWriter i ()+saveDocumentById collection i content =   tell [documentFullKey collection (findById i) >>=-        \k -> case k of-          Just key -> writeDocument collection key content-          Nothing -> return ()]+        \k → case k of+          Just key → writeDocument collection key content+          Nothing → return ()]  -- | Adds a write action to a transaction. -- Will update the document with the given numeric id.-saveDocumentByName :: (MonadIO i, ToJSON a) => FilePath -> String -> a -> TransactionWriter i ()-saveDocumentByName collection n content = do+saveDocumentByName ∷ (MonadIO i, Functor i, ToJSON a) ⇒ FilePath → String → a → TransactionWriter i ()+saveDocumentByName collection n content =   tell [documentFullKey collection (findByName n) >>=-        \k -> case k of-          Just key -> writeDocument collection key content-          Nothing -> return ()]+        \k → case k of+          Just key → writeDocument collection key content+          Nothing → return ()]  -- | Lists collections in the current repository.-listCollections :: (MonadIO i) => i [FilePath]+listCollections ∷ (MonadIO i, Functor i) ⇒ i [FilePath] listCollections = liftIO $ do-  contents <- try (getDirectoryContents =<< getCurrentDirectory) :: IO (Either IOException [FilePath])+  contents ← try (getDirectoryContents =<< getCurrentDirectory) ∷ IO (Either IOException [FilePath])   filterDirs $ fromMaybe [] $ hush contents  -- | Lists document keys in a collection.-listDocumentKeys :: (MonadIO i) => FilePath -> i [FileName]+listDocumentKeys ∷ (MonadIO i, Functor i) ⇒ FilePath → i [FileName] listDocumentKeys collection = liftIO $ do-  contents <- try (getDirectoryContents collection) :: IO (Either IOException [String])-  return $ filterFilenamesAsKeys $ fromMaybe [] $ hush contents+  contents ← try (getDirectoryContents collection) ∷ IO (Either IOException [String])+  return . filterFilenamesAsKeys . fromMaybe [] $ hush contents  -- | Lists entries in a collection.-listEntries :: (MonadIO i, FromJSON a) => FilePath -> i [a]+listEntries ∷ (MonadIO i, Functor i, FromJSON a) ⇒ FilePath → i [a] listEntries collection = liftIO $ do-  ks <- listDocumentKeys collection-  maybes <- mapM (readDocument collection) ks-  return $ fromMaybe [] $ sequence maybes+  maybes ← mapM (readDocument collection) =<< listDocumentKeys collection+  return . fromMaybe [] $ sequence maybes  -- | Reads a document from a collection by key.-readDocument :: (MonadIO i, FromJSON a) => FilePath -> FileName -> i (Maybe a)+readDocument ∷ (MonadIO i, Functor i, FromJSON a) ⇒ FilePath → FileName → i (Maybe a) readDocument collection key = liftIO $ do-  jsonString <- try (BL.readFile $ documentPath collection key) :: IO (Either IOException BL.ByteString)+  jsonString ← try (BL.readFile $ documentPath collection key) ∷ IO (Either IOException BL.ByteString)   return $ decode =<< hush jsonString -readDocument' :: (MonadIO i, FromJSON a) => FilePath -> Maybe FileName -> i (Maybe a)+readDocument' ∷ (MonadIO i, Functor i, FromJSON a) ⇒ FilePath → Maybe FileName → i (Maybe a) readDocument' collection key = liftIO $ case key of-  Just key -> readDocument collection key-  Nothing -> return Nothing+  Just key → readDocument collection key+  Nothing → return Nothing  -- | Reads a document from a collection by numeric id (for example, key "00001-hello" has id 1).-readDocumentById :: (MonadIO i, FromJSON a) => FilePath -> Int -> i (Maybe a)+readDocumentById ∷ (MonadIO i, Functor i, FromJSON a) ⇒ FilePath → Int → i (Maybe a) readDocumentById collection i =-  documentFullKey collection (findById i) >>=-  readDocument' collection+  readDocument' collection =<< documentFullKey collection (findById i)  -- | Reads a document from a collection by name (for example, key "00001-hello" has name "hello").-readDocumentByName :: (MonadIO i, FromJSON a) => FilePath -> String -> i (Maybe a)+readDocumentByName ∷ (MonadIO i, Functor i, FromJSON a) ⇒ FilePath → String → i (Maybe a) readDocumentByName collection n =-  documentFullKey collection (findByName n) >>=-  readDocument' collection+  readDocument' collection =<< documentFullKey collection (findByName n)  -- | Returns a document's id by name (for example, "hello" will return 23 when key "00023-hello" exists). -- Does not read the document!-documentIdFromName :: (MonadIO i) => FilePath -> String -> i (Maybe Int)+documentIdFromName ∷ (MonadIO i, Functor i) ⇒ FilePath → String → i (Maybe Int) documentIdFromName collection n =-  splitFindDocument collection (findByName n) >>=-  return . (fst <$> fst <$>)+  (fst <$> fst <$>) <$> splitFindDocument collection (findByName n)  -- | Returns a document's name by id (for example, 23 will return "hello" when key "00023-hello" exists). -- Does not read the document!-documentNameFromId :: (MonadIO i) => FilePath -> Int -> i (Maybe String)+documentNameFromId ∷ (MonadIO i, Functor i) ⇒ FilePath → Int → i (Maybe String) documentNameFromId collection i =-  splitFindDocument collection (findById i) >>=-  return . (drop 1 . snd <$> fst <$>)+  (drop 1 . snd <$> fst <$>) <$> splitFindDocument collection (findById i)
library/Gitson/Util.hs view
@@ -36,13 +36,13 @@  -- | Filters a list of file paths, leaving only paths to existing non-hidden directories. filterDirs :: [FilePath] -> IO [FilePath]-filterDirs = (filterM doesDirectoryExist) . filter (not . isPrefixOf ".")+filterDirs = filterM doesDirectoryExist . filter (not . isPrefixOf ".")  -- | Returns an IO action that switches the current directory to a given path, -- executes the given IO action and switches the current directory back. insideDirectory :: (MonadIO i) => FilePath -> i a -> i a insideDirectory path action = do-  prevPath <- liftIO $ getCurrentDirectory+  prevPath <- liftIO getCurrentDirectory   liftIO $ setCurrentDirectory path   result <- action   liftIO $ setCurrentDirectory prevPath@@ -93,7 +93,7 @@ -- >>> nextKeyId ["1-hell0-w0rld-123456.json", "002-my-second-post.json"] -- 3 nextKeyId :: [String] -> Int-nextKeyId = (+1) . maxOrZero . catMaybes . map maybeReadInt+nextKeyId = (+1) . maxOrZero . mapMaybe maybeReadInt   where maybeReadInt x = fst <$> maybeReadIntString x         maxOrZero [] = 0         maxOrZero xs = maximum xs
test-suite/GitsonSpec.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TemplateHaskell, UnicodeSyntax #-}  module GitsonSpec (spec) where @@ -12,10 +12,12 @@ import           Gitson import           Gitson.Util (insideDirectory, lastCommitText) -data Thing = Thing { val :: Int } deriving (Eq, Show, Ord)+{-# ANN module ("HLint: ignore Redundant do" :: String) #-}++data Thing = Thing { val ∷ Int } deriving (Eq, Show, Ord) $(deriveJSON defaultOptions ''Thing) -spec :: Spec+spec ∷ Spec spec = before setup $ after cleanup $ do   context "transaction" $ do     describe "saveDocument" $ do@@ -23,14 +25,14 @@         transaction "tmp/repo" $ do           saveDocument "things" "first-thing" Thing {val = 1}           saveDocument "things" "second-thing" Thing {val = 2}-          liftIO $ (readFile "things/first-thing.json") `shouldThrow` anyIOException-          liftIO $ (readFile "things/second-thing.json") `shouldThrow` anyIOException+          liftIO $ readFile "things/first-thing.json" `shouldThrow` anyIOException+          liftIO $ readFile "things/second-thing.json" `shouldThrow` anyIOException         insideDirectory "tmp/repo" $ do-          first <- readFile "things/first-thing.json"+          first ← readFile "things/first-thing.json"           first `shouldBe` "{\n  \"val\": 1\n}"-          second <- readFile "things/second-thing.json"+          second ← readFile "things/second-thing.json"           second `shouldBe` "{\n  \"val\": 2\n}"-          commitMsg <- lastCommitText+          commitMsg ← lastCommitText           commitMsg `shouldBe` "Gitson transaction"      describe "saveNextDocument" $ do@@ -39,108 +41,108 @@           saveNextDocument "things" "hello" Thing {val = 1}           saveNextDocument "things" "world" Thing {val = 2}         insideDirectory "tmp/repo" $ do-          first <- readFile "things/000001-hello.json"+          first ← readFile "things/000001-hello.json"           first `shouldBe` "{\n  \"val\": 1\n}"-          second <- readFile "things/000002-world.json"+          second ← readFile "things/000002-world.json"           second `shouldBe` "{\n  \"val\": 2\n}"      describe "saveDocumentById" $ do       it "updates an entry by specified id" $ do         createDirectoryIfMissing True "tmp/repo/things"-        _ <- liftIO $ writeFile "tmp/repo/things/000004-second-thing.json" "{\"val\":1}"+        _ ← liftIO $ writeFile "tmp/repo/things/000004-second-thing.json" "{\"val\":1}"         transaction "tmp/repo" $ do           saveDocumentById "things" 4 Thing {val = 2}         insideDirectory "tmp/repo" $ do-          second <- readFile "things/000004-second-thing.json"+          second ← readFile "things/000004-second-thing.json"           second `shouldBe` "{\n  \"val\": 2\n}"      describe "saveDocumentByName" $ do       it "updates an entry by specified name" $ do         createDirectoryIfMissing True "tmp/repo/things"-        _ <- liftIO $ writeFile "tmp/repo/things/000004-second-thing.json" "{\"val\":1}"+        _ ← liftIO $ writeFile "tmp/repo/things/000004-second-thing.json" "{\"val\":1}"         transaction "tmp/repo" $ do           saveDocumentByName "things" "second-thing" Thing {val = 2}         insideDirectory "tmp/repo" $ do-          second <- readFile "things/000004-second-thing.json"+          second ← readFile "things/000004-second-thing.json"           second `shouldBe` "{\n  \"val\": 2\n}"    describe "readDocument" $ do     it "returns Just the document when reading an document by key" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/second-thing.json" "{\"val\":2}"-      content <- readDocument "tmp/repo/things" "second-thing" :: IO (Maybe Thing)+      _ ← writeFile "tmp/repo/things/second-thing.json" "{\"val\":2}"+      content ← readDocument "tmp/repo/things" "second-thing" ∷ IO (Maybe Thing)       content `shouldBe` Just Thing {val = 2}      it "returns Nothing when reading by a nonexistent key" $ do-      content <- readDocument "tmp/repo/things" "totally-not-a-thing" :: IO (Maybe Thing)+      content ← readDocument "tmp/repo/things" "totally-not-a-thing" ∷ IO (Maybe Thing)       content `shouldBe` Nothing    describe "readDocumentById" $ do     it "returns Just the document when reading an document by id" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/000004-second-thing.json" "{\"val\":1}"-      content <- readDocumentById "tmp/repo/things" 4 :: IO (Maybe Thing)+      _ ← writeFile "tmp/repo/things/000004-second-thing.json" "{\"val\":1}"+      content ← readDocumentById "tmp/repo/things" 4 ∷ IO (Maybe Thing)       content `shouldBe` Just Thing {val = 1}      it "returns Nothing when reading by a nonexistent id" $ do-      content <- readDocumentById "tmp/repo/things" 1 :: IO (Maybe Thing)+      content ← readDocumentById "tmp/repo/things" 1 ∷ IO (Maybe Thing)       content `shouldBe` Nothing    describe "readDocumentByName" $ do     it "returns Just the document when reading an document by name" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/000098-second-thing.json" "{\"val\":1}"-      content <- readDocumentByName "tmp/repo/things" "second-thing" :: IO (Maybe Thing)+      _ ← writeFile "tmp/repo/things/000098-second-thing.json" "{\"val\":1}"+      content ← readDocumentByName "tmp/repo/things" "second-thing" ∷ IO (Maybe Thing)       content `shouldBe` Just Thing {val = 1}      it "returns Nothing when reading by a nonexistent name" $ do-      content <- readDocumentByName "tmp/repo/things" "yolo" :: IO (Maybe Thing)+      content ← readDocumentByName "tmp/repo/things" "yolo" ∷ IO (Maybe Thing)       content `shouldBe` Nothing    describe "documentIdFromName" $ do     it "returns Just the document id from the document's name" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/000098-some-thing.json" "THIS SHOULD NOT BE READ"-      i <- documentIdFromName "tmp/repo/things" "some-thing"+      _ ← writeFile "tmp/repo/things/000098-some-thing.json" "THIS SHOULD NOT BE READ"+      i ← documentIdFromName "tmp/repo/things" "some-thing"       i `shouldBe` Just 98      it "returns Nothing when run with a nonexistent name" $ do-      i <- documentIdFromName "tmp/repo/things" "yolo"+      i ← documentIdFromName "tmp/repo/things" "yolo"       i `shouldBe` Nothing    describe "documentNameFromId" $ do     it "returns Just the document name from the document's id" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/000098-some-thing.json" "THIS SHOULD NOT BE READ"-      i <- documentNameFromId "tmp/repo/things" 98+      _ ← writeFile "tmp/repo/things/000098-some-thing.json" "THIS SHOULD NOT BE READ"+      i ← documentNameFromId "tmp/repo/things" 98       i `shouldBe` Just "some-thing"      it "returns Nothing when run with a nonexistent id" $ do-      i <- documentNameFromId "tmp/repo/things" 123+      i ← documentNameFromId "tmp/repo/things" 123       i `shouldBe` Nothing    describe "listDocumentKeys" $ do     it "returns a list of document keys when listing a collection" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/first-thing.json" "{}"-      _ <- writeFile "tmp/repo/things/second-thing.json" "{}"-      list <- listDocumentKeys "tmp/repo/things"+      _ ← writeFile "tmp/repo/things/first-thing.json" "{}"+      _ ← writeFile "tmp/repo/things/second-thing.json" "{}"+      list ← listDocumentKeys "tmp/repo/things"       sort list `shouldBe` ["first-thing", "second-thing"]      it "returns an empty when listing a nonexistent collection" $ do-      list <- listDocumentKeys "nonsense"+      list ← listDocumentKeys "nonsense"       list `shouldBe` []    describe "listEntries" $ do     it "returns a list of entries when listing a collection" $ do       createDirectoryIfMissing True "tmp/repo/things"-      _ <- writeFile "tmp/repo/things/first-thing.json" "{\"val\":1}"-      _ <- writeFile "tmp/repo/things/second-thing.json" "{\"val\":2}"-      list <- listEntries "tmp/repo/things" :: IO [Thing]+      _ ← writeFile "tmp/repo/things/first-thing.json" "{\"val\":1}"+      _ ← writeFile "tmp/repo/things/second-thing.json" "{\"val\":2}"+      list ← listEntries "tmp/repo/things" ∷ IO [Thing]       sort list `shouldBe` [Thing {val = 1}, Thing {val = 2}]      it "returns an empty list when listing a nonexistent collection" $ do-      list <- listEntries "nonsense" :: IO [Thing]+      list ← listEntries "nonsense" ∷ IO [Thing]       list `shouldBe` []    describe "listCollections" $ do@@ -148,17 +150,17 @@       createDirectoryIfMissing True "tmp/repo/bits"       createDirectoryIfMissing True "tmp/repo/pieces"       insideDirectory "tmp/repo" $ do-        list <- listCollections+        list ← listCollections         sort list `shouldBe` ["bits", "pieces"]      it "returns an empty list when listing an empty repo" $ do       createDirectoryIfMissing True "tmp/repo"       insideDirectory "tmp/repo" $ do-        list <- listCollections+        list ← listCollections         sort list `shouldBe` [] -setup :: IO ()+setup ∷ IO () setup = createRepo "tmp/repo" -cleanup :: IO ()-cleanup = void (try (removeDirectoryRecursive "tmp/repo") :: IO (Either IOException ()))+cleanup ∷ ActionWith ()+cleanup () = void (try (removeDirectoryRecursive "tmp/repo") ∷ IO (Either IOException ()))