refactor(seam): dispatcher-built CommandOpts, typed adapter ops, named ParsedCommand, one DispatchFn home (#609 items 26+23) (#772)

* wip(seam): py dispatcher builds CommandOpts; handlers take (accessor, paths, texts, opts); adapter op protocols; DispatchFn typing

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* refactor(seam): dispatcher-built CommandOpts, typed adapter ops, named ParsedCommand, one DispatchFn home (#609 items 26+23)

Item 26 (T2-8 seam typing), all four pieces, plus task 23's static
FlagView query-name gate:

- The python dispatcher (Mount.execute_cmd) now constructs one
  CommandOpts per invocation and calls every handler as
  fn(accessor, paths, texts, opts) — the TS convention. Flags stop
  sharing a bag with injected context, accepts_kwarg opt-in dies, and
  all 72 builders, 75 bespoke wrappers and ~30 provision functions
  become pure wiring; the provision path builds the same bag with
  command/spec set. Generics that took trailing fact params (ls, find,
  tree, zip, tar, du, file, stat) now read opts.links/opts.mounts/...
  like their TS twins.
- adapter.py gains per-slot op protocols (ReaddirOp/StatOp/WriteOp/...)
  mirroring adapter.ts, so wiring readdir where stat belongs no longer
  type-checks; Builder.fn/provision and CommandIO fields are typed.
  Surfaced and fixed real drift: hf/databricks mkdir had index before
  parents (a positional parents call would land in index), wget -O
  dropped its PathSpec value under as_str.
- TS parseFlags returns a named ParsedCommand (twin of the py
  NamedTuple) instead of a 15-slot positional tuple; optionError takes
  (cmdName, parsed) like python's option_error.
- DispatchFn moves to runtime/types.ts (python's home for the same
  protocol); the crossmount re-export inversion and the CommandDispatch
  duplicate are gone; 38 python files stop spelling it
  Callable[..., Any]. The dead CommandOpts.resource field (zero readers)
  is deleted along with the CrossResourceStub that existed to fill it.
- Task 23: tests/commands/test_flag_query_names.py and
  commands/flag_query_names.test.ts fail on a FlagView query naming a
  dest no spec bound in the module declares, without needing the code
  path to run (both verified to fire on a planted typo).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(examples): redis example calls provisions with the 4-positional shape

file_read_provision/head_tail_provision/metadata_provision are
(accessor, paths, texts, opts) now; the direct demo calls still passed
the old command= kwarg, crashing the example before the persistence
section (CI examples gate).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(integ): pin the #772 fixes where the harness can reach them

- find -empty on chroma + github (the wrappers used to drop the flag,
  which would flood every path; -empty now rides find_generic), plus a
  -not -empty composition case on each so the pin has positive output
- mkdir -p gains databricks/databricks-prefix (zero prior mkdir
  coverage on that backend; the param-order bug itself is only
  reachable positionally, so MkdirOp+mypy is its real gate)
- wget -O / curl -o were already pinned by integ/resources/http

Both hosts green: py chroma+github 83 ok, ts all five targets 4070 ok.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: bytecii <bytecii@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
bytecii
2026-08-13 05:53:25 -07:00
committed by GitHub
parent 320519d0fd
commit ef99849b94
418 changed files with 3534 additions and 4512 deletions
+14 -30
View File
@@ -323,35 +323,19 @@ they bite:
backend, so a backend author never implements, stores, or forwards one. This
is the whole point of keeping them in the namespace; do not push link
awareness down into a resource or an accessor.
- **A command opts in by naming the parameter, nothing else.** Declare
`links: LinkView | None = None` on the wrapper and the generic and the
dispatcher starts passing it; delete the parameter and it stops. `execute_cmd`
offers the fact to every handler and `accepts_kwarg` (`utils/params.py`)
decides delivery from the signature, so there is no allowlist, spec field, or
registry that can fall out of step. A bare `**kwargs` deliberately does not
count as consent: every wrapper has one, and it is the opaque bag of the
user's typed command-line flags, forwarded wholesale to the generic. Counting
it would file a live namespace object among the parsed flags of every command
in the repo. This is the same rule already stated for `stdin`/`index`/`prefix`
under "Command wrappers and flags", and `stat_overlay` is delivered the same
way.
`LinkView` bundles every link fact (`stat_at`, `children`, `subtree`,
`resolve`, `exists`, `target_stat`) so a command that grows a new need adds a
field read, not a new keyword threaded through `execute_cmd`, the builder and
the generic. Families wired today: `ls`, `stat`, `find`, `du`, `file`.
`exists` and `target_stat` answer through the op dispatcher, not one
- **A command consumes a fact by reading the `opts` field, nothing else.**
Every namespace fact (`links`, `stat_overlay`, `stat_path`, `readdir_path`,
`child_mounts`, `mounts`) rides `CommandOpts` into every handler, identically
in both languages; the generic that wants one reads `opts.links` and the rest
ignore it, so there is no opt-in registry, spec field, or signature
convention that can fall out of step. `LinkView` bundles every link fact
(`stat_at`, `children`, `subtree`, `resolve`, `exists`, `target_stat`) so a
command that grows a new need adds a field read, not a new keyword threaded
through `execute_cmd`, the builder and the generic. Families reading links
today: `ls`, `stat`, `find`, `du`, `file` — in the generics, so a bespoke
wrapper that delegates (as all of them now do) inherits link awareness for
free. `exists` and `target_stat` answer through the op dispatcher, not one
backend's stat, so a link that points into another mount resolves correctly.
- **A bespoke command in one of those families must declare it too.** The
opt-in is the whole mechanism, so a backend that ships its own `find`/`ls`/
`du`/`stat`/`file` and omits the parameter still runs, still exits 0, and
simply cannot see a link, which nothing notices until someone makes one on
that backend. `tests/commands/test_links_optin.py` asserts it instead: it
derives the link-aware names from the generic builders and fails naming any
registered command that shadows one without accepting `links`. TypeScript
needs no equivalent because wrappers forward the whole `opts` object, so a
generic reads `opts.links` whatever the wrapper declares; the bespoke email
find routes through `findGeneric`/`walkFind` like the factory, so no TS
command walks its own tree any more.
- **Merge links in the generic, above the native-op/walk fork.** `find` and `du`
each have two paths: a backend with a native op (`find_core`, `du_size`/
`du_entries`) and a backend walked by `readdir`. Link merging lives in one
@@ -632,8 +616,8 @@ Invoke the venv's `pre-commit` binary directly (not via `uv --directory python r
- Don't add too many printings or comments in the code.
- Don't add README.md unless I ask you to do so.
- Use uv add to install new dependencies.
- **Command wrappers and flags.** The dispatcher passes parsed command-line flags as keyword arguments. Wrappers must declare dispatcher-injected parameters (`stdin`, `index`, `prefix`) explicitly in their signature — never fish them out of `**flags` with `.get()`. Treat `**flags: FlagValue` as an opaque bag of true command-line flags and forward it wholesale to the generic command. A wrapper must not name a flag it cannot receive: the parser maps every spelling onto one canonical dest (the long form whenever an option declares one), so a parameter named after a short spelling with a long twin is permanently unfilled`tests/commands/test_no_dead_flag_params.py` fails on one. When a wrapper genuinely needs a flag value itself (e.g. a search push-down), read it through `FlagView` (`fl = FlagView(flags)` then `fl.as_bool("F")`, `fl.as_int("m")`, `fl.as_str("type")`, `fl.as_list("e")`) or a shared domain accessor like `pattern_arg` — never raw `flags.get(...)` / isinstance chains, and never a raw `kwargs`/`_extra` read either (`tests/commands/test_no_raw_flag_reads.py` matches all three bag names). **A PATH-typed flag reaches a python command as a `PathSpec`, not a string** — the executor promotes it (`workspace/executor/command/flags.py`), so read it with `fl.as_paths(name)`; `as_str` reads it as absent and the operand is silently never used. TypeScript's bag carries the resolved virtual-path string instead, so its twin is `fl.asStr(name)`.
- **Generic commands own flag interpretation.** Backend wrappers are wiring only (glob resolution, backend I/O injection, pass-through of `texts` and `flags`); all flag semantics live in the generic command for that family, mirroring the TS generics. Adding or changing a flag should touch the spec and the generic, not N wrappers.
- **Command handlers take `(accessor, paths, texts, opts)` — the same four positionals in both languages.** The dispatcher (`Mount.execute_cmd` / `Mount.executeCmd`) constructs one `CommandOpts` per invocation carrying stdin, the flag bag, cwd, the mount prefix, the index, and every namespace fact; the provision path builds the same bag with `command`/`spec` set (`ProvisionFn` has the same four-positional shape). Handlers never declare a flag or an injected fact as a parameter`tests/commands/test_no_dead_flag_params.py` pins the exact signature. When a wrapper genuinely needs a flag value itself (e.g. a search push-down), read it through a spec-bound `FlagView` (`fl = FlagView(opts.flags, spec=SPECS["grep"])` then `fl.as_bool("F")`, `fl.as_int("m")`, `fl.as_str("type")`, `fl.as_list("e")`) or a shared domain accessor like `pattern_arg` — never raw `flags.get(...)` / isinstance chains, and never a raw `kwargs`/`_extra` read either (`tests/commands/test_no_raw_flag_reads.py`). To override a flag before delegating, pass `dataclasses.replace(opts, flags=bag)` down, never a hand-built `CommandOpts`. **A PATH-typed flag reaches a python command as a `PathSpec`, not a string** — the executor promotes it (`workspace/executor/command/flags.py`), so read it with `fl.as_paths(name)`; `as_str` reads it as absent and the operand is silently never used. TypeScript's bag carries the resolved virtual-path string instead, so its twin is `fl.asStr(name)`.
- **Generic commands own flag interpretation.** Backend wrappers are wiring only (glob resolution, backend I/O injection, pass-through of `texts` and `opts`); all flag semantics live in the generic command for that family, mirroring the TS generics. Adding or changing a flag should touch the spec and the generic, not N wrappers. Every literal `FlagView` query name must be a dest of a spec bound in the same module — `tests/commands/test_flag_query_names.py` and `commands/flag_query_names.test.ts` fail on a typo'd spelling without needing the code path to run.
- **Generics parse flags once into a frozen struct.** Each generic defines a `@dataclass(frozen=True, slots=True)` flag struct plus a module-level `parse_flags(fl, ...)` (mirroring the TS `parseFlags` struct); the function body reads only struct attributes, never string keys. Construct the FlagView with the command's spec (`FlagView(flags, spec=SPECS["grep"])`) so a typo in a flag name raises KeyError instead of silently reading as False/None.
- **Never annotate anything as `object`** — not a parameter, not a return, not a type argument (`dict[str, object]`, `Callable[..., object]`). `object` reads as "we did not decide": it accepts bytes where JSON was meant and a PathSpec where a flag value was meant, so every use site pays for it with an isinstance chain back to the set the author had in mind. Name the real type instead:
- a parsed command-line flag is `FlagValue` (`mirage.commands.spec.types`) — `**flags: FlagValue`, `Mapping[str, FlagValue]`, `FlagValue | None` for a single raw read. The TypeScript side has always called it `FlagValue` too (`commands/spec/types.ts`); keep the two spellings identical.
@@ -22,6 +22,7 @@ from mirage import MountMode, Workspace
from mirage.commands.builtin.redis._provision import (file_read_provision,
head_tail_provision,
metadata_provision)
from mirage.commands.config import CommandOpts
from mirage.resource.redis import RedisResource
from mirage.types import PathSpec
@@ -196,17 +197,20 @@ async def main() -> None:
]
accessor = resource.accessor
read_cost = await file_read_provision(accessor, paths, command="cat")
read_cost = await file_read_provision(accessor, paths, [],
CommandOpts(command="cat"))
print(" file_read_provision(accessor, [hello.txt, user.json]):")
print(f" network_read = {read_cost.network_read} bytes "
f"({read_cost.read_ops} reads)")
print(f" precision = {read_cost.precision}")
head_cost = await head_tail_provision(accessor, paths, command="head -n 1")
head_cost = await head_tail_provision(accessor, paths, [],
CommandOpts(command="head -n 1"))
print(" head_tail_provision(...) — Redis fetches full value regardless:")
print(f" network_read = {head_cost.network_read} bytes")
meta_cost = await metadata_provision(accessor, paths, command="stat")
meta_cost = await metadata_provision(accessor, paths, [],
CommandOpts(command="stat"))
print(" metadata_provision(...) — stat/ls/find cost zero network bytes:")
print(f" network_read = {meta_cost.network_read} bytes")
print(f" read_ops = {meta_cost.read_ops}")
+26
View File
@@ -610,6 +610,32 @@
"stdout": "CHANGELOG.md\nguides\npolicies\n",
"stderr": ""
}
},
{
"id": "chroma_find_empty",
"seq": 630054,
"targets": [
"chroma"
],
"command": "find /knowledge/ -empty",
"expect": {
"exit": 0,
"stdout": "",
"stderr": ""
}
},
{
"id": "chroma_find_empty_composes_with_name",
"seq": 630055,
"targets": [
"chroma"
],
"command": "find /knowledge/ -name '*.md' -not -empty",
"expect": {
"exit": 0,
"stdout": "/knowledge/CHANGELOG.md\n/knowledge/guides/auth.md\n/knowledge/guides/quickstart.md\n/knowledge/policies/archived.md\n/knowledge/policies/privacy.md\n/knowledge/policies/refunds.md\n",
"stderr": ""
}
}
]
}
+26
View File
@@ -142,6 +142,32 @@
"stdout": "/repo/docs/architecture.md\n/repo/src/auth/step_1.py\n/repo/src/route/step_3.py\n",
"stderr": ""
}
},
{
"id": "github_find_empty",
"seq": 530012,
"targets": [
"github"
],
"command": "find /repo -empty",
"expect": {
"exit": 0,
"stdout": "",
"stderr": ""
}
},
{
"id": "github_find_empty_composes_with_name",
"seq": 530013,
"targets": [
"github"
],
"command": "find /repo/docs -name '*.md' -not -empty",
"expect": {
"exit": 0,
"stdout": "/repo/docs/architecture.md\n/repo/docs/contributing.md\n/repo/docs/release.md\n",
"stderr": ""
}
}
]
}
+6
View File
@@ -9,8 +9,10 @@
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"dropbox",
"dropbox-root",
@@ -41,8 +43,10 @@
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"dropbox",
"dropbox-root",
@@ -73,8 +77,10 @@
"redis",
"opfs",
"s3",
"databricks",
"gridfs",
"s3-prefix",
"databricks-prefix",
"gridfs-prefix",
"dropbox",
"dropbox-root",
+12 -19
View File
@@ -13,14 +13,14 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.box import BoxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.box.narrow import narrow_scope
from mirage.commands.builtin.generic.grep import grep as generic_grep
from mirage.commands.builtin.generic_bind.adapter import bound_op
from mirage.commands.builtin.grep_helper import pattern_arg
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.box.read import read as _read
from mirage.core.box.read import stream as _stream
from mirage.core.box.readdir import readdir as _readdir
@@ -30,16 +30,9 @@ from mirage.types import PathSpec
@command("grep", resource="box", spec=SPECS["grep"])
async def grep(
accessor: BoxAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["grep"])
async def grep(accessor: BoxAccessor, paths: list[PathSpec], texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["grep"])
pattern = pattern_arg(texts, fl)
resolved: list[PathSpec] = []
@@ -49,7 +42,7 @@ async def grep(
# zero counts (-c) from files a narrowed superset would never visit.
resolved, used_search = await narrow_scope(
accessor,
index,
opts.index,
paths,
pattern,
fixed_string=fl.as_bool("F"),
@@ -63,10 +56,10 @@ async def grep(
return await generic_grep(
resolved,
texts,
flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(_read, accessor, index),
read_stream=bound_op(_stream, accessor, index),
stdin=stdin,
opts.flags,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(_read, accessor, opts.index),
read_stream=bound_op(_stream, accessor, opts.index),
stdin=opts.stdin,
)
+12 -19
View File
@@ -15,11 +15,11 @@
from collections.abc import Mapping
from mirage.accessor.box import BoxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.box.narrow import narrow_scope
from mirage.commands.builtin.generic.rg import rg as generic_rg
from mirage.commands.builtin.generic_bind.adapter import bound_op
from mirage.commands.builtin.grep_helper import pattern_arg
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
@@ -67,26 +67,19 @@ def _keep_visible(
@command("rg", resource="box", spec=SPECS["rg"])
async def rg(
accessor: BoxAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["rg"])
async def rg(accessor: BoxAccessor, paths: list[PathSpec], texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["rg"])
pattern_str = pattern_arg(texts, fl)
run_flags: Mapping[str, FlagValue] = flags
run_flags: Mapping[str, FlagValue] = opts.flags
if paths:
# -v needs the walk (a narrowed superset hides fully non-matching
# files whose every line matches inverted); --type/--glob keep the
# walk so their file filtering stays in one place.
narrowed, used_search = await narrow_scope(
accessor,
index,
opts.index,
paths,
pattern_str,
fixed_string=fl.as_bool("F"),
@@ -103,16 +96,16 @@ async def rg(
# arrive as explicit operands, so force the label flag -- unless
# -I suppresses labels.
if not fl.as_bool("args_I"):
run_flags = {**flags, "H": True}
run_flags = {**opts.flags, "H": True}
paths = narrowed
return await generic_rg(
paths,
texts,
run_flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(_read, accessor, index),
read_stream=bound_op(_stream, accessor, index),
stdin=stdin,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(_read, accessor, opts.index),
read_stream=bound_op(_stream, accessor, opts.index),
stdin=opts.stdin,
)
+27 -41
View File
@@ -1,28 +1,28 @@
from dataclasses import replace
from functools import partial
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.chroma.io import resolve_glob
from mirage.commands.builtin.generic.find import find as generic_find
from mirage.commands.builtin.generic.find import find_generic
from mirage.commands.builtin.utils.output import format_records
from mirage.commands.builtin.utils.paths import default_paths
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.core.chroma.find import find as find_core
from mirage.core.chroma.stat import stat as stat_core
from mirage.core.chroma.stat import stat_light
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import LinkView, StatPath
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_prefix_of
def _is_bare_name(texts: tuple[str, ...]) -> bool:
def _is_bare_name(texts: list[str]) -> bool:
return bool(texts) and not texts[0].startswith("-") and texts[0] not in (
"(", ")", "!")
def _default_name(name: str | None, texts: tuple[str, ...]) -> str | None:
def _default_name(name: str | None, texts: list[str]) -> str | None:
if name is not None:
return name
if _is_bare_name(texts):
@@ -30,9 +30,9 @@ def _default_name(name: str | None, texts: tuple[str, ...]) -> str | None:
return None
def _expr_texts(texts: tuple[str, ...]) -> tuple[str, ...]:
def _expr_texts(texts: list[str]) -> list[str]:
if _is_bare_name(texts):
return ()
return []
return texts
@@ -54,42 +54,28 @@ async def _normalize_find_output(
async def find(
accessor,
paths: list[PathSpec],
*texts: str,
name: str | None = None,
type: str | None = None,
maxdepth: str | None = None,
size: str | None = None,
mtime: str | None = None,
iname: str | None = None,
path: str | None = None,
mindepth: str | None = None,
index: IndexCacheStore,
cwd: PathSpec | None = None,
L: bool = False,
links: LinkView | None = None,
stat_path: StatPath | None = None,
**_extra: FlagValue,
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
paths = default_paths(paths, cwd)
paths = await resolve_glob(accessor, paths, index)
paths = default_paths(paths, opts.cwd)
paths = await resolve_glob(accessor, paths, opts.index)
search_path = paths[0]
stat_fn = (partial(stat_core, accessor, index=index) if mtime is not None
else partial(stat_light, accessor, index=index))
stdout, io = await generic_find(paths,
fl = FlagView(opts.flags, spec=SPECS["find"])
# Push-down choices: a bare word acts as the -name filter, and the
# heavier per-document stat is only paid when -mtime needs times.
bag = dict(opts.flags)
default_name = _default_name(fl.as_str("name"), texts)
if default_name is not None:
bag["name"] = default_name
stat_fn = (partial(stat_core, accessor, index=opts.index)
if fl.as_str("mtime") is not None else partial(
stat_light, accessor, index=opts.index))
stdout, io = await find_generic(paths,
_expr_texts(texts),
replace(opts, flags=bag),
find_core=partial(find_core,
accessor,
index=index),
stat=stat_fn,
name=_default_name(name, texts),
type=type,
size=size,
mtime=mtime,
maxdepth=maxdepth,
iname=iname,
path=path,
mindepth=mindepth,
links=links,
stat_path=stat_path,
follow=L)
index=opts.index),
stat=stat_fn)
return await _normalize_find_output(stdout, search_path), io
+15 -15
View File
@@ -1,9 +1,9 @@
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.chroma.io import resolve_glob
from mirage.commands.builtin.utils.paths import default_paths
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.core.chroma import search as search_core
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -20,27 +20,27 @@ def is_mount_root(path: PathSpec) -> bool:
async def search(
accessor,
paths: list[PathSpec],
*texts: str,
top_k: str | int = 10,
index: IndexCacheStore,
cwd: PathSpec | None = None,
**_extra: FlagValue,
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["search"])
if not texts:
raise ValueError("search: query is required")
query = texts[0]
target_paths = default_paths(paths, cwd)
target_paths = default_paths(paths, opts.cwd)
mount_prefix = mount_prefix_of(
target_paths[0].virtual,
target_paths[0].resource_path) if target_paths else ""
if any(is_mount_root(path) for path in target_paths):
resolved_paths: list[PathSpec] = []
else:
resolved_paths = await resolve_glob(accessor, target_paths, index)
output = await search_core.search_segments(accessor,
query,
resolved_paths,
index,
top_k=int(top_k),
mount_prefix=mount_prefix)
resolved_paths = await resolve_glob(accessor, target_paths, opts.index)
top_k = fl.as_int("top_k")
output = await search_core.search_segments(
accessor,
query,
resolved_paths,
opts.index,
top_k=top_k if top_k is not None else 10,
mount_prefix=mount_prefix)
return output, IOResult()
+7 -14
View File
@@ -1,4 +1,3 @@
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic.cat import cat_generic
from mirage.commands.builtin.generic_bind import CommandIO
from mirage.commands.builtin.generic_bind.adapter import (bound_op,
@@ -8,7 +7,6 @@ from mirage.commands.builtin.generic_bind.builders.common import \
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -22,20 +20,15 @@ def make_cat(ops: CommandIO):
"""
@command("cat", resource="dify", spec=SPECS["cat"])
async def cat(
accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
async def cat(accessor, paths: list[PathSpec], texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await cat_generic(resolved,
list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_stream, accessor, index),
opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_stream, accessor,
opts.index),
local=ops.local)
return cat
+19 -29
View File
@@ -1,6 +1,6 @@
from dataclasses import replace
from functools import partial
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.dify.io import resolve_glob
from mirage.commands.builtin.generic.find import find_generic
from mirage.commands.builtin.utils.output import format_records
@@ -8,22 +8,21 @@ from mirage.commands.builtin.utils.paths import default_paths
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.dify.find import find as find_core
from mirage.core.dify.stat import stat as stat_core
from mirage.core.dify.stat import stat_light
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import LinkView, StatPath
from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_prefix_of
def _is_bare_name(texts: tuple[str, ...]) -> bool:
def _is_bare_name(texts: list[str]) -> bool:
return bool(texts) and not texts[0].startswith("-") and texts[0] not in (
"(", ")", "!")
def _default_name(name: str | None, texts: tuple[str, ...]) -> str | None:
def _default_name(name: str | None, texts: list[str]) -> str | None:
if name is not None:
return name
if _is_bare_name(texts):
@@ -31,9 +30,9 @@ def _default_name(name: str | None, texts: tuple[str, ...]) -> str | None:
return None
def _expr_texts(texts: tuple[str, ...]) -> tuple[str, ...]:
def _expr_texts(texts: list[str]) -> list[str]:
if _is_bare_name(texts):
return ()
return []
return texts
@@ -52,36 +51,27 @@ async def _normalize_find_output(
@command("find", resource="dify", spec=SPECS["find"])
async def find(
accessor,
paths: list[PathSpec],
*texts: str,
index: IndexCacheStore,
cwd: PathSpec | None = None,
links: LinkView | None = None,
stat_path: StatPath | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
paths = default_paths(paths, cwd)
paths = await resolve_glob(accessor, paths, index)
async def find(accessor, paths: list[PathSpec], texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
paths = default_paths(paths, opts.cwd)
paths = await resolve_glob(accessor, paths, opts.index)
search_path = paths[0]
fl = FlagView(flags, spec=SPECS["find"])
fl = FlagView(opts.flags, spec=SPECS["find"])
# Push-down choices: a bare word acts as the -name filter, and the
# heavier per-document stat is only paid when -mtime needs times.
bag = dict(flags)
bag = dict(opts.flags)
default_name = _default_name(fl.as_str("name"), texts)
if default_name is not None:
bag["name"] = default_name
stat_fn = (partial(stat_core, accessor, index=index) if fl.as_str("mtime")
is not None else partial(stat_light, accessor, index=index))
stat_fn = (partial(stat_core, accessor, index=opts.index)
if fl.as_str("mtime") is not None else partial(
stat_light, accessor, index=opts.index))
stdout, io = await find_generic(paths,
list(_expr_texts(texts)),
CommandOpts(flags=bag),
_expr_texts(texts),
replace(opts, flags=bag),
find_core=partial(find_core,
accessor,
index=index),
stat=stat_fn,
stat_path=stat_path,
links=links)
index=opts.index),
stat=stat_fn)
return await _normalize_find_output(stdout, search_path), io
+17 -19
View File
@@ -1,9 +1,9 @@
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.dify.io import resolve_glob
from mirage.commands.builtin.utils.paths import default_paths
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.core.dify import search as search_core
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -20,31 +20,29 @@ def is_mount_root(path: PathSpec) -> bool:
async def search(
accessor,
paths: list[PathSpec],
*texts: str,
method: str = "semantic",
top_k: str | int = 10,
threshold: str | float = 0.0,
index: IndexCacheStore,
cwd: PathSpec | None = None,
**_extra: FlagValue,
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["search"])
if not texts:
raise ValueError("search: query is required")
query = texts[0]
target_paths = default_paths(paths, cwd)
target_paths = default_paths(paths, opts.cwd)
mount_prefix = mount_prefix_of(
target_paths[0].virtual,
target_paths[0].resource_path) if target_paths else ""
if any(is_mount_root(path) for path in target_paths):
resolved_paths: list[PathSpec] = []
else:
resolved_paths = await resolve_glob(accessor, target_paths, index)
output = await search_core.search_segments(accessor,
query,
resolved_paths,
index,
method=method,
top_k=int(top_k),
threshold=float(threshold),
mount_prefix=mount_prefix)
resolved_paths = await resolve_glob(accessor, target_paths, opts.index)
top_k = fl.as_int("top_k")
output = await search_core.search_segments(
accessor,
query,
resolved_paths,
opts.index,
method=fl.as_str("method") or "semantic",
top_k=top_k if top_k is not None else 10,
threshold=fl.as_float("threshold") or 0.0,
mount_prefix=mount_prefix)
return output, IOResult()
+21 -29
View File
@@ -13,18 +13,19 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import logging
from dataclasses import replace
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.discord._provision import file_read_provision
from mirage.commands.builtin.discord.io import resolve_glob
from mirage.commands.builtin.generic.grep import grep as generic_grep
from mirage.commands.builtin.generic_bind.adapter import bound_op
from mirage.commands.builtin.grep_helper import pattern_arg
from mirage.commands.builtin.utils.output import format_records
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.discord.channels import list_channels
from mirage.core.discord.entry import channel_dirname
from mirage.core.discord.formatters import format_grep_results
@@ -41,39 +42,30 @@ from mirage.utils.key_prefix import mount_prefix_of
logger = logging.getLogger(__name__)
async def grep_provision(
accessor: DiscordAccessor,
paths: list[PathSpec],
*texts: str,
**_extra: FlagValue,
) -> ProvisionResult:
return await file_read_provision(
accessor, paths,
"grep " + " ".join(texts + tuple(str(p) for p in paths)))
async def grep_provision(accessor: DiscordAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> ProvisionResult:
line = "grep " + " ".join(list(texts) + [str(p) for p in paths])
return await file_read_provision(accessor, paths, texts,
replace(opts, command=line))
@command("grep",
resource="discord",
spec=SPECS["grep"],
provision=grep_provision)
async def grep(
accessor: DiscordAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["grep"])
async def grep(accessor: DiscordAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["grep"])
pattern = pattern_arg(texts, fl)
max_count = fl.as_int("m")
pushdown_warnings: list[str] = []
if paths and pattern is not None and "\n" not in pattern:
scope = await detect_scope(paths[0], index)
scope = await detect_scope(paths[0], opts.index)
if scope.level in ("messages", "file_blob", "date"):
coalesced = await coalesce_scopes(paths, index)
coalesced = await coalesce_scopes(paths, opts.index)
if coalesced is not None:
scope = coalesced
@@ -123,16 +115,16 @@ async def grep(
"falling back to per-file scan", exc)
resolved = await resolve_glob(accessor, paths,
index=index) if paths else []
index=opts.index) if paths else []
out, io = await generic_grep(
resolved,
texts,
flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(discord_read, accessor, index),
opts.flags,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(discord_read, accessor, opts.index),
read_stream=None,
stdin=stdin,
stdin=opts.stdin,
)
if pushdown_warnings:
extra = ("\n".join(pushdown_warnings) + "\n").encode()
+16 -27
View File
@@ -13,9 +13,9 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import json
from dataclasses import replace
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.discord._provision import file_read_provision
from mirage.commands.builtin.discord.io import IO
from mirage.commands.builtin.generic.head import head as generic_head
@@ -26,7 +26,6 @@ from mirage.commands.builtin.generic_bind.builders.common import \
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.core.discord._client import discord_get
from mirage.core.discord.history import date_to_snowflake
from mirage.core.discord.read import read as discord_read
@@ -36,37 +35,28 @@ from mirage.provision.types import ProvisionResult
from mirage.types import PathSpec
async def head_provision(
accessor: DiscordAccessor,
paths: list[PathSpec],
*texts: str,
**_extra: FlagValue,
) -> ProvisionResult:
return await file_read_provision(
accessor, paths,
"head " + " ".join(p.virtual if isinstance(p, PathSpec) else p
for p in paths))
async def head_provision(accessor: DiscordAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> ProvisionResult:
line = "head " + " ".join(p.virtual for p in paths)
return await file_read_provision(accessor, paths, texts,
replace(opts, command=line))
@command("head",
resource="discord",
spec=SPECS["head"],
provision=head_provision)
async def head(
accessor: DiscordAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def head(accessor: DiscordAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
try:
parsed = parse_flags(flags)
parsed = parse_flags(opts.flags)
except ValueError as exc:
return None, IOResult(exit_code=1, stderr=str(exc).encode())
lines = parsed.lines if parsed.lines is not None else 10
if paths:
scope = await detect_scope(paths[0], index)
scope = await detect_scope(paths[0], opts.index)
# Smart head: fetch only first N messages for a single date.
if (len(paths) == 1 and scope.level == "file" and scope.channel_id
@@ -87,8 +77,7 @@ async def head(
json.dumps(m, ensure_ascii=False, separators=(",", ":"))
for m in msgs) + "\n"
return generic_head(jsonl.encode(), n=lines), IOResult()
resolved = await resolve_or_empty(IO, accessor, paths, index)
return await head_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(IO.stat, accessor, index),
bound_op(discord_read, accessor, index))
resolved = await resolve_or_empty(IO, accessor, paths, opts.index)
return await head_generic(resolved, list(texts), opts,
bound_op(IO.stat, accessor, opts.index),
bound_op(discord_read, accessor, opts.index))
+14 -20
View File
@@ -15,16 +15,16 @@
import logging
from mirage.accessor.discord import DiscordAccessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.discord.io import resolve_glob
from mirage.commands.builtin.generic.rg import rg as generic_rg
from mirage.commands.builtin.generic_bind.adapter import bound_op
from mirage.commands.builtin.grep_helper import pattern_arg
from mirage.commands.builtin.utils.output import format_records
from mirage.commands.config import CommandOpts
from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.discord.channels import list_channels
from mirage.core.discord.entry import channel_dirname
from mirage.core.discord.formatters import format_grep_results
@@ -41,16 +41,10 @@ logger = logging.getLogger(__name__)
@command("rg", resource="discord", spec=SPECS["rg"])
async def rg(
accessor: DiscordAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["rg"])
async def rg(accessor: DiscordAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["rg"])
pattern_str = pattern_arg(texts, fl)
if pattern_str is None:
raise UsageError("rg: usage: rg [flags] pattern [path]")
@@ -58,9 +52,9 @@ async def rg(
pushdown_warnings: list[str] = []
if paths and "\n" not in pattern_str:
scope = await detect_scope(paths[0], index)
scope = await detect_scope(paths[0], opts.index)
if scope.level in ("messages", "file_blob", "date"):
coalesced = await coalesce_scopes(paths, index)
coalesced = await coalesce_scopes(paths, opts.index)
if coalesced is not None:
scope = coalesced
@@ -110,16 +104,16 @@ async def rg(
"falling back to per-file scan", exc)
resolved = await resolve_glob(accessor, paths,
index=index) if paths else []
index=opts.index) if paths else []
stdout, io = await generic_rg(
resolved,
texts,
flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(discord_read, accessor, index),
opts.flags,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(discord_read, accessor, opts.index),
read_stream=None,
stdin=stdin,
stdin=opts.stdin,
)
if pushdown_warnings:
io.stderr = ("\n".join(pushdown_warnings) + "\n").encode()
+13 -19
View File
@@ -13,14 +13,14 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.dropbox.narrow import narrow_scope
from mirage.commands.builtin.generic.grep import grep as generic_grep
from mirage.commands.builtin.generic_bind.adapter import bound_op
from mirage.commands.builtin.grep_helper import pattern_arg
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.dropbox.read import read as _read
from mirage.core.dropbox.read import stream as _stream
from mirage.core.dropbox.readdir import readdir as _readdir
@@ -30,16 +30,10 @@ from mirage.types import PathSpec
@command("grep", resource="dropbox", spec=SPECS["grep"])
async def grep(
accessor: DropboxAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["grep"])
async def grep(accessor: DropboxAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["grep"])
pattern = pattern_arg(texts, fl)
resolved: list[PathSpec] = []
@@ -50,7 +44,7 @@ async def grep(
# visit.
resolved, used_search = await narrow_scope(
accessor,
index,
opts.index,
paths,
pattern,
fixed_string=fl.as_bool("F"),
@@ -64,10 +58,10 @@ async def grep(
return await generic_grep(
resolved,
texts,
flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(_read, accessor, index),
read_stream=bound_op(_stream, accessor, index),
stdin=stdin,
opts.flags,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(_read, accessor, opts.index),
read_stream=bound_op(_stream, accessor, opts.index),
stdin=opts.stdin,
)
+13 -19
View File
@@ -15,11 +15,11 @@
from collections.abc import Mapping
from mirage.accessor.dropbox import DropboxAccessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.dropbox.narrow import narrow_scope
from mirage.commands.builtin.generic.rg import rg as generic_rg
from mirage.commands.builtin.generic_bind.adapter import bound_op
from mirage.commands.builtin.grep_helper import pattern_arg
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
@@ -67,26 +67,20 @@ def _keep_visible(
@command("rg", resource="dropbox", spec=SPECS["rg"])
async def rg(
accessor: DropboxAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["rg"])
async def rg(accessor: DropboxAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["rg"])
pattern_str = pattern_arg(texts, fl)
run_flags: Mapping[str, FlagValue] = flags
run_flags: Mapping[str, FlagValue] = opts.flags
if paths:
# -v needs the walk (a narrowed superset hides fully non-matching
# files whose every line matches inverted); --type/--glob keep the
# walk so their file filtering stays in one place.
narrowed, used_search = await narrow_scope(
accessor,
index,
opts.index,
paths,
pattern_str,
fixed_string=fl.as_bool("F"),
@@ -103,16 +97,16 @@ async def rg(
# arrive as explicit operands, so force the label flag —
# unless -I suppresses labels.
if not fl.as_bool("args_I"):
run_flags = {**flags, "H": True}
run_flags = {**opts.flags, "H": True}
paths = narrowed
return await generic_rg(
paths,
texts,
run_flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(_read, accessor, index),
read_stream=bound_op(_stream, accessor, index),
stdin=stdin,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(_read, accessor, opts.index),
read_stream=bound_op(_stream, accessor, opts.index),
stdin=opts.stdin,
)
+28 -35
View File
@@ -12,18 +12,19 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from dataclasses import replace
from functools import partial
from mirage.accessor.email import EmailAccessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.email._provision import metadata_provision
from mirage.commands.builtin.email.io import resolve_glob
from mirage.commands.builtin.generic.find import (is_link, parse_find_args,
resolve_start, walk_find)
from mirage.commands.builtin.utils.output import format_records
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_headers
from mirage.core.email.readdir import _date_bucket, _sanitize
from mirage.core.email.readdir import readdir as _readdir
@@ -31,7 +32,6 @@ from mirage.core.email.scope import extract_folder
from mirage.core.email.search import search_messages
from mirage.core.email.stat import stat as _stat
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import LinkView, StatPath
from mirage.provision.types import ProvisionResult
from mirage.types import PathSpec
from mirage.utils.fnmatch import fnmatch
@@ -46,14 +46,12 @@ def _is_folder_level(paths: list[PathSpec]) -> bool:
return len(parts) <= 1
async def find_provision(
accessor: EmailAccessor,
paths: list[PathSpec],
*texts: str,
**_extra: FlagValue,
) -> ProvisionResult:
return await metadata_provision("find " + " ".join(
p.virtual if isinstance(p, PathSpec) else p for p in paths))
async def find_provision(accessor: EmailAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> ProvisionResult:
return await metadata_provision(
accessor, paths, texts,
replace(opts, command="find " + " ".join(p.virtual for p in paths)))
@command("find",
@@ -63,25 +61,20 @@ async def find_provision(
async def find(
accessor: EmailAccessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
name: str | None = None,
type: str | None = None,
maxdepth: str | None = None,
size: str | None = None,
mtime: str | None = None,
iname: str | None = None,
path: str | None = None,
mindepth: str | None = None,
empty: bool = False,
prefix: str = "",
index: IndexCacheStore,
L: bool = False,
links: LinkView | None = None,
stat_path: StatPath | None = None,
**_extra: FlagValue,
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
paths = await resolve_glob(accessor, paths, index)
fl = FlagView(opts.flags, spec=SPECS["find"])
name = fl.as_str("name")
type = fl.as_str("type")
maxdepth = fl.as_str("maxdepth")
size = fl.as_str("size")
mtime = fl.as_str("mtime")
iname = fl.as_str("iname")
path = fl.as_str("path")
mindepth = fl.as_str("mindepth")
empty = fl.as_bool("empty")
paths = await resolve_glob(accessor, paths, opts.index)
# A pure -name search at folder level pushes the subject query down to
# IMAP search instead of walking every message; any other predicate
# falls through to the local walk so nothing is silently dropped.
@@ -92,7 +85,7 @@ async def find(
search_prefix = mount_prefix_of(p0.virtual, p0.resource_path)
return await _find_server_side(accessor, paths, name, search_prefix)
args = parse_find_args(texts,
args = parse_find_args(tuple(texts),
name=name,
type=type,
size=size,
@@ -111,18 +104,18 @@ async def find(
# directory has a subtree to walk.
start = await resolve_start(search,
args,
stat_path,
is_link=is_link(links, search))
opts.stat_path,
is_link=is_link(opts.links, search))
if not start.walk:
results.extend(start.results)
continue
results.extend(await walk_find(search,
readdir=partial(_readdir, accessor),
stat=partial(_stat, accessor),
index=index,
index=opts.index,
args=args,
links=links,
follow=L))
links=opts.links,
follow=fl.as_bool("L")))
return format_records(results), IOResult()
+20 -30
View File
@@ -12,8 +12,9 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from dataclasses import replace
from mirage.accessor.email import EmailAccessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.aggregators import prefix_aggregate
from mirage.commands.builtin.email._provision import file_read_provision
from mirage.commands.builtin.email.io import resolve_glob
@@ -23,9 +24,10 @@ from mirage.commands.builtin.grep_helper import (compile_pattern,
grep_count_has_matches,
grep_lines, pattern_arg)
from mirage.commands.builtin.utils.output import format_records
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.email.read import read as email_read
from mirage.core.email.readdir import readdir as _readdir
from mirage.core.email.scope import EmailScope, detect_scope
@@ -37,18 +39,12 @@ from mirage.types import PathSpec
from mirage.utils.key_prefix import mount_prefix_of
async def grep_provision(
accessor: EmailAccessor,
paths: list[PathSpec],
*texts: str,
index: IndexCacheStore,
**_extra: FlagValue,
) -> ProvisionResult:
return await file_read_provision(
accessor,
paths,
"grep " + " ".join(texts + tuple(str(p) for p in paths)),
index=index)
async def grep_provision(accessor: EmailAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> ProvisionResult:
line = "grep " + " ".join(list(texts) + [str(p) for p in paths])
return await file_read_provision(accessor, paths, texts,
replace(opts, command=line))
@command("grep",
@@ -56,16 +52,10 @@ async def grep_provision(
spec=SPECS["grep"],
provision=grep_provision,
aggregate=prefix_aggregate)
async def grep(
accessor: EmailAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["grep"])
async def grep(accessor: EmailAccessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["grep"])
pattern = pattern_arg(texts, fl)
if paths and pattern is not None and "\n" not in pattern and (
@@ -86,16 +76,16 @@ async def grep(
o=fl.as_bool("o"),
max_count=fl.as_int("m"))
resolved = await resolve_glob(accessor, paths, index) if paths else []
resolved = await resolve_glob(accessor, paths, opts.index) if paths else []
return await generic_grep(
resolved,
texts,
flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(email_read, accessor, index),
opts.flags,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(email_read, accessor, opts.index),
read_stream=None,
stdin=stdin,
stdin=opts.stdin,
)
+11 -18
View File
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.email import EmailAccessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.email.io import resolve_glob
from mirage.commands.builtin.generic.rg import rg as generic_rg
from mirage.commands.builtin.generic_bind.adapter import bound_op
@@ -21,10 +20,11 @@ from mirage.commands.builtin.grep_helper import (compile_pattern,
grep_count_has_matches,
grep_lines, pattern_arg)
from mirage.commands.builtin.utils.output import format_records
from mirage.commands.config import CommandOpts
from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.core.email._client import fetch_message
from mirage.core.email.read import read as email_read
from mirage.core.email.readdir import readdir as _readdir
@@ -38,16 +38,9 @@ from mirage.utils.key_prefix import mount_prefix_of
@command("rg", resource="email", spec=SPECS["rg"])
async def rg(
accessor: EmailAccessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["rg"])
async def rg(accessor: EmailAccessor, paths: list[PathSpec], texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["rg"])
pattern_str = pattern_arg(texts, fl)
if pattern_str is None:
raise UsageError("rg: usage: rg [flags] pattern [path]")
@@ -112,14 +105,14 @@ async def rg(
return b"", IOResult(exit_code=1)
return format_records(all_results), IOResult()
resolved = await resolve_glob(accessor, paths, index) if paths else []
resolved = await resolve_glob(accessor, paths, opts.index) if paths else []
return await generic_rg(
resolved,
texts,
flags,
readdir=bound_op(_readdir, accessor, index),
stat=bound_op(_stat, accessor, index),
read_bytes=bound_op(email_read, accessor, index),
opts.flags,
readdir=bound_op(_readdir, accessor, opts.index),
stat=bound_op(_stat, accessor, opts.index),
read_bytes=bound_op(email_read, accessor, opts.index),
read_stream=None,
stdin=stdin,
stdin=opts.stdin,
)
+11 -10
View File
@@ -15,12 +15,13 @@
import math
from typing import Any
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic_bind.provision import pure_provision
from mirage.commands.builtin.utils.stream import _read_stdin_async
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -52,14 +53,14 @@ def _eval_bc(expression: str, use_math: bool) -> str:
@command("bc", resource=None, spec=SPECS["bc"], provision=pure_provision)
async def bc(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
stdin: ByteSource | None = None,
args_l: bool = False,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
raw = await _read_stdin_async(stdin)
fl = FlagView(opts.flags, spec=SPECS["bc"])
use_math = fl.as_bool("args_l")
raw = await _read_stdin_async(opts.stdin)
if raw is None:
raw = b""
lines = raw.decode(errors="replace").strip().splitlines()
@@ -67,7 +68,7 @@ async def bc(
for line in lines:
line = line.strip()
if line:
results.append(_eval_bc(line, args_l))
results.append(_eval_bc(line, use_math))
if not results:
return b"", IOResult()
return ("\n".join(results) + "\n").encode(), IOResult()
+24 -29
View File
@@ -12,16 +12,15 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.utils.http import (HttpConnectError,
_http_form_request,
_http_request)
from mirage.commands.config import CommandOpts
from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
from mirage.utils.errors import (WALK_ERRORS, OperationNotSupportedError,
@@ -36,13 +35,14 @@ EXIT_HTTP_ERROR = 22
EXIT_WRITE = 23
def _resolve_target(o: str | PathSpec, cwd: PathSpec | None) -> PathSpec:
def _resolve_target(o: str | PathSpec, cwd: PathSpec | str | None) -> PathSpec:
if isinstance(o, PathSpec):
return o
if o.startswith("/"):
path = o
else:
base = cwd.virtual.rstrip("/") if cwd is not None else ""
base = (cwd.virtual if isinstance(cwd, PathSpec) else
(cwd or "")).rstrip("/")
path = f"{base}/{o}" if base else f"/{o}"
last_slash = path.rfind("/")
directory = path[:last_slash + 1] if last_slash >= 0 else "/"
@@ -54,24 +54,19 @@ def _resolve_target(o: str | PathSpec, cwd: PathSpec | None) -> PathSpec:
@command("curl", resource=None, spec=SPECS["curl"])
async def curl(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
H: str | None = None,
A: str | None = None,
X: str | None = None,
d: str | None = None,
F: str | None = None,
o: PathSpec | str | None = None,
L: bool = False,
fail: bool = False,
s: bool = False,
S: bool = False,
dispatch: Callable[..., Any] | None = None,
cwd: PathSpec | None = None,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["curl"])
H = fl.as_str("H")
A = fl.as_str("A")
X = fl.as_str("X")
d = fl.as_str("d")
F = fl.as_str("F")
o = fl.raw("o")
L = fl.as_bool("L")
headers: dict[str, str] = {}
if H:
k, _, v = H.partition(":")
@@ -84,7 +79,7 @@ async def curl(
"curl: try 'curl --help' or 'curl --manual' for more information",
exit_code=EXIT_NO_URL)
# -s silences the message, -S puts it back. Neither changes the exit code.
quiet = s and not S
quiet = fl.as_bool("s") and not fl.as_bool("S")
try:
if F:
method = X or "POST"
@@ -108,18 +103,18 @@ async def curl(
f"{exc.port}: Could not connect to server\n").encode()
return None, IOResult(exit_code=EXIT_CONNECT, stderr=err)
# Only -f makes an error status an error, and then nothing is written.
if fail and resp.is_error:
if fl.as_bool("fail") and resp.is_error:
err = b"" if quiet else (
f"curl: ({EXIT_HTTP_ERROR}) The requested URL returned error: "
f"{resp.status}\n").encode()
return None, IOResult(exit_code=EXIT_HTTP_ERROR, stderr=err)
result = resp.body
if o is not None:
if isinstance(o, (PathSpec, str)):
o_str = o.virtual if isinstance(o, PathSpec) else o
if dispatch is not None:
scope = _resolve_target(o, cwd)
if opts.dispatch is not None:
scope = _resolve_target(o, opts.cwd)
try:
await dispatch("write", scope, data=result)
await opts.dispatch("write", scope, data=result)
# WALK_ERRORS is the shared recoverable set (every filesystem error
# plus the ValueError store backends raise for "not a directory"),
# so a missing parent cannot escape the way it did when this caught
+12 -13
View File
@@ -15,11 +15,12 @@
import email.utils
from datetime import datetime, timezone
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic_bind.provision import pure_provision
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import CommandName, FlagValue
from mirage.commands.spec.types import CommandName, FlagView
from mirage.commands.spec.usage import extra_operand_error
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -27,16 +28,14 @@ from mirage.types import PathSpec
@command("date", resource=None, spec=SPECS["date"], provision=pure_provision)
async def date(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
u: bool = False,
d: str | None = None,
args_I: bool = False,
R: bool = False,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["date"])
u = fl.as_bool("u")
d = fl.as_str("d")
if len(texts) > 1:
raise extra_operand_error(CommandName.DATE, texts[1])
if d is not None:
@@ -52,9 +51,9 @@ async def date(
if t.startswith("+"):
fmt = t[1:]
break
if args_I:
if fl.as_bool("args_I"):
result = dt.strftime("%Y-%m-%d")
elif R:
elif fl.as_bool("R"):
result = email.utils.format_datetime(dt)
elif fmt is not None:
result = dt.strftime(fmt)
+6 -10
View File
@@ -14,16 +14,16 @@
import re
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic_bind.provision import pure_provision
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
def _expr_eval(args: tuple[str, ...]) -> tuple[str, int]:
def _expr_eval(args: list[str]) -> tuple[str, int]:
if len(args) == 3 and args[1] == ":":
pattern = args[2]
m = re.match(pattern, args[0])
@@ -86,13 +86,9 @@ def _expr_eval(args: tuple[str, ...]) -> tuple[str, int]:
@command("expr", resource=None, spec=SPECS["expr"], provision=pure_provision)
async def expr(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
**_extra: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def expr(accessor: Accessor, paths: list[PathSpec] | None,
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not texts:
return b"\n", IOResult(exit_code=2)
result, exit_code = _expr_eval(texts)
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from dataclasses import dataclass, field
from typing import Any, Callable, Literal, TypeAlias
from typing import Any, Literal, TypeAlias
from mirage.commands.builtin.utils.paths import resolve_script
from mirage.commands.builtin.utils.stream import _read_stdin_async
@@ -21,7 +21,7 @@ from mirage.io.types import ByteSource, CommandOutput, IOResult
from mirage.runtime.base import Runtime
from mirage.runtime.language import LanguageRuntime
from mirage.runtime.python.base import PythonRuntime
from mirage.runtime.types import RunArgs, RunResult
from mirage.runtime.types import DispatchFn, RunArgs, RunResult
from mirage.types import PathSpec
@@ -159,11 +159,11 @@ class Source:
async def resolve_source(
label: str,
paths: list[PathSpec] | None,
texts: tuple[str, ...],
texts: list[str],
payload: str | None,
stdin: ByteSource | None,
dispatch: Callable[..., Any] | None,
cwd: PathSpec | None,
dispatch: DispatchFn | None,
cwd: PathSpec | str | None,
exec_allowed: bool,
module: str | None = None,
argv0_rules: Argv0Rules = Argv0Rules(),
@@ -179,12 +179,13 @@ async def resolve_source(
Args:
label (str): the command name used in error messages.
paths (list[PathSpec] | None): positional path operands.
texts (tuple[str, ...]): positional text operands.
texts (list[str]): positional text operands.
payload (str | None): the -c/-e flag value, if given.
stdin (ByteSource | None): piped stdin.
dispatch (Callable[..., Any] | None): workspace dispatch for
dispatch (DispatchFn | None): workspace dispatch for
reading the script operand.
cwd (PathSpec | None): the session cwd for script resolution.
cwd (PathSpec | str | None): the session cwd for script
resolution, as ``CommandOpts.cwd`` carries it.
exec_allowed (bool): whether the root mount is in EXEC mode.
module (str | None): the -m module name, if given.
argv0_rules (Argv0Rules): what this interpreter calls itself in
+17 -25
View File
@@ -12,43 +12,35 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.general.interpreter import (resolve_source,
run_code)
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, CommandOutput
from mirage.runtime.base import Runtime
from mirage.commands.spec.types import FlagView
from mirage.io.types import CommandOutput
from mirage.types import PathSpec
async def _js(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
e: str | None = None,
module: bool = False,
stdin: ByteSource | None = None,
dispatch: Callable[..., Any] | None = None,
cwd: PathSpec | None = None,
env: dict[str, str] | None = None,
exec_allowed: bool = True,
runtime: Runtime | None = None,
runtime_unavailable: str | None = None,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> CommandOutput:
error, prepared = await resolve_source("js", paths, texts, e, stdin,
dispatch, cwd, exec_allowed)
fl = FlagView(opts.flags, spec=SPECS["js"])
error, prepared = await resolve_source("js", paths, texts, fl.as_str("e"),
opts.stdin, opts.dispatch, opts.cwd,
opts.exec_allowed)
if error is not None or prepared is None:
assert error is not None
return error
as_module = module or (prepared.script_path is not None
and prepared.script_path.virtual.endswith(".mjs"))
return await run_code("js", prepared, env, {"module": as_module}, runtime,
runtime_unavailable)
as_module = fl.as_bool("module") or (
prepared.script_path is not None
and prepared.script_path.virtual.endswith(".mjs"))
return await run_code("js", prepared, opts.env, {"module": as_module},
opts.runtime, opts.runtime_unavailable)
js = command("js", resource=None, spec=SPECS["js"])(_js)
@@ -12,52 +12,33 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from typing import Any
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.general.interpreter import (CPYTHON_ARGV0,
resolve_source,
run_code)
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, CommandOutput
from mirage.runtime.base import Runtime
from mirage.commands.spec.types import FlagView
from mirage.io.types import CommandOutput
from mirage.types import PathSpec
async def _python3(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
c: str | None = None,
m: str | None = None,
u: bool = False,
q: bool = False,
b: int = 0,
B: bool = False,
E: bool = False,
P: bool = False,
s: bool = False,
S: bool = False,
x: bool = False,
args_I: bool = False,
args_O: int = 0,
W: list[str] | None = None,
X: list[str] | None = None,
check_hash_based_pycs: str | None = None,
stdin: ByteSource | None = None,
dispatch: Callable[..., Any] | None = None,
cwd: PathSpec | None = None,
env: dict[str, str] | None = None,
exec_allowed: bool = True,
runtime: Runtime | None = None,
runtime_unavailable: str | None = None,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> CommandOutput:
error, prepared = await resolve_source("python3", paths, texts, c, stdin,
dispatch, cwd, exec_allowed, m,
CPYTHON_ARGV0, x)
fl = FlagView(opts.flags, spec=SPECS["python3"])
error, prepared = await resolve_source("python3", paths, texts,
fl.as_str("c"), opts.stdin,
opts.dispatch,
opts.cwd, opts.exec_allowed,
fl.as_str("m"), CPYTHON_ARGV0,
fl.as_bool("x"))
if error is not None or prepared is None:
assert error is not None
return error
@@ -69,20 +50,20 @@ async def _python3(
# than configuring an interpreter, so resolve_source answered it
# above.
init_flags: dict[str, Any] = {
"b": b,
"B": B,
"E": E,
"I": args_I,
"O": args_O,
"P": P,
"s": s,
"S": S,
"W": W or [],
"X": X or [],
"check_hash_based_pycs": check_hash_based_pycs,
"b": fl.as_int("b") or 0,
"B": fl.as_bool("B"),
"E": fl.as_bool("E"),
"I": fl.as_bool("args_I"),
"O": fl.as_int("args_O") or 0,
"P": fl.as_bool("P"),
"s": fl.as_bool("s"),
"S": fl.as_bool("S"),
"W": fl.as_list("W"),
"X": fl.as_list("X"),
"check_hash_based_pycs": fl.as_str("check_hash_based_pycs"),
}
return await run_code("python3", prepared, env, init_flags, runtime,
runtime_unavailable)
return await run_code("python3", prepared, opts.env, init_flags,
opts.runtime, opts.runtime_unavailable)
python3 = command("python3", resource=None, spec=SPECS["python3"])(_python3)
+12 -13
View File
@@ -12,17 +12,18 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic_bind.provision import pure_provision
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import CommandName, FlagValue
from mirage.commands.spec.types import CommandName, FlagView
from mirage.commands.spec.usage import extra_operand_error
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
def _seq_generate(texts: tuple[str, ...], separator: str, width: bool,
def _seq_generate(texts: list[str], separator: str, width: bool,
fmt: str | None) -> str:
nums = [float(t) for t in texts]
if len(nums) == 1:
@@ -53,17 +54,15 @@ def _seq_generate(texts: tuple[str, ...], separator: str, width: bool,
@command("seq", resource=None, spec=SPECS["seq"], provision=pure_provision)
async def seq(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
s: str | None = None,
w: bool = False,
f: str | None = None,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["seq"])
if len(texts) > 3:
raise extra_operand_error(CommandName.SEQ, texts[3])
separator = s if s is not None else "\n"
result = _seq_generate(texts, separator, w, f)
sep = fl.as_str("s")
separator = sep if sep is not None else "\n"
result = _seq_generate(texts, separator, fl.as_bool("w"), fl.as_str("f"))
return result.encode(), IOResult()
+14 -16
View File
@@ -12,15 +12,14 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from mirage.accessor.base import Accessor, NOOPAccessor
from mirage.accessor.base import Accessor
from mirage.commands.builtin.general.curl import _resolve_target
from mirage.commands.builtin.utils.http import HttpConnectError, _http_get
from mirage.commands.config import CommandOpts
from mirage.commands.errors import UsageError
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
from mirage.utils.errors import WALK_ERRORS
@@ -40,17 +39,16 @@ USAGE = ("wget: missing URL\n"
@command("wget", resource=None, spec=SPECS["wget"])
async def wget(
accessor: Accessor = NOOPAccessor(),
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
args_O: str | None = None,
q: bool = False,
spider: bool = False,
dispatch: Callable[..., Any] | None = None,
cwd: PathSpec | None = None,
**_extra: FlagValue,
accessor: Accessor,
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["wget"])
args_O = fl.raw("args_O")
q = fl.as_bool("q")
spider = fl.as_bool("spider")
dispatch = opts.dispatch
if not texts:
raise UsageError(USAGE, exit_code=EXIT_GENERIC)
url = texts[0]
@@ -74,7 +72,7 @@ async def wget(
return None, IOResult(stderr=err)
dest_raw: str | PathSpec
if args_O:
if isinstance(args_O, (str, PathSpec)) and args_O:
dest_raw = args_O
elif paths:
dest_raw = paths[0]
@@ -86,7 +84,7 @@ async def wget(
# truncates the -O target before it learns the response code.
data = b"" if resp.is_error else resp.body
if dispatch is not None:
scope = _resolve_target(dest_raw, cwd)
scope = _resolve_target(dest_raw, opts.cwd)
try:
await dispatch("write", scope, data=data)
# WALK_ERRORS is the shared recoverable set (every filesystem error
@@ -13,18 +13,18 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from typing import Any, Callable
from mirage.commands.builtin.generic.cmp import cmp_cmd as generic_cmp
from mirage.commands.builtin.generic.crossmount.types import CrossResult
from mirage.commands.builtin.generic.crossmount.utils import flat_scopes, relay
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
async def run_cmp(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any]) -> CrossResult:
dispatch: DispatchFn) -> CrossResult:
"""Byte-compare two files on different mounts via the shared generic.
Pure wiring: both sides are read through dispatch-relayed primitives.
@@ -32,7 +32,7 @@ async def run_cmp(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
Args:
scopes (list[PathSpec]): The two path operands.
flag_kwargs (dict): Flags parsed against the shared cmp spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
"""
fl = FlagView(flag_kwargs, spec=SPECS["cmp"])
limit = fl.as_str("n")
@@ -13,18 +13,18 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from typing import Any, Callable
from mirage.commands.builtin.generic.comm import comm as generic_comm
from mirage.commands.builtin.generic.crossmount.types import CrossResult
from mirage.commands.builtin.generic.crossmount.utils import flat_scopes, relay
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
async def run_comm(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any]) -> CrossResult:
dispatch: DispatchFn) -> CrossResult:
"""Compare two sorted files on different mounts via the generic comm.
Pure wiring: both sides are read through dispatch-relayed primitives
@@ -33,7 +33,7 @@ async def run_comm(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
Args:
scopes (list[PathSpec]): The two path operands.
flag_kwargs (dict): Flags parsed against the shared comm spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
"""
fl = FlagView(flag_kwargs, spec=SPECS["comm"])
return await generic_comm(flat_scopes(scopes),
@@ -12,7 +12,7 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from typing import Callable
from mirage.commands.builtin.generic.cp import cp as generic_cp
from mirage.commands.builtin.generic.cp import parse_cp_flags
@@ -21,13 +21,14 @@ from mirage.commands.builtin.generic.crossmount.utils import (
flat_scopes, transfer_primitives)
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec, PrimitiveCopy
async def run_cp(
scopes: list[PathSpec],
flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any],
dispatch: DispatchFn,
storage_key: Callable[[PathSpec], str] | None = None) -> CrossResult:
"""Copy operands that span mounts via the shared generic cp.
@@ -38,7 +39,7 @@ async def run_cp(
Args:
scopes (list[PathSpec]): Path operands in command-line order.
flag_kwargs (dict): Flags parsed against the shared cp spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
storage_key (Callable | None): Maps an operand to its storage
identity so two prefixes over one store compare equal.
"""
@@ -13,18 +13,18 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from typing import Any, Callable
from mirage.commands.builtin.generic.crossmount.types import CrossResult
from mirage.commands.builtin.generic.crossmount.utils import flat_scopes, relay
from mirage.commands.builtin.generic.diff import diff as generic_diff
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
async def run_diff(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any]) -> CrossResult:
dispatch: DispatchFn) -> CrossResult:
"""Diff two files on different mounts via the shared generic diff.
Pure wiring: both sides are read through dispatch-relayed primitives.
@@ -32,7 +32,7 @@ async def run_diff(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
Args:
scopes (list[PathSpec]): The two path operands.
flag_kwargs (dict): Flags parsed against the shared diff spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
"""
p = functools.partial
fl = FlagView(flag_kwargs, spec=SPECS["diff"])
@@ -13,18 +13,18 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from typing import Any, Callable
from mirage.commands.builtin.generic.crossmount.types import CrossResult
from mirage.commands.builtin.generic.crossmount.utils import flat_scopes, relay
from mirage.commands.builtin.generic.join import join_cmd as generic_join
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
async def run_join(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any]) -> CrossResult:
dispatch: DispatchFn) -> CrossResult:
"""Join two files on different mounts via the shared generic join.
Pure wiring: both sides are read through dispatch-relayed primitives
@@ -33,7 +33,7 @@ async def run_join(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
Args:
scopes (list[PathSpec]): The two path operands.
flag_kwargs (dict): Flags parsed against the shared join spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
"""
fl = FlagView(flag_kwargs, spec=SPECS["join"])
field1 = fl.as_str("args_1")
@@ -13,7 +13,7 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from typing import Any, Callable
from typing import Callable
from mirage.commands.builtin.generic.crossmount.types import CrossResult
from mirage.commands.builtin.generic.crossmount.utils import (
@@ -22,13 +22,14 @@ from mirage.commands.builtin.generic.mv import mv as generic_mv
from mirage.commands.builtin.generic.mv import parse_mv_flags
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec, PrimitiveMove
async def run_mv(
scopes: list[PathSpec],
flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any],
dispatch: DispatchFn,
storage_key: Callable[[PathSpec], str] | None = None) -> CrossResult:
"""Move operands that span mounts via the shared generic mv.
@@ -38,7 +39,7 @@ async def run_mv(
Args:
scopes (list[PathSpec]): Path operands in command-line order.
flag_kwargs (dict): Flags parsed against the shared mv spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
storage_key (Callable | None): Maps an operand to its storage
identity. Without it a move between two prefixes over one
store would copy the object onto itself and then unlink the
@@ -13,18 +13,18 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from typing import Any, Callable
from mirage.commands.builtin.generic.crossmount.types import CrossResult
from mirage.commands.builtin.generic.crossmount.utils import flat_scopes, relay
from mirage.commands.builtin.generic.paste import paste as generic_paste
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
async def run_paste(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any]) -> CrossResult:
dispatch: DispatchFn) -> CrossResult:
"""Paste files on different mounts via the shared generic paste.
Pure wiring: every operand is read through dispatch-relayed
@@ -33,7 +33,7 @@ async def run_paste(scopes: list[PathSpec], flag_kwargs: dict[str, FlagValue],
Args:
scopes (list[PathSpec]): Path operands in command-line order.
flag_kwargs (dict): Flags parsed against the shared paste spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
"""
fl = FlagView(flag_kwargs, spec=SPECS["paste"])
d = fl.as_str("delimiters")
@@ -12,7 +12,7 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from typing import Callable
from mirage.commands.builtin.generic.crossmount.relay.cmp import run_cmp
from mirage.commands.builtin.generic.crossmount.relay.comm import run_comm
@@ -23,6 +23,7 @@ from mirage.commands.builtin.generic.crossmount.relay.mv import run_mv
from mirage.commands.builtin.generic.crossmount.relay.paste import run_paste
from mirage.commands.builtin.generic.crossmount.types import Cmd, CrossResult
from mirage.commands.spec.types import FlagValue
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
@@ -30,7 +31,7 @@ async def run_relay(
cmd_name: str,
scopes: list[PathSpec],
flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any],
dispatch: DispatchFn,
storage_key: Callable[[PathSpec], str] | None = None) -> CrossResult:
"""Run a command whose data must colocate across mounts.
@@ -42,7 +43,7 @@ async def run_relay(
cmd_name (str): One of cp, mv, diff, cmp, paste, comm, join.
scopes (list[PathSpec]): Path operands in command-line order.
flag_kwargs (dict): Flags parsed against the shared command spec.
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
storage_key (Callable | None): Maps an operand to its storage
identity, for the transfer commands that must tell a real
move from one whose two prefixes address a single store.
@@ -12,7 +12,7 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from typing import Any, Callable
from typing import Callable
from mirage.commands.builtin.generic.crossmount.detect import strategy_for
from mirage.commands.builtin.generic.crossmount.fanout import run_fanout
@@ -24,6 +24,7 @@ from mirage.commands.builtin.generic.crossmount.types import (CrossResult,
from mirage.commands.spec.types import FlagValue
from mirage.io import IOResult
from mirage.io.types import ByteSource
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
from mirage.utils.errors import FS_ERRORS, format_fs_error
@@ -33,7 +34,7 @@ async def handle_cross_mount(
scopes: list[PathSpec],
text_args: list[str],
flag_kwargs: dict[str, FlagValue],
dispatch: Callable[..., Any],
dispatch: DispatchFn,
run_single: RunSingle,
stdin: ByteSource | None = None,
storage_key: Callable[[PathSpec], str] | None = None,
@@ -54,7 +55,7 @@ async def handle_cross_mount(
text_args (list[str]): Positional text operands (grep pattern,
find expression).
flag_kwargs (dict): Flags parsed from the shared command spec.
dispatch (Callable): Workspace operation dispatcher (RELAY).
dispatch (DispatchFn): Workspace operation dispatcher (RELAY).
run_single (RunSingle): Executor-injected single-mount runner
(STREAM and FANOUT).
stdin (ByteSource | None): Original stdin (tee re-feeds it per
@@ -14,18 +14,19 @@
import dataclasses
import functools
from typing import Any, Callable
from typing import Any
from mirage.commands.builtin.generic.crossmount.types import (OperandRun,
RunSingle)
from mirage.commands.spec.types import FlagValue
from mirage.io import IOResult
from mirage.io.stream import materialize
from mirage.runtime.types import DispatchFn
from mirage.types import PathSpec
from mirage.utils.errors import FS_ERRORS, fs_error_line
async def relay(dispatch: Callable[..., Any], name: str, path: PathSpec,
async def relay(dispatch: DispatchFn, name: str, path: PathSpec,
**kwargs: Any) -> Any:
# Relay one op for one path to the mount that owns it. The generics call
# ops as (path); dispatch keys off the path.
@@ -98,11 +99,11 @@ def flat_scopes(scopes: list[PathSpec]) -> list[PathSpec]:
]
def transfer_primitives(dispatch: Callable[..., Any]) -> dict[str, Any]:
def transfer_primitives(dispatch: DispatchFn) -> dict[str, Any]:
"""Dispatch-relayed primitives shared by the transfer generics (cp/mv).
Args:
dispatch (Callable): Workspace operation dispatcher.
dispatch (DispatchFn): Workspace operation dispatcher.
"""
p = functools.partial
return dict(
@@ -35,7 +35,7 @@ def _split_by_patterns(
async def csplit(
paths: list[PathSpec],
patterns: tuple[str, ...],
patterns: list[str],
*,
read_bytes: Callable[..., Awaitable[bytes]],
write_bytes: Callable[..., Awaitable[None]],
+2 -6
View File
@@ -587,8 +587,6 @@ async def du_generic(
compute_size: ComputeSize,
compute_entries: ComputeEntries,
truncated: Callable[[], bool] | None = None,
links: LinkView | None = None,
mounts: MountView | None = None,
) -> tuple[bytes, IOResult]:
"""Run du over the given operands; mirrors duGeneric.
@@ -609,8 +607,6 @@ async def du_generic(
compute_size (ComputeSize): Recursive byte size of one operand.
compute_entries (ComputeEntries): Per-file breakdown.
truncated (Callable[[], bool] | None): Whether the walk was cut.
links (LinkView | None): The namespace's symlink facts.
mounts (MountView | None): Mounts nested under this one.
"""
fl = FlagView(opts.flags, spec=SPECS["du"])
out = await run_du(
@@ -626,7 +622,7 @@ async def du_generic(
c=fl.as_bool("c"),
max_depth=fl.as_str("max_depth"),
truncated=truncated,
links=None if fl.as_bool("L") else links,
mounts=mounts,
links=None if fl.as_bool("L") else opts.links,
mounts=opts.mounts,
)
return out.stdout, IOResult(stderr=out.stderr, exit_code=out.exit_code)
@@ -107,16 +107,11 @@ def parse_flags(flags: Mapping[str, FlagValue]) -> FileFlags:
return FileFlags(brief=fl.as_bool("b"), mime=fl.as_bool("i"))
async def file_generic(paths,
texts,
opts: CommandOpts,
read_bytes,
stat_fn,
links=None):
async def file_generic(paths, texts, opts: CommandOpts, read_bytes, stat_fn):
parsed = parse_flags(opts.flags)
return await file_cmd(paths,
read_bytes=read_bytes,
stat_fn=stat_fn,
b=parsed.brief,
i=parsed.mime,
links=links)
links=opts.links)
+9 -15
View File
@@ -809,29 +809,26 @@ async def find_generic(
*,
find_core: Callable[..., Awaitable[list[str]]],
stat: Callable[[PathSpec], Awaitable[FileStat]] | None = None,
stat_path: StatPath | None = None,
dir_empty: Callable[[PathSpec], Awaitable[bool]] | None = None,
links: LinkView | None = None,
) -> tuple[ByteSource | None, IOResult]:
"""Run find through a backend's native op; mirrors findGeneric.
Args:
paths (list[PathSpec]): Glob-resolved start points.
texts (list[str]): The raw expression words.
opts (CommandOpts): Flags from the dispatcher.
opts (CommandOpts): Flags and namespace facts (stat_path, links)
from the dispatcher.
find_core (Callable): The backend's native find op, bound.
stat (Callable | None): Bound overlaid stat, when the backend
serves local stats cheaply.
stat_path (StatPath | None): Dispatcher-backed stat of one path.
dir_empty (Callable | None): Whether a directory start point is
empty, for ``-empty``.
links (LinkView | None): The namespace's symlink facts.
"""
parsed = parse_flags(opts.flags)
return await find(paths,
tuple(texts),
find_core=find_core,
stat_path=stat_path,
stat_path=opts.stat_path,
stat=stat,
dir_empty=dir_empty,
name=parsed.name,
@@ -843,7 +840,7 @@ async def find_generic(
path=parsed.path,
mindepth=parsed.mindepth,
empty=parsed.empty,
links=links,
links=opts.links,
follow=parsed.follow)
@@ -854,9 +851,6 @@ async def find_walk_generic(
*,
readdir: Callable[..., Awaitable[list[str]]],
stat: Callable[..., Awaitable[FileStat]],
index: IndexCacheStore,
stat_path: StatPath | None = None,
links: LinkView | None = None,
) -> tuple[ByteSource | None, IOResult]:
"""Run find by walking readdir/stat; the no-native-op twin.
@@ -867,14 +861,14 @@ async def find_walk_generic(
Args:
paths (list[PathSpec]): Glob-resolved start points.
texts (list[str]): The raw expression words.
opts (CommandOpts): Flags from the dispatcher.
opts (CommandOpts): Flags, the index for the walk, and namespace
facts (stat_path, links) from the dispatcher.
readdir (Callable): Bound readdir called as ``readdir(p, index)``.
stat (Callable): Bound overlaid stat called as ``stat(p, index)``.
index (IndexCacheStore): Index cache store for the walk.
stat_path (StatPath | None): Dispatcher-backed stat of one path.
links (LinkView | None): The namespace's symlink facts.
"""
parsed = parse_flags(opts.flags)
stat_path = opts.stat_path
links = opts.links
searches = paths if paths else [
PathSpec(virtual="/", directory="/", resource_path="")
]
@@ -907,7 +901,7 @@ async def find_walk_generic(
walked = await walk_find(search,
readdir=readdir,
stat=stat,
index=index,
index=opts.index,
args=args,
links=links,
follow=parsed.follow)
@@ -4,6 +4,7 @@ from typing import Any
import orjson
from mirage.commands.config import CommandOpts
from mirage.commands.errors import UsageError
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
@@ -221,6 +222,26 @@ async def _read_stdin_bytes(stdin: ByteSource | None) -> bytes:
return raw
async def jq_generic(
paths: list[PathSpec],
texts: list[str],
opts: CommandOpts,
read_bytes: Callable[..., Awaitable[bytes]],
read_stream: Callable[..., AsyncIterator[bytes]],
) -> tuple[ByteSource | None, IOResult]:
"""Full-command jq entry; mirrors jqGeneric's (paths, texts, opts).
The kwargs core below keeps the historical shape; this entry is the
dispatcher-facing seam so the builder stays wiring.
"""
return await jq(paths,
*texts,
read_bytes=read_bytes,
read_stream=read_stream,
stdin=opts.stdin,
**opts.flags)
async def jq(
paths: list[PathSpec],
*texts: str,
+7 -11
View File
@@ -685,25 +685,21 @@ async def ls_generic(
opts: CommandOpts,
readdir: Readdir,
stat: Stat,
index: IndexCacheStore = NULL_INDEX,
links: LinkView | None = None,
child_mounts: ChildMounts | None = None,
) -> tuple[bytes, IOResult]:
"""Run ls over resolved operands, GNU semantics; mirrors lsGeneric.
The wiring resolves globs, defaults the operands from the cwd, and
binds the backend ops (including the stat overlay); flag semantics
live here.
live here, and the namespace facts (links, child mounts) and index
ride ``opts``.
Args:
paths (list[PathSpec]): Glob-resolved operands, cwd-defaulted.
texts (list[str]): Non-path words, unused by ls.
opts (CommandOpts): Flags from the dispatcher.
opts (CommandOpts): Flags and namespace facts from the
dispatcher.
readdir (Readdir): Bound readdir called as ``readdir(p, index)``.
stat (Stat): Bound (overlaid) stat called as ``stat(p, index)``.
index (IndexCacheStore): Index cache store for the walk.
links (LinkView | None): The namespace's symlink facts.
child_mounts (ChildMounts | None): Mounts nested under this one.
"""
parsed = parse_flags(opts.flags)
return await ls(paths,
@@ -718,10 +714,10 @@ async def ls_generic(
recursive=parsed.recursive,
list_dir=parsed.list_dir,
classify=parsed.classify,
index=index,
links=links,
index=opts.index,
links=opts.links,
deref=parsed.deref,
child_mounts=child_mounts)
child_mounts=opts.child_mounts)
__all__ = [
@@ -16,11 +16,11 @@ from collections.abc import Awaitable, Callable
from typing import Any
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.utils.output import format_optional_records
from mirage.commands.config import CommandOpts
from mirage.commands.registry import command
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
from mirage.utils.errors import FS_ERRORS, fs_strerror
@@ -49,21 +49,21 @@ def make_rm(
async def rm(
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
f: bool = False,
v: bool = False,
index: IndexCacheStore = NULL_INDEX,
**_extra: FlagValue,
texts: list[str],
opts: CommandOpts,
) -> tuple[ByteSource | None, IOResult]:
if not paths:
raise ValueError("rm: missing operand")
paths = await glob_fn(accessor, paths, index)
fl = FlagView(opts.flags, spec=SPECS["rm"])
f = fl.as_bool("f")
v = fl.as_bool("v")
paths = await glob_fn(accessor, paths, opts.index)
verbose_parts: list[str] = []
errors: list[str] = []
removed: dict[str, ByteSource] = {}
for p in paths:
try:
await unlink(accessor, p, index)
await unlink(accessor, p, opts.index)
except FS_ERRORS as exc:
if f and isinstance(exc, FileNotFoundError):
continue
@@ -21,7 +21,7 @@ def _sample(items: list[str], count: int | None,
async def shuf(
paths: list[PathSpec],
texts: tuple[str, ...],
texts: list[str],
*,
read_bytes: Callable[..., Awaitable[bytes]],
stdin: ByteSource | None = None,
@@ -325,11 +325,11 @@ def parse_flags(flags: Mapping[str, FlagValue]) -> StatFlags:
)
async def stat_generic(paths, texts, opts: CommandOpts, stat_fn, links=None):
async def stat_generic(paths, texts, opts: CommandOpts, stat_fn):
parsed = parse_flags(opts.flags)
return await stat(paths,
stat_fn=stat_fn,
c=parsed.format,
f=parsed.file_system,
L=parsed.deref,
links=links)
links=opts.links)
@@ -246,17 +246,8 @@ def parse_flags(flags: Mapping[str, FlagValue]) -> TarFlags:
)
async def tar_generic(paths,
texts,
opts: CommandOpts,
read_bytes,
write_bytes,
mkdir_fn,
stat,
walk,
is_dir,
links=None,
mounts=None):
async def tar_generic(paths, texts, opts: CommandOpts, read_bytes, write_bytes,
mkdir_fn, stat, walk, is_dir):
parsed = parse_flags(opts.flags)
return await tar(paths,
read_bytes=read_bytes,
@@ -277,5 +268,5 @@ async def tar_generic(paths,
C=list(parsed.directories) or None,
strip_components=parsed.strip_components,
exclude=parsed.exclude,
links=links,
mounts=mounts)
links=opts.links,
mounts=opts.mounts)
@@ -153,7 +153,7 @@ async def write_output(
async def tee(
paths: list[PathSpec],
texts: tuple[str, ...],
texts: list[str],
*,
read_stream: Callable[..., AsyncIterator[bytes]],
write_bytes: Callable[..., Awaitable[None]],
+1 -1
View File
@@ -76,7 +76,7 @@ async def _tr_stream(
async def tr(
paths: list[PathSpec],
texts: tuple[str, ...],
texts: list[str],
*,
read_stream: Callable[..., AsyncIterator[bytes]],
stdin: ByteSource | None = None,
+5 -14
View File
@@ -327,16 +327,7 @@ def parse_flags(flags: Mapping[str, FlagValue]) -> TreeFlags:
)
async def tree_generic(paths,
texts,
opts: CommandOpts,
readdir,
stat,
*,
index,
stat_path=None,
readdir_path=None,
mounts=None):
async def tree_generic(paths, texts, opts: CommandOpts, readdir, stat):
parsed = parse_flags(opts.flags)
return await tree(paths[0],
readdir=readdir,
@@ -346,7 +337,7 @@ async def tree_generic(paths,
ignore_pattern=parsed.ignore_pattern,
dirs_only=parsed.dirs_only,
match_pattern=parsed.match_pattern,
index=index,
stat_path=stat_path,
readdir_path=readdir_path,
mounts=mounts)
index=opts.index,
stat_path=opts.stat_path,
readdir_path=opts.readdir_path,
mounts=opts.mounts)
@@ -285,15 +285,8 @@ def parse_flags(flags: Mapping[str, FlagValue]) -> ZipFlags:
)
async def zip_generic(paths,
texts,
opts: CommandOpts,
read_bytes,
write_bytes,
stat,
walk,
links=None,
mounts=None):
async def zip_generic(paths, texts, opts: CommandOpts, read_bytes, write_bytes,
stat, walk):
parsed = parse_flags(opts.flags)
return await zip_cmd(paths,
read_bytes=read_bytes,
@@ -305,5 +298,5 @@ async def zip_generic(paths,
q=parsed.quiet,
y=parsed.store_links,
x=list(parsed.exclude) or None,
links=links,
mounts=mounts)
links=opts.links,
mounts=opts.mounts)
@@ -13,14 +13,16 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import functools
from collections.abc import AsyncIterator, Callable
from collections.abc import AsyncIterator, Awaitable, Callable, Sequence
from dataclasses import dataclass
from enum import StrEnum
from typing import Any, overload
from typing import Any, Protocol, overload
from mirage.accessor.base import Accessor
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic.du import DEFAULT_MAX_DU_ENTRIES
from mirage.commands.builtin.generic.du import (DEFAULT_MAX_DU_ENTRIES,
DuEntries)
from mirage.commands.config import CommandFnResult, CommandOpts, ProvisionFn
from mirage.ops.types import StatOverlay
from mirage.types import FileStat, FileType, PathSpec
from mirage.utils.errors import MISS_ERRORS, eisdir
@@ -29,6 +31,181 @@ from mirage.utils.path import norm, parent
OperationFn = Callable[..., Any]
# Per-slot op shapes, the twins of adapter.ts's ReaddirOp/StatOp/...
# generics. The accessor parameter stays Any on purpose: every backend
# annotates its own concrete accessor, and a `accessor: Accessor`
# protocol parameter would reject all of them under contravariance
# (TS solves this with `<A extends Accessor>`; a generic frozen
# dataclass plus functools.partial makes that plumbing cost more here
# than the accessor check is worth — the slot SHAPE is the guard that
# stops readdir being wired where stat belongs). The leading two
# parameters are positional-only because backends name the path
# parameter both `path` and `path_spec`.
class ReaddirOp(Protocol):
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...) -> Awaitable[list[str]]:
...
class ReadBytesOp(Protocol):
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...) -> Awaitable[bytes]:
...
class ReadStreamOp(Protocol):
"""Backend streams are async iterators; the polymorphic reader
contract (bytes / awaitable) exists only at the generics' bound-
reader boundary (``normalized_read``), never on the slot itself:
the cache wrapper and the dir-refusing chokepoint both ``async
for`` over this directly."""
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...) -> AsyncIterator[bytes]:
...
class StatOp(Protocol):
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...) -> Awaitable[FileStat]:
...
class ReadRangeOp(Protocol):
"""A byte window without reading the whole object.
Called as ``(accessor, path, index, offset, size)``; most backends
point it at their own ``read_bytes``, which already takes the
window.
"""
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...,
offset: int = ...,
size: int | None = ...) -> Awaitable[bytes]:
...
class WriteOp(Protocol):
def __call__(self, accessor: Any, path: PathSpec, data: bytes,
/) -> Awaitable[None]:
...
class ExistsOp(Protocol):
def __call__(self, accessor: Any, path: PathSpec, /) -> Awaitable[bool]:
...
class PathOp(Protocol):
def __call__(self, accessor: Any, path: PathSpec, /) -> Awaitable[None]:
...
class RmTreeOp(Protocol):
"""Remove a subtree. The builders ignore any returned value
(databricks reports the removed keys for its own rename path), so
the return stays loose where unlink/rmdir pin None."""
def __call__(self, accessor: Any, path: PathSpec, /) -> Awaitable[Any]:
...
class MkdirOp(Protocol):
def __call__(self,
accessor: Any,
path: PathSpec,
/,
parents: bool = ...) -> Awaitable[None]:
...
class PairOp(Protocol):
"""Rename/copy/dir-copy: two paths on the same backend."""
def __call__(self, accessor: Any, src: PathSpec, dst: PathSpec,
/) -> Awaitable[None]:
...
class TruncateOp(Protocol):
def __call__(self, accessor: Any, path: PathSpec, length: int,
/) -> Awaitable[None]:
...
class IsMountedOp(Protocol):
def __call__(self, accessor: Any, /) -> bool:
...
class DuSizeOp(Protocol):
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...) -> Awaitable[int]:
...
class DuEntriesOp(Protocol):
def __call__(self,
accessor: Any,
path: PathSpec,
/,
index: IndexCacheStore = ...) -> Awaitable[DuEntries]:
...
class ResolveGlobOp(Protocol):
def __call__(self,
accessor: Any,
paths: Sequence[str | PathSpec],
/,
index: IndexCacheStore = ...) -> Awaitable[list[PathSpec]]:
...
class BuilderFn(Protocol):
"""Builder body: a CommandFn with the backend's ops bound in front."""
def __call__(self, ops: "CommandIO", accessor: Any, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> Awaitable[CommandFnResult]:
...
AggregateFn = Callable[[list[tuple[str, bytes]]], Awaitable[bytes]]
async def overlaid_stat(stat: OperationFn, overlay: StatOverlay,
path: PathSpec, index: IndexCacheStore) -> FileStat:
@@ -103,32 +280,32 @@ class DuOps:
by omission.
Args:
size (OperationFn): recursive byte total for one path.
entries (OperationFn): per-file breakdown, leaf files only.
size (DuSizeOp): recursive byte total for one path.
entries (DuEntriesOp): per-file breakdown, leaf files only.
"""
size: OperationFn
entries: OperationFn
size: DuSizeOp
entries: DuEntriesOp
@dataclass(frozen=True)
class Builder:
name: str
fn: OperationFn
provision: OperationFn | None = None
fn: BuilderFn
provision: Callable[[StatOp], ProvisionFn] | None = None
write: bool = False
aggregate: OperationFn | None = None
aggregate: AggregateFn | None = None
read: bool = False
requirements: frozenset[Operation] = frozenset()
@dataclass(frozen=True)
class CommandIO:
readdir: OperationFn
read_bytes: OperationFn
read_stream: OperationFn
stat: OperationFn
is_mounted: OperationFn
readdir: ReaddirOp
read_bytes: ReadBytesOp
read_stream: ReadStreamOp
stat: StatOp
is_mounted: IsMountedOp
local: bool = True
max_glob_matches: int | None = DEFAULT_MAX_GLOB_MATCHES
# Fetch a byte range without pulling the whole object. Absent means
@@ -139,26 +316,31 @@ class CommandIO:
# Called as (accessor, path, index, offset, size), so most backends
# point it at their own read_bytes, which already takes the window;
# disk needs a separate function because its read_bytes does not.
read_range: OperationFn | None = None
write: OperationFn | None = None
exists: OperationFn | None = None
mkdir: OperationFn | None = None
unlink: OperationFn | None = None
rmdir: OperationFn | None = None
rm_r: OperationFn | None = None
rename: OperationFn | None = None
copy: OperationFn | None = None
dir_copy: OperationFn | None = None
create: OperationFn | None = None
truncate: OperationFn | None = None
read_range: ReadRangeOp | None = None
write: WriteOp | None = None
exists: ExistsOp | None = None
mkdir: MkdirOp | None = None
unlink: PathOp | None = None
rmdir: PathOp | None = None
rm_r: RmTreeOp | None = None
rename: PairOp | None = None
copy: PairOp | None = None
dir_copy: PairOp | None = None
create: PathOp | None = None
truncate: TruncateOp | None = None
# Filter kwargs drift per backend (name/type/size bounds/...), the
# repo's kwargs spelling of TS's FindOptions object; a Protocol
# naming them would reject every backend, so the slot stays loose.
find: OperationFn | None = None
du: DuOps | None = None
max_du_entries: int | None = DEFAULT_MAX_DU_ENTRIES
append: OperationFn | None = None
# Typed like `write`, now that the tee generic actually calls it.
append: WriteOp | None = None
# Kwargs vary per backend (mode/times/owner); loose like TS's any.
set_attrs: OperationFn | None = None
@property
def resolve_glob(self) -> OperationFn:
def resolve_glob(self) -> ResolveGlobOp:
return make_resolve_glob(self.readdir, self.max_glob_matches)
def operation(self, op: Operation) -> OperationFn | None:
@@ -13,35 +13,28 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.awk import awk as generic_awk
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.spec.types import FlagValue
from mirage.commands.config import CommandOpts
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def awk(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
paths = await resolve_or_empty(ops, accessor, paths, index)
async def awk(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
paths = await resolve_or_empty(ops, accessor, paths, opts.index)
return await generic_awk(
paths,
texts,
flags,
read_bytes=bound_op(ops.read_bytes, accessor, index),
read_stream=bound_op(ops.read_stream, accessor, index),
stdin=stdin,
index=index,
opts.flags,
read_bytes=bound_op(ops.read_bytes, accessor, opts.index),
read_stream=bound_op(ops.read_stream, accessor, opts.index),
stdin=opts.stdin,
index=opts.index,
)
@@ -13,31 +13,23 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.base64_cmd import base64_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def base64(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await base64_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_stream, accessor, index))
async def base64(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await base64_generic(
resolved, list(texts), opts,
bound_op(ops.read_stream, accessor, opts.index))
BUILDER = Builder('base64', base64, None, False, None, read=True)
@@ -16,21 +16,17 @@ from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic.basename import \
basename as generic_basename
from mirage.commands.builtin.generic_bind.adapter import Builder, CommandIO
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def basename(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["basename"])
async def basename(ops: CommandIO, accessor: Accessor,
paths: list[PathSpec] | None, texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["basename"])
suffix = fl.as_str("suffix")
return await generic_basename(
*texts,
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.aggregators import concat_aggregate
from mirage.commands.builtin.generic.cat import cat_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
@@ -22,26 +21,19 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def cat(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
async def cat(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await cat_generic(resolved,
list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_stream, accessor, index),
opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_stream, accessor, opts.index),
local=ops.local)
@@ -13,31 +13,22 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.cmp import cmp_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def cmp_cmd(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def cmp_cmd(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor) or len(paths) < 2:
raise ValueError('cmp: requires two paths')
resolved = await ops.resolve_glob(accessor, paths, index)
return await cmp_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index))
resolved = await ops.resolve_glob(accessor, paths, opts.index)
return await cmp_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('cmp', cmp_cmd, None, False, None, read=True)
@@ -13,31 +13,22 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.column import column_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def column(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await column_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index))
async def column(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await column_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('column', column, None, False, None, read=True)
@@ -13,31 +13,22 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.comm import comm_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def comm(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def comm(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor) or len(paths) < 2:
raise ValueError("comm: requires two paths")
resolved = await ops.resolve_glob(accessor, paths, index)
return await comm_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index))
resolved = await ops.resolve_glob(accessor, paths, opts.index)
return await comm_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('comm', comm, None, False, None, read=True)
@@ -15,7 +15,7 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic.cp import cp as generic_cp
from mirage.commands.builtin.generic.cp import parse_cp_flags
from mirage.commands.builtin.generic.find import parse_find_args, walk_find
@@ -24,8 +24,9 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
OperationFn,
bound_op,
overlaid_stat)
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import StatOverlay
from mirage.types import NativeCopy, PathSpec
@@ -77,33 +78,27 @@ def overlayable_stat(ops: CommandIO, accessor: Accessor,
index=index)
async def cp(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
stat_overlay: StatOverlay | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def cp(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor):
raise ValueError("cp: no resource")
fl = FlagView(flags, spec=SPECS["cp"])
fl = FlagView(opts.flags, spec=SPECS["cp"])
parsed = parse_cp_flags(fl)
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
dir_copy = partial(ops.dir_copy, accessor) if ops.dir_copy else None
mkdir = partial(ops.mkdir, accessor) if ops.mkdir else None
strategy = NativeCopy(copy=partial(ops.require(Operation.COPY), accessor),
find=_make_find(ops, accessor, index),
find=_make_find(ops, accessor, opts.index),
dir_copy=dir_copy,
mkdir=mkdir)
return await generic_cp(paths,
strategy=strategy,
stat=overlayable_stat(ops, accessor, index,
stat_overlay),
stat=overlayable_stat(ops, accessor, opts.index,
opts.stat_overlay),
flags=parsed,
readdir=bound_op(ops.readdir, accessor, index))
readdir=bound_op(ops.readdir, accessor,
opts.index))
BUILDER = Builder('cp',
@@ -15,37 +15,31 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.csplit import csplit as generic_csplit
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation, bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def csplit(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["csplit"])
paths = await resolve_or_empty(ops, accessor, paths, index)
async def csplit(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["csplit"])
paths = await resolve_or_empty(ops, accessor, paths, opts.index)
prefix_flag = fl.raw("prefix")
prefix = prefix_flag if isinstance(prefix_flag, (str, PathSpec)) else "xx"
return await generic_csplit(
paths,
texts,
read_bytes=bound_op(ops.read_bytes, accessor, index),
read_bytes=bound_op(ops.read_bytes, accessor, opts.index),
write_bytes=partial(ops.require(Operation.WRITE), accessor),
stdin=stdin,
stdin=opts.stdin,
prefix=prefix,
digits=int(fl.as_str("digits") or "2"),
suffix_format=fl.as_str("suffix_format"),
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.cut import cut_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def cut(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await cut_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_stream, accessor, index))
async def cut(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await cut_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_stream, accessor, opts.index))
BUILDER = Builder('cut', cut, None, False, None, read=True)
@@ -13,33 +13,24 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.diff import diff_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def diff(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def diff(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor):
raise ValueError("diff: no resource")
resolved = await ops.resolve_glob(accessor, paths, index)
return await diff_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index),
bound_op(ops.readdir, accessor, index),
bound_op(ops.stat, accessor, index))
resolved = await ops.resolve_glob(accessor, paths, opts.index)
return await diff_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index),
bound_op(ops.readdir, accessor, opts.index),
bound_op(ops.stat, accessor, opts.index))
BUILDER = Builder('diff', diff, None, False, None, read=True)
@@ -15,21 +15,17 @@
from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic.dirname import dirname as generic_dirname
from mirage.commands.builtin.generic_bind.adapter import Builder, CommandIO
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def dirname(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["dirname"])
async def dirname(ops: CommandIO, accessor: Accessor,
paths: list[PathSpec] | None, texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["dirname"])
return await generic_dirname(*texts, zero=fl.as_bool("zero"))
@@ -16,15 +16,13 @@ from dataclasses import dataclass
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic.du import (ComputeEntries, ComputeSize,
DuEntries, du_generic)
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
OperationFn)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import LinkView, MountView
from mirage.types import FileType, PathSpec
from mirage.utils.key_prefix import mount_key, mount_prefix_of, rekey
@@ -152,18 +150,9 @@ def _budget_hit(budget: WalkBudget) -> bool:
return budget.hit
async def du(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
cwd: PathSpec | str = "/",
links: LinkView | None = None,
mounts: MountView | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def du(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor):
raise ValueError("du: no resource")
budget = WalkBudget(ops.max_du_entries)
@@ -171,21 +160,21 @@ async def du(
compute_size: ComputeSize
compute_entries: ComputeEntries
if native is None:
compute_size = partial(_walk_size, ops, accessor, index, budget)
compute_entries = partial(_walk_entries, ops, accessor, index, budget)
compute_size = partial(_walk_size, ops, accessor, opts.index, budget)
compute_entries = partial(_walk_entries, ops, accessor, opts.index,
budget)
else:
compute_size = partial(_op_size, native.size, accessor, index)
compute_entries = partial(_op_entries, native.entries, accessor, index)
compute_size = partial(_op_size, native.size, accessor, opts.index)
compute_entries = partial(_op_entries, native.entries, accessor,
opts.index)
return await du_generic(paths,
list(texts),
CommandOpts(stdin=stdin, flags=flags, cwd=cwd),
partial(_resolve, ops, accessor, index),
partial(_stat, ops, accessor, index),
opts,
partial(_resolve, ops, accessor, opts.index),
partial(_stat, ops, accessor, opts.index),
compute_size,
compute_entries,
truncated=partial(_budget_hit, budget),
links=links,
mounts=mounts)
truncated=partial(_budget_hit, budget))
BUILDER = Builder('du', du, None, False, None)
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.expand import expand_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def expand(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await expand_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_bytes, accessor, index))
async def expand(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await expand_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('expand', expand, None, False, None, read=True)
@@ -13,36 +13,23 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.file import file_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import LinkView
from mirage.types import PathSpec
async def file(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
links: LinkView | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def file(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor) or not paths:
raise ValueError("file: missing operand")
resolved = await ops.resolve_glob(accessor, paths, index)
return await file_generic(resolved,
list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index),
bound_op(ops.stat, accessor, index),
links=links)
resolved = await ops.resolve_glob(accessor, paths, opts.index)
return await file_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index),
bound_op(ops.stat, accessor, opts.index))
BUILDER = Builder('file', file, None, False, None, read=True)
@@ -12,19 +12,18 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from collections.abc import Awaitable, Callable
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic.find import (find_generic,
find_walk_generic)
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
overlaid_stat)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import LinkView, StatOverlay, StatPath
from mirage.types import PathSpec
from mirage.types import FileStat, PathSpec
async def _dir_is_empty(ops: CommandIO, accessor: Accessor,
@@ -45,51 +44,38 @@ async def _dir_is_empty(ops: CommandIO, accessor: Accessor,
return not await ops.readdir(accessor, search, index=index)
async def find(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
stat_overlay: StatOverlay | None = None,
links: LinkView | None = None,
stat_path: StatPath | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def find(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor):
raise ValueError("find: no resource")
resolved = await ops.resolve_glob(accessor, paths, index)
opts = CommandOpts(stdin=stdin, flags=flags)
resolved = await ops.resolve_glob(accessor, paths, opts.index)
if ops.find is None:
# -mtime must see namespace times (touch results, observed
# writes on mtime-less backends), same as ls.
walk_stat = partial(ops.stat, accessor)
if stat_overlay is not None:
walk_stat = partial(overlaid_stat, walk_stat, stat_overlay)
walk_stat: Callable[...,
Awaitable[FileStat]] = partial(ops.stat, accessor)
if opts.stat_overlay is not None:
walk_stat = partial(overlaid_stat, walk_stat, opts.stat_overlay)
return await find_walk_generic(resolved,
list(texts),
opts,
readdir=partial(ops.readdir, accessor),
stat=walk_stat,
index=index,
stat_path=stat_path,
links=links)
stat = (partial(ops.stat, accessor, index=index) if ops.local else None)
if stat is not None and stat_overlay is not None:
stat=walk_stat)
stat: Callable[..., Awaitable[FileStat]] | None = (partial(
ops.stat, accessor, index=opts.index) if ops.local else None)
if stat is not None and opts.stat_overlay is not None:
stat = partial(overlaid_stat,
partial(ops.stat, accessor),
stat_overlay,
index=index)
opts.stat_overlay,
index=opts.index)
return await find_generic(resolved,
list(texts),
opts,
find_core=partial(ops.find, accessor),
stat=stat,
stat_path=stat_path,
dir_empty=partial(_dir_is_empty, ops, accessor,
index),
links=links)
opts.index))
BUILDER = Builder('find', find, None, False, None)
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.fmt import fmt_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def fmt(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await fmt_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_bytes, accessor, index))
async def fmt(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await fmt_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('fmt', fmt, None, False, None, read=True)
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.fold import fold_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def fold(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await fold_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_bytes, accessor, index))
async def fold(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await fold_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('fold', fold, None, False, None, read=True)
@@ -13,36 +13,29 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.aggregators import prefix_aggregate
from mirage.commands.builtin.generic.grep import grep as generic_grep
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def grep(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore = NULL_INDEX,
**flags,
) -> tuple[ByteSource | None, IOResult]:
resolved = (await ops.resolve_glob(accessor, paths, index)
async def grep(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = (await ops.resolve_glob(accessor, paths, opts.index)
if paths and ops.is_mounted(accessor) else [])
return await generic_grep(
resolved,
texts,
flags,
readdir=bound_op(ops.readdir, accessor, index),
stat=bound_op(ops.stat, accessor, index),
read_bytes=bound_op(ops.read_bytes, accessor, index),
read_stream=bound_op(ops.read_stream, accessor, index),
stdin=stdin,
opts.flags,
readdir=bound_op(ops.readdir, accessor, opts.index),
stat=bound_op(ops.stat, accessor, opts.index),
read_bytes=bound_op(ops.read_bytes, accessor, opts.index),
read_stream=bound_op(ops.read_stream, accessor, opts.index),
stdin=opts.stdin,
)
@@ -15,29 +15,22 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.gunzip import gunzip_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation, bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def gunzip(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await ops.resolve_glob(accessor, paths, index) if paths else []
async def gunzip(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await ops.resolve_glob(accessor, paths,
opts.index) if paths else []
return await gunzip_generic(
resolved, list(texts), CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index),
resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index),
partial(ops.require(Operation.WRITE), accessor),
partial(ops.require(Operation.UNLINK), accessor))
@@ -15,29 +15,21 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.gzip import gzip_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation, bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def gzip(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await ops.resolve_glob(accessor, paths, index) if paths else []
return await gzip_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index),
async def gzip(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await ops.resolve_glob(accessor, paths,
opts.index) if paths else []
return await gzip_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index),
partial(ops.require(Operation.WRITE), accessor),
partial(ops.require(Operation.UNLINK), accessor))
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.aggregators import header_aggregate
from mirage.commands.builtin.generic.head import head_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
@@ -22,25 +21,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def head(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await head_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_stream, accessor, index))
async def head(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await head_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_stream, accessor, opts.index))
BUILDER = Builder('head', head, None, False, header_aggregate, read=True)
@@ -15,31 +15,22 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.iconv import iconv_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation, bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def iconv(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await iconv_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index),
async def iconv(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await iconv_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index),
partial(ops.require(Operation.WRITE), accessor))
@@ -13,31 +13,22 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.join import join_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def join(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def join(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor) or len(paths) < 2:
raise ValueError("join: requires two paths")
resolved = await ops.resolve_glob(accessor, paths, index)
return await join_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index))
resolved = await ops.resolve_glob(accessor, paths, opts.index)
return await join_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('join', join, None, False, None, read=True)
@@ -13,35 +13,23 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.jq import jq as generic_jq
from mirage.commands.builtin.generic.jq import jq_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.spec.types import FlagValue
from mirage.commands.config import CommandOpts
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def jq(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
paths = await resolve_or_empty(ops, accessor, paths, index)
return await generic_jq(paths,
*texts,
read_bytes=bound_op(ops.read_bytes, accessor,
index),
read_stream=bound_op(ops.read_stream, accessor,
index),
stdin=stdin,
**flags)
async def jq(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
paths = await resolve_or_empty(ops, accessor, paths, opts.index)
return await jq_generic(paths, texts, opts,
bound_op(ops.read_bytes, accessor, opts.index),
bound_op(ops.read_stream, accessor, opts.index))
BUILDER = Builder('jq', jq, None, False, None, read=True)
@@ -13,32 +13,26 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation)
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def ln(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["ln"])
async def ln(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["ln"])
n = fl.as_bool("n")
v = fl.as_bool("v")
if not ops.is_mounted(accessor) or len(paths) < 2:
raise ValueError("ln: usage: ln [-s] [-f] source dest")
exists = ops.require(Operation.EXISTS)
write = ops.require(Operation.WRITE)
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
source_path = paths[0]
dest_path = paths[1]
if n and await exists(accessor, dest_path):
@@ -13,31 +13,22 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.look import look_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def look(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await look_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index))
async def look(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await look_generic(resolved, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('look', look, None, False, None, read=True)
@@ -12,57 +12,40 @@
# limitations under the License.
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from collections.abc import Awaitable, Callable
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.ls import ls_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
overlaid_stat)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import ChildMounts, LinkView, StatOverlay
from mirage.types import PathSpec
from mirage.types import FileStat, PathSpec
async def ls(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
cwd: PathSpec | str = "/",
stat_overlay: StatOverlay | None = None,
links: LinkView | None = None,
child_mounts: ChildMounts | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def ls(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor):
raise ValueError("ls: no resource")
if not paths:
cwd_virtual = cwd.virtual if isinstance(cwd, PathSpec) else cwd
cwd_rp = (cwd.resource_path
if isinstance(cwd, PathSpec) else cwd.strip("/"))
cwd_virtual = opts.cwd.virtual if isinstance(opts.cwd,
PathSpec) else opts.cwd
cwd_rp = (opts.cwd.resource_path
if isinstance(opts.cwd, PathSpec) else opts.cwd.strip("/"))
paths = [
PathSpec(virtual=cwd_virtual,
directory=cwd_virtual,
resolved=False,
resource_path=cwd_rp)
]
resolved = await ops.resolve_glob(accessor, paths, index)
stat_fn = partial(ops.stat, accessor)
if stat_overlay is not None:
stat_fn = partial(overlaid_stat, stat_fn, stat_overlay)
return await ls_generic(resolved,
list(texts),
CommandOpts(stdin=stdin, flags=flags, cwd=cwd),
partial(ops.readdir, accessor),
stat_fn,
index=index,
links=links,
child_mounts=child_mounts)
resolved = await ops.resolve_glob(accessor, paths, opts.index)
stat_fn: Callable[..., Awaitable[FileStat]] = partial(ops.stat, accessor)
if opts.stat_overlay is not None:
stat_fn = partial(overlaid_stat, stat_fn, opts.stat_overlay)
return await ls_generic(resolved, list(texts), opts,
partial(ops.readdir, accessor), stat_fn)
BUILDER = Builder('ls', ls, None, False, None)
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.md5 import md5_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def md5(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await md5_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_bytes, accessor, index))
async def md5(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await md5_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('md5', md5, None, False, None, read=True)
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.md5sum import md5sum_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
dir_aware_stat,
@@ -21,26 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def md5sum(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
cwd: PathSpec | str = "/",
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await md5sum_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags, cwd=cwd),
dir_aware_stat(ops, accessor, index),
dir_aware_stream(ops, accessor, index))
async def md5sum(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await md5sum_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
dir_aware_stream(ops, accessor, opts.index))
BUILDER = Builder('md5sum', md5sum, None, False, None, read=True)
@@ -13,11 +13,11 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation)
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
from mirage.utils.errors import (FS_ERRORS, error_path, fs_strerror,
@@ -25,16 +25,10 @@ from mirage.utils.errors import (FS_ERRORS, error_path, fs_strerror,
from mirage.utils.mode import DEFAULT_DIR_MODE, parse_chmod
async def mkdir(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["mkdir"])
async def mkdir(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["mkdir"])
parents = fl.as_bool("parents")
verbose = fl.as_bool("verbose")
mode_text = fl.as_str("mode")
@@ -51,7 +45,7 @@ async def mkdir(
raise NotImplementedError(
"mkdir: --mode is not supported on this backend")
mkdir_fn = ops.require(Operation.MKDIR)
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
lines: list[str] = []
errors: list[str] = []
for path in paths:
@@ -19,21 +19,15 @@ from mirage.commands.builtin.generic.mktemp import mktemp_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def mktemp(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def mktemp(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
return await mktemp_generic(
paths, list(texts), CommandOpts(stdin=stdin, flags=flags),
paths, list(texts), opts,
partial(ops.require(Operation.MKDIR), accessor),
partial(ops.require(Operation.WRITE), accessor))
@@ -15,41 +15,33 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.mv import mv as generic_mv
from mirage.commands.builtin.generic.mv import parse_mv_flags
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation, bound_op)
from mirage.commands.builtin.generic_bind.builders.cp import overlayable_stat
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.ops.types import StatOverlay
from mirage.types import NativeMove, PathSpec
async def mv(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
stat_overlay: StatOverlay | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def mv(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not ops.is_mounted(accessor):
raise ValueError("mv: no resource")
fl = FlagView(flags, spec=SPECS["mv"])
fl = FlagView(opts.flags, spec=SPECS["mv"])
parsed = parse_mv_flags(fl)
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
return await generic_mv(
paths,
strategy=NativeMove(
rename=partial(ops.require(Operation.RENAME), accessor)),
stat=overlayable_stat(ops, accessor, index, stat_overlay),
stat=overlayable_stat(ops, accessor, opts.index, opts.stat_overlay),
flags=parsed,
readdir=bound_op(ops.readdir, accessor, index))
readdir=bound_op(ops.readdir, accessor, opts.index))
BUILDER = Builder('mv',
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.nl import nl_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def nl(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await nl_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_stream, accessor, index))
async def nl(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await nl_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_stream, accessor, opts.index))
BUILDER = Builder('nl', nl, None, False, None, read=True)
@@ -1,24 +1,20 @@
from mirage.accessor.base import Accessor
from mirage.commands.builtin.generic.numfmt import numfmt as generic_numfmt
from mirage.commands.builtin.generic_bind.adapter import Builder, CommandIO
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def numfmt(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["numfmt"])
async def numfmt(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["numfmt"])
return await generic_numfmt(
*texts,
stdin=stdin,
stdin=opts.stdin,
to_mode=fl.as_str("to") or "none",
from_mode=fl.as_str("from") or "none",
suffix=fl.as_str("suffix") or "",
@@ -1,28 +1,22 @@
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.od import od as generic_od
from mirage.commands.builtin.generic.od import parse_count
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def od(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["od"])
paths = await resolve_or_empty(ops, accessor, paths, index)
async def od(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["od"])
paths = await resolve_or_empty(ops, accessor, paths, opts.index)
formats = fl.as_list("format")
# as_str, not `x or y`: the latter would swallow an explicitly empty
# value, which GNU rejects loudly (`od -N ''` is an invalid argument,
@@ -31,8 +25,8 @@ async def od(
limit_value = fl.as_str("read_bytes")
return await generic_od(
paths,
read_stream=bound_op(ops.read_stream, accessor, index),
stdin=stdin,
read_stream=bound_op(ops.read_stream, accessor, opts.index),
stdin=opts.stdin,
address_radix=fl.as_str("address_radix") or "o",
skip=(parse_count(skip_value, "-j") if skip_value is not None else 0),
limit=(parse_count(limit_value, "-N")
@@ -13,33 +13,27 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.paste import paste as generic_paste
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def paste(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["paste"])
paths = await resolve_or_empty(ops, accessor, paths, index)
async def paste(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["paste"])
paths = await resolve_or_empty(ops, accessor, paths, opts.index)
return await generic_paste(paths,
read_bytes=bound_op(ops.read_bytes, accessor,
index),
stdin=stdin,
opts.index),
stdin=opts.stdin,
delimiters=fl.as_str("delimiters") or "\t",
serial=fl.as_bool("serial"),
zero_terminated=fl.as_bool("zero_terminated"))
@@ -15,28 +15,19 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.patch import patch_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation, bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def patch(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
return await patch_generic(paths, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.read_bytes, accessor, index),
async def patch(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
return await patch_generic(paths, list(texts), opts,
bound_op(ops.read_bytes, accessor, opts.index),
partial(ops.require(Operation.WRITE), accessor),
ops.is_mounted(accessor))
@@ -13,29 +13,20 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.readlink import readlink_generic
from mirage.commands.builtin.generic_bind.adapter import Builder, CommandIO
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def readlink(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def readlink(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if not paths:
raise ValueError("readlink: missing operand")
resolved = await ops.resolve_glob(accessor, paths, index)
return await readlink_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags))
resolved = await ops.resolve_glob(accessor, paths, opts.index)
return await readlink_generic(resolved, list(texts), opts)
BUILDER = Builder('readlink', readlink, None, False, None)
@@ -13,29 +13,20 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.realpath import realpath_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def realpath(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec] | None = None,
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await ops.resolve_glob(accessor, paths or [], index)
return await realpath_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
bound_op(ops.stat, accessor, index))
async def realpath(ops: CommandIO, accessor: Accessor,
paths: list[PathSpec] | None, texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await ops.resolve_glob(accessor, paths or [], opts.index)
return await realpath_generic(resolved, list(texts), opts,
bound_op(ops.stat, accessor, opts.index))
BUILDER = Builder('realpath', realpath, None, False, None)
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.rev import rev_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op,
@@ -21,25 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def rev(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await rev_generic(resolved, list(texts),
CommandOpts(stdin=stdin, flags=flags),
dir_aware_stat(ops, accessor, index),
bound_op(ops.read_bytes, accessor, index))
async def rev(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await rev_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
bound_op(ops.read_bytes, accessor, opts.index))
BUILDER = Builder('rev', rev, None, False, None, read=True)
@@ -13,35 +13,28 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.rg import rg as generic_rg
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.config import CommandOpts
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def rg(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
prefix: str = "",
index: IndexCacheStore = NULL_INDEX,
**flags,
) -> tuple[ByteSource | None, IOResult]:
async def rg(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
if paths and ops.is_mounted(accessor):
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
return await generic_rg(
paths,
texts,
flags,
readdir=bound_op(ops.readdir, accessor, index),
stat=bound_op(ops.stat, accessor, index),
read_bytes=bound_op(ops.read_bytes, accessor, index),
read_stream=bound_op(ops.read_stream, accessor, index),
stdin=stdin,
opts.flags,
readdir=bound_op(ops.readdir, accessor, opts.index),
stat=bound_op(ops.stat, accessor, opts.index),
read_bytes=bound_op(ops.read_bytes, accessor, opts.index),
read_stream=bound_op(ops.read_stream, accessor, opts.index),
stdin=opts.stdin,
)
@@ -15,34 +15,28 @@
import functools
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.cp import walk
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation)
from mirage.commands.builtin.utils.output import format_optional_records
from mirage.commands.builtin.utils.verbose import removal_lines
from mirage.commands.config import CommandOpts
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import FileType, PathSpec
async def rm(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(flags, spec=SPECS["rm"])
async def rm(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
fl = FlagView(opts.flags, spec=SPECS["rm"])
f = fl.as_bool("f")
v = fl.as_bool("v")
d = fl.as_bool("d")
if not ops.is_mounted(accessor) or not paths:
raise ValueError("rm: missing operand")
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
recursive = fl.as_bool("r") or fl.as_bool("R")
verbose_parts: list[str] = []
errors: list[str] = []
@@ -66,7 +60,7 @@ async def rm(
if v:
readdir = functools.partial(ops.readdir,
accessor,
index=index)
index=opts.index)
entry_lines = removal_lines(await walk(
readdir, functools.partial(ops.stat, accessor), p))
await ops.rm_r(accessor, p)
@@ -74,7 +68,7 @@ async def rm(
if ops.rmdir is None:
raise NotImplementedError(
"rm: directory remove not supported on this backend")
if await ops.readdir(accessor, p, index=index):
if await ops.readdir(accessor, p, index=opts.index):
errors.append(f"rm: cannot remove '{p.virtual}': "
"Directory not empty")
continue
@@ -13,33 +13,27 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
Operation)
from mirage.commands.builtin.utils.output import format_optional_records
from mirage.commands.config import CommandOpts
from mirage.commands.errors import UsageError
from mirage.commands.spec import SPECS
from mirage.commands.spec.types import FlagValue, FlagView
from mirage.commands.spec.types import FlagView
from mirage.io.types import ByteSource, IOResult
from mirage.types import FileType, PathSpec
async def rmdir(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: bytes | None = None,
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
v = FlagView(flags, spec=SPECS["rmdir"]).as_bool("v")
async def rmdir(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
v = FlagView(opts.flags, spec=SPECS["rmdir"]).as_bool("v")
if not ops.is_mounted(accessor) or not paths:
raise UsageError(
"rmdir: missing operand\n"
"Try 'rmdir --help' for more information.", 1)
rmdir_fn = ops.require(Operation.RMDIR)
paths = await ops.resolve_glob(accessor, paths, index)
paths = await ops.resolve_glob(accessor, paths, opts.index)
verbose_parts: list[str] = []
errors: list[str] = []
removed: dict[str, ByteSource] = {}
@@ -54,7 +48,7 @@ async def rmdir(
errors.append(
f"rmdir: failed to remove '{p.virtual}': Not a directory")
continue
if await ops.readdir(accessor, p, index=index):
if await ops.readdir(accessor, p, index=opts.index):
errors.append(f"rmdir: failed to remove '{p.virtual}': "
"Directory not empty")
continue
@@ -15,13 +15,12 @@
from functools import partial
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.cache.index import IndexCacheStore
from mirage.commands.builtin.generic.sed import sed_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
bound_op)
from mirage.commands.builtin.generic_bind.provision import make_sed_provision
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
@@ -31,20 +30,12 @@ async def _resolve(ops: CommandIO, accessor: Accessor, index: IndexCacheStore,
return await ops.resolve_glob(accessor, targets, index)
async def sed(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
cwd: PathSpec | str = "/",
index: IndexCacheStore = NULL_INDEX,
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
async def sed(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
return await sed_generic(
paths, list(texts), CommandOpts(stdin=stdin, flags=flags, cwd=cwd),
partial(_resolve, ops, accessor, index),
bound_op(ops.read_bytes, accessor, index),
paths, list(texts), opts, partial(_resolve, ops, accessor, opts.index),
bound_op(ops.read_bytes, accessor, opts.index),
(partial(ops.write, accessor) if ops.write is not None else None))
@@ -13,7 +13,6 @@
# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
from mirage.accessor.base import Accessor
from mirage.cache.index import NULL_INDEX, IndexCacheStore
from mirage.commands.builtin.generic.sha1sum import sha1sum_generic
from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
dir_aware_stat,
@@ -21,26 +20,17 @@ from mirage.commands.builtin.generic_bind.adapter import (Builder, CommandIO,
from mirage.commands.builtin.generic_bind.builders.common import \
resolve_or_empty
from mirage.commands.config import CommandOpts
from mirage.commands.spec.types import FlagValue
from mirage.io.types import ByteSource, IOResult
from mirage.types import PathSpec
async def sha1sum(
ops: CommandIO,
accessor: Accessor,
paths: list[PathSpec],
*texts: str,
stdin: ByteSource | None = None,
index: IndexCacheStore = NULL_INDEX,
cwd: PathSpec | str = "/",
**flags: FlagValue,
) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, index)
return await sha1sum_generic(
resolved, list(texts), CommandOpts(stdin=stdin, flags=flags, cwd=cwd),
dir_aware_stat(ops, accessor, index),
dir_aware_stream(ops, accessor, index))
async def sha1sum(ops: CommandIO, accessor: Accessor, paths: list[PathSpec],
texts: list[str],
opts: CommandOpts) -> tuple[ByteSource | None, IOResult]:
resolved = await resolve_or_empty(ops, accessor, paths, opts.index)
return await sha1sum_generic(resolved, list(texts), opts,
dir_aware_stat(ops, accessor, opts.index),
dir_aware_stream(ops, accessor, opts.index))
BUILDER = Builder('sha1sum', sha1sum, None, False, None, read=True)

Some files were not shown because too many files have changed in this diff Show More