packages feed

sak (empty) → 0.1.0.0

raw patch · 8 files changed

+462/−0 lines, 8 filesdep +basedep +bytestringdep +bz2

Dependencies added: base, bytestring, bz2, filepath, lz4-hs, lzlib, lzma, optparse-applicative, zlib, zstd

Files

+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# sak++## 0.1.0.0++Initial release
+ LICENSE view
@@ -0,0 +1,11 @@+Copyright Vanessa McHale (c) 2020++Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,21 @@+# sak++sak is a command-line tool for compression.++## Use++To convert compression format:++```+sak transcode sak-0.1.0.0.tar.gz sak-0.1.0.0.tar.lz+```++### Bash Completions++To get bash completions in your current shell session:++```+eval "$(sak --bash-completion-script sak)"+```++You can put this in your `~/.bashrc` or `~/.bash_profile` as needed.
+ man/sak.1 view
@@ -0,0 +1,76 @@+.\" Automatically generated by Pandoc 2.9.2+.\"+.TH "sak (1)" "" "" "" ""+.hy+.SH NAME+.PP+sak - \f[I]S\f[R]wiss \f[I]A\f[R]rmy \f[I]K\f[R]nife for compression+.SH DESCRIPTION+.PP+\f[B]sak\f[R] is a compression tool+.SH SYNOPSIS+.PP+sak transcode tarball.tar.gz tarball.tar.zst+.PP+sak verify file.gz+.SH SUBCOMMANDS+.PP+\f[B]compress\f[R] - Compress a file+.PP+\f[B]decompress\f[R] - Decompress a file+.PP+\f[B]transcode\f[R] - Convert compression format, in-memory (and+streaming)+.PP+\f[B]verify\f[R] - Check the integrity of a compressed file+.SH OPTIONS+.TP+\f[B]-h\f[R] \f[B]--help\f[R]+Display help+.TP+\f[B]-V\f[R] \f[B]--version\f[R]+Display version information+.TP+\f[B]-l\f[R] \f[B]--compression-level\f[R]+Set compression level (usually 0-9)+.TP+\f[B]--best\f[R]+Use the maximum compression for the format+.TP+\f[B]--fastest\f[R]+Use the fastest compression for the format+.SH SUPPORTED FORMATS+.SS COMPRESSION+.IP \[bu] 2+gzip (0-9)+.IP \[bu] 2+bzip2 (1-9)+.IP \[bu] 2+lzma (0-9)+.IP \[bu] 2+lzip (0-9)+.IP \[bu] 2+zstd (0-22)+.IP \[bu] 2+lz4 (n/a)+.IP \[bu] 2+deflate (0-9)+.SH SHELL COMPLETIONS+.PP+To get shell completions in your current session:+.PP+\f[C]eval \[dq]$(sak --bash-completion-script sak)\[dq]\f[R]+.PP+Put this in your \f[C]\[ti]/.bashrc\f[R] or+\f[C]\[ti]/.bash_profile\f[R] to install them.+.SH BUGS+.PP+Please report any bugs you may come across to+http://hub.darcs.net/vmchale/sak/issues.+.SH COPYRIGHT+.PP+Copyright 2020.+Vanessa McHale.+All Rights Reserved.+.SH AUTHORS+Vanessa McHale<vamchale@gmail.com>.
+ sak.cabal view
@@ -0,0 +1,57 @@+cabal-version:      1.18+name:               sak+version:            0.1.0.0+license:            BSD3+license-file:       LICENSE+copyright:          Copyright: (c) 2020 Vanessa McHale+maintainer:         vamchale@gmail.com+author:             Vanessa McHale+synopsis:           Compression command-line tool+description:+    sak is a command-line tool that detects and handles various compression formats++category:           CommandLine, Compression+build-type:         Simple+extra-source-files:+    README.md+    CHANGELOG.md+    man/sak.1++source-repository head+    type:     darcs+    location: https://hub.darcs.net/vmchale/sak++executable sak+    main-is:            Main.hs+    build-tool-depends: cpphs:cpphs -any+    hs-source-dirs:     src+    other-modules:+        Version+        Compression+        Paths_sak++    autogen-modules:    Paths_sak+    default-language:   Haskell2010+    ghc-options:        -Wall+    build-depends:+        base >=4.9 && <5,+        lzlib >=1.0.1.0,+        bz2 >=0.1.1.0,+        zlib -any,+        zstd -any,+        lzma -any,+        lz4-hs >=0.1.3.0,+        optparse-applicative -any,+        filepath -any,+        bytestring -any++    if impl(ghc >=8.0)+        ghc-options:+            -Wincomplete-uni-patterns -Wincomplete-record-updates+            -Wredundant-constraints -Widentities++    if impl(ghc >=8.4)+        ghc-options: -Wmissing-export-lists++    if impl(ghc >=8.2)+        ghc-options: -Wcpp-undef
+ src/Compression.hs view
@@ -0,0 +1,150 @@+module Compression ( Compression (Lzip)+                   , CompressionLevel (..)+                   , detectCompression+                   , toCompressor+                   , toFileCompressor+                   , toFileDecompressor+                   , uncompressedExt+                   , check+                   -- * Utilities+                   , fileSize+                   ) 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 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+                      | Custom !Int++data Compression = Lzma+                 | Lzip+                 | BZip+                 | GZip+                 | Z+                 | Zstd+                 | Lz4+                 | 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"+                   | 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+    | 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 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 }++toInt :: Compression -> CompressionLevel -> Int+toInt Zstd Best       = Zstd.maxCLevel+toInt Zstd Fastest    = 1+toInt Zstd (Custom i) = levelGuard (1, Zstd.maxCLevel) i+toInt Lzip Best       = 9+toInt Lzip Fastest    = 0+toInt Lzip (Custom i) = levelGuard (0, 9) i+toInt Lzma Best       = 9+toInt Lzma Fastest    = 0+toInt Lzma (Custom i) = levelGuard (0, 9) i+toInt BZip Best       = 9+toInt BZip Fastest    = 1+toInt BZip (Custom i) = i+toInt GZip Best       = 9+toInt GZip Fastest    = 0+toInt GZip (Custom i) = i+toInt Z Best          = 9+toInt Z Fastest       = 0+toInt Z (Custom i)    = i+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  _ _           = Lz4.compress -- TODO: compression levels upstream+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
@@ -0,0 +1,121 @@+{-# LANGUAGE OverloadedStrings #-}++module Main ( main ) where++import           Compression+import qualified Data.ByteString.Lazy as BSL+import           Data.Semigroup       ((<>))+import           Options.Applicative+import           Version              (allVersionsString)++data Command = Decompress !FilePath !(Maybe FilePath)+    | Compress !FilePath !FilePath !CompressionLevel+    | Transcode !FilePath !FilePath !CompressionLevel+    | Verify !FilePath++decompressFile :: FilePath -- ^ Compressed file+               -> FilePath -- ^ Output+               -> IO ()+decompressFile inp o = BSL.writeFile o =<< f inp+    where f = toFileDecompressor (detectCompression inp)++compressFile :: CompressionLevel+             -> FilePath -- ^ Input file+             -> FilePath -- ^ Compressed output+             -> IO ()+compressFile lvl inp o = BSL.writeFile o =<< f inp+    where f = toFileCompressor (detectCompression o) lvl++run :: Command -> IO ()+run (Decompress i Nothing)      = decompressFile i (uncompressedExt i)+run (Decompress i (Just o))     = decompressFile i o+run (Compress i o lvl)          = compressFile lvl i o+run (Transcode i o lvl)         = do+    let cO = detectCompression o+    guessSz <- case cO of+        Lzip -> Just . (8*) . fromIntegral <$> fileSize i+        _    -> pure Nothing+    BSL.writeFile o . toCompressor cO lvl guessSz =<< toFileDecompressor (detectCompression i) i+run (Verify i)                  = check (detectCompression i) =<< BSL.readFile i++fileCompletions :: HasCompleter f => Mod f a+fileCompletions = completer (bashCompleter "file -o plusdirs")++inpFile :: Parser FilePath+inpFile = fileHelp "Input file"++verify :: Parser Command+verify = Verify <$> inpFile++compressionLevel :: Parser CompressionLevel+compressionLevel =+        compressCustom+    <|> compressBest+    <|> compressFast++compressCustom :: Parser CompressionLevel+compressCustom =+    Custom <$>+        option auto+        (long "compression-level"+        <> short 'l'+        <> value 6+        <> metavar "LVL"+        <> help "Compression level (usually 0-9)"+        <> completer (listCompleter (show <$> [(0::Int)..22]))+        )++compressBest :: Parser CompressionLevel+compressBest =+    flag' Best (long "best")++compressFast :: Parser CompressionLevel+compressFast =+    flag' Fastest (long "fastest")++transcode :: Parser Command+transcode = Transcode+    <$> inpFile+    <*> fileHelp "Output"+    <*> compressionLevel++decompress :: Parser Command+decompress = Decompress+    <$> fileHelp "File to decompress"+    <*> optional outFile++outFile :: Parser FilePath+outFile = fileHelp "Decompressed output"++compress :: Parser Command+compress = Compress+    <$> fileHelp "File to compress"+    <*> fileHelp "Compressed output"+    <*> compressionLevel++fileHelp :: String -> Parser FilePath+fileHelp helpTxt =+    argument str+    (metavar "FILE"+    <> fileCompletions+    <> help helpTxt)++cmd :: Parser Command+cmd = hsubparser+    (command "decompress" (info decompress (progDesc "Decompress a file"))+    <> command "compress" (info compress (progDesc "Compress a file"))+    <> command "transcode" (info transcode (progDesc "Convert a file's compression"))+    <> command "verify" (info verify (progDesc "Check the integrity of a compressed file"))+    )++versionMod :: Parser (a -> a)+versionMod = infoOption allVersionsString (short 'V' <> long "version" <> help "Show version")++topLevel :: ParserInfo Command+topLevel = info (helper <*> versionMod <*> cmd)+    (fullDesc+    <> progDesc "A Haskell compressor tool"+    <> header "sak - a Swiss-army knife for archiving and compressing")++main :: IO ()+main = run =<< execParser topLevel
+ src/Version.cpphs view
@@ -0,0 +1,21 @@+module Version ( allVersionsString ) where++import           Codec.Compression.BZip+import           Codec.Lz4+import           Codec.Lzip+import qualified Data.Version           as V+import qualified Paths_sak              as P++allVersionsString :: String+allVersionsString =+       "sak version: " ++ V.showVersion P.version ++ "\n"+    ++ "lzlib-hs: " ++ VERSION_lzlib ++ "\n"+    ++ "lzlib: " ++ lZVersion ++ "\n"+    ++ "lzlib API: " ++ show (lZApiVersion :: Int) ++ "\n"+    ++ "zlib-hs: " ++ VERSION_zlib ++ "\n"+    ++ "lzma-hs: " ++ VERSION_lzma ++ "\n"+    ++ "zstd-hs: " ++ VERSION_zstd ++ "\n"+    ++ "bz2-hs: " ++ VERSION_bz2 ++ "\n"+    ++ "bz2: " ++ bZ2BzlibVersion ++ "\n"+    ++ "lz4: " ++ lZ4VersionString ++ "\n"+    ++ "lz4-hs: " ++ VERSION_lz4_hs