A high-level archive library for Pharo, backed by libarchive through FFI. It mirrors
the Compression package's Archive document model, so common operations feel idiomatic,
while the heavy lifting (read/write/extract for many container formats) is delegated to
libarchive — preserving POSIX permissions and modification times for free.
- Document model —
LAArchivebuilds, inspects, edits and round-trips an archive in memory. - Streaming read/write —
LAArchiveReaderandLAArchiveWriterwork directly with files, streams or byte buffers. - Native extraction — permissions and timestamps are preserved with no extra work.
- Format auto-detection on read — you just open the archive; the format is detected.
- Many writer formats — zip, tar (pax), gnutar, 7zip, cpio, xar and iso.
- A Pharo image (developed and tested against Pharo 14).
- The libarchive native library. The FFI resolves it from
CIG/librelative to the Pharo working directory (i.e../CIG/lib/libarchive.{so|dylib|dll}). See Development for how CI supplies it. - The
pharo-cig/pharo-archive(Compression) baseline, which provides the FFI plumbing (LibArchive,ArchiveEntry) and theArchiveConstantspool this project builds on. It is pulled in automatically by the baseline below.
Load the project and its tests with Metacello:
Metacello new
baseline: 'PharoArchive';
repository: 'github://demarey/libarchive:main/src';
load.This loads the whole project including tests (the default group). To load only the core:
Metacello new
baseline: 'PharoArchive';
repository: 'github://demarey/libarchive:main/src';
load: #PharoArchive.If you just need to compress a folder into a zip, or unpack a zip — both in a single expression — this is the API for you. zip is libarchive's default on write and is auto-detected on read.
Build a zip from a directory and all of its subdirectories (one expression):
(LAArchive new
addEntriesFromDirectory: '/path/to/folder';
writeToFile: '/tmp/folder.zip')Unpack that zip back, preserving permissions and modification times (one expression):
(LAArchive new
readFrom: '/tmp/folder.zip';
extractAllTo: '/tmp/extracted')Building records each file's POSIX permission and modification-time metadata; extracting restores them natively. The subsections below keep the more granular, step-by-step coverage.
| archive |
archive := LAArchive new.
archive
addDirectory: 'docs';
addString: 'Hello document!' as: 'hello.txt';
addString: ('# Title', String lf) as: 'docs/notes.md';
addFile: '/path/to/a-file.txt' as: 'assets/a-file.txt'.
archive writeToFile: '/tmp/example.zip'.| archive |
archive := (LAArchive new) readFrom: '/tmp/example.zip'.
archive memberNames. "-> #('docs' 'docs/notes.md' 'hello.txt' 'assets/a-file.txt')"
archive contentsOf: 'hello.txt'. "a ByteArray"
archive extractAllTo: '/tmp/extracted'.The extraction directory now contains the archive contents with their original permissions and modification times.
| target |
target := '/tmp/stream.zip'.
LAArchiveWriter writeToFile: target format: #zip during: [ :writer |
writer
addDirectory: 'docs';
addFileNamed: 'hello.txt' content: 'file contents' asByteArray;
addFileNamed: 'docs/notes.md' content: '# Title' asByteArray ].| archive |
archive := LAArchive new.
archive addEntriesFromDirectory: '/path/to/payload'. "recursively walks the tree"
archive writeToFile: '/tmp/payload.tar'. "choose any supported format"Because the members are taken from real files, their POSIX permissions and mtimes are recorded in the archive.
| reader entry |
reader := LAArchiveReader onFile: '/tmp/example.zip'.
entry := reader entries detect: [ :each | each pathname = 'hello.txt' ].
reader contentsOf: entry. "the raw bytes of that entry"
reader extractTo: '/tmp/all'. "native, perms + mtimes preserved"
| model |
model := LAArchive new readFrom: '/tmp/example.zip'.
model extractMember: 'hello.txt' to: '/tmp/single'. "just one member"Reads are format-agnostic (libarchive auto-detects). Writes pick a format by name:
| archive |
archive := LAArchive new.
archive
addString: 'alpha' as: 'a.txt';
addString: 'beta' as: 'b.txt'.
archive writeToFile: '/tmp/out.zip'. "zip"
archive writeToFile: '/tmp/out.tar'. "tar (pax)"
archive writeTo: '/tmp/out.7z' format: #sevenzip.
archive writeTo: '/tmp/out.cpio' format: #cpio.LAArchive is both a document model and, after readFrom:/writeToFile:, a wrapper over
a backing file. Mutations (addMember:/addFile:/addString:/addDirectory:,
removeMember:, replaceMember:, setContentsOf:) change only the in-memory model; the
backing file is untouched. This divergence is tracked with an internal isDirty flag:
extractAllTo:extracts natively from the backing file while the model still matches it (the fast path, preserving permissions/timestamps). Once the model has been edited, the model is the source of truth and is extracted instead, so your edits are honored.writeTo:/writeToFile:serializes the (possibly edited) model and resets the flag — it produces an archive that faithfully reflects the document model.contentsOf:andextractMember:always honor the model, reading on demand from the backing file when content was not explicitly set.
Example of editing a read archive and having the edits reflected on extraction:
| archive |
archive := LAArchive new readFrom: '/tmp/example.zip'.
archive setContentsOf: 'hello.txt' to: 'edited content'.
archive removeMember: 'docs/notes.md'.
archive addString: 'a new file' as: 'new.txt'.
archive extractAllTo: '/tmp/edited'. "hello.txt has 'edited content'; notes.md is gone; new.txt present"| Class | Responsibility | Notable selectors |
|---|---|---|
LAArchive |
High-level in-memory document model; round-trip to/from disk | addMember: addFile: addString: addDirectory: addEntriesFromDirectory: removeMember: replaceMember: setContentsOf: contentsOf: extractAllTo: extractMember: readFrom: writeTo: writeToFile: members memberNamed: memberNames numberOfMembers isDirty |
LAArchiveReader |
Opens an existing archive; enumerate, read bytes, extract natively | onFile: onBytes: onStream: entries contentsOf: extractTo: |
LAArchiveWriter |
Builds a new archive; streaming or block-based | toFile: toStream: writeToFile:format:during: writeToStream:format:during: addEntryNamed: addFileNamed:content: addDirectory: addEntriesFromDirectory: finish |
LAArchiveMember |
Immutable description of a single entry | pathname size mtime mode filetype isDirectory isFile isSymlink symlink |
LAArchiveFormat |
Value describing an output container format | named: zip tar gnutar sevenZip cpio xar iso |
LAArchiveError |
Raised when libarchive reports a failure (carries a readable message) | signalOn:errorString: |
LAArchiveWorkingDirectory |
Switches CWD around native extraction (thread-safe restore) | in:during: |