2021-04-01 19:12:11 +00:00
|
|
|
import fs from 'fs';
|
|
|
|
import path from 'path';
|
|
|
|
import { glob } from 'glob';
|
2021-12-13 11:52:24 +00:00
|
|
|
import { IdentitySet, formatPosition, Position } from '@preserves/core';
|
2021-04-01 19:12:11 +00:00
|
|
|
import { readSchema } from '../reader';
|
|
|
|
import chalk from 'chalk';
|
2021-10-11 10:56:53 +00:00
|
|
|
import * as M from '../meta';
|
2021-04-01 19:12:11 +00:00
|
|
|
|
|
|
|
export interface Diagnostic {
|
|
|
|
type: 'warn' | 'error';
|
|
|
|
file: string | null;
|
|
|
|
detail: Error | { message: string, pos: Position | null };
|
|
|
|
};
|
|
|
|
|
2021-12-13 11:17:12 +00:00
|
|
|
export type InputFile = {
|
2021-12-13 11:52:24 +00:00
|
|
|
inputBaseDir: string,
|
2021-12-13 11:17:12 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2021-10-11 10:56:53 +00:00
|
|
|
export type Expanded = {
|
2021-12-13 11:52:24 +00:00
|
|
|
baseDirs: Array<string>,
|
2021-12-13 11:17:12 +00:00
|
|
|
inputFiles: Array<InputFile>,
|
|
|
|
xrefFiles: Array<XrefFile>,
|
2021-10-11 10:56:53 +00:00
|
|
|
failures: Array<Diagnostic>,
|
|
|
|
};
|
|
|
|
|
2021-04-01 19:12:11 +00:00
|
|
|
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++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-03 15:12:13 +00:00
|
|
|
export function inputToInputGlob(userInput: string): { baseDir: string, glob: string } {
|
2021-12-13 11:52:24 +00:00
|
|
|
const colonPos = userInput.lastIndexOf(':');
|
|
|
|
let [base, g] = (colonPos === -1)
|
|
|
|
? [userInput, '**/*.prs']
|
|
|
|
: [userInput.slice(0, colonPos), userInput.slice(colonPos + 1)];
|
|
|
|
if (base[base.length - 1] !== '/') {
|
|
|
|
base = base + '/';
|
|
|
|
}
|
2022-05-03 15:12:13 +00:00
|
|
|
return { baseDir: base, glob: g };
|
|
|
|
}
|
|
|
|
|
|
|
|
export function findSchemas(userInput: string): ({
|
|
|
|
names: string[],
|
|
|
|
base: string,
|
|
|
|
}) {
|
|
|
|
const { baseDir: base, glob: g } = inputToInputGlob(userInput);
|
2021-12-13 11:52:24 +00:00
|
|
|
const names = glob.sync(base + g);
|
|
|
|
return { names, base };
|
|
|
|
}
|
|
|
|
|
|
|
|
export function expandInputGlob(input: string[], xrefs: string[]): Expanded {
|
2021-04-01 19:12:11 +00:00
|
|
|
const failures: Array<Diagnostic> = [];
|
2021-12-13 11:52:24 +00:00
|
|
|
const baseDirs = new IdentitySet<string>();
|
2021-04-01 19:12:11 +00:00
|
|
|
|
2021-12-13 11:17:12 +00:00
|
|
|
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)[] {
|
2021-12-13 11:52:24 +00:00
|
|
|
if (!filePath.startsWith(base)) {
|
2021-12-13 11:17:12 +00:00
|
|
|
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 {
|
2021-12-13 11:52:24 +00:00
|
|
|
return [{ inputBaseDir: base, inputFilePath: filePath, text, baseRelPath, modulePath, schema }];
|
2021-12-13 11:17:12 +00:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
failures.push({ type: 'error', file: filePath, detail: e as Error });
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-13 11:52:24 +00:00
|
|
|
const inputFiles = input.flatMap(userInput => {
|
|
|
|
const { names, base } = findSchemas(userInput);
|
|
|
|
baseDirs.add(base);
|
|
|
|
return names.flatMap(f => processInputFile(base, f, false));
|
|
|
|
});
|
|
|
|
|
|
|
|
const xrefFiles = xrefs.flatMap(userInput => {
|
|
|
|
const { names, base } = findSchemas(userInput);
|
|
|
|
baseDirs.add(base);
|
|
|
|
return names.flatMap(f => processInputFile(base, f, true));
|
|
|
|
});
|
2021-12-13 11:17:12 +00:00
|
|
|
|
2021-04-01 19:12:11 +00:00
|
|
|
return {
|
2021-12-13 11:52:24 +00:00
|
|
|
baseDirs: Array.from(baseDirs),
|
|
|
|
inputFiles,
|
|
|
|
xrefFiles,
|
2021-04-01 19:12:11 +00:00
|
|
|
failures,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function changeExt(p: string, newext: string): string {
|
|
|
|
return p.slice(0, -path.extname(p).length) + newext;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatFailures(failures: Array<Diagnostic>, traceback = false): void {
|
|
|
|
for (const d of failures) {
|
2021-05-21 15:33:29 +00:00
|
|
|
console.error(
|
2021-04-01 19:12:11 +00:00
|
|
|
(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) {
|
2021-05-21 15:33:29 +00:00
|
|
|
console.error();
|
2021-04-01 19:12:11 +00:00
|
|
|
}
|
|
|
|
}
|