UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

210 lines • 8.13 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 { FileStat, FileType } from "../../types.js"; import { rstripSlash } from "../../utils/slash.js"; import { ALL_MODE_BITS, CHAR_MODE, DIR_MODE, FIFO_MODE, FILE_MODE, LINK_MODE, } from "../../utils/stat_view.js"; import { lsModeString } from "./utils/formatting.js"; import { groupName, ownerName } from "./utils/identity.js"; // One mode per kind, spelled from the same constants every stat // translator uses (utils/stat_view.ts); links are 777 the way ls draws // them. A reported mode (chmod overlay, a backend that knows) supplies // the permission bits; the kind always fixes the type bits. const KIND_MODE = { d: DIR_MODE, l: LINK_MODE, c: CHAR_MODE, p: FIFO_MODE, f: FILE_MODE, }; const KIND_TYPE = { d: FileType.DIRECTORY, l: FileType.SYMLINK, c: FileType.CHAR_DEVICE, p: FileType.FIFO, f: FileType.FILE, }; function relativePart(row, base) { if (row === base) return ''; const stem = base.endsWith('/') ? base : base + '/'; if (row.startsWith(stem)) return row.slice(stem.length); return row; } function pad(n, width, fill = '0') { return String(n).padStart(width, fill); } const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; const MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; // Midnight UTC of a calendar day, its year as given: `Date.UTC` reads 0-99 // as 1900-1999. function utcDay(year, month, day) { const date = new Date(0); date.setUTCFullYear(year, month, day); return date; } // The ISO 8601 week-numbering year and week of a UTC date. function isoWeek(dt) { const thursday = utcDay(dt.getUTCFullYear(), dt.getUTCMonth(), dt.getUTCDate()); thursday.setUTCDate(thursday.getUTCDate() + 3 - ((dt.getUTCDay() + 6) % 7)); const year = thursday.getUTCFullYear(); const week = 1 + Math.floor((thursday.getTime() - utcDay(year, 0, 1).getTime()) / (7 * 86_400_000)); return [year, week]; } /** * Every `%T<letter>` GNU find renders, for one UTC timestamp. `%TS`, `%TT` * and `%TX` carry the fraction GNU appends. Mirrors Python's `_time_fields`. */ function timeFields(ts) { const total = Math.round(ts * 1_000_000); const whole = Math.floor(total / 1_000_000); const dt = new Date(whole * 1000); const frac = `${pad(total - whole * 1_000_000, 6)}0000`; const [isoYear, isoWeekNo] = isoWeek(dt); const year = dt.getUTCFullYear(); const month = dt.getUTCMonth(); const hour = dt.getUTCHours(); const wday = dt.getUTCDay(); const isoDay = wday === 0 ? 7 : wday; const yday = Math.floor((utcDay(year, month, dt.getUTCDate()).getTime() - utcDay(year, 0, 1).getTime()) / 86_400_000); const hour12 = hour % 12 === 0 ? 12 : hour % 12; const day = DAY_NAMES[wday] ?? ''; const monthName = MONTH_NAMES[month] ?? ''; const clock = `${pad(hour, 2)}:${pad(dt.getUTCMinutes(), 2)}:${pad(dt.getUTCSeconds(), 2)}`; const date = `${pad(month + 1, 2)}/${pad(dt.getUTCDate(), 2)}/${pad(year % 100, 2)}`; const meridiem = hour < 12 ? 'AM' : 'PM'; return { '@': `${String(whole)}.${frac}`, '+': `${pad(year, 4)}-${pad(month + 1, 2)}-${pad(dt.getUTCDate(), 2)}+${clock}.${frac}`, a: day.slice(0, 3), A: day, b: monthName.slice(0, 3), B: monthName, c: `${day.slice(0, 3)} ${monthName.slice(0, 3)} ${pad(dt.getUTCDate(), 2, ' ')} ${clock} ${String(year)}`, C: pad(Math.floor(year / 100), 2), d: pad(dt.getUTCDate(), 2), D: date, e: pad(dt.getUTCDate(), 2, ' '), F: `${pad(year, 4)}-${pad(month + 1, 2)}-${pad(dt.getUTCDate(), 2)}`, g: pad(isoYear % 100, 2), G: String(isoYear), h: monthName.slice(0, 3), H: pad(hour, 2), I: pad(hour12, 2), j: pad(yday + 1, 3), k: pad(hour, 2, ' '), l: pad(hour12, 2, ' '), m: pad(month + 1, 2), M: pad(dt.getUTCMinutes(), 2), n: '\n', p: meridiem, P: meridiem.toLowerCase(), r: `${pad(hour12, 2)}:${pad(dt.getUTCMinutes(), 2)}:${pad(dt.getUTCSeconds(), 2)} ${meridiem}`, R: `${pad(hour, 2)}:${pad(dt.getUTCMinutes(), 2)}`, s: String(whole), S: `${pad(dt.getUTCSeconds(), 2)}.${frac}`, t: '\t', T: `${clock}.${frac}`, u: String(isoDay), U: pad(Math.floor((yday + 7 - wday) / 7), 2), V: pad(isoWeekNo, 2), w: String(wday), W: pad(Math.floor((yday + 7 - (isoDay - 1)) / 7), 2), x: date, X: `${clock}.${frac}`, y: pad(year % 100, 2), Y: String(year), z: '+0000', Z: 'UTC', }; } function modeBits(st, kind) { const base = KIND_MODE[kind]; const mode = st?.mode ?? null; if (mode === null) return base; return (base & ~ALL_MODE_BITS) | (mode & ALL_MODE_BITS); } // Render one compiled -printf format against one result row. Directives // cover what GNU's find agents actually use: the path family (%p %P %f %h // %d), the stat family (%s %y %Y %m %M), %T times, and the backslash // escapes, which mirage-rs expanded when it compiled the format. Times // render in UTC (mirage timestamps are zone-carrying ISO strings; GNU // renders the local zone). %Y on a symlink row reports the target's kind, // N when the link dangles; on any other row it is %y. Mirrors the Python // expand_printf. export function expandPrintf(parts, row, startBase, st, identity = null) { const kind = st === null ? 'f' : st.kind; return parts .map((part) => { if ('lit' in part) return part.lit; if ('time' in part) return timeFields(st === null ? 0 : st.mtimeEpoch)[part.time] ?? ''; return directive(part.dir, row, startBase, st, identity, kind); }) .join(''); } function directive(code, row, startBase, st, identity, kind) { if (code === 'p') return row; if (code === 'P') return relativePart(row, startBase); if (code === 'f') { const trimmed = rstripSlash(row); return trimmed === '' ? '/' : (trimmed.split('/').pop() ?? trimmed); } if (code === 'h') { const trimmed = rstripSlash(row); if (!trimmed.includes('/')) return trimmed === '' ? '/' : '.'; const head = trimmed.slice(0, trimmed.lastIndexOf('/')); return head === '' ? '/' : head; } if (code === 'd') { const rel = relativePart(row, startBase); return rel === '' ? '0' : String(rel.split('/').length); } if (code === 's') return String(st === null ? 0 : st.size); if (code === 'y') return st === null ? 'U' : kind; if (code === 'Y') { if (st === null) return 'U'; return kind === 'l' ? (st.targetKind ?? 'N') : kind; } if (code === 'u' || code === 'U') return ownerName(st?.uid ?? null, identity); if (code === 'g' || code === 'G') return groupName(st?.gid ?? null, identity); if (code === 'm') return (modeBits(st, kind) & ALL_MODE_BITS).toString(8); const bits = modeBits(st, kind); return lsModeString(new FileStat({ name: '', type: KIND_TYPE[kind], mode: bits & ALL_MODE_BITS })); } //# sourceMappingURL=find_printf.js.map