import fs from 'fs'; import path from 'path'; import { glob } from 'glob'; import { formatPosition, Position } from '@preserves/core'; import { readSchema } from '../reader'; import chalk from 'chalk'; import * as M from '../meta'; export interface Diagnostic { type: 'warn' | 'error'; file: string | null; detail: Error | { message: string, pos: Position | null }; }; export type InputFile = { inputFilePath: string, text: string, baseRelPath: string, modulePath: M.ModulePath, schema: M.Schema, }; export type XrefFile = { xrefFilePath: string, text: string, modulePath: M.ModulePath, schema: M.Schema, }; export type Expanded = { base: string, inputFiles: Array, xrefFiles: Array, failures: Array, }; export function computeBase(paths: string[]): string { if (paths.length === 0) { return ''; } else if (paths.length === 1) { const d = path.dirname(paths[0]); return (d === '.') ? '' : d + '/'; } else { let i = 0; while (true) { let ch: string | null = null for (const p of paths) { if (i >= p.length) return p.slice(0, i); if (ch === null) ch = p[i]; if (p[i] !== ch) return p.slice(0, i); } i++; } } } export function expandInputGlob( input: string[], xrefs: string[], base0: string | undefined, ): Expanded { const matches = input.flatMap(i => glob.sync(i)); const failures: Array = []; function processInputFile(base: string, filePath: string, xref: true): XrefFile[]; function processInputFile(base: string, filePath: string, xref: false): InputFile[]; function processInputFile(base: string, filePath: string, xref: boolean): (InputFile | XrefFile)[] { if (!xref && !filePath.startsWith(base)) { throw new Error(`Input filename ${filePath} falls outside base ${base}`); } try { const text = fs.readFileSync(filePath, 'utf-8'); const baseRelPath = filePath.slice(base.length); const modulePath = baseRelPath.split('/').map(p => p.split('.')[0]).map(Symbol.for); const schema = readSchema(text, { name: filePath, readInclude(includePath: string): string { return fs.readFileSync( path.resolve(path.dirname(filePath), includePath), 'utf-8'); }, }); if (xref) { return [{ xrefFilePath: filePath, text, modulePath, schema }]; } else { return [{ inputFilePath: filePath, text, baseRelPath, modulePath, schema }]; } } catch (e) { failures.push({ type: 'error', file: filePath, detail: e as Error }); return []; } } const base = base0 ?? computeBase(matches); return { base, inputFiles: matches.flatMap(f => processInputFile(base, f, false)), xrefFiles: xrefs.flatMap(i => { const names = glob.sync(i); const base = computeBase(names); return names.flatMap(f => processInputFile(base, f, true)); }), failures, }; } export function changeExt(p: string, newext: string): string { return p.slice(0, -path.extname(p).length) + newext; } export function formatFailures(failures: Array, traceback = false): void { for (const d of failures) { console.error( (d.type === 'error' ? chalk.redBright('[ERROR]') : chalk.yellowBright('[WARNING]')) + ' ' + chalk.blueBright(formatPosition((d.detail as any).pos ?? d.file)) + ': ' + d.detail.message + (traceback && (d.detail instanceof Error) ? '\n' + d.detail.stack : '')); } if (failures.length > 0) { console.error(); } }