{"id":"boatbomber/decant","name":"decant","scope":"boatbomber","platform":"roblox","description":"Decompression in pure Luau","version":"1.1.0","latest":"1.1.0","versions":["1.0.0","1.1.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":["The package archive does not include its license text; the license is declared in its manifest metadata."],"licenseVerified":false,"dependencies":{},"integrity":"3d6acbdba2d39db09d5cae4153e1a167730767377d535afa5fc633b23bdcfa18","likes":0,"downloads":0,"install":"forest install boatbomber/decant","url":"https://forest.dev/p/roblox/boatbomber/decant","files":"https://api.forest.dev/ai/package/roblox/boatbomber/decant/files","readme":"# Decant\n\nDecompression in pure Luau.\n\n## Installation\n\n### Wally\n\n```toml\n[dependencies]\nDecant = \"boatbomber/decant@1.1.0\"\n```\n\n### Roblox model\n\nYou can download a model file from the [Releases](https://github.com/boatbomber/Decant/releases) page.\n\n## Simple Example\n\n```Luau\nlocal Decant = require(script.Decant)\nlocal HttpService = game:GetService(\"HttpService\")\n\nlocal source_zip = buffer.fromstring(HttpService:RequestAsync({\n    Method = \"GET\",\n    Url = \"https://github.com/boatbomber/Decant/archive/refs/tags/v1.1.0.zip\",\n}).Body)\n\nfor path, content in Decant.zip.iterateFiles(source_zip) do\n    print(string.format(\"%4.1f KB  %s\", buffer.len(content) / 1024, path))\nend\n```\n\n## API\n\n### Decant.zip\n\n```Lua\nDecant.zip.extractFile(data: buffer, path: string): buffer?\n```\n\nFinds a file by path in a ZIP archive and returns its decompressed contents, or `nil` if the archive has no file at that path. Throws if the archive is malformed, an entry uses a compression method Decant can't decode, or a decompressed entry's checksum doesn't match its central directory record.\n\n```Lua\nDecant.zip.extractAt(data: buffer, metadata: FileMetadata): buffer\n```\n\nExtracts a single entry you already found through `iterateFileMetadata`, reading its bytes straight from the offset the `FileMetadata` records without walking the central directory a second time. It handles stored and deflated entries alike and verifies the entry's CRC-32.\n\n```Lua\nDecant.zip.iterateFiles(data: buffer, filter: ((path: string, size: number) -> boolean)?): () -> (string?, buffer)\n```\n\nReturns an iterator over every file in the archive, yielding each file's path and decompressed contents. The optional filter runs on each file's path and uncompressed size before anything is decompressed, so returning `false` skips that entry without ever inflating it.\n\n```Lua\nDecant.zip.iterateFileMetadata(data: buffer): () -> FileMetadata?\n```\n\nReturns an iterator over the archive's central directory, yielding one `FileMetadata` per entry without decompressing anything. It's a cheap way to list an archive's contents, or to hold onto an entry's metadata for a later `extractAt` call.\n\n```Lua\nDecant.zip.readComment(data: buffer): string\n```\n\nReturns the archive's comment or an empty string when there isn't one.\n\n```Lua\nDecant.zip.isPathSafe(path: string): boolean\n```\n\nReports whether an entry path is safe to use as a relative path on a real filesystem. A hostile archive can name entries with absolute paths, drive letters, or enough `..` traversals to climb out of the extraction root, and this rejects all of those along with embedded null bytes. Decant itself never touches a filesystem, so this is a helper for callers who do.\n\n```Lua\nexport type FileMetadata = {\n    path: string,\n    size: number,\n    offset: number,\n    packed: boolean,\n    crc: number,\n    method: number,\n    compressedSize: number,\n    modified: number,\n    isDirectory: boolean,\n    attributes: number,\n    utf8: boolean,\n}\n```\n\nThe first five fields drive extraction: the entry's path as the archive stored it, its uncompressed size, the 0-based offset of its data, whether it's compressed rather than stored, and its expected CRC-32. The rest describe the entry: `method` is the raw compression method id, `compressedSize` is how many bytes the entry occupies in the archive, `modified` is the recorded modification time as Unix epoch seconds, `isDirectory` marks folder entries, `attributes` is the raw external attributes word (an archive made on Unix keeps the file mode in its high sixteen bits), and `utf8` reports whether the writer flagged the path as UTF-8 encoded.\n\n### Decant.gz\n\n```Lua\nDecant.gz.decompress(data: buffer): buffer\n```\n\nDecompresses a gzip stream and verifies its CRC-32 checksum.\n\n### Decant.zlib\n\n```Lua\nDecant.zlib.decompress(data: buffer): buffer\n```\n\nDecompresses a zlib stream and verifies its Adler-32 checksum.\n\n### Decant.deflate\n\n```Lua\nDecant.deflate.decompress(data: buffer): buffer\n```\n\nDecompresses a raw deflate stream, one with no gzip or zlib wrapper around it. A raw stream has no header or checksum, so there's nothing to parse or verify around the deflate data.\n\n### Decant.decompress\n\n```Lua\nDecant.decompress(data: buffer): buffer\n```\n\nDecompresses a gzip or zlib stream, telling the two apart by their header. Raw deflate has no header to detect, so use `Decant.deflate.decompress` for that instead. Throws if the header matches neither format.\n\n## Reference\n\nThis library draws a lot of inspiration from [zzlib](https://codeberg.org/zerkman/zzlib) and [luau-unzip](https://github.com/0x5eal/luau-unzip).\n","readmeTruncated":false}