sak 0.1.2.1 → 0.1.2.3
raw patch · 8 files changed
+264/−190 lines, 8 files
Files
- CHANGELOG.md +8/−0
- README.md +4/−0
- man/sak.1 +5/−1
- sak.cabal +12/−3
- src/Compression.cpphs +203/−0
- src/Compression.hs +0/−186
- src/Main.hs +29/−0
- src/Version.cpphs +3/−0
CHANGELOG.md view
@@ -1,5 +1,13 @@ # sak +## 0.1.2.3++ * Add `recompress` subcommand++## 0.1.2.2++ * Add `with-brotli` cabal package flag to enable/disable brotli support+ ## 0.1.2.1 * Add brotli format support (`.br`)
README.md view
@@ -19,3 +19,7 @@ ``` You can put this in your `~/.bashrc` or `~/.bash_profile` as needed.++### Manpages++`man/sak.1` contains manpages for `sak`
man/sak.1 view
@@ -14,7 +14,9 @@ .PP sak verify file.gz .PP-sak matrix release-binary \[en]best+sak matrix release-binary --best+.PP+sak recompress binary.tar.zst --best .SH SUBCOMMANDS .PP \f[B]compress\f[R] - Compress a file@@ -27,6 +29,8 @@ \f[B]verify\f[R] - Check the integrity of a compressed file .PP \f[B]matrix\f[R] - Generate multiple compressed files in various formats+.PP+\f[B]recompress\f[R] - Recompress a file in-memory. .SH OPTIONS .TP \f[B]-h\f[R] \f[B]--help\f[R]
sak.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.18 name: sak-version: 0.1.2.1+version: 0.1.2.3 license: BSD3 license-file: LICENSE copyright: Copyright: (c) 2020 Vanessa McHale@@ -21,6 +21,10 @@ type: darcs location: https://hub.darcs.net/vmchale/sak +flag with-brotli+ description:+ Build with support for brotli encoding/decoding (disable this to statically link)+ executable sak main-is: Main.hs build-tool-depends: cpphs:cpphs -any@@ -45,8 +49,13 @@ filepath -any, bytestring -any, directory >=1.3.1.0,- parallel-io -any,- brotli -any+ parallel-io -any++ if flag(with-brotli)+ build-depends: brotli -any++ if flag(with-brotli)+ cpp-options: -DBROTLI if impl(ghc >=8.0) ghc-options:
+ src/Compression.cpphs view
@@ -0,0 +1,203 @@+module Compression ( Compression (Lzma, Lz4, Lzip, BZip)+ , CompressionLevel (..)+ , detectCompression+ , toCompressor+ , toDecompressor+ , toFileCompressor+ , toFileDecompressor+ , uncompressedExt+ , check+ , ext+ -- * Utilities+ , fileSize+ ) where++#ifdef BROTLI+import qualified Codec.Compression.Brotli as Br+#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 qualified Codec.Lzip as Lzip+import qualified Data.ByteString.Lazy as BSL+import Data.List (isSuffixOf)+import System.FilePath (dropExtension, (-<.>))+import System.IO (IOMode (ReadMode), hFileSize, withFile)++data CompressionLevel = Best+ | Fastest+ | Default+ | Custom !Int++data Compression = Lzma+ | Lzip+ | BZip+ | GZip+ | Zstd+#ifdef BROTLI+ | Brotli+#endif+ | Lz4+ | Z+ | None+ deriving (Enum)++ext :: Compression -> String+ext Lzma = ".xz"+ext Lzip = ".lz"+ext BZip = ".bz2"+ext GZip = ".gz"+ext Zstd = ".zst"+ext Lz4 = ".lz4"+ext Z = ".Z"+#ifdef BROTLI+ext Brotli = ".br"+#endif+ext None = ""++uncompressedExt :: FilePath -> FilePath+uncompressedExt fp | ".tlz" `isSuffixOf` fp = fp -<.> ".tar"+ | ".txz" `isSuffixOf` fp = fp -<.> ".tar"+ | ".tlz" `isSuffixOf` fp = fp -<.> ".tar"+ | ".tgz" `isSuffixOf` fp = fp -<.> ".tar"+ | ".cpgz" `isSuffixOf` fp = fp -<.> ".cpio"+ | ".cpbz2" `isSuffixOf` fp = fp -<.> ".cpio"+ | ".xcfgz" `isSuffixOf` fp = fp -<.> ".xcf"+ | ".xcfbz2" `isSuffixOf` fp = fp -<.> ".xcf"+ | ".tbz2" `isSuffixOf` fp = fp -<.> "tar"+ | ".tbz" `isSuffixOf` fp = fp -<.> "tar"+ | ".tbr" `isSuffixOf` fp = fp -<.> "tar"+ | otherwise = dropExtension fp++detectCompression :: FilePath -> Compression+detectCompression fp+ | ".xz" `isSuffixOf` fp = Lzma+ | ".txz" `isSuffixOf` fp = Lzma+ | ".lz" `isSuffixOf` fp = Lzip+ | ".tlz" `isSuffixOf` fp = Lzip+ | ".tbz2" `isSuffixOf` fp = BZip+ | ".tbz" `isSuffixOf` fp = BZip+ | ".bz2" `isSuffixOf` fp = BZip+ | ".gz" `isSuffixOf` fp = GZip+ | ".tgz" `isSuffixOf` fp = GZip+ | ".Z" `isSuffixOf` fp = Z+ | ".zst" `isSuffixOf` fp = Zstd+ | ".lz4" `isSuffixOf` fp = Lz4+ | ".xcfgz" `isSuffixOf` fp = GZip+ | ".xcfbz2" `isSuffixOf` fp = BZip+ | ".cpgz" `isSuffixOf` fp = GZip+ | ".cpbz2" `isSuffixOf` fp = BZip+#ifdef BROTLI+ | ".br" `isSuffixOf` fp = Brotli+#endif+ | otherwise = None -- error "Suffix not supported or invalid"++toFileDecompressor :: Compression -> FilePath -> IO BSL.ByteString+toFileDecompressor Lz4 = lz4DecompressFile+toFileDecompressor x = fmap (toDecompressor x) . BSL.readFile++toDecompressor :: Compression -> BSL.ByteString -> BSL.ByteString+toDecompressor Lzma = Lzma.decompress+toDecompressor Lzip = Lzip.decompress+toDecompressor BZip = BZip.decompress+toDecompressor GZip = GZip.decompress+toDecompressor Z = Zlib.decompress+toDecompressor Zstd = Zstd.decompress+toDecompressor Lz4 = Lz4.decompress+#ifdef BROTLI+toDecompressor Brotli = Br.decompress+#endif+toDecompressor None = id++check :: Compression -> BSL.ByteString -> IO ()+check = (forceBSL .) . toDecompressor++levelGuard :: (Int, Int) -> Int -> Int+levelGuard (min', max') lvl | lvl < min' || lvl > max' =+ error ("Invalid compression level. Compression must be between " ++ show min' ++ " and " ++ show max')+ | otherwise = lvl++toFileCompressor :: Compression -> CompressionLevel -> FilePath -> IO BSL.ByteString+toFileCompressor Lzip lvl = Lzip.compressFileLevel (toEnum $ toInt Lzip lvl)+toFileCompressor x lvl = fmap (toCompressor x lvl Nothing) . BSL.readFile++gzipCompression :: Int -> GZip.CompressParams+gzipCompression lvl = GZip.defaultCompressParams { GZip.compressLevel = GZip.compressionLevel lvl }++zlibCompression :: Int -> Zlib.CompressParams+zlibCompression lvl = Zlib.defaultCompressParams { Zlib.compressLevel = Zlib.compressionLevel lvl }++lzmaCompression :: Int -> Lzma.CompressParams+lzmaCompression lvl = Lzma.defaultCompressParams { Lzma.compressLevel = toEnum lvl }++#ifdef BROTLI+brotliCompression :: Int -> Br.CompressParams+brotliCompression lvl = Br.defaultCompressParams { Br.compressLevel = toEnum lvl }+#endif++toInt :: Compression -> CompressionLevel -> Int+#ifdef BROTLI+toInt Brotli Fastest = fromEnum (minBound :: Br.CompressionLevel)+toInt Brotli (Custom i) = levelGuard (0,11) i+toInt Brotli _ = fromEnum (maxBound :: Br.CompressionLevel)+#endif+toInt Zstd Best = Zstd.maxCLevel+toInt Zstd Fastest = 1+toInt Zstd (Custom i) = levelGuard (1, Zstd.maxCLevel) i+toInt Zstd Default = 3+toInt Lzip Best = fromEnum (maxBound :: Lzip.CompressionLevel)+toInt Lzip Fastest = fromEnum (minBound :: Lzip.CompressionLevel)+toInt Lzip (Custom i) = levelGuard (0, 9) i+toInt Lzip Default = 6+toInt Lzma Best = 9+toInt Lzma Fastest = 0+toInt Lzma (Custom i) = levelGuard (0, 9) i+toInt Lzma Default = 6+toInt BZip Best = 9+toInt BZip Fastest = 1+toInt BZip (Custom i) = i+toInt BZip Default = 7+toInt GZip Best = 9+toInt GZip Fastest = 0+toInt GZip (Custom i) = i+toInt GZip Default = 6+toInt Z Best = 9+toInt Z Fastest = 0+toInt Z (Custom i) = i+toInt Z Default = 6+toInt Lz4 Best = Lz4.lZ4HCClevelMax+toInt Lz4 Fastest = 0+toInt Lz4 (Custom i) = levelGuard (0, Lz4.lZ4HCClevelMax) i+toInt Lz4 Default = 0 -- 1?+toInt None _ = error "Internal error."++toCompressor :: Compression -> CompressionLevel -> Maybe Int -> BSL.ByteString -> BSL.ByteString+toCompressor Lzma lvl _ = Lzma.compressWith (lzmaCompression $ toInt Lzma lvl)+toCompressor Lzip lvl (Just sz) = flip (Lzip.compressWithSz (toEnum $ toInt Lzip lvl)) sz+toCompressor Lzip _ _ = error "Internal error."+toCompressor BZip lvl _ = BZip.compressWith (fromIntegral $ toInt BZip lvl) 30+toCompressor GZip lvl _ = GZip.compressWith (gzipCompression $ toInt GZip lvl)+toCompressor Z lvl _ = Zlib.compressWith (zlibCompression $ toInt Z lvl)+toCompressor Zstd lvl _ = Zstd.compress (toInt Zstd lvl)+toCompressor Lz4 lvl _ = Lz4.compressSz (toInt Lz4 lvl)+#ifdef BROTLI+toCompressor Brotli lvl _ = Br.compressWith (brotliCompression $ toInt Brotli lvl)+#endif+toCompressor None _ _ = id++fileSize :: FilePath -> IO Integer+fileSize fp = withFile fp ReadMode hFileSize++lz4DecompressFile :: FilePath -> IO BSL.ByteString+lz4DecompressFile fp = do+ fSz <- fileSize fp+ let f = if fSz <= 32 * 1024+ then Lz4.decompressBufSz (32 * 1024)+ else Lz4.decompressBufSz (128 * 1024)+ f <$> BSL.readFile fp++forceBSL :: BSL.ByteString -> IO ()+forceBSL bsl = last (BSL.toChunks bsl) `seq` mempty
− src/Compression.hs
@@ -1,186 +0,0 @@-module Compression ( Compression (Lzma,Lz4,Lzip)- , CompressionLevel (..)- , detectCompression- , toCompressor- , toFileCompressor- , toFileDecompressor- , uncompressedExt- , check- , ext- -- * Utilities- , fileSize- ) where--import qualified Codec.Compression.Brotli as Br-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 qualified Codec.Lzip as Lzip-import qualified Data.ByteString.Lazy as BSL-import Data.List (isSuffixOf)-import System.FilePath (dropExtension, (-<.>))-import System.IO (IOMode (ReadMode), hFileSize, withFile)--data CompressionLevel = Best- | Fastest- | Default- | Custom !Int--data Compression = Lzma- | Lzip- | BZip- | GZip- | Zstd- | Brotli- | Lz4- | Z- | None- deriving (Enum)--ext :: Compression -> String-ext Lzma = ".xz"-ext Lzip = ".lz"-ext BZip = ".bz2"-ext GZip = ".gz"-ext Zstd = ".zst"-ext Lz4 = ".lz4"-ext Z = ".Z"-ext Brotli = ".br"-ext None = ""--uncompressedExt :: FilePath -> FilePath-uncompressedExt fp | ".tlz" `isSuffixOf` fp = fp -<.> ".tar"- | ".txz" `isSuffixOf` fp = fp -<.> ".tar"- | ".tlz" `isSuffixOf` fp = fp -<.> ".tar"- | ".tgz" `isSuffixOf` fp = fp -<.> ".tar"- | ".cpgz" `isSuffixOf` fp = fp -<.> ".cpio"- | ".cpbz2" `isSuffixOf` fp = fp -<.> ".cpio"- | ".xcfgz" `isSuffixOf` fp = fp -<.> ".xcf"- | ".xcfbz2" `isSuffixOf` fp = fp -<.> ".xcf"- | ".tbz2" `isSuffixOf` fp = fp -<.> "tar"- | ".tbz" `isSuffixOf` fp = fp -<.> "tar"- | ".tbr" `isSuffixOf` fp = fp -<.> "tar"- | otherwise = dropExtension fp--detectCompression :: FilePath -> Compression-detectCompression fp- | ".xz" `isSuffixOf` fp = Lzma- | ".txz" `isSuffixOf` fp = Lzma- | ".lz" `isSuffixOf` fp = Lzip- | ".tlz" `isSuffixOf` fp = Lzip- | ".tbz2" `isSuffixOf` fp = BZip- | ".tbz" `isSuffixOf` fp = BZip- | ".bz2" `isSuffixOf` fp = BZip- | ".gz" `isSuffixOf` fp = GZip- | ".tgz" `isSuffixOf` fp = GZip- | ".Z" `isSuffixOf` fp = Z- | ".zst" `isSuffixOf` fp = Zstd- | ".lz4" `isSuffixOf` fp = Lz4- | ".xcfgz" `isSuffixOf` fp = GZip- | ".xcfbz2" `isSuffixOf` fp = BZip- | ".cpgz" `isSuffixOf` fp = GZip- | ".cpbz2" `isSuffixOf` fp = BZip- | ".br" `isSuffixOf` fp = Brotli- | otherwise = None -- error "Suffix not supported or invalid"--toFileDecompressor :: Compression -> FilePath -> IO BSL.ByteString-toFileDecompressor Lz4 = lz4DecompressFile-toFileDecompressor x = fmap (toDecompressor x) . BSL.readFile--toDecompressor :: Compression -> BSL.ByteString -> BSL.ByteString-toDecompressor Lzma = Lzma.decompress-toDecompressor Lzip = Lzip.decompress-toDecompressor BZip = BZip.decompress-toDecompressor GZip = GZip.decompress-toDecompressor Z = Zlib.decompress-toDecompressor Zstd = Zstd.decompress-toDecompressor Lz4 = Lz4.decompress-toDecompressor Brotli = Br.decompress-toDecompressor None = id--check :: Compression -> BSL.ByteString -> IO ()-check = (forceBSL .) . toDecompressor--levelGuard :: (Int, Int) -> Int -> Int-levelGuard (min', max') lvl | lvl < min' || lvl > max' =- error ("Invalid compression level. Compression must be between " ++ show min' ++ " and " ++ show max')- | otherwise = lvl--toFileCompressor :: Compression -> CompressionLevel -> FilePath -> IO BSL.ByteString-toFileCompressor Lzip lvl = Lzip.compressFileLevel (toEnum $ toInt Lzip lvl)-toFileCompressor x lvl = fmap (toCompressor x lvl Nothing) . BSL.readFile--gzipCompression :: Int -> GZip.CompressParams-gzipCompression lvl = GZip.defaultCompressParams { GZip.compressLevel = GZip.compressionLevel lvl }--zlibCompression :: Int -> Zlib.CompressParams-zlibCompression lvl = Zlib.defaultCompressParams { Zlib.compressLevel = Zlib.compressionLevel lvl }--lzmaCompression :: Int -> Lzma.CompressParams-lzmaCompression lvl = Lzma.defaultCompressParams { Lzma.compressLevel = toEnum lvl }--brotliCompression :: Int -> Br.CompressParams-brotliCompression lvl = Br.defaultCompressParams { Br.compressLevel = toEnum lvl }--toInt :: Compression -> CompressionLevel -> Int-toInt Brotli Fastest = fromEnum (minBound :: Br.CompressionLevel)-toInt Brotli (Custom i) = levelGuard (0,11) i-toInt Brotli _ = fromEnum (maxBound :: Br.CompressionLevel)-toInt Zstd Best = Zstd.maxCLevel-toInt Zstd Fastest = 1-toInt Zstd (Custom i) = levelGuard (1, Zstd.maxCLevel) i-toInt Zstd Default = 3-toInt Lzip Best = fromEnum (maxBound :: Lzip.CompressionLevel)-toInt Lzip Fastest = fromEnum (minBound :: Lzip.CompressionLevel)-toInt Lzip (Custom i) = levelGuard (0, 9) i-toInt Lzip Default = 6-toInt Lzma Best = 9-toInt Lzma Fastest = 0-toInt Lzma (Custom i) = levelGuard (0, 9) i-toInt Lzma Default = 6-toInt BZip Best = 9-toInt BZip Fastest = 1-toInt BZip (Custom i) = i-toInt BZip Default = 7-toInt GZip Best = 9-toInt GZip Fastest = 0-toInt GZip (Custom i) = i-toInt GZip Default = 6-toInt Z Best = 9-toInt Z Fastest = 0-toInt Z (Custom i) = i-toInt Z Default = 6-toInt Lz4 Best = Lz4.lZ4HCClevelMax-toInt Lz4 Fastest = 0-toInt Lz4 (Custom i) = levelGuard (0, Lz4.lZ4HCClevelMax) i-toInt Lz4 Default = 0 -- 1?-toInt None _ = error "Internal error."--toCompressor :: Compression -> CompressionLevel -> Maybe Int -> BSL.ByteString -> BSL.ByteString-toCompressor Lzma lvl _ = Lzma.compressWith (lzmaCompression $ toInt Lzma lvl)-toCompressor Lzip lvl (Just sz) = flip (Lzip.compressWithSz (toEnum $ toInt Lzip lvl)) sz-toCompressor Lzip _ _ = error "Internal error."-toCompressor BZip lvl _ = BZip.compressWith (fromIntegral $ toInt BZip lvl) 30-toCompressor GZip lvl _ = GZip.compressWith (gzipCompression $ toInt GZip lvl)-toCompressor Z lvl _ = Zlib.compressWith (zlibCompression $ toInt Z lvl)-toCompressor Zstd lvl _ = Zstd.compress (toInt Zstd lvl)-toCompressor Lz4 lvl _ = Lz4.compressSz (toInt Lz4 lvl)-toCompressor Brotli lvl _ = Br.compressWith (brotliCompression $ toInt Brotli lvl)-toCompressor None _ _ = id--fileSize :: FilePath -> IO Integer-fileSize fp = withFile fp ReadMode hFileSize--lz4DecompressFile :: FilePath -> IO BSL.ByteString-lz4DecompressFile fp = do- fSz <- fileSize fp- let f = if fSz <= 32 * 1024- then Lz4.decompressBufSz (32 * 1024)- else Lz4.decompressBufSz (128 * 1024)- f <$> BSL.readFile fp--forceBSL :: BSL.ByteString -> IO ()-forceBSL bsl = last (BSL.toChunks bsl) `seq` mempty
src/Main.hs view
@@ -19,10 +19,32 @@ data Command = Decompress !FilePath !(Maybe FilePath) | Compress !FilePath !FilePath !CompressionLevel+ | Recompress !FilePath !CompressionLevel | Transcode !FilePath !FilePath !CompressionLevel | Verify !FilePath | Matrix !FilePath !CompressionLevel +forceBSL :: BSL.ByteString -> IO ()+forceBSL = (`seq` mempty) . last . BSL.toChunks++recompressFile :: CompressionLevel+ -> FilePath+ -> IO ()+recompressFile lvl inp = do+ let enc = detectCompression inp+ pre <- case enc of+ BZip -> do { contents <- BSL.readFile inp+ ; toDecompressor enc contents <$ forceBSL contents+ }+ _ -> do { res <- toFileDecompressor enc inp+ ; forceBSL res+ ; pure res+ }+ let guessSz = case enc of+ Lzip -> Just $ 8 * fromIntegral (BSL.length pre)+ _ -> Nothing+ BSL.writeFile inp $ toCompressor enc lvl guessSz pre+ decompressFile :: FilePath -- ^ Compressed file -> FilePath -- ^ Output -> IO ()@@ -44,6 +66,7 @@ BSL.writeFile (inp ++ ext c) =<< toFileCompressor c lvl inp run :: Command -> IO ()+run (Recompress i lvl) = recompressFile lvl =<< reifyPath i run (Decompress i Nothing) = flip decompressFile (uncompressedExt i) =<< reifyPath i run (Decompress i (Just o)) = flip decompressFile o =<< reifyPath i run (Compress i o lvl) = flip (compressFile lvl) o =<< reifyPath i@@ -112,6 +135,11 @@ <$> fileHelp "File to compress" <*> compressionLevel +recompress :: Parser Command+recompress = Recompress+ <$> fileHelp "File to recompress"+ <*> compressionLevel+ compress :: Parser Command compress = Compress <$> fileHelp "File to compress"@@ -132,6 +160,7 @@ <> command "transcode" (info transcode (progDesc "Convert a file's compression")) <> command "verify" (info verify (progDesc "Check the integrity of a compressed file")) <> command "matrix" (info matrix (progDesc "Compress a file to all available formats"))+ <> command "recompress" (info recompress (progDesc "Recompress a file (for instance, to compress it at a higher level)")) ) versionMod :: Parser (a -> a)
src/Version.cpphs view
@@ -19,3 +19,6 @@ ++ "bz2: " ++ bZ2BzlibVersion ++ "\n" ++ "lz4: " ++ lZ4VersionString ++ "\n" ++ "lz4-hs: " ++ VERSION_lz4_hs+#ifdef BROTLI+ ++ "\nbrotli-hs: " ++ VERSION_brotli+#endif