Paths extension
Work in progress
argtype is early-stage and the extension mechanism is still being designed; this extension's surface may change.
Extension name: paths
Annotates a path terminal with how a runner must handle the file at that path, beyond simply passing its string on the command line. Two behaviours are covered today:
.mutable()— the tool modifies the file in place. It is read and written, not just read..resolveParent()— the tool needs the path's parent directory to exist, typically because the file does not exist yet at invocation time (an output the tool creates) or because the tool writes sibling files next to it.
Both are extensions rather than core because they are annotation-only: they do not change the argv grammar (the argument is still exactly one path element) and they do not narrow the set of valid path strings. They describe the runtime contract of a path (what the tool does to the filesystem), which a consumer that only cares about which argv is valid can ignore entirely.
This is distinct from the mediatypes extension, which describes the contents expected at a path (image/png). paths describes what the tool does with the file; mediatypes describes what the file is. They compose on the same path.
---
exe: "normalize"
---
/// Rescale an image's intensities, editing it in place.
normalize: seq(
/// Image to normalize. Modified in place.
image: path.mutable(),
/// Output basename; the tool writes `{prefix}.log` next to it.
opt("-o", prefix: path.resolveParent())
.output(log: `{prefix}.log`),
)Semantics
.mutable()
The tool writes to the file named by this argument, changing its contents. A runner that stages inputs (into a container, a sandbox, or a remote worker) must therefore:
- stage the file read-write, not read-only; and
- if the original must be preserved, operate on a writable copy and treat the modified copy as a produced artifact.
Because a mutable input is effectively also an output (the tool leaves a changed file behind), a consumer that implements both paths and outputs may surface the mutated file as an output without an explicit .output(...) template - the produced path is the input path. .mutable() does not itself declare an output template; it only marks that the file changes.
.resolveParent()
The tool needs the directory containing this path, not (only) the file itself. This is the common case for an output path or basename: the file does not exist when the tool starts, so there is nothing to stage - but the tool must be able to create it, which requires the parent directory to be present and writable. It also covers tools that emit sibling files ({path}, {path}.log, {path}.json) into the same directory.
A runner resolves/stages the parent directory so that writes to the path (and its siblings) succeed.
What consumers do with it
- Runners / executors stage the file read-write (
.mutable()) or ensure the parent directory exists (.resolveParent()); this is the primary consumer. - Wrapper / API generators may surface a
.mutable()file as an output in the generated interface (see theoutputsinteraction above), or document that the argument is modified. - Validators / GUIs may warn before overwriting a
.mutable()input.
Like every extension, these are advisory: a consumer that does not implement paths treats the argument as a plain path and processes the grammar unchanged.
Methods
| Method | Meaning |
|---|---|
path.mutable() | The tool modifies the file at this path in place (read and written). |
path.resolveParent() | The tool needs this path's parent directory to exist (e.g. an output path it creates, or sibling files it writes). |
Both are flag-like: they take no argument and may be chained in any order, and with other path methods (.name(...), .mediaType(...)).
Chaining API
// On TerminalNode, for `path` (provided by the paths extension):
interface TerminalNode {
mutable(): TerminalNode
resolveParent(): TerminalNode
}