filestore 0.2 → 0.3
raw patch · 7 files changed
+169/−61 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Data.FileStore.Types: type ResourceName = String
+ Data.FileStore.Generic: richDirectory :: FileStore -> FilePath -> IO [(Resource, Either String Revision)]
+ Data.FileStore.Types: FSDirectory :: FilePath -> Resource
+ Data.FileStore.Types: FSFile :: FilePath -> Resource
+ Data.FileStore.Types: data Resource
+ Data.FileStore.Types: directory :: FileStore -> FilePath -> IO [Resource]
+ Data.FileStore.Types: instance Eq Resource
+ Data.FileStore.Types: instance Ord Resource
+ Data.FileStore.Types: instance Read Resource
+ Data.FileStore.Types: instance Show Resource
+ Data.FileStore.Types: instance Typeable Resource
- Data.FileStore.Generic: create :: (Contents a) => FileStore -> ResourceName -> Author -> Description -> a -> IO ()
+ Data.FileStore.Generic: create :: (Contents a) => FileStore -> FilePath -> Author -> Description -> a -> IO ()
- Data.FileStore.Generic: diff :: FileStore -> ResourceName -> Maybe RevisionId -> Maybe RevisionId -> IO [(DI, [String])]
+ Data.FileStore.Generic: diff :: FileStore -> FilePath -> Maybe RevisionId -> Maybe RevisionId -> IO [(DI, [String])]
- Data.FileStore.Generic: modify :: (Contents a) => FileStore -> ResourceName -> RevisionId -> Author -> Description -> a -> IO (Either MergeInfo ())
+ Data.FileStore.Generic: modify :: (Contents a) => FileStore -> FilePath -> RevisionId -> Author -> Description -> a -> IO (Either MergeInfo ())
- Data.FileStore.Generic: searchRevisions :: FileStore -> Bool -> ResourceName -> Description -> IO [Revision]
+ Data.FileStore.Generic: searchRevisions :: FileStore -> Bool -> FilePath -> Description -> IO [Revision]
- Data.FileStore.Generic: smartRetrieve :: (Contents a) => FileStore -> Bool -> ResourceName -> Maybe String -> IO a
+ Data.FileStore.Generic: smartRetrieve :: (Contents a) => FileStore -> Bool -> FilePath -> Maybe String -> IO a
- Data.FileStore.Types: Added :: ResourceName -> Change
+ Data.FileStore.Types: Added :: FilePath -> Change
- Data.FileStore.Types: Deleted :: ResourceName -> Change
+ Data.FileStore.Types: Deleted :: FilePath -> Change
- Data.FileStore.Types: FileStore :: IO () -> (forall a. (Contents a) => ResourceName -> Author -> Description -> a -> IO ()) -> (forall a. (Contents a) => ResourceName -> Maybe RevisionId -> IO a) -> (ResourceName -> Author -> Description -> IO ()) -> (ResourceName -> ResourceName -> Author -> Description -> IO ()) -> ([ResourceName] -> TimeRange -> IO [Revision]) -> (ResourceName -> IO RevisionId) -> (RevisionId -> IO Revision) -> IO [ResourceName] -> (RevisionId -> RevisionId -> Bool) -> (SearchQuery -> IO [SearchMatch]) -> FileStore
+ Data.FileStore.Types: FileStore :: IO () -> (forall a. (Contents a) => FilePath -> Author -> Description -> a -> IO ()) -> (forall a. (Contents a) => FilePath -> Maybe RevisionId -> IO a) -> (FilePath -> Author -> Description -> IO ()) -> (FilePath -> FilePath -> Author -> Description -> IO ()) -> ([FilePath] -> TimeRange -> IO [Revision]) -> (FilePath -> IO RevisionId) -> (RevisionId -> IO Revision) -> IO [FilePath] -> (FilePath -> IO [Resource]) -> (RevisionId -> RevisionId -> Bool) -> (SearchQuery -> IO [SearchMatch]) -> FileStore
- Data.FileStore.Types: Modified :: ResourceName -> Change
+ Data.FileStore.Types: Modified :: FilePath -> Change
- Data.FileStore.Types: SearchMatch :: ResourceName -> Integer -> String -> SearchMatch
+ Data.FileStore.Types: SearchMatch :: FilePath -> Integer -> String -> SearchMatch
- Data.FileStore.Types: delete :: FileStore -> ResourceName -> Author -> Description -> IO ()
+ Data.FileStore.Types: delete :: FileStore -> FilePath -> Author -> Description -> IO ()
- Data.FileStore.Types: history :: FileStore -> [ResourceName] -> TimeRange -> IO [Revision]
+ Data.FileStore.Types: history :: FileStore -> [FilePath] -> TimeRange -> IO [Revision]
- Data.FileStore.Types: index :: FileStore -> IO [ResourceName]
+ Data.FileStore.Types: index :: FileStore -> IO [FilePath]
- Data.FileStore.Types: latest :: FileStore -> ResourceName -> IO RevisionId
+ Data.FileStore.Types: latest :: FileStore -> FilePath -> IO RevisionId
- Data.FileStore.Types: matchResourceName :: SearchMatch -> ResourceName
+ Data.FileStore.Types: matchResourceName :: SearchMatch -> FilePath
- Data.FileStore.Types: rename :: FileStore -> ResourceName -> ResourceName -> Author -> Description -> IO ()
+ Data.FileStore.Types: rename :: FileStore -> FilePath -> FilePath -> Author -> Description -> IO ()
- Data.FileStore.Types: retrieve :: FileStore -> forall a. (Contents a) => ResourceName -> Maybe RevisionId -> IO a
+ Data.FileStore.Types: retrieve :: FileStore -> forall a. (Contents a) => FilePath -> Maybe RevisionId -> IO a
- Data.FileStore.Types: save :: FileStore -> forall a. (Contents a) => ResourceName -> Author -> Description -> a -> IO ()
+ Data.FileStore.Types: save :: FileStore -> forall a. (Contents a) => FilePath -> Author -> Description -> a -> IO ()
Files
- CHANGES +26/−0
- Data/FileStore/Darcs.hs +34/−17
- Data/FileStore/Generic.hs +18/−5
- Data/FileStore/Git.hs +30/−13
- Data/FileStore/Types.hs +21/−14
- Tests.lhs +19/−2
- filestore.cabal +21/−10
CHANGES view
@@ -1,3 +1,27 @@+Version 0.3 released 08 Apr 2009++* Added new 'directory' function, which returns a list of resources given+ a directory name. Resources are marked as either FSFile or+ FSDirectory. Thanks to Thomas Hartman for showing the need for+ 'directory', and for distinguishing explicitly between files and+ directories. In 'index' the distinction was previously left implicit,+ which worked for git but not darcs: 'index' provided no way of+ distinguishing an empty directory from a file.++* In 'directory' and 'index', git ls-tree is used instead of git ls-files;+ this guarantees that only files that have been committed are returned.+ 'index' now lists only files, and no longer includes empty directories+ even in darcs.++* Added new richDirectory function to Data.FileStore.Generic.+ richDirectory returns a directory that includes information+ about the latest revision of each file. Thanks to Thomas Hartman for+ the patch.++* Replaced ResourceName type with FilePath.++* Added repository information to cabal file.+ Version 0.2 released 08 Feb 2009 * Changed diff to do a line-by-line rather than word-by-word diff.@@ -27,6 +51,8 @@ and for creating a second file in a subdirectory. * Minor code cleanup.++* Added CHANGES. Version 0.1 released 24 Jan 2009
Data/FileStore/Darcs.hs view
@@ -22,12 +22,12 @@ import Data.DateTime (parseDateTime, toSqlString) import Data.FileStore.Types import Data.FileStore.Utils (hashsMatch, isInsideRepo, parseMatchLine, runShellCommand, escapeRegexSpecialChars)-import Data.List (intersect, nub)+import Data.List (intersect, nub, sort, isPrefixOf) import Data.Maybe (fromMaybe, fromJust) import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import System.Directory (doesFileExist, doesDirectoryExist, createDirectoryIfMissing) import System.Exit (ExitCode(ExitSuccess))-import System.FilePath ((</>), takeDirectory, dropFileName)+import System.FilePath ((</>), takeDirectory, dropFileName, addTrailingPathSeparator) import Text.Regex.Posix ((=~)) import Text.XML.Light import qualified Data.ByteString.Lazy as B (ByteString, writeFile)@@ -45,6 +45,7 @@ , latest = darcsLatestRevId repo , revision = darcsGetRevision repo , index = darcsIndex repo+ , directory = darcsDirectory repo , search = darcsSearch repo , idsMatch = const hashsMatch repo } @@ -138,7 +139,7 @@ return $ lines $ toString r -- | If name doesn't exist in repo or is not a file, throw NotFound-ensureFileExists :: FilePath -> ResourceName -> IO ()+ensureFileExists :: FilePath -> FilePath -> IO () ensureFileExists repo name = do isFile <- doesFileExist (repo </> encodeString name) when (not isFile) $ throwIO NotFound@@ -160,7 +161,7 @@ else throwIO $ UnknownError $ "darcs init failed:\n" ++ err -- | Save changes (creating the file and directory if needed), add, and commit.-darcsSave :: Contents a => FilePath -> ResourceName -> Author -> Description -> a -> IO ()+darcsSave :: Contents a => FilePath -> FilePath -> Author -> Description -> a -> IO () darcsSave repo name author logMsg contents = do let filename = repo </> encodeString name inside <- isInsideRepo repo filename@@ -174,7 +175,7 @@ -- | Commit changes to a resource. Raise 'Unchanged' exception if there were none. -- This is not for creating a new file; see 'darcsSave'. This is just for updating.-darcsCommit :: FilePath -> [ResourceName] -> Author -> Description -> IO ()+darcsCommit :: FilePath -> [FilePath] -> Author -> Description -> IO () darcsCommit repo names author logMsg = do let args = ["--all", "-A", (authorName author ++ " <" ++ authorEmail author ++ ">"), "-m", logMsg] ++ names (statusCommit, errCommit, _) <- runDarcsCommand repo "record" args@@ -185,7 +186,7 @@ else UnknownError $ "Could not darcs record " ++ unwords names ++ "\n" ++ errCommit -- | Change the name of a resource.-darcsMove :: FilePath -> ResourceName -> ResourceName -> Author -> Description -> IO ()+darcsMove :: FilePath -> FilePath -> FilePath -> Author -> Description -> IO () darcsMove repo oldName newName author logMsg = do let newPath = repo </> newName inside <- isInsideRepo repo newPath@@ -199,7 +200,7 @@ else throwIO NotFound -- | Delete a resource from the repository.-darcsDelete :: FilePath -> ResourceName -> Author -> Description -> IO ()+darcsDelete :: FilePath -> FilePath -> Author -> Description -> IO () darcsDelete repo name author logMsg = do runShellCommand repo Nothing "rm" [name] darcsCommit repo [name] author logMsg@@ -211,7 +212,7 @@ -- | Return list of log entries for the list of resources. -- If list of resources is empty, log entries for all resources are returned.-darcsLog :: FilePath -> [ResourceName] -> TimeRange -> IO [Revision]+darcsLog :: FilePath -> [FilePath] -> TimeRange -> IO [Revision] darcsLog repo names (TimeRange begin end) = do let opts = timeOpts begin end do (status, err, output) <- runDarcsCommand repo "changes" $ ["--xml-output", "--summary"] ++ names ++ opts@@ -239,7 +240,7 @@ return $ head result -- | Return revision ID for latest commit for a resource.-darcsLatestRevId :: FilePath -> ResourceName -> IO RevisionId+darcsLatestRevId :: FilePath -> FilePath -> IO RevisionId darcsLatestRevId repo name = do ensureFileExists repo name -- changes always succeeds, so no need to check error@@ -252,7 +253,7 @@ -- | Retrieve the contents of a resource. darcsRetrieve :: Contents a => FilePath- -> ResourceName+ -> FilePath -> Maybe RevisionId -- ^ @Just@ revision ID, or @Nothing@ for latest -> IO a darcsRetrieve repo name Nothing =@@ -266,14 +267,30 @@ else throwIO $ UnknownError $ "Error in darcs query contents:\n" ++ err -- | Get a list of all known files inside and managed by a repository.-darcsIndex :: FilePath ->IO [ResourceName]+darcsIndex :: FilePath ->IO [FilePath] darcsIndex repo = do- (status, errOutput, output) <- runDarcsCommand repo "query" ["manifest"]- if status == ExitSuccess- -- We need to do 'drop 2' because Darcs returns files like ["./foobar"], and- -- the ./ is always present & always not wanted.- then return (map (drop 2) . lines . toString $ output)- else error $ "'darcs query manifest' returned error status.\n" ++ errOutput+ (status, _errOutput, output) <- runDarcsCommand repo "query" ["files","--no-directories"]+ if status == ExitSuccess+ then return $ map (drop 2) . lines . toString $ output+ else return [] -- return empty list if invalid path (see gitIndex)++-- | Get a list of all resources inside a directory in the repository.+darcsDirectory :: FilePath -> FilePath -> IO [Resource]+darcsDirectory repo dir = do+ let dir' = if null dir then "" else addTrailingPathSeparator dir+ (status1, _errOutput1, output1) <- runDarcsCommand repo "query" ["files","--no-directories"]+ (status2, _errOutput2, output2) <- runDarcsCommand repo "query" ["files","--no-files"]+ if status1 == ExitSuccess && status2 == ExitSuccess+ then do+ let files = map (drop $ length dir' + 2) . filter (("." </> dir') `isPrefixOf`) . lines . toString $ output1+ -- We need to do 'drop $ length dir' + 3' because Darcs returns files like ["./foo/foobar"].+ let dirs = map (drop $ length dir' + 2) . filter (("." </> dir') `isPrefixOf`) . drop 1 . lines . toString $ output2+ -- We need the drop 1 to eliminate the root directory, which appears first.+ -- Now, select the ones that are in THIS directory and convert to Resources:+ let files' = map FSFile $ filter ('/' `notElem`) files+ let dirs' = map FSDirectory $ filter ('/' `notElem`) dirs+ return $ sort (files' ++ dirs') + else return [] -- returns empty list for invalid path (see gitDirectory) -- | Uses grep to search repository. darcsSearch :: FilePath -> SearchQuery -> IO [SearchMatch]
Data/FileStore/Generic.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE ScopedTypeVariables #-} {- | Module : Data.FileStore.Generic Copyright : Copyright (C) 2009 John MacFarlane, Gwern Branwen, Sebastiaan Visser@@ -17,6 +18,7 @@ , diff , searchRevisions , smartRetrieve+ , richDirectory ) where@@ -26,6 +28,7 @@ import Data.FileStore.Utils import Data.List (isInfixOf) import Data.Algorithm.Diff (DI(..), getGroupedDiff)+import System.FilePath ((</>)) import Prelude hiding (catch) handleUnknownError :: SomeException -> IO a@@ -35,7 +38,7 @@ -- error. create :: Contents a => FileStore- -> ResourceName -- ^ Resource to create.+ -> FilePath -- ^ Resource to create. -> Author -- ^ Author of change. -> Description -- ^ Description of change. -> a -- ^ Contents of resource.@@ -50,7 +53,7 @@ -- @Left@ merge information is returned. Otherwise, @Right@ the new contents are saved. modify :: Contents a => FileStore- -> ResourceName -- ^ Resource to create.+ -> FilePath -- ^ Resource to create. -> RevisionId -- ^ ID of previous revision that is being modified. -> Author -- ^ Author of change. -> Description -- ^ Description of change.@@ -75,7 +78,7 @@ -- or @B@ (in both), and the list is a list of lines (without -- newlines at the end). diff :: FileStore- -> ResourceName -- ^ Resource name to get diff for.+ -> FilePath -- ^ Resource name to get diff for. -> Maybe RevisionId -- ^ @Just@ old revision ID, or @Nothing@ for empty. -> Maybe RevisionId -- ^ @Just@ oew revision ID, or @Nothing@ for latest. -> IO [(DI, [String])]@@ -93,7 +96,7 @@ -> Bool -- ^ When true the description must -- match exactly, when false partial -- hits are allowed.- -> ResourceName -- ^ The resource to search history for.+ -> FilePath -- ^ The resource to search history for. -> Description -- ^ Revision description to search for. -> IO [Revision] @@ -112,7 +115,7 @@ :: Contents a => FileStore -> Bool -- ^ @True@ for exact description match, @False@ for partial match.- -> ResourceName -- ^ Resource name to retrieve.+ -> FilePath -- ^ Resource name to retrieve. -> Maybe String -- ^ @Just@ revision ID or description, or @Nothing@ for empty. -> IO a smartRetrieve fs exact name mrev = do@@ -135,4 +138,14 @@ -- Retrieve resource for latest matching revision. else retrieve fs name (Just $ revId $ Prelude.head revs)++-- | Like 'directory', but returns information about the latest revision.+richDirectory :: FileStore -> FilePath -> IO [(Resource, Either String Revision)]+richDirectory fs fp = do+ rs <- directory fs fp+ mapM f rs+ where f r = Control.Exception.catch (g r) (\(e :: FileStoreError)-> return $ ( r, Left . show $ e ) )+ g r@(FSDirectory _dir) = return (r,Left "richDirectory, we don't care about revision info for repos")+ g res@(FSFile file) = do rev <- revision fs =<< latest fs ( fp </> file )+ return (res,Right rev)
Data/FileStore/Git.hs view
@@ -17,6 +17,7 @@ ) where import Data.FileStore.Types+import Data.Maybe (mapMaybe) import System.Exit import Data.Time.Clock.POSIX (posixSecondsToUTCTime) import Data.FileStore.Utils (hashsMatch, isInsideRepo, runShellCommand, escapeRegexSpecialChars) @@ -48,6 +49,7 @@ , latest = gitLatestRevId repo , revision = gitGetRevision repo , index = gitIndex repo+ , directory = gitDirectory repo , search = gitSearch repo , idsMatch = const hashsMatch repo }@@ -82,7 +84,7 @@ -- | Commit changes to a resource. Raise 'Unchanged' exception if there were -- no changes.-gitCommit :: FilePath -> [ResourceName] -> Author -> String -> IO ()+gitCommit :: FilePath -> [FilePath] -> Author -> String -> IO () gitCommit repo names author logMsg = do (statusCommit, errCommit, _) <- runGitCommand repo "commit" $ ["--author", authorName author ++ " <" ++ authorEmail author ++ ">", "-m", logMsg] ++ names@@ -93,7 +95,7 @@ else UnknownError $ "Could not git commit " ++ unwords names ++ "\n" ++ errCommit -- | Save changes (creating file and directory if needed), add, and commit.-gitSave :: Contents a => FilePath -> ResourceName -> Author -> Description -> a -> IO ()+gitSave :: Contents a => FilePath -> FilePath -> Author -> Description -> a -> IO () gitSave repo name author logMsg contents = do let filename = repo </> encodeString name inside <- isInsideRepo repo filename@@ -108,7 +110,7 @@ -- | Retrieve contents from resource. gitRetrieve :: Contents a => FilePath- -> ResourceName+ -> FilePath -> Maybe RevisionId -- ^ @Just@ revision ID, or @Nothing@ for latest -> IO a gitRetrieve repo name revid = do@@ -124,7 +126,7 @@ else throwIO $ UnknownError $ "Error in git cat-file:\n" ++ err' -- | Delete a resource from the repository.-gitDelete :: FilePath -> ResourceName -> Author -> Description -> IO ()+gitDelete :: FilePath -> FilePath -> Author -> Description -> IO () gitDelete repo name author logMsg = do (statusAdd, errRm, _) <- runGitCommand repo "rm" [name] if statusAdd == ExitSuccess@@ -132,7 +134,7 @@ else throwIO $ UnknownError $ "Could not git rm '" ++ name ++ "'\n" ++ errRm -- | Change the name of a resource.-gitMove :: FilePath -> ResourceName -> ResourceName -> Author -> Description -> IO ()+gitMove :: FilePath -> FilePath -> FilePath -> Author -> Description -> IO () gitMove repo oldName newName author logMsg = do gitLatestRevId repo oldName -- will throw a NotFound error if oldName doesn't exist let newPath = repo </> encodeString newName@@ -146,7 +148,7 @@ else throwIO $ UnknownError $ "Could not git mv " ++ oldName ++ " " ++ newName ++ "\n" ++ err -- | Return revision ID for latest commit for a resource.-gitLatestRevId :: FilePath -> ResourceName -> IO RevisionId+gitLatestRevId :: FilePath -> FilePath -> IO RevisionId gitLatestRevId repo name = do (status, _, output) <- runGitCommand repo "rev-list" ["--max-count=1", "HEAD", "--", name] if status == ExitSuccess@@ -169,14 +171,29 @@ Right xs -> throwIO $ UnknownError $ "git rev-list returned more than one result: " ++ show xs else throwIO NotFound --- | Get list of files in repository.-gitIndex :: FilePath -> IO [ResourceName]+-- | Get a list of all known files inside and managed by a repository.+gitIndex :: FilePath ->IO [FilePath] gitIndex repo = do- (status, errOutput, output) <- runGitCommand repo "ls-files" []+ (status, _err, output) <- runGitCommand repo "ls-tree" ["-r","-t","HEAD"] if status == ExitSuccess- then return $ map convertEncoded $ lines $ toString output- else error $ "git ls-files returned error status.\n" ++ errOutput+ then return $ mapMaybe (lineToFilename . words) . lines . toString $ output+ else return [] -- if error, will return empty list+ -- note: on a newly initialized repo, 'git ls-tree HEAD' returns an error+ where lineToFilename (_:"blob":_:rest) = Just $ convertEncoded $ unwords rest+ lineToFilename _ = Nothing +-- | Get list of resources in one directory of the repository.+gitDirectory :: FilePath -> FilePath -> IO [Resource]+gitDirectory repo dir = do+ (status, _err, output) <- runGitCommand repo "ls-tree" ["HEAD:" ++ dir]+ if status == ExitSuccess+ then return $ map (lineToResource . words) $ lines $ toString output+ else return [] -- if error, this will return empty list+ -- note: on a newly initialized repo, 'git ls-tree HEAD:' returns an error+ where lineToResource (_:"blob":_:rest) = FSFile $ convertEncoded $ unwords rest+ lineToResource (_:"tree":_:rest) = FSDirectory $ convertEncoded $ unwords rest+ lineToResource _ = error "Encountered an item that is neither blob nor tree in git ls-tree"+ -- | Uses git-grep to search repository. Escape regex special characters, so the pattern -- is interpreted as an ordinary string. gitSearch :: FilePath -> SearchQuery -> IO [SearchMatch]@@ -199,7 +216,7 @@ {- -- | Uses git-diff to get a dif between two revisions.-gitDiff :: FilePath -> ResourceName -> RevisionId -> RevisionId -> IO String+gitDiff :: FilePath -> FilePath -> RevisionId -> RevisionId -> IO String gitDiff repo name from to = do (status, _, output) <- runGitCommand repo "diff" [from, to, name] if status == ExitSuccess@@ -214,7 +231,7 @@ -- | Return list of log entries for the given time frame and list of resources. -- If list of resources is empty, log entries for all resources are returned.-gitLog :: FilePath -> [ResourceName] -> TimeRange -> IO [Revision]+gitLog :: FilePath -> [FilePath] -> TimeRange -> IO [Revision] gitLog repo names (TimeRange mbSince mbUntil) = do (status, err, output) <- runGitCommand repo "whatchanged" $ ["--pretty=format:%h%n%ct%n%an%n%ae%n%s%n"] ++
Data/FileStore/Types.hs view
@@ -13,7 +13,7 @@ module Data.FileStore.Types ( RevisionId- , ResourceName+ , Resource(..) , Author(..) , Change(..) , Description@@ -35,10 +35,13 @@ import Data.DateTime (DateTime) import Control.Exception (Exception) import Prelude hiding (catch)+import System.FilePath (FilePath) type RevisionId = String -type ResourceName = String+data Resource = FSFile FilePath+ | FSDirectory FilePath+ deriving (Show, Read, Eq, Typeable, Ord) data Author = Author {@@ -47,9 +50,9 @@ } deriving (Show, Read, Eq, Typeable) data Change =- Added ResourceName- | Deleted ResourceName- | Modified ResourceName+ Added FilePath+ | Deleted FilePath+ | Modified FilePath deriving (Show, Read, Eq, Typeable) type Description = String@@ -127,7 +130,7 @@ data SearchMatch = SearchMatch {- matchResourceName :: ResourceName+ matchResourceName :: FilePath , matchLineNumber :: Integer , matchLine :: String } deriving (Show, Read, Eq, Typeable)@@ -141,7 +144,7 @@ -- | Save contents in the filestore. , save :: Contents a- => ResourceName -- Resource to save.+ => FilePath -- Resource to save. -> Author -- Author of change. -> Description -- Description of change. -> a -- New contents of resource.@@ -149,32 +152,32 @@ -- | Retrieve the contents of the named resource. , retrieve :: Contents a- => ResourceName -- Resource to retrieve.+ => FilePath -- Resource to retrieve. -> Maybe RevisionId -- @Just@ a particular revision ID, or @Nothing@ for latest -> IO a -- | Delete a named resource, providing author and log message.- , delete :: ResourceName -- Resource to delete.+ , delete :: FilePath -- Resource to delete. -> Author -- Author of change. -> Description -- Description of change. -> IO () -- | Rename a resource, providing author and log message.- , rename :: ResourceName -- Resource original name.- -> ResourceName -- Resource new name.+ , rename :: FilePath -- Resource original name.+ -> FilePath -- Resource new name. -> Author -- Author of change. -> Description -- Description of change. -> IO () -- | Get history for a list of named resources in a (possibly openended) time range. -- If the list is empty, history for all resources will be returned. - , history :: [ResourceName] -- List of resources to get history for, or @[]@ for all.+ , history :: [FilePath] -- List of resources to get history for, or @[]@ for all. -> TimeRange -- Time range within which to get history. -> IO [Revision] -- | Return the revision ID of the latest change for a resource. Raises 'NotFound' -- if the resource is not found.- , latest :: ResourceName -- Resource to get revision ID for.+ , latest :: FilePath -- Resource to get revision ID for. -> IO RevisionId -- | Return information about a revision, given the ID. Raises 'NotFound' if there is@@ -183,7 +186,11 @@ -> IO Revision -- | Return a list of resources in the filestore.- , index :: IO [ResourceName]+ , index :: IO [FilePath]++ -- | Return a list of resources in a directory of the filestore.+ , directory :: FilePath -- Directory to list (empty for root)+ -> IO [Resource] -- | @True@ if the revision IDs match, in the sense that the -- can be treated as specifying the same revision.
Tests.lhs view
@@ -6,6 +6,7 @@ runghc -idist/build/autogen Tests.lhs > import Data.FileStore+> import Data.List (sort) > import Test.HUnit > import System.Directory (removeDirectoryRecursive) > import Control.Monad (forM)@@ -14,6 +15,7 @@ > import Data.DateTime > import Data.Maybe (mapMaybe) > import System.Directory (doesFileExist)+> import System.FilePath > import System.Process > import Data.Algorithm.Diff (DI(..)) @@ -33,6 +35,7 @@ > , ("create resource in subdirectory", createTest2) > , ("create resource with non-ascii name", createTest3) > , ("try to create resource outside repo", createTest4)+> , ("directory", directoryTest) > , ("retrieve resource", retrieveTest1) > , ("retrieve resource in a subdirectory", retrieveTest2) > , ("retrieve resource with non-ascii name", retrieveTest3)@@ -84,7 +87,7 @@ > create fs subdirTestTitle testAuthor "description of change" testContents > revid <- latest fs testTitle > assertBool "revision returns a revision after create" (not (null revid))-> create fs "subdir/Subdir title2.txt" testAuthor "+Second file" testContents+> create fs (subdirTestTitle ++ "2") testAuthor "+Second file" testContents *** Create a resource with a non-ascii title, and check to see that revision returns a revision for it: @@ -102,6 +105,20 @@ > exists <- doesFileExist "tmp/oops" > assertBool "file ../oops was created outside repository" (not exists) +*** Test directory++> directoryTest fs = TestCase $ do++ Get directory for top-level++> files <- directory fs ""+> assertEqual "result of directory on top-level" (sort [FSDirectory "subdir", FSFile testTitle, FSFile nonasciiTestTitle]) (sort files)++ Get contents of subdirectory++> subdirFiles <- directory fs "subdir"+> assertEqual "result of directory on subdir" [FSFile (takeFileName subdirTestTitle), FSFile (takeFileName (subdirTestTitle ++ "2"))] subdirFiles+ *** Retrieve latest version of a resource: > retrieveTest1 fs = TestCase $ do@@ -180,7 +197,7 @@ > delete fs toBeDeleted testAuthor "goodbye" > ind <- index fs-> assertBool "index does not contain resource that was deleted" (not (toBeDeleted `elem` ind))+> assertBool "index does not contain resource that was deleted" (toBeDeleted `notElem` ind) *** Rename a resource:
filestore.cabal view
@@ -1,5 +1,5 @@ Name: filestore-Version: 0.2+Version: 0.3 Cabal-version: >= 1.2 Build-type: Custom Tested-with: GHC==6.10.1@@ -14,13 +14,24 @@ License-File: LICENSE Author: John MacFarlane, Gwern Branwen, Sebastiaan Visser Maintainer: jgm@berkeley.edu-Build-depends: base >= 4, bytestring, utf8-string, filepath, directory, datetime,- parsec >= 2 && < 3, process, time, datetime, regex-posix, xml, split, Diff-Extra-source-files: Tests.lhs-Exposed-modules: Data.FileStore, Data.FileStore.Types, Data.FileStore.Git, Data.FileStore.Darcs,- -- Data.FileStore.Sqlite3,- Data.FileStore.Utils, Data.FileStore.Generic-Other-modules: Paths_filestore+Homepage: http://johnmacfarlane.net/repos/filestore+ Data-Files: extra/post-update, CHANGES-Ghc-Options: -Wall-Ghc-Prof-Options: -auto-all+Extra-source-files: Tests.lhs++-- Commented out until Cabal 1.6 becomes common.+-- Source-repository head+-- Type: darcs+-- Location: http://johnmacfarlane.net/repos/filestore++Library+ Build-depends: base >= 4, bytestring, utf8-string, filepath, directory, datetime,+ parsec >= 2 && < 3, process, time, datetime, regex-posix, xml, split, Diff++ Exposed-modules: Data.FileStore, Data.FileStore.Types, Data.FileStore.Git, Data.FileStore.Darcs,+ -- Data.FileStore.Sqlite3,+ Data.FileStore.Utils, Data.FileStore.Generic+ Other-modules: Paths_filestore++ Ghc-Options: -Wall+ Ghc-Prof-Options: -auto-all