ztar 0.2.0 → 1.0.0
raw patch · 7 files changed
+85/−61 lines, 7 filesdep −extradep ~filepathdep ~zip
Dependencies removed: extra
Dependency ranges changed: filepath, zip
Files
- CHANGELOG.md +5/−0
- src/Codec/Archive/ZTar.hs +2/−1
- src/Codec/Archive/ZTar/GZip.hs +0/−1
- src/Codec/Archive/ZTar/Tar.hs +0/−2
- src/Codec/Archive/ZTar/Zip.hs +17/−10
- test/Test.hs +57/−43
- ztar.cabal +4/−4
CHANGELOG.md view
@@ -1,3 +1,8 @@+# ztar 1.0.0++Breaking changes:+* Compiled against `zip-1.0.0`+ # ztar 0.2.0 Breaking changes:
src/Codec/Archive/ZTar.hs view
@@ -62,7 +62,7 @@ createFrom' compression (toFilePath -> archive) (toFilePath -> dir) paths = createFrom compression archive dir paths --- | Extract an archive to the given directory. Automatically detects the compression algorithm.+-- | Extract an archive to the given directory. Automatically detects the compression algorithm -- used in the archive. extract :: FilePath -- ^ archive to extract -> FilePath -- ^ destination directory@@ -71,6 +71,7 @@ Tar.TarFormat -> Tar.extract archive dir GZip.GZipFormat -> GZip.extract archive dir Zip.ZipFormat -> Zip.extract archive dir+ "" -> fail $ "Trying to extract empty file: " ++ archive _ -> fail $ "Could not recognize archive format: " ++ archive -- | Same as 'extract' but using Path types.
src/Codec/Archive/ZTar/GZip.hs view
@@ -6,7 +6,6 @@ Functions to create/extract GZIP-compressed archives. -}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}
src/Codec/Archive/ZTar/Tar.hs view
@@ -6,8 +6,6 @@ Functions to create/extract uncompressed tar archives. -}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ViewPatterns #-}
src/Codec/Archive/ZTar/Zip.hs view
@@ -21,11 +21,10 @@ import Control.Monad.IO.Class (liftIO) import Data.ByteString.Lazy (ByteString) import qualified Data.ByteString.Lazy as BS-import Path (parseAbsFile, parseAbsDir, parseRelFile) import System.Directory ( createDirectoryIfMissing- , doesFileExist , doesDirectoryExist+ , doesFileExist , listDirectory , makeAbsolute , withCurrentDirectory@@ -34,8 +33,19 @@ -- | A pattern matching any ByteString in the Zip format. pattern ZipFormat :: ByteString-pattern ZipFormat <- ((BS.pack [0x50, 0x4B, 0x03, 0x04] `BS.isPrefixOf`) -> True)+pattern ZipFormat <- (matchesZip -> True) +matchesZip :: ByteString -> Bool+matchesZip s = any (`BS.isPrefixOf` s) zipMagicNumbers+ where+ zipMagicNumbers = map BS.pack+ [ [0x50, 0x4B, 0x03, 0x04]+ -- empty archive+ , [0x50, 0x4B, 0x05, 0x06]+ -- spanned archive+ , [0x50, 0x4B, 0x07, 0x08]+ ]+ -- | Create a new archive compressed with Zip from the given paths. -- -- It is equivalent to calling the standard 'zip' program like so:@@ -46,7 +56,7 @@ -> [FilePath] -- ^ files and paths to compress, relative to base directory -> IO () create archive base paths = do- archive' <- makeAbsolute archive >>= parseAbsFile+ archive' <- makeAbsolute archive withCurrentDirectory base $ Zip.createArchive archive' $ mapM_ insert paths where insert path = do@@ -57,9 +67,8 @@ | isDir -> insertDir path | otherwise -> fail $ "Path does not exist: " ++ path insertFile path = do- path' <- parseRelFile path- path'' <- Zip.mkEntrySelector path'- Zip.loadEntry Zip.BZip2 (const $ return path'') path'+ path' <- Zip.mkEntrySelector path+ Zip.loadEntry Zip.BZip2 path' path insertDir path = let mkPath = if path == "." then id else (path </>) in mapM_ (insert . mkPath) =<< liftIO (listDirectory path)@@ -74,6 +83,4 @@ -> IO () extract archive dir = do createDirectoryIfMissing True dir- archive' <- makeAbsolute archive >>= parseAbsFile- dir' <- makeAbsolute dir >>= parseAbsDir- Zip.withArchive archive' $ Zip.unpackInto dir'+ Zip.withArchive archive $ Zip.unpackInto dir
test/Test.hs view
@@ -2,24 +2,25 @@ import Codec.Archive.ZTar import Control.Monad (forM, forM_)-import Control.Monad.Extra (unlessM) import qualified Data.ByteString as BS import Data.ByteString.Arbitrary (ArbByteString(..))-import Data.List (dropWhileEnd, intercalate, nub)+import Data.List (isPrefixOf, nub) import Data.Maybe (fromJust) import Path ( Dir , File- , Rel , Path+ , Rel , absdir , fromAbsFile+ , fromRelDir , parent , parseRelDir , parseRelFile , (</>) ) import Path.IO (doesFileExist, ensureDir, isLocationOccupied, withTempDir)+import qualified System.FilePath.Windows as Windows import Test.QuickCheck import Test.QuickCheck.Monadic import Test.Tasty (defaultMain, testGroup)@@ -27,34 +28,32 @@ main :: IO () main = defaultMain $ testGroup "ztar"- [ testProperty "Create/extract uncompressed tar archives" $ testZTar NoCompression- , testProperty "Create/extract GZip tar archives" $ testZTar GZip- , testProperty "Create/extract Zip archives" $ testZTar Zip+ [ testProperty "Create/extract uncompressed tar archives" $ testZTar 25 NoCompression+ , testProperty "Create/extract GZip tar archives" $ testZTar 20 GZip+ , testProperty "Create/extract Zip archives" $ testZTar 15 Zip ] -testZTar :: Compression -> Property-testZTar compression = monadicIO $ do- archive:src:dest:paths <- pick $ uniqueListOf 4 arbitrary-- files <- forM paths $ \path -> do- Blind (ABS contents) <- pick arbitrary- return (toRelFile path, contents)+testZTar :: Int -> Compression -> Property+testZTar n compression = withMaxSuccess n $ monadicIO $ do+ [archive, src, dest] <- pick $ uniqueListOf 3 arbitrary+ files <- pick arbitraryFileTree run $ withTempDir [absdir|/tmp|] "" $ \dir -> do let archive' = dir </> toRelFile archive src' = dir </> toRelDir src dest' = dir </> toRelDir dest + ensureDir $ parent archive'+ ensureDir src'+ ensureDir dest'+ -- write files to be bundled forM_ files $ \(path, contents) -> do let path' = src' </> path- -- case writing `a` when `a/b` already exists- unlessM (isLocationOccupied path') $ do- ensureDir $ parent path'- BS.writeFile (fromAbsFile path') contents+ ensureDir $ parent path'+ BS.writeFile (fromAbsFile path') contents -- create and extract archive- ensureDir $ parent archive' create' compression archive' src' extract' archive' dest' @@ -72,44 +71,59 @@ {- Helpers -} --- | Generate a unique list with length at least N.+toRelFile :: ValidName -> Path Rel File+toRelFile = fromJust . parseRelFile . unName++toRelDir :: ValidName -> Path Rel Dir+toRelDir = fromJust . parseRelDir . unName++maybeSlash :: Maybe (Path Rel Dir) -> Path Rel t -> Path Rel t+maybeSlash Nothing path = path+maybeSlash (Just dir) path = dir </> path++-- | Generate a unique list with length of N. uniqueListOf :: Eq a => Int -> Gen a -> Gen [a]-uniqueListOf 0 gen = nub <$> listOf gen+uniqueListOf 0 _ = return [] uniqueListOf n gen = do rest <- uniqueListOf (n - 1) gen x <- gen `suchThat` (`notElem` rest) return $ x : rest --- | A valid relative file path.-newtype ValidPath = ValidPath { unPath :: FilePath }- deriving (Show,Eq)--instance Arbitrary ValidPath where- arbitrary = ValidPath . truncatePath . intercalate "/" . map unName <$> listOf1 arbitrary- where- truncatePath = dropWhileEnd (`elem` ['/', '.']) . take 50--toRelFile :: ValidPath -> Path Rel File-toRelFile = fromJust . parseRelFile . unPath--toRelDir :: ValidPath -> Path Rel Dir-toRelDir = fromJust . parseRelDir . unPath+-- | An arbitrary file tree and their contents.+arbitraryFileTree :: Gen [(Path Rel File, BS.ByteString)]+arbitraryFileTree = mkFileTree Nothing (0 :: Int)+ where+ mkFileTree (Just dir) _+ | length (fromRelDir dir) > 50 = return []+ mkFileTree _ 5 = return [] -- max depth of 5 directories+ mkFileTree dir depth = do+ paths <- nub <$> listOf arbitrary+ fmap concat $ forM paths $ \path -> do+ -- exponentially less likely to go into subdirectories+ isFile <- frequency [(1, pure False), (2^depth, pure True)]+ if isFile+ then do+ Blind (ABS contents) <- arbitrary+ return [(dir `maybeSlash` toRelFile path, contents)]+ else mkFileTree (Just $ dir `maybeSlash` toRelDir path) (depth + 1) -- | A valid file or directory name. newtype ValidName = ValidName { unName :: String }- deriving (Show)+ deriving (Show,Eq) +{-# ANN module "HLint: ignore Use ||" #-} instance Arbitrary ValidName where arbitrary = do -- https://stackoverflow.com/a/2306003/8565175 name <- take 14 <$> listOf1 (elements validChars)- if or- [ last name == '.'- , name `elem` ["nul"]- -- https://superuser.com/questions/259703/get-mac-tar-to-stop-putting-filenames-in-tar-archives- , name !! 0 == '.' && name !! 1 == '_'- ]- then arbitrary- else return $ ValidName name+ if isValid name+ then return $ ValidName name+ else arbitrary where validChars = ['a'..'z'] ++ ['0'..'9'] ++ ['.', '_', '-']+ isValid name = not $ or+ [ last name == '.'+ -- https://superuser.com/questions/259703/get-mac-tar-to-stop-putting-filenames-in-tar-archives+ , "._" `isPrefixOf` name+ , not $ Windows.isValid name+ ]
ztar.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: c39489aefc941fc38bd650d9f61d4cfbc64a46eb644a01468a3c27fad9de23b4+-- hash: f6bbd45a4e7adaa7ce7e25a88acfaf3aff646afa6bfd8d1835c36d21c23449e8 name: ztar-version: 0.2.0+version: 1.0.0 synopsis: Creating and extracting arbitrary archives description: Creating and extracting arbitrary archives. category: Codec@@ -52,7 +52,7 @@ , process , text , unix-compat- , zip >=0.2 && <0.3+ , zip >=1.0 && <1.2 , zlib >=0.6 && <0.7 if flag(dev) ghc-options: -Werror@@ -88,7 +88,7 @@ , base , bytestring , bytestring-arbitrary- , extra+ , filepath , path , path-io , tasty