hstar 0.1.0.1 → 0.1.0.2
raw patch · 7 files changed
+195/−74 lines, 7 filesdep +archive-tar-bytestringdep +brotli
Dependencies added: archive-tar-bytestring, brotli
Files
- CHANGELOG.md +4/−0
- README.md +51/−0
- hstar.cabal +35/−13
- src/Compression.cpphs +76/−0
- src/Compression.hs +0/−60
- src/Main.hs +26/−1
- src/Version.cpphs +3/−0
+ CHANGELOG.md view
@@ -0,0 +1,4 @@+# 0.1.0.2++ * Add support for brotli (`.br`) compressed archives with `with-brotli` flag+ * Add `sanitize` subcommand
+ README.md view
@@ -0,0 +1,51 @@+# hstar++`hstar` is a command-line tool like [tar](https://www.gnu.org/software/tar/),+but written in Haskell and taking advantage of+[backpack](http://blog.ezyang.com/category/haskell/backpack/).++It can be built with+a [libarchive](http://hackage.haskell.org/package/libarchive) backend wrapping+the C library, or the [tar](http://hackage.haskell.org/package/tar) library+written in Haskell.++## Use++To generate bash shell completions:++```+hstar --bash-completion-script hstar+```++## Performance++Benchmarks run on Linux.++`hstar` built with the `libarchive` backend.++| Program | Compression | Command | Time |+| ------- | ----------- | ------- | ---: |+| bsdtar | lz4 | `bsdtar -xf ghc-8.8.2-x86_64-deb9-linux.tar.lz4` | 2.411 s |+| hstar | lz4 | `hstar unpack ghc-8.8.2-x86_64-deb9-linux.tar.lz4` | 2.529 s |+| GNU tar | lz4 | `lz4 -cd ghc-8.8.2-x86_64-deb9-linux.tar.lz4 \| tar -xf -` | 2.539 s |+| Schily tar | lz4 | `lz4 -cd ghc-8.8.2-x86_64-deb9-linux.tar.lz4 \| star -xf -` | 1.572 s |+| bsdtar | gzip | `bsdtar -xf ghc-8.8.2-x86_64-deb9-linux.tar.gz` | 4.625 s |+| hstar | gzip | `hstar unpack ghc-8.8.2-x86_64-deb9-linux.tar.gz` | 6.378 s |+| GNU tar | gzip | `tar xf ghc-8.8.2-x86_64-deb9-linux.tar.gz` | 8.335 s |+| Schily tar | gzip | `star xf ghc-8.8.2-x86_64-deb9-linux.tar.gz` | 8.379 s |+| bsdtar | lzip | `bsdtar -xf ghc-8.8.2-x86_64-deb9-linux.tar.lz` | 12.49 s |+| hstar | lzip | `hstar unpack ghc-8.8.2-x86_64-deb9-linux.tar.lz` | 16.12 s |+| GNU tar | lzip | `tar xf ghc-8.8.2-x86_64-deb9-linux.tar.lz` | 16.71 s |+| Schily tar | lzip | `star xf ghc-8.8.2-x86_64-deb9-linux.tar.lz` | 15.81 s |+| bsdtar | lzma | `bsdtar -xf ghc-8.8.2-x86_64-deb9-linux.tar.xz` | 14.95 s |+| hstar | lzma | `hstar unpack ghc-8.8.2-x86_64-deb9-linux.tar.xz` | 15.37 s |+| GNU tar | lzma | `tar xf ghc-8.8.2-x86_64-deb9-linux.tar.xz` | 14.32 s |+| Schily tar | lzma | `star xf ghc-8.8.2-x86_64-deb9-linux.tar.xz` | 14.37 s |+| bsdtar | bzip2 | `bsdtar -xf ghc-8.8.2-x86_64-deb9-linux.tar.bz2` | 37.85 s |+| hstar | bzip2 | `hstar unpack ghc-8.8.2-x86_64-deb9-linux.tar.bz2` | 38.99 s |+| GNU tar | bzip2 | `tar xf ghc-8.8.2-x86_64-deb9-linux.tar.bz2` | 37.90 s |+| Schily tar | bzip2 | `star xf ghc-8.8.2-x86_64-deb9-linux.tar.bz2` | 38.27 s |+| bsdtar | zstd | `bsdtar -xf ghc-8.8.2-x86_64-deb9-linux.tar.zst` | 2.232 s |+| hstar | zstd | `hstar unpack ghc-8.8.2-x86_64-deb9-linux.tar.zst` | 3.053 s |+| GNU tar | zstd | `tar xf ghc-8.8.2-x86_64-deb9-linux.tar.zst` | 2.644 s |+| Schily tar | zstd | `star xf ghc-8.8.2-x86_64-deb9-linux.tar.zst` | 2.237 s |
hstar.cabal view
@@ -1,17 +1,20 @@-cabal-version: 2.0-name: hstar-version: 0.1.0.1-license: BSD3-license-file: LICENSE-copyright: Copyright: (c) 2019-2020 Vanessa McHale-maintainer: vamchale@gmail.com-author: Vanessa McHale-synopsis: Haskell version of tar CLI utility+cabal-version: 2.0+name: hstar+version: 0.1.0.2+license: BSD3+license-file: LICENSE+copyright: Copyright: (c) 2019-2020 Vanessa McHale+maintainer: vamchale@gmail.com+author: Vanessa McHale+synopsis: Haskell version of tar CLI utility description: Haskell implementation of the tar utility, demonstrating backpack -category: Codec, Tar, Archive-build-type: Simple+category: Codec, Tar, Archive+build-type: Simple+extra-source-files:+ CHANGELOG.md+ README.md source-repository head type: git@@ -27,6 +30,13 @@ description: Use Haskell backend instead of libarchive default: False +flag tar-bytestring+ description: Use tar-bytestring+ default: False++flag with-brotli+ description: Enable brotli compression support+ executable hstar main-is: Main.hs build-tool-depends: cpphs:cpphs -any@@ -54,19 +64,31 @@ lz4-hs >=0.1.1.0, lzlib >=1.0.1.0 + if flag(with-brotli)+ build-depends: brotli -any++ if flag(with-brotli)+ cpp-options: -DBROTLI+ if impl(ghc >=8.10) ghc-options: -Wunused-packages if flag(pure) build-depends: archive-tar -any - else+ if flag(tar-bytestring)+ build-depends: archive-tar-bytestring -any++ if (!flag(pure) && !flag(tar-bytestring)) build-depends: archive-libarchive -any if flag(pure) mixins: archive-tar (Archive.Tar as Archive) - else+ if flag(tar-bytestring)+ mixins: archive-tar-bytestring (Archive.Tar as Archive)++ if (!flag(pure) && !flag(tar-bytestring)) mixins: archive-libarchive (Archive.FFI as Archive) if flag(development)
+ src/Compression.cpphs view
@@ -0,0 +1,76 @@+module Compression ( compressionByFileExt+ , decompressor+ , compressor+ ) where++#ifdef BROTLI+import qualified Codec.Compression.Brotli as Brotli+#endif+import qualified Codec.Compression.BZip as BZip+import qualified Codec.Compression.GZip as GZip+import qualified Codec.Compression.Lzma as Lzma+import qualified Codec.Compression.Zlib as Zlib+import qualified Codec.Compression.Zstd.Lazy as Zstd+import qualified Codec.Lz4 as Lz4+import Codec.Lzip as Lzip+import qualified Data.ByteString.Lazy as BSL+import Data.List (isSuffixOf)++data Compressor = Lzma+ | Lz+ | Bz2+ | GZip+ | Zstd+ | Deflate+ | Lz4+#ifdef BROTLI+ | Brotli+#endif+ | None++compressionByFileExt :: FilePath -> Compressor+compressionByFileExt fp | ".tgz" `isSuffixOf` fp = GZip+ | ".tar.bz2" `isSuffixOf` fp = Bz2+ | ".tar.bz" `isSuffixOf` fp = Bz2+ | ".tbz2" `isSuffixOf` fp = Bz2+ | ".tbz" `isSuffixOf` fp = Bz2+ | ".tar.gz" `isSuffixOf` fp = GZip+ | ".tar.xz" `isSuffixOf` fp = Lzma+ | ".txz" `isSuffixOf` fp = Lzma+ | ".tar.lz" `isSuffixOf` fp = Lz+ | ".tlz" `isSuffixOf` fp = Lz+ | ".tar.zst" `isSuffixOf` fp = Zstd+ | ".tar.Z" `isSuffixOf` fp = Deflate+ | ".tar.lz4" `isSuffixOf` fp = Lz4+#ifdef BROTLI+ | ".tbr" `isSuffixOf` fp = Brotli+ | ".tar.br" `isSuffixOf` fp = Brotli+#endif+ | ".tar" `isSuffixOf` fp = None+ | otherwise = error "Suffix not supported or invalid."++decompressor :: Compressor -> (BSL.ByteString -> BSL.ByteString)+decompressor Lzma = Lzma.decompress+decompressor Bz2 = BZip.decompress+decompressor GZip = GZip.decompress+decompressor Lz = Lzip.decompress+decompressor Zstd = Zstd.decompress+decompressor Deflate = Zlib.decompress+decompressor Lz4 = Lz4.decompress+#ifdef BROTLI+decompressor Brotli = Brotli.decompress+#endif+decompressor None = id++compressor :: Compressor -> (BSL.ByteString -> BSL.ByteString)+compressor Lzma = Lzma.compress+compressor Bz2 = BZip.compress+compressor GZip = GZip.compress+compressor Lz = Lzip.compress+compressor Zstd = Zstd.compress 3+compressor Deflate = Zlib.compress+compressor Lz4 = Lz4.compress+#ifdef BROTLI+compressor Brotli = Brotli.compress+#endif+compressor None = id
− src/Compression.hs
@@ -1,60 +0,0 @@-module Compression ( compressionByFileExt- , decompressor- , compressor- ) where--import qualified Codec.Compression.BZip as BZip-import qualified Codec.Compression.GZip as GZip-import qualified Codec.Compression.Lzma as Lzma-import qualified Codec.Compression.Zlib as Zlib-import qualified Codec.Compression.Zstd.Lazy as Zstd-import qualified Codec.Lz4 as Lz4-import Codec.Lzip as Lzip-import qualified Data.ByteString.Lazy as BSL-import Data.List (isSuffixOf)--data Compressor = Lzma- | Lz- | Bz2- | GZip- | Zstd- | Deflate- | Lz4- | None--compressionByFileExt :: FilePath -> Compressor-compressionByFileExt fp | ".tgz" `isSuffixOf` fp = GZip- | ".tar.bz2" `isSuffixOf` fp = Bz2- | ".tar.bz" `isSuffixOf` fp = Bz2- | ".tbz2" `isSuffixOf` fp = Bz2- | ".tbz" `isSuffixOf` fp = Bz2- | ".tar.gz" `isSuffixOf` fp = GZip- | ".tar.xz" `isSuffixOf` fp = Lzma- | ".txz" `isSuffixOf` fp = Lzma- | ".tar.lz" `isSuffixOf` fp = Lz- | ".tlz" `isSuffixOf` fp = Lz- | ".tar.zst" `isSuffixOf` fp = Zstd- | ".tar.Z" `isSuffixOf` fp = Deflate- | ".tar.lz4" `isSuffixOf` fp = Lz4- | ".tar" `isSuffixOf` fp = None- | otherwise = error "Suffix not supported or invalid."--decompressor :: Compressor -> (BSL.ByteString -> BSL.ByteString)-decompressor Lzma = Lzma.decompress-decompressor Bz2 = BZip.decompress-decompressor GZip = GZip.decompress-decompressor Lz = Lzip.decompress-decompressor Zstd = Zstd.decompress-decompressor Deflate = Zlib.decompress-decompressor Lz4 = Lz4.decompress-decompressor None = id--compressor :: Compressor -> (BSL.ByteString -> BSL.ByteString)-compressor Lzma = Lzma.compress-compressor Bz2 = BZip.compress-compressor GZip = GZip.compress-compressor Lz = Lzip.compress-compressor Zstd = Zstd.compress 3-compressor Deflate = Zlib.compress-compressor Lz4 = Lz4.compress-compressor None = id
src/Main.hs view
@@ -1,8 +1,11 @@ module Main ( main ) where +import Archive import Archive.Compression import Compression-import Data.Maybe (fromMaybe)+import Control.Exception (throw)+import qualified Data.ByteString.Lazy as BSL+import Data.Maybe (fromMaybe) import Options.Applicative import Version @@ -11,8 +14,22 @@ | Pack ![FilePath] !FilePath | Unpack !FilePath !(Maybe FilePath) | PackSrc !FilePath !FilePath+ | Sanitize !FilePath +forceBSL :: BSL.ByteString -> IO ()+forceBSL = (`seq` mempty) . last . BSL.toChunks++sanitize :: FilePath -> IO ()+sanitize fp = do+ let enc = compressionByFileExt fp+ contents <- decompressor enc <$> BSL.readFile fp+ forceBSL contents+ let es = either throw id $ readArchiveBytes contents+ paxContents = writeArchiveBytes es+ BSL.writeFile fp (compressor enc paxContents)+ run :: Command -> IO ()+run (Sanitize src) = sanitize src run (Unpack src dest) = let dec = decompressor (compressionByFileExt src) in unpackFileToDirAndDecompress dec src (fromMaybe "." dest)@@ -26,6 +43,13 @@ let comp = compressor (compressionByFileExt tar) in packSrcDirAndCompress comp dir' tar +sanitizeP :: Parser Command+sanitizeP = Sanitize+ <$> argument str+ (metavar "SRC"+ <> fileCompletions+ <> help "Archive to pax-ify")+ unpack :: Parser Command unpack = Unpack <$> argument str@@ -81,6 +105,7 @@ <> command "pack-dir" (info packDir (progDesc "Pack a directory's contents into an archive")) <> command "pack" (info pack (progDesc "Pack an archive from a list of files")) <> command "pack-src" (info packSrc (progDesc "Pack up a source directory as a bundle, ignoring version control and artifact directories"))+ <> command "sanitize" (info sanitizeP (progDesc "Sanitize a tar archive so it is pax-compatible")) ) versionMod :: Parser (a -> a)
src/Version.cpphs view
@@ -21,5 +21,8 @@ ++ "lz4-hs: " ++ VERSION_lz4_hs ++ "\n" ++ "lz4: " ++ lZ4VersionString ++ "\n" ++ "zstd-hs: " ++ VERSION_zstd ++ "\n"+#ifdef BROTLI+ ++ "brotli-hs: " ++ VERSION_brotli ++ "\n"+#endif ++ "bz2-hs: " ++ VERSION_bz2 ++ "\n" ++ "bz2: " ++ bZ2BzlibVersion