Skip to content

Commit 16899fd

Browse files
Add an option to generate a minimal project with Yeoman (#1967)
The `.js` extension is no longer imposed when generating a file. Now, the `destination` parameter is required and indicates where to generate the file, and with what extension. The `filePath` parameter is renamed as `source` to better fit the new behaviour and make the code more readable. --------- Co-authored-by: NicolasChusseau <nicolas.chusseau@etu.univ-nantes.fr>
1 parent be7349c commit 16899fd

40 files changed

+545
-34
lines changed

packages/generator-langium/src/index.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
1717

1818
const BASE_DIR = '../templates';
1919
const PACKAGE_LANGUAGE = 'packages/language';
20+
const PACKAGE_LANGUAGE_EXAMPLE = 'packages/language-example';
21+
const PACKAGE_LANGUAGE_MINIMAL = 'packages/language-minimal';
2022
const PACKAGE_CLI = 'packages/cli';
23+
const PACKAGE_CLI_EXAMPLE = 'packages/cli-example';
24+
const PACKAGE_CLI_MINIMAL = 'packages/cli-minimal';
2125
const PACKAGE_EXTENSION = 'packages/extension';
2226
const USER_DIR = '.';
2327

@@ -30,6 +34,8 @@ const LANGUAGE_NAME = /<%= LanguageName %>/g;
3034
const LANGUAGE_ID = /<%= language-id %>/g;
3135
const LANGUAGE_PATH_ID = /language-id/g;
3236

37+
const ENTRY_NAME = /<%= EntryName %>/g;
38+
3339
const NEWLINES = /\r?\n/g;
3440

3541
export interface Answers {
@@ -38,7 +44,9 @@ export interface Answers {
3844
fileExtensions: string;
3945
includeVSCode: boolean;
4046
includeCLI: boolean;
47+
includeExampleProject: boolean;
4148
includeTest: boolean;
49+
entryName: string;
4250
}
4351

4452
export interface PostAnwers {
@@ -89,7 +97,7 @@ export class LangiumGenerator extends Generator {
8997
name: 'extensionName',
9098
prefix: description(
9199
'Welcome to Langium!',
92-
'This tool generates a VS Code extension with a "Hello World" language to get started quickly.',
100+
'This tool generates one or more npm packages to create your own language based on Langium.',
93101
'The extension name is an identifier used in the extension marketplace or package registry.'
94102
),
95103
message: 'Your extension name:',
@@ -124,6 +132,15 @@ export class LangiumGenerator extends Generator {
124132
? true
125133
: 'A file extension can start with . and must contain only letters and digits. Extensions must be separated by commas.',
126134
} as PromptQuestion<Answers>,
135+
{
136+
type: 'input',
137+
name: 'entryName',
138+
prefix: description(
139+
'The name of the entry rule in your grammar file.'
140+
),
141+
message: 'Your grammar entry rule name:',
142+
default: 'Model',
143+
} as PromptQuestion<Answers>,
127144
{
128145
type: 'confirm',
129146
name: 'includeVSCode',
@@ -133,6 +150,16 @@ export class LangiumGenerator extends Generator {
133150
message: 'Include VSCode extension?',
134151
default: true
135152
} as PromptQuestion<Answers>,
153+
{
154+
type: 'confirm',
155+
name: 'includeExampleProject',
156+
prefix: description(
157+
'You can generate an example project to play around with Langium (with a "Hello world" grammar, generator and validator).',
158+
'If not, a minimal project will be generated.'
159+
),
160+
message: 'Generate example project?',
161+
default: true
162+
} as PromptQuestion<Answers>,
136163
{
137164
type: 'confirm',
138165
name: 'includeCLI',
@@ -149,8 +176,8 @@ export class LangiumGenerator extends Generator {
149176
'You can add the setup for language tests using Vitest.'
150177
),
151178
message: 'Include language tests?',
152-
default: true
153-
} as PromptQuestion<Answers>
179+
default: true,
180+
} as PromptQuestion<Answers>,
154181
]);
155182
}
156183

@@ -201,7 +228,8 @@ export class LangiumGenerator extends Generator {
201228
// .gitignore files don't get published to npm, so we need to copy it under a different name
202229
this.fs.copy(this.templatePath('gitignore.txt'), this._extensionPath('.gitignore'));
203230

204-
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_LANGUAGE}`));
231+
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${this.answers.includeExampleProject ? PACKAGE_LANGUAGE_EXAMPLE : PACKAGE_LANGUAGE_MINIMAL}`));
232+
205233
const languageFiles = [
206234
'package.json',
207235
'README.md',
@@ -241,6 +269,9 @@ export * from './generated/ast.js';
241269
export * from './generated/grammar.js';
242270
export * from './generated/module.js';
243271
`;
272+
// Write language index.ts and langium-config.json
273+
this.fs.write(this._extensionPath('packages/language/src/index.ts'), languageIndex);
274+
this.fs.writeJSON(this._extensionPath('packages/language/langium-config.json'), langiumConfigJson, undefined, 4);
244275

245276
if (this.answers.includeTest) {
246277
mainPackageJson.scripts.test = 'npm run --workspace packages/language test';
@@ -261,7 +292,9 @@ export * from './generated/module.js';
261292
}
262293

263294
if (this.answers.includeCLI) {
264-
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_CLI}`));
295+
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${
296+
this.answers.includeExampleProject ? PACKAGE_CLI_EXAMPLE : PACKAGE_CLI_MINIMAL
297+
}`));
265298
const cliFiles = [
266299
'package.json',
267300
'tsconfig.json',
@@ -280,10 +313,6 @@ export * from './generated/module.js';
280313
tsConfigBuildJson.references.push({ path: './packages/cli/tsconfig.json' });
281314
}
282315

283-
// Write language index.ts and langium-config.json
284-
this.fs.write(this._extensionPath('packages/language/src/index.ts'), languageIndex);
285-
this.fs.writeJSON(this._extensionPath('packages/language/langium-config.json'), langiumConfigJson, undefined, 4);
286-
287316
if (this.answers.includeVSCode) {
288317
this.sourceRoot(path.join(__dirname, `${BASE_DIR}/${PACKAGE_EXTENSION}`));
289318
const extensionFiles = [
@@ -363,6 +392,7 @@ export * from './generated/module.js';
363392
.replace(FILE_EXTENSION_GLOB, fileExtensionGlob)
364393
.replace(LANGUAGE_NAME, languageName)
365394
.replace(LANGUAGE_ID, languageId)
395+
.replace(ENTRY_NAME, this.answers.entryName)
366396
.replace(NEWLINES, EOL);
367397
}
368398

File renamed without changes.

packages/generator-langium/templates/packages/cli/src/generator.ts renamed to packages/generator-langium/templates/packages/cli-example/src/generator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { Model } from '<%= language-id %>-language';
1+
import type { <%= EntryName %> } from '<%= language-id %>-language';
22
import { expandToNode, joinToNode, toString } from 'langium/generate';
33
import * as fs from 'node:fs';
44
import * as path from 'node:path';
55
import { extractDestinationAndName } from './util.js';
66

7-
export function generateJavaScript(model: Model, filePath: string, destination: string | undefined): string {
7+
export function generateJavaScript(model: <%= EntryName %>, filePath: string, destination: string | undefined): string {
88
const data = extractDestinationAndName(filePath, destination);
99
const generatedFilePath = `${path.join(data.destination, data.name)}.js`;
1010

packages/generator-langium/templates/packages/cli/src/main.ts renamed to packages/generator-langium/templates/packages/cli-example/src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Model } from '<%= language-id %>-language';
1+
import type { <%= EntryName %> } from '<%= language-id %>-language';
22
import { create<%= LanguageName %>Services, <%= LanguageName %>LanguageMetaData } from '<%= language-id %>-language';
33
import chalk from 'chalk';
44
import { Command } from 'commander';
@@ -15,7 +15,7 @@ const packageContent = await fs.readFile(packagePath, 'utf-8');
1515

1616
export const generateAction = async (fileName: string, opts: GenerateOptions): Promise<void> => {
1717
const services = create<%= LanguageName %>Services(NodeFileSystem).<%= LanguageName %>;
18-
const model = await extractAstNode<Model>(fileName, services);
18+
const model = await extractAstNode<<%= EntryName %>>(fileName, services);
1919
const generatedFilePath = generateJavaScript(model, fileName, opts.destination);
2020
console.log(chalk.green(`JavaScript code generated successfully: ${generatedFilePath}`));
2121
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Command-line interface (CLI)
2+
3+
Check [this part](https://langium.org/docs/learn/minilogo/customizing_cli/) of the Langium Minilogo Tutorial as a useful guide to the CLI.
4+
5+
## What's in the folder?
6+
7+
- [package.json](./package.json) - The manifest file of your cli package.
8+
- [tsconfig.src.json](./tsconfig.src.json) - The package specific TypeScript compiler configuration extending the [base config](../../tsconfig.json).
9+
- [tsconfig.json](./tsconfig.json) - TypeScript compiler configuration options required for proper functionality of VSCode.
10+
- [bin/cli.js](bin/cli/cli.js) - Script referenced in the [package.json](./package.json) and used to execute the command-line interface.
11+
- [src/cli/main.ts](src/cli/main.ts) - The entry point of the command line interface (CLI) of your language.
12+
- [src/cli/generator.ts](src/cli/generator.ts) - The code generator used by the CLI to write output files from DSL documents.
13+
- [src/cli/util.ts](src/cli/util.ts) - Utility code for the CLI.
14+
15+
## Instructions
16+
17+
Run `node ./bin/cli` to see options for the CLI; `node ./bin/cli generate <file>` generates code for a given DSL file.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
3+
import main from '../out/main.js';
4+
main();

0 commit comments

Comments
 (0)