UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

145 lines • 6 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 { materialize } from "../../io/types.js"; import { RegexError } from "../../rs/errors.js"; import { LineRegex } from "../../rs/regex.js"; import { decodeText } from "../../shell/bytes.js"; import { PathSpec } from "../../types.js"; import { UsageError } from "../errors.js"; import { RegexSyntax } from "./types.js"; export const NEVER_MATCH = '(?!)'; // The matcher options, as GNU grep names them: each one picks the dialect, // and two different ones on a line are refused. const MATCHERS = { E: RegexSyntax.EXTENDED, P: RegexSyntax.PERL, }; const CONFLICTING_MATCHERS = 'conflicting matchers specified'; // Resolve the pattern-list argument from -e values (list[str] when // multiple) or the positional. Returns the POSIX newline-joined pattern // list, or null when neither was supplied. export function patternArg(texts, flags) { const e = flags.asList('regexp'); if (e.length > 0) return e.join('\n'); if (texts.length > 0 && texts[0] !== undefined) return texts[0]; return null; } // Resolve the full pattern list from -e values, the positional, and -f // pattern files (read via the backend stream). Shared by the grep, rg, and // zgrep generics. When -f supplies zero patterns the NEVER_MATCH sentinel is // returned with neverMatch=true (callers must skip -F escaping for it). export async function resolvePattern(name, texts, flags, stream) { let pattern = patternArg(texts, flags); let neverMatch = false; // `raw` rather than `asList`, mirroring Python's `flags.raw(file_key)`: an // empty -f list still means "-f was supplied", which is what turns on the // NEVER_MATCH sentinel below. const patternFiles = flags.raw('file'); if (Array.isArray(patternFiles)) { for (const file of patternFiles) { const patternSpec = PathSpec.fromStrPath(file instanceof PathSpec ? file.virtual : file); let fileData; try { fileData = await materialize(stream(patternSpec)); } catch (err) { const msg = err instanceof Error ? err.message : String(err); const shown = file instanceof PathSpec ? file.rawPath : file; return { pattern: null, neverMatch: false, error: `${name}: ${shown}: ${msg}\n` }; } pattern = mergePatternList(pattern, fileData); } if (pattern === null) { pattern = NEVER_MATCH; neverMatch = true; } } return { pattern, neverMatch, error: null }; } export function mergePatternList(pattern, fileData) { const parts = pattern === null ? [] : pattern.split('\n'); if (fileData !== null && fileData.length > 0) { let text = decodeText(fileData); if (text.endsWith('\n')) text = text.slice(0, -1); for (const item of text.split('\n')) parts.push(item); } if (parts.length === 0) return null; return parts.join('\n'); } /** * The dialect grep's matcher options pick, refusing a mixture. GNU grep 3.11 * keeps one matcher: -G, -E, -F and -P each name one, repeating the same is * harmless, and any two different ones are `conflicting matchers specified` * (exit 2) in either order. -F is carried as `fixedString`, so it only takes * part in the check. */ export function matcherSyntax(fl, prog = 'grep') { const dests = { basic_regexp: 'G', extended_regexp: 'E', fixed_strings: 'F', perl_regexp: 'P', }; const chosen = new Set(); for (const [dest, matcher] of Object.entries(dests)) if (fl.asBool(dest)) chosen.add(matcher); if (chosen.size > 1) throw new UsageError(`${prog}: ${CONFLICTING_MATCHERS}`); for (const matcher of chosen) { const syntax = MATCHERS[matcher]; if (syntax !== undefined) return syntax; } return RegexSyntax.BASIC; } /** * GNU grep's compile-time warnings for a pattern list, as stderr. Only an * extended expression has any: GNU warns about a repetition operator at the * start of an expression (`grep: warning: * at start of expression`), once per * occurrence, which is also how GNU reads `(?<=...)`. */ export function patternWarnings(matcher, prog = 'grep') { return matcher.warnings.map((w) => `${prog}: warning: ${w}\n`).join(''); } /** * Compile a pattern list into one matcher; `utf8` means the lines are text * under a UTF-8 locale. A refusal is GNU grep's wording, and exits 2 as grep * does. */ export function compilePattern(pattern, ignoreCase = false, fixedString = false, wholeWord = false, syntax = RegexSyntax.EXTENDED, utf8 = false, lineRegexp = false) { // GNU grep 3.11: -x overrides -w in either order. wholeWord = wholeWord && !lineRegexp; const kind = fixedString ? 'fixed' : syntax === RegexSyntax.PERL ? 'perl' : syntax === RegexSyntax.BASIC ? 'basic' : 'extended'; try { return new LineRegex(pattern.split('\n'), kind, ignoreCase, utf8, wholeWord, lineRegexp); } catch (err) { if (err instanceof RegexError) throw new UsageError(`grep: ${err.message}`); throw err; } } //# sourceMappingURL=grep_pattern.js.map