Skip to content

Commit 5b08b6b

Browse files
committed
Add tar builtins
1 parent f64ce56 commit 5b08b6b

12 files changed

Lines changed: 1066 additions & 2 deletions

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
arbitrary base (2–36). `fromBase` returns `Maybe[int]`.
2323
- `toHex` / `toOctal` / `toBin` and `parseHex` / `parseOctal` / `parseBin`:
2424
convenience wrappers over `toBase` / `fromBase` for the common bases.
25+
- `tarDirInc` / `tarDirExc` / `tarPack` / `tarList` / `tarExtract` /
26+
`tarExtractEntry` / `tarRead`: create, list, extract, and read `.tar`
27+
archives, mirroring the existing `zip*` functions (same argument order and
28+
option dicts). Compression is chosen from the destination extension when
29+
writing (`.tar.gz` / `.tgz` → gzip, `.tar` → uncompressed) and auto-detected
30+
from the gzip magic bytes when reading, so `.tar.gz` is handled
31+
transparently. Symlinks are preserved on pack and recreated on extract
32+
(with a guard against targets escaping the destination); hard links and
33+
device nodes are rejected.
2534
- Optional fields in dictionary shape types, written `name?: T` (and
2635
`"name"?: T` in `def` signatures). An optional field may be absent from a
2736
value; when present, its value is still type-checked. This lets option-style

doc/functions.inc.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,14 @@ <h1 id="functions-file-directory">File and Directory <a class="section-link" hre
126126
<tr> <td><code>zipExtract</code></td> <td>Extract an entire archive. Options dict is required; defaults: <code>overwrite=false</code>, <code>skipExisting=false</code> (mutually exclusive), <code>stripComponents=0</code>, <code>pattern=""</code> (glob matched before stripping), <code>preservePermissions=true</code>. Destination is created if missing.</td> <td><code>(<span class="sig-type sig-type-path">path</span>:zipPath <span class="sig-type sig-type-path">path</span>:destDir <span class="sig-type sig-type-dict">dict</span>:options -- )</code></td> </tr>
127127
<tr> <td><code>zipExtractEntry</code></td> <td>Extract a single entry (file or directory subtree) to a destination path. Options dict is required; defaults: <code>overwrite=false</code>, <code>skipExisting=false</code> (mutually exclusive), <code>preservePermissions=true</code>, <code>mkdirs=true</code> (create parent directories when needed).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:zipPath <span class="sig-type sig-type-str">str</span>:entry <span class="sig-type sig-type-path">path</span>:dest <span class="sig-type sig-type-dict">dict</span>:options -- )</code></td> </tr>
128128
<tr> <td><code>zipRead</code></td> <td>Read an entry’s bytes directly into the stack without writing to disk. Returns <code>none</code> when the entry does not exist.</td> <td><code>(<span class="sig-type sig-type-path">path</span>:zipPath <span class="sig-type sig-type-str">str</span>:entry -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-binary">binary</span>])</code></td> </tr>
129+
<tr> <td colspan="3"><em>Tar functions mirror the <code>zip*</code> surface (same argument order and option dicts). Compression is chosen from the destination extension when writing (<code>.tar.gz</code> / <code>.tgz</code> → gzip, <code>.tar</code> → uncompressed) and auto-detected from the gzip magic bytes when reading. Symlinks are preserved: packed as symlink entries and recreated on extraction (with an escape guard); hard links and device nodes are rejected.</em></td> </tr>
130+
<tr> <td><code>tarDirInc</code></td> <td>Create/overwrite a <code>.tar</code> / <code>.tar.gz</code> from a directory; the archive root contains the directory’s contents (no parent folder).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:sourceDir <span class="sig-type sig-type-path">path</span>:tarPath -- )</code></td> </tr>
131+
<tr> <td><code>tarDirExc</code></td> <td>Create/overwrite a <code>.tar</code> / <code>.tar.gz</code> that includes the source directory itself at the archive root (entries are prefixed with the directory name).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:sourceDir <span class="sig-type sig-type-path">path</span>:tarPath -- )</code></td> </tr>
132+
<tr> <td><code>tarPack</code></td> <td>Create/overwrite a <code>.tar</code> / <code>.tar.gz</code> by packing a list of entries. Each entry is either a bare string/path (the file or directory to add, keeping its base name and mode) or a dictionary requiring <code>path</code>; in the dictionary form <code>archivePath</code> (override the in-archive name) and <code>mode</code> are optional. <code>mode</code> is a Go <a href="https://pkg.go.dev/io/fs#FileMode" target="_blank" rel="noopener noreferrer"><code>os.FileMode</code></a>; write it with an octal literal, e.g. <code>0o644</code> (<code>rw-r--r--</code>), <code>0o755</code> (<code>rwxr-xr-x</code>), <code>0o600</code>. If <code>mode</code> is omitted, the entry keeps the source file’s own mode.</td> <td><code>([<span class="sig-type sig-type-str">str</span>|<span class="sig-type sig-type-path">path</span>|<span class="sig-type sig-type-dict">dict</span>] <span class="sig-type sig-type-path">path</span>:tarPath -- )</code></td> </tr>
133+
<tr> <td><code>tarList</code></td> <td>List archive entries as dictionaries with keys: <code>name</code> (string, forward-slash paths, directories end with <code>/</code>), <code>compressedSize</code> and <code>uncompressedSize</code> (int bytes; equal, since tar has no per-entry compressed size), <code>isDir</code> (bool), <code>perm</code> (int POSIX permission bits), <code>executable</code> (bool), <code>modified</code> (datetime), <code>type</code> (<code>"file"</code>/<code>"dir"</code>/<code>"symlink"</code>), and <code>linkTarget</code> (symlink target, empty otherwise).</td> <td><code>(<span class="sig-type sig-type-path">path</span> -- [<span class="sig-type sig-type-dict">dict</span>])</code></td> </tr>
134+
<tr> <td><code>tarExtract</code></td> <td>Extract an entire archive. Options dict is required; defaults: <code>overwrite=false</code>, <code>skipExisting=false</code> (mutually exclusive), <code>stripComponents=0</code>, <code>pattern=""</code> (glob matched before stripping), <code>preservePermissions=true</code>. Destination is created if missing.</td> <td><code>(<span class="sig-type sig-type-path">path</span>:tarPath <span class="sig-type sig-type-path">path</span>:destDir <span class="sig-type sig-type-dict">dict</span>:options -- )</code></td> </tr>
135+
<tr> <td><code>tarExtractEntry</code></td> <td>Extract a single entry (file or directory subtree) to a destination path. Options dict is required; defaults: <code>overwrite=false</code>, <code>skipExisting=false</code> (mutually exclusive), <code>preservePermissions=true</code>, <code>mkdirs=true</code> (create parent directories when needed).</td> <td><code>(<span class="sig-type sig-type-path">path</span>:tarPath <span class="sig-type sig-type-str">str</span>:entry <span class="sig-type sig-type-path">path</span>:dest <span class="sig-type sig-type-dict">dict</span>:options -- )</code></td> </tr>
136+
<tr> <td><code>tarRead</code></td> <td>Read an entry’s bytes directly into the stack without writing to disk. Returns <code>none</code> when the entry does not exist.</td> <td><code>(<span class="sig-type sig-type-path">path</span>:tarPath <span class="sig-type sig-type-str">str</span>:entry -- <span class="sig-type sig-type-maybe">Maybe</span>[<span class="sig-type sig-type-binary">binary</span>])</code></td> </tr>
129137
<tr> <td><code>readFile</code></td> <td>Read a file into a string.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-str">str</span>)</code></td> </tr>
130138
<tr> <td><code>readFileBytes</code></td> <td>Read a file into binary data.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- <span class="sig-type sig-type-binary">binary</span>)</code></td> </tr>
131139
<tr> <td><code>readTsvFile</code></td> <td>Read a TSV file into a list of rows.</td> <td><code>(<span class="sig-type sig-type-str">str</span> -- [[<span class="sig-type sig-type-str">str</span>]])</code></td> </tr>

doc/mshell.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,6 +1419,37 @@ See [Regexp.Expand](https://pkg.go.dev/regexp#Regexp.Expand) for replacement syn
14191419
- `zipExtractEntry`: Extract a single entry (file or directory subtree) to a destination path. Options dict is required; defaults: `overwrite=false`, `skipExisting=false` (mutually exclusive), `preservePermissions=true`, `mkdirs=true`. `(path:zipPath str:entry path:dest dict:options -- )`
14201420
- `zipRead`: Read an entry's bytes directly onto the stack without writing to disk. Returns `none` when the entry does not exist. `(path:zipPath str:entry -- Maybe[binary])`
14211421

1422+
## Archive (Tar) Functions
1423+
1424+
The tar functions mirror the `zip*` surface exactly: same names with a `tar`
1425+
prefix, same argument order, and the same option dictionaries.
1426+
Two things differ, both driven by the tar format:
1427+
1428+
- Compression is selected by the destination extension when writing:
1429+
`.tar.gz` or `.tgz` produce a gzip-compressed tarball, `.tar` is uncompressed.
1430+
When reading, the gzip magic bytes are auto-detected, so a gzipped tarball is
1431+
read transparently regardless of its filename.
1432+
- Symlinks are preserved: `tarPack`/`tarDir*` store symlinks as symlink entries,
1433+
and `tarExtract`/`tarExtractEntry` recreate them (rejecting any whose target
1434+
would escape the destination directory). Hard links and device/fifo nodes are
1435+
rejected with an error.
1436+
1437+
- `tarDirInc`: Create/overwrite a `.tar`/`.tar.gz` from a directory; the archive root contains the directory's contents (no parent folder). `(path:sourceDir path:tarPath -- )`
1438+
- `tarDirExc`: Create/overwrite a `.tar`/`.tar.gz` that includes the source directory itself at the archive root (entries are prefixed with the directory name). `(path:sourceDir path:tarPath -- )`
1439+
- `tarPack`: Create/overwrite a `.tar`/`.tar.gz` by packing a list of entries.
1440+
Each entry is either a bare string/path (the file or directory to add,
1441+
keeping its base name and mode) or a dictionary.
1442+
Each dictionary entry requires `path` (the file or directory to add);
1443+
`archivePath` (override the in-archive name) and `mode` are optional.
1444+
`mode` is a Go `os.FileMode`; write it with an octal literal,
1445+
e.g. `0o644` (`rw-r--r--`), `0o755` (`rwxr-xr-x`), `0o600`.
1446+
If `mode` is omitted, the entry keeps the source file's own mode.
1447+
Type: `([str | path | {path: str | path, archivePath?: str | path, mode?: int}] str | path -- )`
1448+
- `tarList`: List archive entries as dictionaries with keys: `name` (string, forward-slash paths, directories end with `/`), `compressedSize` and `uncompressedSize` (int bytes; equal, since tar has no per-entry compressed size), `isDir` (bool), `perm` (int POSIX permission bits), `executable` (bool), `modified` (datetime from the archive entry), `type` (`"file"`/`"dir"`/`"symlink"`), and `linkTarget` (symlink target, empty otherwise). `(path -- [dict])`
1449+
- `tarExtract`: Extract an entire archive. Options dict is required; defaults: `overwrite=false`, `skipExisting=false` (mutually exclusive), `stripComponents=0`, `pattern=""` (glob matched before stripping), `preservePermissions=true`. Destination is created if missing. `(path:tarPath path:destDir dict:options -- )`
1450+
- `tarExtractEntry`: Extract a single entry (file or directory subtree) to a destination path. Options dict is required; defaults: `overwrite=false`, `skipExisting=false` (mutually exclusive), `preservePermissions=true`, `mkdirs=true`. `(path:tarPath str:entry path:dest dict:options -- )`
1451+
- `tarRead`: Read an entry's bytes directly onto the stack without writing to disk. Returns `none` when the entry does not exist. `(path:tarPath str:entry -- Maybe[binary])`
1452+
14221453
## Variables
14231454

14241455
You can store to several variables in one go by separating the store tokens with commas.

mshell/BuiltInList.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,13 @@ var BuiltInList = map[string]struct{}{
221221
"writeFile": {},
222222
"wsplit": {},
223223
"year": {},
224+
"tarDirExc": {},
225+
"tarDirInc": {},
226+
"tarExtract": {},
227+
"tarExtractEntry": {},
228+
"tarList": {},
229+
"tarPack": {},
230+
"tarRead": {},
224231
"zipDirExc": {},
225232
"zipDirInc": {},
226233
"zipExtract": {},

0 commit comments

Comments
 (0)