UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

237 lines • 8.23 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 { shlexSplit } from "../utils/shlex.js"; import { byteChar, decodeText, encodeText } from "./bytes.js"; // The ANSI-C escape table bash decodes $'...' with. \e/\E // are here although printf lacks them; \c takes an argument here while // printf's \c means stop, which is why the printf reader is not reused. const SIMPLE = { a: '\x07', b: '\b', e: '\x1b', E: '\x1b', f: '\f', n: '\n', r: '\r', t: '\t', v: '\v', '\\': '\\', "'": "'", '"': '"', '?': '?', }; const HEX = /[0-9a-fA-F]/; const OCTAL = /[0-7]/; function scanHex(content, start, limit) { let end = start; while (end < content.length && end - start < limit && HEX.test(content[end] ?? '')) { end += 1; } return content.slice(start, end); } /** * bash's UTF-8 encoding of an escape's value past ASCII. * * UTF-8-shaped bytes for any 32-bit value: surrogate halves encode like * ordinary three-byte characters, values past Unicode take the old-style * four- to six-byte forms, and 0x80000000 and past produce nothing at all. */ function u32Utf8(value) { if (value < 0x800) return [0xc0 | (value >> 6), 0x80 | (value & 0x3f)]; if (value < 0x10000) { return [0xe0 | (value >> 12), 0x80 | ((value >> 6) & 0x3f), 0x80 | (value & 0x3f)]; } if (value < 0x200000) { return [ 0xf0 | (value >> 18), 0x80 | ((value >> 12) & 0x3f), 0x80 | ((value >> 6) & 0x3f), 0x80 | (value & 0x3f), ]; } if (value < 0x4000000) { return [ 0xf8 | (value >> 24), 0x80 | ((value >> 18) & 0x3f), 0x80 | ((value >> 12) & 0x3f), 0x80 | ((value >> 6) & 0x3f), 0x80 | (value & 0x3f), ]; } if (value < 0x80000000) { return [ 0xfc | (value >> 30), 0x80 | ((value >> 24) & 0x3f), 0x80 | ((value >> 18) & 0x3f), 0x80 | ((value >> 12) & 0x3f), 0x80 | ((value >> 6) & 0x3f), 0x80 | (value & 0x3f), ]; } return []; } /** * The text a `\u` or `\U` escape writes for its value. * * bash UTF-8-encodes every value under a UTF-8 locale: a * valid scalar is its character, while surrogate halves and values past * Unicode become raw UTF-8-shaped bytes, and 0x80000000 and past produce * nothing. Pinned: `\uD800` is ed a0 80, `\U00110000` is f4 90 80 80, * `\UFFFFFFFF` is empty. */ export function codePointText(value) { if (value <= 0x7f || (value <= 0x10ffff && !(value >= 0xd800 && value <= 0xdfff))) { return String.fromCodePoint(value); } return u32Utf8(value).map(byteChar).join(''); } /** * The decoded pieces as text, a byte run read back as its characters. * * bash builds a $'...' word as bytes, so `\xc3\xa9` is the two bytes of an é * and names the same file as a typed one. The escapes arrive one raw byte at * a time, so the joined word is read back once: a run that is valid UTF-8 * becomes its characters and any other byte stays raw. */ function asText(out) { return decodeText(encodeText(out.join(''))); } /** * Decode the body of a $'...' word to the text it names. * * Follows bash 5.2 (under a UTF-8 locale): simple * escapes, 1-3 octal digits with the value masked to a byte, \xHH * bytes, \u and \U values UTF-8-encoded (surrogates and * values past Unicode come out as raw UTF-8-shaped bytes), \cX control * characters (X of `?` is DEL, an escaped backslash counts as one * operand), and any other or incomplete escape kept verbatim, backslash * included. A NUL truncates the rest of this word segment, the C-string * behavior; the segment alone is cut, so `x$'a\0b'y` still expands to * `xay`. */ export function decodeAnsiC(content) { const out = []; let i = 0; while (i < content.length) { const char = content[i] ?? ''; if (char !== '\\' || i + 1 === content.length) { out.push(char); i += 1; continue; } const marker = content[i + 1] ?? ''; const simple = SIMPLE[marker]; if (simple !== undefined) { out.push(simple); i += 2; continue; } if (OCTAL.test(marker)) { let end = i + 1; while (end < content.length && end - i <= 3 && OCTAL.test(content[end] ?? '')) { end += 1; } const value = parseInt(content.slice(i + 1, end), 8); if ((value & 0xff) === 0) return asText(out); out.push(byteChar(value)); i = end; continue; } if (marker === 'x') { const digits = scanHex(content, i + 2, 2); if (digits === '') { out.push('\\x'); i += 2; continue; } const value = parseInt(digits, 16); if (value === 0) return asText(out); out.push(byteChar(value)); i += 2 + digits.length; continue; } if (marker === 'u' || marker === 'U') { const digits = scanHex(content, i + 2, marker === 'u' ? 4 : 8); if (digits === '') { out.push(`\\${marker}`); i += 2; continue; } const value = parseInt(digits, 16); if (value === 0) return asText(out); out.push(codePointText(value)); i += 2 + digits.length; continue; } if (marker === 'c') { if (i + 2 === content.length) { out.push('\\c'); i += 2; continue; } const operand = content[i + 2] ?? ''; i += 3; if (operand === '\\' && content[i] === '\\') { // \c\\ spells an escaped backslash operand; both characters // belong to it. i += 1; } const value = operand === '?' ? 0x7f : (operand.toUpperCase().codePointAt(0) ?? 0) & 0x1f; if (value === 0) return asText(out); out.push(String.fromCharCode(value)); continue; } out.push(char + marker); i += 2; } return asText(out); } // The text an unquoted word's backslash escapes name. export function unescapeUnquoted(text) { if (!text.includes('\\')) return text; const parts = shlexSplit(text); return parts[0] ?? text; } // The text a double-quoted segment's escapes name. Bash recognizes // \$, \`, \", \\ and \<newline> inside double quotes; every other // backslash stays. export function unescapeDquoted(text) { const NUL = String.fromCharCode(0); let out = text; out = out.replaceAll('\\\\', NUL); out = out.replaceAll('\\"', '"'); out = out.replaceAll('\\$', '$'); out = out.replaceAll('\\`', '`'); out = out.replaceAll('\\\n', ''); return out.replaceAll(NUL, '\\'); } /** * Whether an odd run of backslashes ends `text`, so its last backslash * escapes what follows: the newline it continues, or the end of the input it * quotes. An even run is escaped backslashes only. Mirrors Python's * ends_escaped. */ export function endsEscaped(text) { let run = 0; while (run < text.length && text.charCodeAt(text.length - 1 - run) === 92) run += 1; return run % 2 === 1; } //# sourceMappingURL=escapes.js.map