UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

134 lines • 5.36 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 { specOf } from "../../spec/builtins.js"; import { OPERAND } from "../../spec/constants.js"; import { FlagView } from "../../spec/flag_view.js"; import { versionLine } from "../../spec/standard.js"; import { UsageError } from "../../errors.js"; import { IOResult } from "../../../io/types.js"; import { LineFacts } from "../../../types.js"; import { byteView } from "../../../shell/bytes.js"; import { rstripSlash } from "../../../utils/slash.js"; import { extractDest } from "./archive/extract.js"; import { StreamHost, hostedStream } from "../utils/stream_host.js"; const ENC = new TextEncoder(); // Info-ZIP answers an option it does not know with its usage block and exit // 10; -1, -2, -s, -m and -h are zipinfo's letters and mean nothing to unzip // proper (Info-ZIP's `unzip -h` is its help screen). const USAGE_EXIT = 10; const ZIPINFO_LETTERS = ['1', '2', 's', 'm', 'h']; // The letters mirage-rs reads, by the flag each one is. const LETTERS = { l: 'args_l', v: 'v', t: 't', p: 'p', Z: 'Z', '1': 'args_1', '2': '2', s: 's', m: 'm', h: 'h', o: 'o', n: 'n', }; function parseFlags(fl) { return { letters: Object.freeze(Object.fromEntries(Object.entries(LETTERS).map(([letter, flag]) => [letter, fl.asBool(flag)]))), // Only none, -q and -qq differ; mirage-rs takes a byte. quiet: Math.min(fl.asInt('q', 0), 2), dest: fl.asPath('d') ?? null, typed: fl.raw('d') !== undefined, excludes: fl.asList('x'), }; } /** * The member patterns and the -x patterns, as Info-ZIP reads them: -x takes * every operand typed after it, up to a -d, so the operands are placed by * where the flag tape recorded them; the first is the archive. A line whose * operands the tape does not account for keeps one pattern per -x. Mirrors * Python's _patterns. */ function patterns(fl, texts, excludes) { const tape = fl.occurrences('x', 'd', OPERAND); if (tape.filter(([name]) => name === OPERAND).length !== texts.length + 1) { return [[...texts], [...excludes]]; } const members = []; const excluded = []; let listing = false; let index = -1; for (const [name, value] of tape) { if (name === OPERAND) { const word = texts[index]; if (index >= 0 && word !== undefined) (listing ? excluded : members).push(word); index += 1; } else if (name === 'x' && typeof value === 'string') { excluded.push(value); listing = true; } else if (name === 'd') { listing = false; } } return [members, excluded]; } /** * List, test, pipe or extract an archive through mirage-rs's unzip, which * reads it from a host spool and writes each member where -d or the working * directory says, on whichever mount owns it. Mirrors unzip.py. */ export function unzipGeneric(paths, members, ctx, stream) { const fl = new FlagView(ctx.flags, specOf('unzip')); const flags = parseFlags(fl); const { letters, dest } = flags; const archive = paths[0]; if (archive === undefined) { // Info-ZIP answers -v without an archive with its version banner, and // mirage's version line is that banner here. if (letters.v === true) return [ENC.encode(versionLine('unzip')), new IOResult()]; return [null, new IOResult({ exitCode: 1, stderr: ENC.encode('unzip: missing operand\n') })]; } if (letters.Z !== true) { for (const letter of ZIPINFO_LETTERS) { if (letters[letter] === true) throw new UsageError(`unzip: -${letter} is a ZipInfo option and needs -Z`, USAGE_EXIT); } } const [chosen, excludes] = patterns(fl, members, flags.excludes); const config = { archive: byteView(archive.rawPath), members: chosen.map((member) => byteView(member)), excludes: excludes.map((pattern) => byteView(pattern)), base: byteView(rstripSlash(extractDest(dest, ctx.cwd.virtual))), q: flags.quiet, ...letters, }; if (flags.typed) config.dest = byteView(dest?.rawPath ?? ''); const host = new StreamHost([archive], stream, ctx, false); const io = new IOResult(); return [hostedStream('unzip', config, host, io, ctx.signal), io]; } /** * An unzip line writes unless it lists, tests or pipes (`-l`, `-t`, `-v`, * `-p`, `-Z`). Mirrors Python's `facts`. */ export function facts(fl, _operands) { return new LineFacts({ writes: !['args_l', 't', 'v', 'p', 'Z'].some((dest) => fl.asBool(dest)) }); } //# sourceMappingURL=unzip.js.map