UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

107 lines • 5.06 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { IOResult } from "../../../../../io/types.js"; import { FileType } from "../../../../../types.js"; import { isFsError } from "../../../../../errors/fs.js"; import { runDispatch } from "../../../generic_bind/dispatch.js"; import { isStdin } from "../../../utils/stream.js"; import { formatCountRows, numberWidth, parseFlags } from "../../wc.js"; import { DISPATCH_BUILDERS } from "../constants.js"; import { Cmd, } from "../types.js"; import { mergeOperandIos, runOperands, statOp } from "../utils.js"; const ENC = new TextEncoder(); // GNU prints a row's counts in this order whichever flags ask for them. const COLUMNS = ['lines', 'words', 'chars', 'bytes', 'maxLineLength']; /** * The size GNU sizes the columns by, which it takes from fstat. A stream or a * directory has none. A file whose size stat cannot give without rendering it, * or that is gone by the time it is sized, counts as its widest count, a lower * bound, which is the width a count-only mount pads to on its own. Mirrors * Python's operand_size. */ async function operandSize(dispatch, path, counts) { if (isStdin(path)) return null; let info; try { info = await statOp(dispatch)(path); } catch (err) { // Gone since its mount counted it: the width is only layout, so the // counts already taken still print, padded to the lower bound. if (!isFsError(err)) throw err; return Math.max(...counts); } if (info.type === FileType.DIRECTORY) return null; return info.size ?? Math.max(...counts); } /** * The run as counted, recounting it through the dispatcher if needed. A * mount's wc that succeeds without counts (one not built on the generic) * leaves its operand to the generic over the dispatcher, and only that * operand: the others keep what their own mount counted. Mirrors Python's * recount. */ export async function recount(run, bag, dispatch, cwd, vfs, stdin) { if (run.io.records !== null || run.io.exitCode !== 0) return run; const builder = DISPATCH_BUILDERS.get(Cmd.WC); if (builder === undefined) throw new Error('No dispatch builder for wc'); const [, io] = await runDispatch(builder, [run.scope], [], bag, dispatch, cwd, vfs, stdin); return { scope: run.scope, data: new Uint8Array(0), io }; } /** * Count each operand on its own mount and lay the rows out together. Each * operand runs through its owning mount's wc, so a mount that counts without * reading its file (a database row count) still does, and reading mounts * stream. Only the layout spans the line: the counts each run reports * (`IOResult.records`) go through the generic's formatter with GNU's * column width, and no mount's output text is read back. A run that succeeds * without counts is recounted on its own (`recount`). Mirrors Python's * run_wc. */ export async function runWc(scopes, flagKwargs, dispatch, runSingle, cwd = '/', vfs, stdin = null) { const parsed = parseFlags(flagKwargs); if (typeof parsed === 'string') { return [null, new IOResult({ exitCode: 1, stderr: ENC.encode(parsed) })]; } const asked = COLUMNS.filter((c) => parsed[c]); const columns = asked.length > 0 ? asked : ['lines', 'words', 'bytes']; const each = { ...flagKwargs, total: 'never' }; const runs = []; for (const run of await runOperands(runSingle, Cmd.WC, scopes, [], each)) { runs.push(await recount(run, each, dispatch, cwd, vfs, stdin)); } const rows = []; const sizes = []; const totals = columns.map(() => 0); for (const run of runs) { for (const counted of run.io.records ?? []) { const values = [...(counted.values ?? [])]; rows.push({ values, label: counted.path.rawPath }); sizes.push(await operandSize(dispatch, run.scope, values)); values.forEach((value, i) => { const sum = totals[i] ?? 0; totals[i] = columns[i] === 'maxLineLength' ? Math.max(sum, value) : sum + value; }); } } const width = numberWidth(sizes, scopes.length, columns.length); const body = formatCountRows(rows, totals, scopes.length, parsed.total, width); return [body, await mergeOperandIos(runs, Math.max(...runs.map((run) => run.io.exitCode)))]; } //# sourceMappingURL=wc.js.map