zip2tar (empty) → 0.0
raw patch · 4 files changed
+269/−0 lines, 4 filesdep +basedep +bytestringdep +conduitsetup-changed
Dependencies added: base, bytestring, conduit, containers, optparse-applicative, tar-conduit, time, zip
Files
- LICENSE +30/−0
- Setup.lhs +3/−0
- src/Main.hs +166/−0
- zip2tar.cabal +70/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2026, Henning Thielemann++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * 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.++ * Neither the name of Henning Thielemann nor the names of other+ 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+OWNER 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.
+ Setup.lhs view
@@ -0,0 +1,3 @@+#! /usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ src/Main.hs view
@@ -0,0 +1,166 @@+module Main where++import qualified Options.Applicative as OP+import System.Posix.Types (FileMode)++import qualified Codec.Archive.Zip.Unix as ZipUnix+import qualified Codec.Archive.Zip as Zip+import qualified Data.Conduit.Tar as CTar+import qualified Conduit as C+import Data.Conduit (runConduitRes, (.|))++import qualified Data.Time.Clock.POSIX as PosixTime+import qualified Data.ByteString.Char8 as BC+import qualified Data.Map as Map+import qualified Data.List as List+import qualified Data.Char as Char+import Data.Traversable (for)+import Data.Ord (comparing)+import Data.Bits ((.&.), (.|.))+import Data.Word (Word32)++import Control.Monad (join, void)++++info :: OP.Parser a -> OP.ParserInfo a+info p =+ OP.info+ (OP.helper <*> p)+ (OP.fullDesc <>+ OP.progDesc "Convert ZIP to TAR archive")++data Sort = SortPosition | SortLexicographic+ deriving (Eq, Ord)++parser :: OP.Parser (IO ())+parser =+ pure convertZipToTar+ <*> parseInfoConversion+ <*>+ OP.flag SortPosition SortLexicographic+ (OP.long "sort-by-name" <>+ OP.help+ "sort files by names (default: storage order)")+ <*>+ OP.strArgument+ (OP.metavar "INPUT" <>+ OP.help "Input ZIP archive")++parseInfoConversion ::+ OP.Parser (Zip.EntrySelector -> Zip.EntryDescription -> CTar.FileInfo)+parseInfoConversion =+ pure fileInfoFromZipEntry+ <*>+ OP.option (OP.eitherReader packName)+ (OP.long "user-name" <>+ OP.metavar "NAME" <>+ OP.value BC.empty <>+ OP.help "UNIX user name for files in TAR output")+ <*>+ OP.option OP.auto+ (OP.long "user-id" <>+ OP.metavar "UID" <>+ OP.value 0 <>+ OP.help "UNIX user id for files in TAR output")+ <*>+ OP.option (OP.eitherReader packName)+ (OP.long "group-name" <>+ OP.metavar "NAME" <>+ OP.value BC.empty <>+ OP.help "UNIX group name for files in TAR output")+ <*>+ OP.option OP.auto+ (OP.long "group-id" <>+ OP.metavar "GID" <>+ OP.value 0 <>+ OP.help "UNIX group id for files in TAR output")+ <*>+ OP.flag AttributeWindows AttributeUNIX+ (OP.long "use-unix-permissions" <>+ OP.help+ ("respect UNIX file permissions of zipped files " +++ "(default: read Windows attributes)"))++packName :: String -> Either String BC.ByteString+packName name =+ if all Char.isAscii name+ then Right $ BC.pack name+ else Left "name contains non-ASCII characters"+++main :: IO ()+main = join $ OP.execParser $ info parser+++convertZipToTar ::+ (Zip.EntrySelector -> Zip.EntryDescription -> CTar.FileInfo) ->+ Sort -> FilePath -> IO ()+convertZipToTar fileInfoFromZipEntry_ sortMode zipPath =+ Zip.withArchive zipPath $ do+ let sort =+ case sortMode of+ SortPosition -> List.sortBy (comparing (Zip.edOffset . snd))+ SortLexicographic -> id+ entries <- sort . Map.toList <$> Zip.getEntries+ files <-+ for entries $ \(selector, descr) ->+ flip fmap (Zip.getEntrySource selector) $ \src ->+ src+ .|+ (C.yield (Left $ fileInfoFromZipEntry_ selector descr)+ >>+ C.mapC Right)+ C.liftIO $ runConduitRes $+ sequence_ files+ .|+ void CTar.tar+ .|+ C.stdoutC+++data AttributeSource = AttributeWindows | AttributeUNIX+ deriving (Eq, Ord)+++windowsReadonlyAttr :: Word32+windowsReadonlyAttr = 1++unixUserReadable :: FileMode+unixUserReadable = 0o400++unixUserWriteable :: FileMode+unixUserWriteable = 0o200++unixPermFromWindowsAttr :: Word32 -> FileMode+unixPermFromWindowsAttr attrs =+ unixUserReadable+ .|.+ if attrs .&. windowsReadonlyAttr == 0+ then unixUserWriteable+ else 0++fileInfoFromZipEntry ::+ BC.ByteString -> CTar.UserID ->+ BC.ByteString -> CTar.GroupID ->+ AttributeSource ->+ Zip.EntrySelector -> Zip.EntryDescription -> CTar.FileInfo+fileInfoFromZipEntry userName userId groupName groupId attrSrc sel descr =+ CTar.FileInfo {+ CTar.filePath = BC.pack $ Zip.unEntrySelector sel,+ CTar.fileUserId = userId,+ CTar.fileUserName = userName,+ CTar.fileGroupId = groupId,+ CTar.fileGroupName = groupName,+ CTar.fileMode =+ (case attrSrc of+ AttributeWindows -> unixPermFromWindowsAttr+ AttributeUNIX -> ZipUnix.toFileMode) $+ Zip.edExternalFileAttrs descr,+ CTar.fileSize = fromIntegral $ Zip.edUncompressedSize descr,+ CTar.fileType = CTar.FTNormal,+ CTar.fileModTime =+ fromInteger $ round $+ (realToFrac $ PosixTime.utcTimeToPOSIXSeconds $ Zip.edModTime descr+ :: Rational)+ }
+ zip2tar.cabal view
@@ -0,0 +1,70 @@+Name: zip2tar+Version: 0.0+Synopsis: Convert ZIP to TAR archives+Description:+ Convert ZIP to TAR archives with minimal memory footprint.+ .+ Usage:+ .+ > zip2tar input.zip >output.tar+ .+ > zip2tar input.zip | lzip >output.tar.lz+ .+ ZIP archives cannot be read in a purely sequential way,+ because the central file catalogue is stored at the end of the archive file.+ Thus we do not provide reading ZIP archives from Stdin.+ .+ Per default @zip2tar@ stores files in the TAR archive+ in the same order as the ZIP archive in order to minimize seeking.+ However this means unsorted filenames.+ You can order with respect to filenames with the @--sort-by-name@ option.+ .+ Per default @zip2tar@ sets user and group+ ids to zero and names to empty strings,+ because we do not know what is available on the system+ where the TAR archive gets unpacked.+ You may set those values individually like in:+ .+ > zip2tar --user-name janedoe --user-id 42 --group-name users --group-id 23 input.zip+ .+ @zip2tar@ does not try to derive id from name or vice versa.+ If you want certain values you must set them explicitly.+ You may want to set the values to the settings of the current system like so:+ .+ > zip2tar --user-name $USER --user-id $UID --group-name $(id -gn) --group-id $(id -g) input.zip+Homepage: https://hub.darcs.net/thielema/zip2tar+License: BSD3+License-File: LICENSE+Author: Henning Thielemann+Maintainer: haskell@henning-thielemann.de+Category: Archive, Codec+Build-Type: Simple+Cabal-Version: >=1.10++Source-Repository this+ Tag: 0.0+ Type: darcs+ Location: https://hub.darcs.net/thielema/zip2tar++Source-Repository head+ Type: darcs+ Location: https://hub.darcs.net/thielema/zip2tar++-- Alternative: https://github.com/dylex/zip-stream 2017+-- Alternative: https://github.com/timcherganov/zip-conduit 2022, has no way to "conduit" the whole archive++Executable zip2tar+ Build-Depends:+ zip >=2.2 && <2.3,+ tar-conduit >=0.4.1 && <0.5,+ conduit >=1.3 && <1.4,+ optparse-applicative >=0.11 && <0.20,+ time >=1.0 && <1.16,+ bytestring >=0.10 && <0.13,+ containers >=0.6 && <0.9,+ base >=4.5 && <5+ Hs-Source-Dirs: src+ Main-Is: Main.hs+ Default-Language: Haskell2010+ GHC-Options:+ -Wall -fwarn-incomplete-uni-patterns -fwarn-tabs