hslua-module-zip-1.1.3/0000755000000000000000000000000007346545000013104 5ustar0000000000000000hslua-module-zip-1.1.3/CHANGELOG.md0000644000000000000000000000131007346545000014710 0ustar0000000000000000# Changelog `hslua-module-zips` uses [PVP Versioning][]. ## hslua-module-zip-1.1.3 Released 2024-05-05. - Fix build on Windows. There are no symlinks on Windows; functions dealing with symlinks are missing from zip-archive and need a placeholder function. ## hslua-module-zip-1.1.2 Released 2024-05-05. - Added a `symlink` method to Entry objects. This allows to check whether an entry represents a symbolic link, and where it links. ## hslua-module-zip-1.1.1 Released 2024-01-18. - Relaxed upper bound for text, and filepath, allowing text-2.1, filepath-1.5. ## hslua-module-zip-1.0.0 Released 2023-03-13. - Initially created. [PVP Versioning]: https://pvp.haskell.org hslua-module-zip-1.1.3/LICENSE0000644000000000000000000000206507346545000014114 0ustar0000000000000000MIT License Copyright © 2020-2024 Albert Krewinkel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hslua-module-zip-1.1.3/README.md0000644000000000000000000000700407346545000014364 0ustar0000000000000000# hslua-module-zip [![GitHub CI][CI badge]](https://github.com/hslua/hslua/actions) [![Hackage][Hackage badge]](https://hackage.haskell.org/package/hslua-module-zip) [![Stackage Lts][Stackage Lts badge]](http://stackage.org/lts/package/hslua-module-zip) [![Stackage Nightly][Stackage Nightly badge]](http://stackage.org/nightly/package/hslua-module-zip) [![MIT license][License badge]](LICENSE) [CI badge]: https://img.shields.io/github/workflow/status/hslua/hslua/CI.svg?logo=github [Hackage badge]: https://img.shields.io/hackage/v/hslua-module-zip.svg?logo=haskell [Stackage Lts badge]: http://stackage.org/package/hslua-module-zip/badge/lts [Stackage Nightly badge]: http://stackage.org/package/hslua-module-zip/badge/nightly [License badge]: https://img.shields.io/badge/license-MIT-blue.svg Lua module to work with file zips. ## zip Functions to create, modify, and extract files from zip archives. The module can be called as a function, in which case it behaves like the `zip` function described below. Zip options are optional; when defined, they must be a table with any of the following keys: - `recursive`: recurse directories when set to `true`; - `verbose`: print info messages to stdout; - `destination`: the value specifies the directory in which to extract; - `location`: value is used as path name, defining where files are placed. - `preserve_symlinks`: Boolean value, controlling whether symbolic links are preserved as such. This option is ignored on Windows. ## Functions ### Archive `Archive (bytestring_or_entries)` Reads an *Archive* structure from a raw zip archive or a list of Entry items; throws an error if the given string cannot be decoded into an archive. *Since: 1.0.0* Parameters: bytestring_or_entries : (string|{ZipEntry,...}) Returns: - (ZipArchive) ### Entry `Entry (path, contents[, modtime])` Generates a zip Entry from a filepath, the file's uncompressed content, and the file's modification time. *Since: 1.0.0* Parameters: path : file path in archive (string) contents : uncompressed contents (string) modtime : modification time (integer) ### read_entry `read_entry (filepath, opts)` Generates a ZipEntry from a file or directory. *Since: 1.0.0* Parameters: filepath : (string) opts : zip options (table) Returns: - a new zip archive entry (ZipEntry) ### zip `zip (filepaths[, options])` Package and compress the given files into a new Archive. *Since: 1.0.0* Parameters: filepaths : list of files from which the archive is created. ({string,...}) options : zip options (table) Returns: - a new archive (ZipArchive) ## Types ### Archive A zip archive with file entries. #### Fields `entries` : files in this zip archive ({Entry,...}) #### Methods `extract([opts])` : Extract all files from this archive, creating directories as needed. Note that the last-modified time is set correctly only in POSIX, not in Windows. This function fails if encrypted entries are present. Use `archive:extract{destination = 'dir'}` to extract to subdirectory `dir`. `bytestring()` : Returns the raw binary string representation of the archive. ### Entry File or directory entry in a zip archive. #### Fields: `path` : relative path, using `/` as separator `modtime` : modification time (seconds since unix epoch) #### Methods: `contents([password])` : Get the uncompressed contents of a zip entry. If `password` is given, then that password is used to decrypt the contents. An error is throws if decrypting fails. hslua-module-zip-1.1.3/hslua-module-zip.cabal0000644000000000000000000000602007346545000017265 0ustar0000000000000000cabal-version: 2.2 name: hslua-module-zip version: 1.1.3 synopsis: Lua module to work with file zips. description: Module with function for creating, modifying, and extracting files from zip archives. homepage: https://hslua.org/ bug-reports: https://github.com/hslua/hslua/issues license: MIT license-file: LICENSE author: Albert Krewinkel maintainer: Albert Krewinkel copyright: © 2020-2024 Albert Krewinkel category: Foreign build-type: Simple extra-doc-files: README.md CHANGELOG.md extra-source-files: test/test-zip.lua tested-with: GHC == 8.4.4 , GHC == 8.6.5 , GHC == 8.8.4 , GHC == 8.10.7 , GHC == 9.0.2 , GHC == 9.2.8 , GHC == 9.4.8 , GHC == 9.6.3 , GHC == 9.8.1 source-repository head type: git location: https://github.com/hslua/hslua subdir: hslua-module-zip common common-options build-depends: base >= 4.11 && < 5 , bytestring , filepath >= 1.4 && < 1.6 , hslua-core >= 2.3 && < 2.4 , hslua-list >= 1.1 && < 1.2 , hslua-marshalling >= 2.3 && < 2.4 , hslua-packaging >= 2.3 && < 2.4 , hslua-typing >= 0.1 && < 0.2 , text >= 1.2 && < 2.2 , time >= 1.5 && < 1.14 , zip-archive >= 0.4 && < 0.5 ghc-options: -Wall -Wcompat -Widentities -Wincomplete-uni-patterns -Wincomplete-record-updates if impl(ghc >= 8.0) ghc-options: -Wredundant-constraints if impl(ghc >= 8.2) ghc-options: -fhide-source-paths if impl(ghc >= 8.4) ghc-options: -Wmissing-export-lists -Wpartial-fields if impl(ghc >= 8.8) ghc-options: -Wmissing-deriving-strategies default-language: Haskell2010 library import: common-options hs-source-dirs: src exposed-modules: HsLua.Module.Zip if os(windows) cpp-options: -D_WINDOWS test-suite hslua-module-zip-test import: common-options type: exitcode-stdio-1.0 hs-source-dirs: test main-is: test-hslua-module-zip.hs build-depends: hslua-module-zip , hslua-module-system , tasty , tasty-hunit , tasty-lua >= 1.0 && < 1.2 , text ghc-options: -threaded -rtsopts -with-rtsopts=-N hslua-module-zip-1.1.3/src/HsLua/Module/0000755000000000000000000000000007346545000016134 5ustar0000000000000000hslua-module-zip-1.1.3/src/HsLua/Module/Zip.hs0000644000000000000000000002636207346545000017243 0ustar0000000000000000{-# LANGUAGE CPP #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-| Module : HsLua.Module.Zip Copyright : © 2022-2024 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel Lua module to work with file zips. -} module HsLua.Module.Zip ( -- * Module documentedModule -- * Zip archives , typeArchive , mkArchive , read_entry , zip -- ** archive methods , extract , bytestring -- * Zip entry , typeEntry , peekEntryFuzzy -- ** entry methods , contents , symlink -- * Zip Options , peekZipOptions ) where import Prelude hiding (zip) import Control.Applicative (optional) import Control.Monad ((<$!>)) import Codec.Archive.Zip ( Archive, Entry, ZipOption (..), emptyArchive #ifndef _WINDOWS , symbolicLinkEntryTarget #endif ) import Data.Functor ((<&>)) import Data.Maybe (catMaybes, fromMaybe) import Data.Time.Clock.POSIX (getPOSIXTime) import Data.Version (Version, makeVersion) import HsLua.Core ( LuaError, NumArgs (..), NumResults (..), Type(..), call, failLua , fromStackIndex, getfield, gettop, replace, liftIO, ltype , nth, nthBottom, pushnil, setmetatable ) import HsLua.List (newListMetatable) import HsLua.Marshalling ( Peeker, Pusher, choice, failPeek, liftLua, peekBool , peekFieldRaw, peekIntegral, peekLazyByteString, peekList, peekString , pushLazyByteString, pushList, pushIntegral, pushString , retrieving, typeMismatchMessage ) import HsLua.Packaging import HsLua.Typing import qualified Codec.Archive.Zip as Zip import qualified Data.Text as T #ifdef _WINDOWS -- | Windows replacement; always returns Nothing. symbolicLinkEntryTarget :: Entry -> Maybe FilePath symbolicLinkEntryTarget = const Nothing #endif -- | The @zip@ module specification. documentedModule :: forall e. LuaError e => Module e documentedModule = Module { moduleName = "zip" , moduleDescription = T.unlines [ "Functions to create, modify, and extract files from zip archives." , "" , "The module can be called as a function, in which case it behaves" , "like the `zip` function described below." , "" , "Zip options are optional; when defined, they must be a table with" , "any of the following keys:" , "" , " - `recursive`: recurse directories when set to `true`;" , " - `verbose`: print info messages to stdout;" , " - `destination`: the value specifies the directory in which to" , " extract;" , " - `location`: value is used as path name, defining where files" , " are placed." , " - `preserve_symlinks`: Boolean value, controlling whether" , " symbolic links are preserved as such. This option is ignored" , " on Windows." ] , moduleFields = fields , moduleFunctions = functions , moduleOperations = [ operation Call $ lambda ### (do -- call function `zip` _ <- getfield (nthBottom 1) (functionName @e zip) replace (nthBottom 1) nargs <- NumArgs . subtract 1 . fromStackIndex <$> gettop call nargs 1 pure (NumResults 1)) =?> "new Archive" ] , moduleTypeInitializers = [ initType typeArchive , initType typeEntry ] } -- | First published version of this library. initialVersion :: Version initialVersion = makeVersion [1,0,0] -- -- Fields -- -- | Exported fields. fields :: [Field e] fields = [] -- -- Functions -- -- | Exported functions functions :: LuaError e => [DocumentedFunction e] functions = [ mkArchive , mkEntry , read_entry , zip ] -- | Creates a new 'Archive' from a list of files. zip :: LuaError e => DocumentedFunction e zip = defun "zip" ### (\filepaths mopts -> let opts = fromMaybe [] mopts in liftIO $! Zip.addFilesToArchive opts emptyArchive filepaths) <#> parameter (peekList peekString) "{string,...}" "filepaths" "list of files from which the archive is created." <#> opt (parameter peekZipOptions "table" "opts" "zip options") =#> udresult typeArchive "a new archive" #? T.unlines [ "Package and compress the given files into a new Archive." ] `since` initialVersion -- | Creates a new 'ZipEntry' from a file; wraps 'Zip.readEntry'. read_entry :: LuaError e => DocumentedFunction e read_entry = defun "read_entry" ### (\filepath mopts -> liftIO $! Zip.readEntry (fromMaybe [] mopts) filepath) <#> parameter peekString "string" "filepath" "" <#> opt (parameter peekZipOptions "table" "opts" "zip options") =#> udresult typeEntry "a new zip archive entry" #? T.unlines [ "Generates a ZipEntry from a file or directory." ] `since` initialVersion -- -- * Options -- peekZipOptions :: LuaError e => Peeker e [ZipOption] peekZipOptions = retrieving "Zip options" . \idx -> catMaybes <$> sequence [ optional (peekFieldRaw peekBool "recursive" idx) <&> \case Just True -> Just OptRecursive _ -> Nothing , optional (peekFieldRaw peekBool "verbose" idx) <&> \case Just True -> Just OptVerbose _ -> Nothing , optional (peekFieldRaw peekString "destination" idx) <&> \case Just fp -> Just (OptDestination fp) _ -> Nothing , optional (peekFieldRaw peekString "location" idx) <&> \case Just fp -> Just (OptLocation fp True) _ -> Nothing , optional (peekFieldRaw peekBool "preserve_symlinks" idx) <&> \case Just True -> (Just OptPreserveSymbolicLinks) _ -> Nothing ] -- -- * Archive -- -- | The Lua 'Archive' type typeArchive :: forall e. LuaError e => DocumentedType e Archive typeArchive = deftype "zip.Archive" [] [ property' "entries" (seqType (udTypeSpec @e typeEntry)) "Files in this zip archive" (pushEntries, Zip.zEntries) (peekList peekEntryFuzzy, \ar entries -> ar { Zip.zEntries = entries }) , method extract , method bytestring ] -- | Wrapper for 'Zip.toArchive'; converts a string into an Archive. mkArchive :: forall e. LuaError e => DocumentedFunction e mkArchive = defun "Archive" ### (\case Nothing -> pure Zip.emptyArchive Just (Left bytestring') -> either failLua pure $ Zip.toArchiveOrFail bytestring' Just (Right entries) -> pure $ foldr Zip.addEntryToArchive emptyArchive entries) <#> opt (parameter (choice [ fmap Left . peekLazyByteString , fmap Right . peekList peekEntryFuzzy ]) (stringType #|# seqType (udTypeSpec @e typeEntry)) "bytestring_or_entries" "binary archive data or list of entries; defaults to an empty list") =#> udresult typeArchive "new Archive" #? T.unlines [ "Reads an *Archive* structure from a raw zip archive or a list of" , "Entry items; throws an error if the given string cannot be decoded" , "into an archive." ] `since` initialVersion -- | Returns the raw binary string representation of the archive; -- wraps 'Zip.extractFilesFromArchive' extract :: LuaError e => DocumentedFunction e extract = defun "extract" ### (\archive mopts -> liftIO $! Zip.extractFilesFromArchive (fromMaybe [] mopts) archive) <#> udparam typeArchive "self" "" <#> opt (parameter peekZipOptions "table" "opts" "zip options") =#> [] #? T.unlines [ "Extract all files from this archive, creating directories as needed." , "Note that the last-modified time is set correctly only in POSIX, not" , "in Windows. This function fails if encrypted entries are present." ] -- | Returns the raw binary string representation of the archive. bytestring :: LuaError e => DocumentedFunction e bytestring = defun "bytestring" ### liftPure Zip.fromArchive <#> udparam typeArchive "self" "" =#> functionResult pushLazyByteString "string" "bytes of the archive" #? "Returns the raw binary string representation of the archive." -- -- * Entry -- -- | The Lua type for 'Entry' objects. typeEntry :: forall e. LuaError e => DocumentedType e Entry typeEntry = deftype "zip.Entry" [] [ property' "path" (udTypeSpec @e typeEntry) "Relative path, using `/` as separator" (pushString, Zip.eRelativePath) (peekString, \entry path -> entry { Zip.eRelativePath = path }) , property' "modtime" integerType "Modification time (seconds since unix epoch)" (pushIntegral, Zip.eLastModified) (peekIntegral, \entry modtime -> entry { Zip.eLastModified = modtime}) , method contents , method symlink ] -- | Creates a new 'ZipEntry' from a file; wraps 'Zip.readEntry'. mkEntry :: LuaError e => DocumentedFunction e mkEntry = defun "Entry" ### (\filepath contents' mmodtime -> do modtime <- maybe (floor <$> liftIO getPOSIXTime) pure mmodtime pure $ Zip.toEntry filepath modtime contents') <#> parameter peekString "string" "path" "file path in archive" <#> parameter peekLazyByteString "string" "contents" "uncompressed contents" <#> opt (parameter peekIntegral "integer" "modtime" "modification time") =#> udresult typeEntry "a new zip archive entry" #? T.unlines [ "Generates a ZipEntry from a filepath, uncompressed content, and" , "the file's modification time." ] `since` initialVersion -- | Returns the uncompressed contents of a zip entry. contents :: LuaError e => DocumentedFunction e contents = defun "contents" ### (\entry mpasswd -> case mpasswd of Nothing -> return $! Zip.fromEntry entry Just passwd -> case Zip.fromEncryptedEntry passwd entry of Just contents' -> return $! contents' Nothing -> failLua "Could not decrypt entry.") <#> udparam typeEntry "self" "" <#> opt (parameter peekString "string" "password" "password for entry") =#> functionResult pushLazyByteString "string" "binary contents" #? T.unlines [ "Get the uncompressed contents of a zip entry. If `password` is given," , "then that password is used to decrypt the contents. An error is throws" , "if decrypting fails." ] -- | Returns the target if the Entry represents a symbolic link. symlink :: LuaError e => DocumentedFunction e symlink = defun "symlink" ### liftPure symbolicLinkEntryTarget <#> udparam typeEntry "self" "" =#> functionResult (maybe pushnil pushString) "string|nil" "link target if entry represents a symbolic link" #? T.unlines [ "Returns the target if the Entry represents a symbolic link," , "and `nil` otherwise. Always returns `nil` on Windows. " ] peekEntryFuzzy :: LuaError e => Peeker e Entry peekEntryFuzzy = retrieving "ZipEntry" . \idx -> liftLua (ltype idx) >>= \case TypeUserdata -> peekUD typeEntry idx TypeTable -> peekEntryFromTable idx _ -> failPeek =<< typeMismatchMessage "ZipEntry userdata or table" idx peekEntryFromTable :: LuaError e => Peeker e Entry peekEntryFromTable idx = Zip.toEntry <$!> peekFieldRaw peekString "path" idx <*> (peekFieldRaw (optional . peekIntegral) "modtime" idx >>= \case Nothing -> pure 0 Just t -> pure t) <*> peekFieldRaw peekLazyByteString "contents" idx -- | Pushes a list of entries as an Entries object, i.e., a list with -- additional methods. pushEntries :: LuaError e => Pusher e [Entry] pushEntries es = do pushList (pushUD typeEntry) es newListMetatable "ZipEntry list" (pure ()) setmetatable (nth 2) hslua-module-zip-1.1.3/test/0000755000000000000000000000000007346545000014063 5ustar0000000000000000hslua-module-zip-1.1.3/test/test-hslua-module-zip.hs0000644000000000000000000000402107346545000020570 0ustar0000000000000000{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} {-| Module : Main Copyright : © 2021-2024 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel Stability : stable Portability : Requires language extensions ForeignFunctionInterface, OverloadedStrings. Tests for the `zip` Lua module. -} module Main (main) where import Control.Monad (void) import HsLua.Core (Lua, top) import HsLua.Packaging ( preloadModule, preloadModuleWithName, pushModule , registerModule) import HsLua.Module.Zip (documentedModule) import Test.Tasty (TestTree, defaultMain, testGroup) import Test.Tasty.HUnit (assertEqual, testCase) import Test.Tasty.Lua (translateResultsFromFile) import qualified HsLua.Core as Lua import qualified HsLua.Module.System as System main :: IO () main = do luaTestResults <- Lua.run @Lua.Exception $ do Lua.openlibs registerModule documentedModule registerModule System.documentedModule Lua.pop 1 translateResultsFromFile "test/test-zip.lua" defaultMain $ testGroup "hslua-module-zip" [tests, luaTestResults] -- | HSpec tests for the Lua 'system' module tests :: TestTree tests = testGroup "HsLua zip module" [ testCase "zip module can be pushed to the stack" $ Lua.run (void (pushModule documentedModule) :: Lua ()) , testCase "zip module can be added to the preloader" . Lua.run $ do Lua.openlibs preloadModule documentedModule assertEqual' "function not added to preloader" Lua.TypeFunction =<< do void $ Lua.getglobal "package" *> Lua.getfield top "preload" *> Lua.getfield top "zip" Lua.ltype (-1) , testCase "zip module can be loaded as hszip" . Lua.run $ do Lua.openlibs preloadModuleWithName documentedModule "hszip" assertEqual' "loading the module fails " Lua.OK =<< Lua.dostring "require 'hszip'" ] assertEqual' :: (Show a, Eq a) => String -> a -> a -> Lua () assertEqual' msg expected = Lua.liftIO . assertEqual msg expected hslua-module-zip-1.1.3/test/test-zip.lua0000644000000000000000000001353507346545000016354 0ustar0000000000000000-- -- Tests for the system module -- local zip = require 'zip' local tasty = require 'tasty' local system = require 'system' local group = tasty.test_group local test = tasty.test_case local assert = tasty.assert local make_sample_archive = function (opts) opts = opts or {} local filename = opts.filename or 'greetings.txt' local contents = opts.contents or 'Hello Bob!\n' return system.with_tmpdir('archive', function (tmpdir) return system.with_wd(tmpdir, function () local fh = io.open(filename, 'w') fh:write(contents) fh:close() return zip{filename} end) end) end local empty_archive = '\80\75\5\6\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0' -- Check existence static fields return { group 'Archive' { test('empty archive', function () assert.are_equal(type(zip.Archive()), 'userdata') end), }, group 'zip' { test('archive with file', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local fh = io.open(filename, 'w') fh:write('Hi Mom!\n') fh:close() assert.are_equal( type(zip.zip{filename}), 'userdata' ) end) end) end), test('recursive', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local dirname = 'greetings' local filename = dirname .. '/' .. 'french.txt' system.mkdir(dirname) local fh = io.open(filename, 'w') fh:write('Bonjour!\n') fh:close() local archive = zip.zip({dirname}, {recursive=true}) assert.are_equal( archive.entries[2].path, filename ) end) end) end), test('module metamethod', function() system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local fh = io.open(filename, 'w') fh:write('Hi Mom!\n') fh:close() assert.are_equal( zip.zip{filename}:bytestring(), zip{filename}:bytestring() ) end) end) end) }, group 'extract' { test('archive with file', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local archive = make_sample_archive{ filename = filename, contents = 'Hi Mom!\n', } archive:extract() assert.are_equal(system.ls()[1], filename) assert.are_equal( io.open(filename):read 'a', 'Hi Mom!\n' ) end) end) end), test('to destination directory', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local archive = make_sample_archive{ filename = filename, contents = 'Hi Mom!\n', } archive:extract{destination = 'foo'} assert.are_equal(system.ls()[1], 'foo') assert.are_equal( io.open('foo/' .. filename):read 'a', 'Hi Mom!\n' ) end) end) end), }, group 'entries' { test('empty archive', function () assert.are_equal(#zip.Archive().entries, 0) end), test('archive with file', function () local archive = make_sample_archive{filename='greetings.txt'} assert.are_equal( archive.entries[1].path, 'greetings.txt' ) end), test('has type "ZipEntry list"', function () local archive = make_sample_archive() assert.are_equal(getmetatable(archive.entries).__name, 'ZipEntry list') end), test('has `insert` method', function () local archive = make_sample_archive() assert.are_equal(type(archive.entries.insert), 'function') end), }, group 'read_entry' { test('has correct file path', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local fh = io.open(filename, 'wb') fh:write('Hi Mom!\n') fh:close() local entry = zip.read_entry(filename) assert.are_equal(entry.path, filename) end) end) end), test('has contents', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local fh = io.open(filename, 'wb') fh:write('Hallo!\n') fh:close() local entry = zip.read_entry(filename) assert.are_equal( entry:contents('foo'), 'Hallo!\n' ) end) end) end), -- Can't reliably test this for actual symlinks, as Windows doesn't -- support them. Only the behavior for normal files is tested. test('has symlink function', function () system.with_tmpdir('archive', function (tmpdir) system.with_wd(tmpdir, function () local filename = 'greetings.txt' local fh = io.open(filename, 'w') fh:write('Hallo!\n') fh:close() local entry = zip.read_entry(filename) assert.is_nil(entry:symlink()) end) end) end) }, group 'bytestring' { test('empty archive', function () assert.are_equal(zip.Archive():bytestring(), empty_archive) end), }, group 'Archive constructor' { test('empty archive', function () assert.are_equal(type(zip.Archive(empty_archive)), 'userdata') end), test('misformed archive', function () assert.error_matches( function () zip.Archive(empty_archive:sub(2)) end, '' ) end), }, }