@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
137 lines • 6.3 kB
JavaScript
// ========= 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 { PolicyDenied } from "../../../../policy/errors.js";
import { encodeText } from "../../../../shell/bytes.js";
import { ReadonlyVariableError } from "../../../../shell/errors.js";
import { positionalParams, sessionView } from "../../../session/state.js";
import { ExecutionNode } from "../../../types.js";
import { builtinUsage, isValidName, requireView } from "../shared.js";
async function getoptsFinish(session, view, name, optValue, optarg, newOptind, newPos, exitCode, stderr = null) {
// The name is assigned last, exactly as bash does: OPTIND/OPTARG and
// the hidden cursor still advance, but a bad destination fails the
// write and turns the call into a status-1 error. Writes go through
// the session view, so a preSession policy or a readonly OPTARG /
// OPTIND refuses here too.
try {
if (!isValidName(name)) {
stderr = encodeText(`bash: getopts: \`${name}': not a valid identifier\n`);
exitCode = 1;
}
else if (session.readonlyVars.has(name)) {
stderr = encodeText(`bash: ${name}: readonly variable\n`);
exitCode = 1;
}
else {
await view.set(name, optValue);
}
if (optarg === null)
await view.unset('OPTARG');
else
await view.set('OPTARG', optarg);
await view.set('OPTIND', String(newOptind));
}
catch (err) {
if (err instanceof ReadonlyVariableError) {
stderr = encodeText(`bash: ${err.varName}: readonly variable\n`);
exitCode = 1;
}
else if (err instanceof PolicyDenied) {
stderr = encodeText(`${err.message}\n`);
exitCode = 1;
}
else {
throw err;
}
}
session.getoptsPos = newPos;
session.getoptsOptind = newOptind;
const io = new IOResult(stderr === null ? { exitCode } : { exitCode, stderr });
const node = stderr === null
? new ExecutionNode({ command: 'getopts', exitCode })
: new ExecutionNode({ command: 'getopts', exitCode, stderr });
return [null, io, node];
}
/** Parse one option per call, with bash's getopts semantics. */
export async function handleGetopts(args, session, callStack = null, state = null) {
if (args.length < 2) {
const err = encodeText(builtinUsage('getopts'));
return [
null,
new IOResult({ exitCode: 2, stderr: err }),
new ExecutionNode({ command: 'getopts', exitCode: 2, stderr: err }),
];
}
const view = requireView(state);
const optstring = args[0] ?? '';
const name = args[1] ?? '';
const params = args.length > 2 ? args.slice(2) : positionalParams(session, callStack);
const silent = optstring.startsWith(':');
const verbose = !silent && (session.env.OPTERR ?? '1') !== '0';
const parsed = Number.parseInt(session.env.OPTIND ?? '1', 10);
let optind = Number.isNaN(parsed) ? 1 : parsed;
// Bash treats a nonpositive OPTIND as a restart at argument 1.
const restart = optind < 1;
if (restart)
optind = 1;
if (restart || session.getoptsOptind !== optind)
session.getoptsPos = 0;
let pos = session.getoptsPos;
if (optind > params.length) {
return getoptsFinish(session, view, name, '?', null, optind, 0, 1);
}
const word = params[optind - 1] ?? '';
// A stale cursor left past the end of the current word (a shorter or
// reused argument) restarts the scan rather than reading undefined.
if (pos >= word.length)
pos = 0;
if (pos === 0) {
if (!word.startsWith('-') || word === '-') {
return getoptsFinish(session, view, name, '?', null, optind, 0, 1);
}
if (word === '--')
return getoptsFinish(session, view, name, '?', null, optind + 1, 0, 1);
pos = 1;
}
const letter = word[pos] ?? '';
const rest = word.slice(pos + 1);
const idx = optstring.indexOf(letter);
const isValid = letter !== ':' && idx !== -1;
const takesArg = isValid && idx + 1 < optstring.length && optstring[idx + 1] === ':';
if (!isValid) {
const [afterOptind, afterPos] = rest ? [optind, pos + 1] : [optind + 1, 0];
if (silent)
return getoptsFinish(session, view, name, '?', letter, afterOptind, afterPos, 0);
const err = verbose ? encodeText(`bash: illegal option -- ${letter}\n`) : null;
return getoptsFinish(session, view, name, '?', null, afterOptind, afterPos, 0, err);
}
if (!takesArg) {
const [afterOptind, afterPos] = rest ? [optind, pos + 1] : [optind + 1, 0];
return getoptsFinish(session, view, name, letter, null, afterOptind, afterPos, 0);
}
if (rest)
return getoptsFinish(session, view, name, letter, rest, optind + 1, 0, 0);
if (optind < params.length) {
return getoptsFinish(session, view, name, letter, params[optind] ?? '', optind + 2, 0, 0);
}
if (silent)
return getoptsFinish(session, view, name, ':', letter, optind + 1, 0, 0);
const err = verbose ? encodeText(`bash: option requires an argument -- ${letter}\n`) : null;
return getoptsFinish(session, view, name, '?', null, optind + 1, 0, 0, err);
}
/** The `getopts` arm. */
export async function getoptsBuiltin(call) {
return handleGetopts([...call.argv.args], call.context.session, call.callStack, sessionView(call.context.session, call.registry.policies, call.context.frame.diagnostics));
}
//# sourceMappingURL=getopts.js.map