Skip to content

Commit

Permalink
Ask before deleting generated folder
Browse files Browse the repository at this point in the history
  • Loading branch information
msujew committed Sep 1, 2021
1 parent cc27b9f commit 1a372ea
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 23 deletions.
46 changes: 29 additions & 17 deletions packages/langium-cli/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,12 +21,12 @@ export type GenerateOptions = {

const services = createLangiumGrammarServices();

export function generate(config: LangiumConfig): boolean {
export async function generate(config: LangiumConfig): Promise<boolean> {
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;
Expand Down Expand Up @@ -56,56 +56,68 @@ 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<boolean> {
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<boolean> {
try {
fs.mkdirsSync(path);
await fs.mkdirs(path);
return false;
} catch (e) {
console.error(`${getTime()}Failed to create directory ${path.red.bold}`, e);
return true;
}
}

function writeWithFail(path: string, content: string): void {
async function writeWithFail(path: string, content: string): Promise<void> {
try {
fs.writeFileSync(path, content);
await fs.writeFile(path, content);
} catch (e) {
console.error(`${getTime()}Failed to write file to ${path.red.bold}`, e);
}
Expand Down
26 changes: 26 additions & 0 deletions packages/langium-cli/src/generator/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -65,6 +66,31 @@ function collectElementKeywords(element: langium.AbstractElement, keywords: Set<
}
}

export function getUserInput(text: string): Promise<string> {
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<R extends string>(text: string, values: R[], defaultValue: R, lowerCase = true): Promise<R> {
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' });
12 changes: 6 additions & 6 deletions packages/langium-cli/src/langium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<boolean>): Promise<void> {
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);
Expand All @@ -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`);
Expand Down

0 comments on commit 1a372ea

Please sign in to comment.