{"id":15009834,"url":"https://github.com/haukex/unzipwalk","last_synced_at":"2026-01-04T02:05:30.619Z","repository":{"id":241162373,"uuid":"804523008","full_name":"haukex/unzipwalk","owner":"haukex","description":"Recursively walk into directories and archives","archived":false,"fork":false,"pushed_at":"2024-05-22T19:16:42.000Z","size":0,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-05-22T19:32:27.346Z","etag":null,"topics":["python3","tar","tgz","zip"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/unzipwalk/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"lgpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haukex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-05-22T18:43:11.000Z","updated_at":"2024-05-22T19:32:30.821Z","dependencies_parsed_at":"2024-05-22T19:32:30.224Z","dependency_job_id":"3e26ed4b-271a-46be-80c5-78afee719ed4","html_url":"https://github.com/haukex/unzipwalk","commit_stats":null,"previous_names":["haukex/unzipwalk"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haukex%2Funzipwalk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haukex%2Funzipwalk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haukex%2Funzipwalk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haukex%2Funzipwalk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haukex","download_url":"https://codeload.github.com/haukex/unzipwalk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244056431,"owners_count":20390720,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["python3","tar","tgz","zip"],"created_at":"2024-09-24T19:28:49.819Z","updated_at":"2026-01-04T02:05:30.613Z","avatar_url":"https://github.com/haukex.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ca id=\"module-unzipwalk\"\u003e\u003c/a\u003e\n\n# Recursively Walk Into Directories and Archives\n\nThis module primarily provides the function [`unzipwalk()`](#function-unzipwalk), which recursively walks into\ndirectories and compressed files and returns all files, directories, etc. found, together with\nbinary file handles (file objects) for reading the files. Currently supported are `zip`, `gz`,\n`bz2`, `xz`, and the various `tar` compressed formats `tar.gz`/`tgz`, `tar.xz`/`txz`,\nand `tar.bz2`/`tbz`/`tbz2`, plus `7z` files if the Python package [`py7zr`](https://py7zr.readthedocs.io/en/stable/api.html#module-py7zr) is installed\n(to get the latter, you can install this package with `pip install unzipwalk[7z]`). File types\nare detected based on the aforementioned extensions.\n\n```pycon\n\u003e\u003e\u003e from unzipwalk import unzipwalk\n\u003e\u003e\u003e results = []\n\u003e\u003e\u003e for result in unzipwalk('.'):\n...     names = tuple( name.as_posix() for name in result.names )\n...     if result.hnd:  # result is a file opened for reading (binary)\n...         # could use result.hnd.read() here, or for line-by-line:\n...         for line in result.hnd:\n...             pass  # do something interesting with the data here\n...     results.append(names + (result.typ.name,))\n\u003e\u003e\u003e print(sorted(results))\n[('bar.zip', 'ARCHIVE'),\n ('bar.zip', 'bar.txt', 'FILE'),\n ('bar.zip', 'test.tar.gz', 'ARCHIVE'),\n ('bar.zip', 'test.tar.gz', 'hello.csv', 'FILE'),\n ('bar.zip', 'test.tar.gz', 'test', 'DIR'),\n ('bar.zip', 'test.tar.gz', 'test/cool.txt.gz', 'ARCHIVE'),\n ('bar.zip', 'test.tar.gz', 'test/cool.txt.gz', 'test/cool.txt', 'FILE'),\n ('foo.txt', 'FILE')]\n```\n\n**Note** that [`unzipwalk()`](#function-unzipwalk) automatically closes files as it goes from file to file.\nThis means that you must use the handles as soon as you get them from the generator -\nsomething as seemingly simple as `sorted(unzipwalk('.'))` would cause the code above to fail,\nbecause all files will have been opened and closed during the call to [`sorted()`](https://docs.python.org/3/library/functions.html#sorted)\nand the handles to read the data would no longer be available in the body of the loop.\nThis is why the above example first processes all the files before sorting the results.\nYou can also use [`recursive_open()`](#unzipwalk.recursive_open) to open the files later, though using that function\nis less efficient that [`unzipwalk()`](#function-unzipwalk) if you are opening multiple files inside of Zip\nor tar archives.\n\nThe yielded file handles can be wrapped in [`io.TextIOWrapper`](https://docs.python.org/3/library/io.html#io.TextIOWrapper) to read them as text files.\nFor example, to read all CSV files in the current directory and below, including within compressed files:\n\n```pycon\n\u003e\u003e\u003e from unzipwalk import unzipwalk, FileType\n\u003e\u003e\u003e from io import TextIOWrapper\n\u003e\u003e\u003e import csv\n\u003e\u003e\u003e for result in unzipwalk('.'):\n...     if result.typ==FileType.FILE and result.names[-1].suffix.lower()=='.csv':\n...         print([ name.as_posix() for name in result.names ])\n...         with TextIOWrapper(result.hnd, encoding='UTF-8', newline='') as handle:\n...             csv_rd = csv.reader(handle, strict=True)\n...             for row in csv_rd:\n...                 print(repr(row))\n['bar.zip', 'test.tar.gz', 'hello.csv']\n['Id', 'Name', 'Address']\n['42', 'Hello', 'World']\n```\n\n#### NOTE\nThe original names of files compressed with gzip, bzip2, and lzma are derived by\nsimply removing the respective `.gz`, `.bz2`, or `.xz` extensions.\n\nUsing the original filename from the gzip file’s header is currently not possible due to\n[limitations in the underlying library](https://github.com/python/cpython/issues/71638).\n\n#### SEE ALSO\n- [zipfile Issues](https://github.com/orgs/python/projects/7)\n- [tarfile Issues](https://github.com/orgs/python/projects/11)\n- [Compression issues](https://github.com/orgs/python/projects/20) (gzip, bzip2, lzma)\n- [py7zr Issues](https://github.com/miurahr/py7zr/issues)\n\n## API\n\n\u003ca id=\"function-unzipwalk\"\u003e\u003c/a\u003e\n\n### unzipwalk.unzipwalk(paths: [str](https://docs.python.org/3/library/stdtypes.html#str) | [PathLike](https://docs.python.org/3/library/os.html#os.PathLike) | [bytes](https://docs.python.org/3/library/stdtypes.html#bytes) | [Iterable](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable)[[str](https://docs.python.org/3/library/stdtypes.html#str) | [PathLike](https://docs.python.org/3/library/os.html#os.PathLike) | [bytes](https://docs.python.org/3/library/stdtypes.html#bytes)], \\*, matcher: [Callable](https://docs.python.org/3/library/collections.abc.html#collections.abc.Callable)[[[Sequence](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence)[[PurePath](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath)]], [bool](https://docs.python.org/3/library/functions.html#bool)] | [None](https://docs.python.org/3/library/constants.html#None) = None, raise_errors: [bool](https://docs.python.org/3/library/functions.html#bool) = True) → [Generator](https://docs.python.org/3/library/collections.abc.html#collections.abc.Generator)[[UnzipWalkResult](#unzipwalk.UnzipWalkResult), [None](https://docs.python.org/3/library/constants.html#None), [None](https://docs.python.org/3/library/constants.html#None)]\n\nThis generator recursively walks into directories and compressed files and yields named tuples of type [`UnzipWalkResult`](#unzipwalk.UnzipWalkResult).\n\n* **Parameters:**\n  * **paths** – A filename or iterable of filenames.\n  * **matcher** – \n\n    When you provide this optional argument, it must be a callable that accepts a sequence of paths\n    as its only argument, and returns a boolean value whether this filename should be further processed or not.\n    If a file is skipped, a [`UnzipWalkResult`](#unzipwalk.UnzipWalkResult) of type [`FileType.SKIP`](#unzipwalk.FileType) is yielded.\n\n    *Be aware* that within Zip and tar archives, all files are basically a flat list, so if your matcher\n    excludes a directory inside an archive, it must also exclude all files within that directory as well.\n    This behavior is different for physical directories in the file system: if you exclude a directory there,\n    it will not be descended into, so you won’t have to exclude the files inside (though it’s good practice\n    to write your matcher to exclude them anyway - see for example [`is_relative_to()`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_relative_to)).\n  * **raise_errors** – When this is turned on (the default), any errors are raised immediately,\n    aborting the iteration. If this is turned off, when decompression errors occur, a\n    [`UnzipWalkResult`](#unzipwalk.UnzipWalkResult) of type [`FileType.ERROR`](#unzipwalk.FileType) is yielded for those files instead.\n\n#### NOTE\nIf [`py7zr`](https://py7zr.readthedocs.io/en/stable/api.html#module-py7zr) is not installed, those archives will not be descended into.\n\n#### NOTE\nDo not rely on the order of results! But see also the discussion in the main documentation about why\ne.g. `sorted(unzipwalk(...))` automatically closes files and so may not be what you want.\n\n* **Raises:**\n  [**Exception**](https://docs.python.org/3/library/exceptions.html#Exception) – Because of the various underlying libraries, both this function and [`recursive_open()`](#unzipwalk.recursive_open) can raise\n  a variety of exceptions: [`zipfile.BadZipFile`](https://docs.python.org/3/library/zipfile.html#zipfile.BadZipFile), [`tarfile.TarError`](https://docs.python.org/3/library/tarfile.html#tarfile.TarError), `py7zr.exceptions.ArchiveError`\n  and its subclasses like [`py7zr.Bad7zFile`](https://py7zr.readthedocs.io/en/stable/api.html#py7zr.Bad7zFile), [`gzip.BadGzipFile`](https://docs.python.org/3/library/gzip.html#gzip.BadGzipFile), [`zlib.error`](https://docs.python.org/3/library/zlib.html#zlib.error), [`lzma.LZMAError`](https://docs.python.org/3/library/lzma.html#lzma.LZMAError),\n  [`EOFError`](https://docs.python.org/3/library/exceptions.html#EOFError), various [`OSError`](https://docs.python.org/3/library/exceptions.html#OSError)s, and other exceptions may be possible. Therefore, you may need to catch\n  all [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception)s to play it safe.\n\n#### IMPORTANT\nErrors from [`gzip`](https://docs.python.org/3/library/gzip.html#module-gzip), [`bz2`](https://docs.python.org/3/library/bz2.html#module-bz2), and [`lzma`](https://docs.python.org/3/library/lzma.html#module-lzma) (`.gz`, `.bz2`, and `.xz` files,\nrespectively) may not be raised until the file is actually read, so you’ll probably also want to add an\nexception handler around your `read()` call!\n\n\u003ca id=\"unzipwalk.UnzipWalkResult\"\u003e\u003c/a\u003e\n\n### *class* unzipwalk.UnzipWalkResult(names: [tuple](https://docs.python.org/3/library/stdtypes.html#tuple)[[PurePath](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath), ...], typ: [FileType](#unzipwalk.FileType), hnd: [ReadOnlyBinary](#unzipwalk.ReadOnlyBinary) | [None](https://docs.python.org/3/library/constants.html#None) = None, size: [int](https://docs.python.org/3/library/functions.html#int) | [None](https://docs.python.org/3/library/constants.html#None) = None)\n\nReturn type for [`unzipwalk()`](#function-unzipwalk).\n\n#### names *: [tuple](https://docs.python.org/3/library/stdtypes.html#tuple)[[PurePath](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath), ...]*\n\nA tuple of the filename(s) as [`pathlib`](https://docs.python.org/3/library/pathlib.html#module-pathlib) objects. The first element is always the physical file in the file system.\nIf the tuple has more than one element, then the yielded file is contained in a compressed file, possibly nested in\nother compressed file(s), and the last element of the tuple will contain the file’s actual name.\n\n#### typ *: [FileType](#unzipwalk.FileType)*\n\nA [`FileType`](#unzipwalk.FileType) value representing the type of the current file.\n\n#### hnd *: [ReadOnlyBinary](#unzipwalk.ReadOnlyBinary) | [None](https://docs.python.org/3/library/constants.html#None)*\n\nWhen [`typ`](#unzipwalk.UnzipWalkResult.typ) is [`FileType.FILE`](#unzipwalk.FileType), this is a [`ReadOnlyBinary`](#unzipwalk.ReadOnlyBinary) file handle (file object)\nfor reading the file contents in binary mode. Otherwise, this is [`None`](https://docs.python.org/3/library/constants.html#None).\nIf this object was produced by [`from_checksum_line()`](#unzipwalk.UnzipWalkResult.from_checksum_line), this handle will read the checksum of the data, *not the data itself!*\n\n#### size *: [int](https://docs.python.org/3/library/functions.html#int) | [None](https://docs.python.org/3/library/constants.html#None)*\n\nWhen [`typ`](#unzipwalk.UnzipWalkResult.typ) is [`FileType.FILE`](#unzipwalk.FileType) or [`FileType.ARCHIVE`](#unzipwalk.FileType), this field *may* hold the size of the\nfile, if the compression format and library support knowing the compressed file’s size in advance. Otherwise, this is [`None`](https://docs.python.org/3/library/constants.html#None).\n\n#### validate()\n\nValidate whether the object’s fields are set properly and throw errors if not.\n\nIntended for internal use, mainly when type checkers are not being used.\n[`unzipwalk()`](#function-unzipwalk) validates all the results it returns.\n\n* **Returns:**\n  The object itself, for method chaining.\n* **Raises:**\n  [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError)**,** [**TypeError**](https://docs.python.org/3/library/exceptions.html#TypeError) – If the object is invalid.\n\n\u003ca id=\"unzipwalk.UnzipWalkResult.checksum_line\"\u003e\u003c/a\u003e\n\n#### checksum_line(hash_algo: [str](https://docs.python.org/3/library/stdtypes.html#str), \\*, raise_errors: [bool](https://docs.python.org/3/library/functions.html#bool) = True) → [str](https://docs.python.org/3/library/stdtypes.html#str)\n\nEncodes this object into a line of text suitable for use as a checksum line.\n\nIntended mostly for internal use by the `--checksum` CLI option.\nSee [`from_checksum_line()`](#unzipwalk.UnzipWalkResult.from_checksum_line) for the inverse operation.\n\n#### WARNING\nRequires that the file handle be open (for files), and will read from it to generate the checksum!\n\n* **Parameters:**\n  **hash_algo** – The hashing algorithm to use, as recognized by [`hashlib.new()`](https://docs.python.org/3/library/hashlib.html#hashlib.new).\n* **Returns:**\n  The checksum line, without trailing newline.\n\n\u003ca id=\"unzipwalk.UnzipWalkResult.from_checksum_line\"\u003e\u003c/a\u003e\n\n#### *classmethod* from_checksum_line(line: [str](https://docs.python.org/3/library/stdtypes.html#str), \\*, windows: [bool](https://docs.python.org/3/library/functions.html#bool) = False) → [UnzipWalkResult](#unzipwalk.UnzipWalkResult) | [None](https://docs.python.org/3/library/constants.html#None)\n\nDecodes a checksum line as produced by [`checksum_line()`](#unzipwalk.UnzipWalkResult.checksum_line).\n\nIntended as a utility function for use when reading files produced by the `--checksum` CLI option.\n\n#### WARNING\nThe `hnd` of the returned object will *not* be a handle to\nthe data from the file, instead it will be a handle to read the checksum of the file!\n(You could use [`recursive_open()`](#unzipwalk.recursive_open) to open the files themselves.)\n\n* **Parameters:**\n  * **line** – The line to parse.\n  * **windows** – Set this to [`True`](https://docs.python.org/3/library/constants.html#True) if the pathname in the line is in Windows format,\n    otherwise it is assumed the filename is in POSIX format.\n* **Returns:**\n  The [`UnzipWalkResult`](#unzipwalk.UnzipWalkResult) object, or [`None`](https://docs.python.org/3/library/constants.html#None) for empty or comment lines.\n* **Raises:**\n  [**ValueError**](https://docs.python.org/3/library/exceptions.html#ValueError) – If the line could not be parsed.\n\n\u003ca id=\"unzipwalk.FileType\"\u003e\u003c/a\u003e\n\n### *class* unzipwalk.FileType(value)\n\nUsed in [`UnzipWalkResult`](#unzipwalk.UnzipWalkResult) to indicate the type of the file.\n\n#### WARNING\nDon’t rely on the numeric value of the enum elements, they are automatically generated and may change!\n\n#### FILE *= 1*\n\nA regular file.\n\n#### ARCHIVE *= 2*\n\nAn archive file, will be descended into.\n\n#### DIR *= 3*\n\nA directory.\n\n#### SYMLINK *= 4*\n\nA symbolic link.\n\n#### OTHER *= 5*\n\nSome other file type (e.g. FIFO).\n\n#### SKIP *= 6*\n\nA file was skipped due to the `matcher` filter.\n\n#### ERROR *= 7*\n\nAn error was encountered with this file, when the `raise_errors` option is off.\n\n\u003ca id=\"unzipwalk.recursive_open\"\u003e\u003c/a\u003e\n\n### unzipwalk.recursive_open(fns: [Sequence](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence)[[str](https://docs.python.org/3/library/stdtypes.html#str) | [PathLike](https://docs.python.org/3/library/os.html#os.PathLike)], encoding=None, errors=None, newline=None) → [Generator](https://docs.python.org/3/library/collections.abc.html#collections.abc.Generator)[[ReadOnlyBinary](#unzipwalk.ReadOnlyBinary) | TextIOWrapper, [None](https://docs.python.org/3/library/constants.html#None), [None](https://docs.python.org/3/library/constants.html#None)]\n\nThis context manager allows opening files nested inside archives directly.\n\n[`unzipwalk()`](#function-unzipwalk) automatically closes files as it iterates through directories and archives;\nthis function exists to allow you to open the returned files after the iteration.\nHowever, this function will be less efficient that [`unzipwalk()`](#function-unzipwalk) if you’re opening\nmultiple files inside of Zip or tar archives.\n\n\u003c!-- note: If *any* of ``encoding``, ``errors``, or ``newline`` is specified, the returned\nfile is wrapped in :class:`io.TextIOWrapper`! --\u003e\n\u003c!-- note: If the last file in the list of files is an archive file, then it won't be decompressed,\ninstead you'll be able to read the archive's raw compressed data from the handle. --\u003e\n\nIn this example, we open a gzip-compressed file, stored inside a tgz archive, which\nin turn is stored in a Zip file:\n\n```pycon\n\u003e\u003e\u003e from unzipwalk import recursive_open\n\u003e\u003e\u003e with recursive_open(('bar.zip', 'test.tar.gz', 'test/cool.txt.gz', 'test/cool.txt'), encoding='UTF-8') as fh:\n...     print(fh.read())\nHi, I'm a compressed file!\n```\n\n* **Raises:**\n  * [**ImportError**](https://docs.python.org/3/library/exceptions.html#ImportError) – If you try to open a 7z file but [`py7zr`](https://py7zr.readthedocs.io/en/stable/api.html#module-py7zr) is not installed.\n  * [**Exception**](https://docs.python.org/3/library/exceptions.html#Exception) – See description in [`unzipwalk()`](#function-unzipwalk).\n\n\u003ca id=\"unzipwalk.ReadOnlyBinary\"\u003e\u003c/a\u003e\n\n### *class* unzipwalk.ReadOnlyBinary(\\*args, \\*\\*kwargs)\n\nInterface for the file handle (file object) used in [`UnzipWalkResult`](#unzipwalk.UnzipWalkResult).\n\nThis is essentially the intersection of what the underlying objects support.\n\n#### close() → [None](https://docs.python.org/3/library/constants.html#None)\n\nClose the file.\n\n#### NOTE\n[`unzipwalk()`](#function-unzipwalk) automatically closes files.\n\n#### *property* closed *: [bool](https://docs.python.org/3/library/functions.html#bool)*\n\n#### readable() → [bool](https://docs.python.org/3/library/functions.html#bool)\n\n#### read(n: [int](https://docs.python.org/3/library/functions.html#int) = -1, /) → [bytes](https://docs.python.org/3/library/stdtypes.html#bytes)\n\n#### readline(limit: [int](https://docs.python.org/3/library/functions.html#int) = -1, /) → [bytes](https://docs.python.org/3/library/stdtypes.html#bytes)\n\n#### seekable() → [bool](https://docs.python.org/3/library/functions.html#bool)\n\n#### seek(offset: [int](https://docs.python.org/3/library/functions.html#int), whence: [int](https://docs.python.org/3/library/functions.html#int) = 0, /) → [int](https://docs.python.org/3/library/functions.html#int)\n\n## Command-Line Interface\n\n```default\nusage: unzipwalk [-h] [-a] [-d | -c ALGO] [-e EXCLUDE] [-r] [-o OUTFILE]\n                 [PATH ...]\n\nRecursively walk into directories and archives\n\npositional arguments:\n  PATH                  paths to process (default is current directory)\n\noptions:\n  -h, --help            show this help message and exit\n  -a, --all-files       also list dirs, symlinks, etc.\n  -d, --dump            also dump file contents\n  -c ALGO, --checksum ALGO\n                        generate a checksum for each file**\n  -e EXCLUDE, --exclude EXCLUDE\n                        filename globs to exclude*\n  -r, --raise-errors    raise errors instead of reporting them in output\n  -o OUTFILE, --outfile OUTFILE\n                        output filename\n\n* Note --exclude currently only matches against the final name in the\nsequence, excluding path names, but this interface may change in future\nversions. For more control, use the library instead of this command-line tool.\n** Possible values for ALGO: blake2b, blake2s, md5, md5-sha1, ripemd160, sha1,\nsha224, sha256, sha384, sha3_224, sha3_256, sha3_384, sha3_512, sha512,\nsha512_224, sha512_256, shake_128, shake_256, sm3\n```\n\nThe available checksum algorithms may vary depending on your system and Python version.\nRun the command with `--help` to see the list of algorithms available on your system.\n\n## Author, Copyright, and License\n\nCopyright (c) 2022-2025 Hauke Dämpfling ([haukex@zero-g.net](mailto:haukex@zero-g.net))\nat the Leibniz Institute of Freshwater Ecology and Inland Fisheries (IGB),\nBerlin, Germany, [https://www.igb-berlin.de/](https://www.igb-berlin.de/)\n\nThis library is free software: you can redistribute it and/or modify it under\nthe terms of the GNU Lesser General Public License as published by the Free\nSoftware Foundation, either version 3 of the License, or (at your option) any\nlater version.\n\nThis library is distributed in the hope that it will be useful, but WITHOUT\nANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS\nFOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more\ndetails.\n\nYou should have received a copy of the GNU Lesser General Public License\nalong with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaukex%2Funzipwalk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaukex%2Funzipwalk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaukex%2Funzipwalk/lists"}