@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
185 lines • 7.69 kB
JavaScript
import { ByteSpool, IO_BUFFER_SIZE } from "../../../io/spool.js";
// ========= 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 { stdinStat, stdinStream, streamOutput } from "../utils/stream.js";
import { splitReadable } from "../utils/operands.js";
import { IOResult } from "../../../io/types.js";
import { closeQuietly } from "../../../io/stream.js";
import { LineFacts } from "../../../types.js";
import { wordRefusalError } from "../../spec/usage.js";
import { matchWord } from "../../spec/word_choice.js";
import { OPERAND } from "../../spec/constants.js";
import { FlagView } from "../../spec/flag_view.js";
import { specOf } from "../../spec/builtins.js";
import { resolveSource } from "../utils/stream.js";
import { StreamRefusal } from "../../../rs/errors.js";
import { refused } from "../../../rs/scan.js";
import { openStream, openStreamSync } from "../../../rs/stream.js";
import { byteView, encodeText } from "../../../shell/bytes.js";
// The --all-repeated and --group words, in the order GNU lists each back. No
// aliases in either, so one candidate per line.
const ALL_REPEATED_ARGS = ['none', 'prepend', 'separate'];
const GROUP_ARGS = ['prepend', 'append', 'separate', 'both'];
function methodWord(value, defaultValue, allowed, option) {
if (value === undefined || value === false)
return null;
const normalized = value === true ? defaultValue : value;
const word = String(normalized);
// A non-string bag value (a number, a list) is not a candidate word at
// all, so it never reaches the matcher and reads as invalid.
const match = typeof normalized === 'string' ? matchWord(word, allowed) : null;
if (match?.matched !== true) {
const kind = match === null ? 'invalid' : match.kind;
throw wordRefusalError('uniq', `--${option}`, word, allowed, undefined, kind);
}
return match.word;
}
function parseFlags(bag, operands) {
const fl = new FlagView(bag, specOf('uniq'));
const typed = fl.texts(OPERAND, 'skip_fields', 'skip_chars', 'check_chars');
if (typed.every(([name]) => name !== OPERAND)) {
typed.push(...operands.map((word) => [OPERAND, word]));
}
const count = fl.asBool('count');
const duplicatesOnly = fl.asBool('repeated');
const uniqueOnly = fl.asBool('unique');
const allRepeated = methodWord(fl.asBool('D') ? true : fl.raw('all_repeated'), 'none', ALL_REPEATED_ARGS, 'all-repeated');
const group = methodWord(fl.raw('group'), 'separate', GROUP_ARGS, 'group');
return {
count,
duplicatesOnly,
uniqueOnly,
typed,
ignoreCase: fl.asBool('ignore_case'),
allRepeated,
group,
zeroTerminated: fl.asBool('zero_terminated'),
};
}
/** The options GNU uniq refuses together once it has read them all. Mirrors
* Python's `_conflict`. */
function conflict(parsed) {
if (parsed.group !== null &&
(parsed.count || parsed.duplicatesOnly || parsed.uniqueOnly || parsed.allRepeated !== null)) {
return 'uniq: --group is mutually exclusive with -c/-d/-D/-u';
}
if (parsed.count && parsed.allRepeated !== null) {
return 'uniq: printing all duplicated lines and repeat counts is meaningless';
}
return null;
}
/**
* uniq's options, its counts and operands refused by the engine in the order
* typed, before any operand is read: the flags, or uniq's refusal. The flags
* carry each operand's glob spread over its matches. Mirrors Python's
* `uniq_flags`.
*/
export function uniqFlags(bag, operands = []) {
let parsed;
try {
parsed = parseFlags(bag, operands);
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
return new IOResult({ exitCode: 1, stderr: encodeText(`${message}\n`) });
}
try {
openStreamSync('uniq', engineConfig(parsed)).close();
}
catch (err) {
if (!(err instanceof StreamRefusal))
throw err;
return refused(err);
}
const refusal = conflict(parsed);
if (refusal !== null)
return new IOResult({ exitCode: 1, stderr: encodeText(`${refusal}\n`) });
return parsed;
}
/** The uniq engine's config, the counts and operands as typed. Mirrors Python's
* `engine_config`. */
export function engineConfig(parsed) {
return {
count: parsed.count,
repeated: parsed.duplicatesOnly,
unique: parsed.uniqueOnly,
typed: parsed.typed.map(([name, text]) => [name, byteView(text)]),
ignore_case: parsed.ignoreCase,
all_repeated: parsed.allRepeated,
group: parsed.group,
zero_terminated: parsed.zeroTerminated,
};
}
export async function uniqGeneric(parsed, paths, ctx, stream, write, stat) {
stream = stdinStream(stream, ctx.stdin);
if (paths.length > 0 && stat !== undefined) {
// The input is stat'ed before the lazy stream starts, so a missing or
// unreadable one is reported in uniq's own words rather than
// surfacing mid-drain.
const [, err] = await splitReadable(paths.slice(0, 1), stdinStat(stat), 'uniq');
if (err !== '')
return [null, new IOResult({ exitCode: 1, stderr: encodeText(err) })];
}
let source;
if (paths.length > 0) {
const input = paths[0];
if (input === undefined)
return [null, new IOResult()];
source = stream(input);
}
else {
try {
source = resolveSource(ctx.stdin);
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
return [null, new IOResult({ exitCode: 1, stderr: encodeText(`${message}\n`) })];
}
}
const output = streamOutput(await openStream('uniq', engineConfig(parsed)), source, ctx.signal);
const outputPath = paths[1];
if (outputPath !== undefined && outputPath.rawPath !== '-') {
if (write === undefined) {
await closeQuietly(output);
return [
null,
new IOResult({ exitCode: 1, stderr: encodeText('uniq: output is not writable\n') }),
];
}
// Consume before writing: Mirage preserves a shared INPUT/OUTPUT;
// GNU truncates it when opening the output.
const stored = new ByteSpool();
try {
await stored.fill(output);
await write(outputPath, stored.stream(0, undefined, IO_BUFFER_SIZE));
}
finally {
await stored.close();
}
return [new Uint8Array(), new IOResult()];
}
return [output, new IOResult()];
}
function namesOutput(operands) {
return operands.length > 1 || operands.some((w) => typeof w !== 'string' && w.pattern !== null);
}
/**
* A uniq line writes its second operand, which a pattern the backend
* expands may name too; a quoted name is no pattern. Mirrors Python's
* `facts`.
*/
export function facts(fl, operands) {
return new LineFacts({ writes: namesOutput(operands) });
}
//# sourceMappingURL=uniq.js.map