From 1a372ea10df4df6d99a6e4c666451b3b14e5df99 Mon Sep 17 00:00:00 2001 From: Mark Sujew Date: Wed, 11 Aug 2021 10:44:08 +0000 Subject: [PATCH] Ask before deleting generated folder --- packages/langium-cli/src/generate.ts | 46 ++++++++++++++-------- packages/langium-cli/src/generator/util.ts | 26 ++++++++++++ packages/langium-cli/src/langium.ts | 12 +++--- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/packages/langium-cli/src/generate.ts b/packages/langium-cli/src/generate.ts index db931c773..cf3b1b618 100644 --- a/packages/langium-cli/src/generate.ts +++ b/packages/langium-cli/src/generate.ts @@ -12,7 +12,7 @@ import { generateAst } from './generator/ast-generator'; import { generateModule } from './generator/module-generator'; import { generateTextMate } from './generator/textmate-generator'; import { serializeGrammar } from './generator/grammar-serializer'; -import { getTime } from './generator/util'; +import { getTime, getUserChoice } from './generator/util'; export type GenerateOptions = { file?: string; @@ -21,12 +21,12 @@ export type GenerateOptions = { const services = createLangiumGrammarServices(); -export function generate(config: LangiumConfig): boolean { +export async function generate(config: LangiumConfig): Promise { const relPath = config[RelativePath]; let grammarFileContent: string; try { - grammarFileContent = fs.readFileSync(path.join(relPath, config.grammar), 'utf-8'); + grammarFileContent = await fs.readFile(path.join(relPath, config.grammar), 'utf-8'); } catch (e) { console.error(`${getTime()}Failed to read grammar file at ${path.join(relPath, config.grammar).red.bold}`, e); return false; @@ -56,46 +56,58 @@ export function generate(config: LangiumConfig): boolean { const output = path.join(relPath, config.out ?? 'src/generated'); console.log(`${getTime()}Writing generated files to ${output.white.bold}`); - if (rmdirWithFail(output)) { + + if (await rmdirWithFail(output, ['ast.ts', 'grammar.ts', 'grammar-access.ts', 'parser.ts', 'module.ts'])) { return false; } - if (mkdirWithFail(output)) { + if (await mkdirWithFail(output)) { return false; } const genAst = generateAst(grammar, config); - writeWithFail(path.join(output, 'ast.ts'), genAst); + await writeWithFail(path.join(output, 'ast.ts'), genAst); const serializedGrammar = serializeGrammar(services, grammar, config); - writeWithFail(path.join(output, 'grammar.ts'), serializedGrammar); + await writeWithFail(path.join(output, 'grammar.ts'), serializedGrammar); const genModule = generateModule(grammar, config); - writeWithFail(path.join(output, 'module.ts'), genModule); + await writeWithFail(path.join(output, 'module.ts'), genModule); if (config.textMate) { const genTmGrammar = generateTextMate(grammar, config); const textMatePath = path.join(relPath, config.textMate.out); console.log(`${getTime()}Writing textmate grammar to ${textMatePath.white.bold}`); const parentDir = path.dirname(textMatePath).split(path.sep).pop(); - parentDir && mkdirWithFail(parentDir); - writeWithFail(textMatePath, genTmGrammar); + parentDir && await mkdirWithFail(parentDir); + await writeWithFail(textMatePath, genTmGrammar); } return true; } -function rmdirWithFail(path: string): boolean { +async function rmdirWithFail(dirPath: string, expectedFiles?: string[]): Promise { try { - fs.removeSync(path); + let deleteDir = true; + if (expectedFiles) { + const existingFiles = await fs.readdir(dirPath); + const unexpectedFiles = existingFiles.filter(file => !expectedFiles.includes(path.basename(file))); + if (unexpectedFiles.length > 0) { + console.log(`${getTime()}Found unexpected files in the generated directory: ${unexpectedFiles.map(e => e.yellow).join(', ')}`); + deleteDir = await getUserChoice(`${getTime()}Do you want to delete the files?`, ['yes', 'no'], 'yes') === 'yes'; + } + } + if (deleteDir) { + await fs.remove(dirPath); + } return false; } catch (e) { - console.error(`${getTime()}Failed to delete directory ${path.red.bold}`, e); + console.error(`${getTime()}Failed to delete directory ${dirPath.red.bold}`, e); return true; } } -function mkdirWithFail(path: string): boolean { +async function mkdirWithFail(path: string): Promise { try { - fs.mkdirsSync(path); + await fs.mkdirs(path); return false; } catch (e) { console.error(`${getTime()}Failed to create directory ${path.red.bold}`, e); @@ -103,9 +115,9 @@ function mkdirWithFail(path: string): boolean { } } -function writeWithFail(path: string, content: string): void { +async function writeWithFail(path: string, content: string): Promise { try { - fs.writeFileSync(path, content); + await fs.writeFile(path, content); } catch (e) { console.error(`${getTime()}Failed to write file to ${path.red.bold}`, e); } diff --git a/packages/langium-cli/src/generator/util.ts b/packages/langium-cli/src/generator/util.ts index 01f2dda19..d635b56a6 100644 --- a/packages/langium-cli/src/generator/util.ts +++ b/packages/langium-cli/src/generator/util.ts @@ -8,6 +8,7 @@ import * as langium from 'langium'; import { CompositeGeneratorNode, GeneratorNode, NL, stream } from 'langium'; import fs from 'fs-extra'; import path from 'path'; +import * as readline from 'readline'; let start = process.hrtime(); @@ -65,6 +66,31 @@ function collectElementKeywords(element: langium.AbstractElement, keywords: Set< } } +export function getUserInput(text: string): Promise { + return new Promise(resolve => { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + rl.question(text, answer => { + resolve(answer); + rl.close(); + }); + }); +} + +export async function getUserChoice(text: string, values: R[], defaultValue: R, lowerCase = true): Promise { + const prompt = text + ' ' + values.map(v => v === defaultValue ? `[${v}]` : v).join('/') + ': '; + const answer = await getUserInput(prompt); + if (!answer) { + return defaultValue; + } + const lcAnswer = lowerCase ? answer.toLowerCase() : answer; + for (const value of values) { + if (value.startsWith(lcAnswer)) { + return value; + } + } + return defaultValue; +} + export const cliVersion = getLangiumCliVersion(); export const generatedHeader = getGeneratedHeader(); export const schema = fs.readJsonSync(path.join(__dirname, '../../schema.json'), { encoding: 'utf-8' }); diff --git a/packages/langium-cli/src/langium.ts b/packages/langium-cli/src/langium.ts index 6ae11d117..23bf8ccf2 100644 --- a/packages/langium-cli/src/langium.ts +++ b/packages/langium-cli/src/langium.ts @@ -27,7 +27,7 @@ program program.parse(process.argv); -function forEachConfig(options: GenerateOptions, callback: (config: LangiumConfig) => boolean): void { +async function forEachConfig(options: GenerateOptions, callback: (config: LangiumConfig) => Promise): Promise { const configs = loadConfigs(options.file); if (!configs.length) { console.error('Could not find a langium configuration. Please add a langium-config.json to your project or a langium section to your package.json.'.red); @@ -38,23 +38,23 @@ function forEachConfig(options: GenerateOptions, callback: (config: LangiumConfi console.error('Your langium configuration is invalid.'.red); process.exit(1); } - + const allSuccessful = await Promise.all(configs.map(callback)); if (options.watch) { - if (configs.every(callback)) { + if (allSuccessful) { console.log(`${getTime()}Langium generator finished ${'successfully'.green.bold} in: ${elapsedTime()}ms`); } console.log(getTime() + 'Langium generator will continue running in watch mode'); configs.forEach(e => { const grammarPath = path.join(e[RelativePath], e.grammar); - fs.watchFile(grammarPath, () => { + fs.watchFile(grammarPath, async () => { console.log(getTime() + 'File change detected. Starting compilation...'); elapsedTime(); - if (callback(e)) { + if (await callback(e)) { console.log(`${getTime()}Langium generator finished ${'successfully'.green.bold} in: ${elapsedTime()}ms`); } }); }); - } else if (configs.some(e => !callback(e))) { + } else if (!allSuccessful) { process.exit(1); } else { console.log(`${getTime()}Langium generator finished ${'successfully'.green.bold} in: ${elapsedTime()}ms`);