mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge pull request #4751 from Yokozuna59/add-pie-langium-parser
feat: add `pie` langium parser
This commit is contained in:
commit
d11bfaa6c4
9
.build/langium-cli.d.ts
vendored
9
.build/langium-cli.d.ts
vendored
@ -1,9 +0,0 @@
|
||||
declare module 'langium-cli' {
|
||||
export interface GenerateOptions {
|
||||
file?: string;
|
||||
mode?: 'development' | 'production';
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
export function generate(options: GenerateOptions): Promise<boolean>;
|
||||
}
|
@ -246,18 +246,18 @@ In this example, the `mermaidAPI` is being called through the `CDN`:
|
||||
<body>
|
||||
Here is one mermaid diagram:
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
B --> D[Server2]
|
||||
</pre>
|
||||
|
||||
And here is another:
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
graph TD
|
||||
A[Client] -->|tcp_123| B
|
||||
B(Load Balancer)
|
||||
B -->|tcp_456| C[Server1]
|
||||
B(Load Balancer)
|
||||
B -->|tcp_456| C[Server1]
|
||||
B -->|tcp_456| D[Server2]
|
||||
</pre>
|
||||
|
||||
@ -278,15 +278,15 @@ In this example, `mermaid.js` is referenced in `src` as a separate JavaScript fi
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
graph LR
|
||||
A --- B
|
||||
B-->C[fa:fa-ban forbidden]
|
||||
graph LR
|
||||
A --- B
|
||||
B-->C[fa:fa-ban forbidden]
|
||||
B-->D(fa:fa-spinner);
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
B --> D[Server2]
|
||||
</pre>
|
||||
<script type="module">
|
||||
|
@ -7,9 +7,9 @@
|
||||
base = ""
|
||||
|
||||
# Directory that contains the deploy-ready HTML files and
|
||||
# assets generated by the build. This is an absolute path relative
|
||||
# assets generated by the build. This is an absolute path relative
|
||||
# to the base directory, which is the root by default (/).
|
||||
# This sample publishes the directory located at the absolute
|
||||
# This sample publishes the directory located at the absolute
|
||||
# path "root/project/build-output"
|
||||
|
||||
publish = "mermaid-live-editor/docs"
|
||||
|
@ -1,74 +0,0 @@
|
||||
/** mermaid
|
||||
* https://knsv.github.io/mermaid
|
||||
* (c) 2015 Knut Sveidqvist
|
||||
* MIT license.
|
||||
*/
|
||||
%lex
|
||||
%options case-insensitive
|
||||
|
||||
%x string
|
||||
%x title
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%%
|
||||
\%\%(?!\{)[^\n]* /* skip comments */
|
||||
[^\}]\%\%[^\n]* /* skip comments */{ /*console.log('');*/ }
|
||||
[\n\r]+ return 'NEWLINE';
|
||||
\%\%[^\n]* /* do nothing */
|
||||
[\s]+ /* ignore */
|
||||
title { this.begin("title");return 'title'; }
|
||||
<title>(?!\n|;|#)*[^\n]* { this.popState(); return "title_value"; }
|
||||
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
|
||||
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
|
||||
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||
<acc_descr_multiline>[\}] { this.popState(); }
|
||||
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||
["] { this.begin("string"); }
|
||||
<string>["] { this.popState(); }
|
||||
<string>[^"]* { return "txt"; }
|
||||
"pie" return 'PIE';
|
||||
"showData" return 'showData';
|
||||
":"[\s]*[\d]+(?:\.[\d]+)? return "value";
|
||||
<<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
%start start
|
||||
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
: eol start
|
||||
| PIE document
|
||||
| PIE showData document {yy.setShowData(true);}
|
||||
;
|
||||
|
||||
document
|
||||
: /* empty */
|
||||
| document line
|
||||
;
|
||||
|
||||
line
|
||||
: statement eol { $$ = $1 }
|
||||
;
|
||||
|
||||
statement
|
||||
:
|
||||
| txt value { yy.addSection($1,yy.cleanupValue($2)); }
|
||||
| title title_value { $$=$2.trim();yy.setDiagramTitle($$); }
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
|
||||
;
|
||||
|
||||
eol
|
||||
: NEWLINE
|
||||
| ';'
|
||||
| EOF
|
||||
;
|
||||
|
||||
%%
|
@ -1,5 +1,4 @@
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import { parser } from './parser/pie.jison';
|
||||
import { parser } from './pieParser.js';
|
||||
import { DEFAULT_PIE_DB, db } from './pieDb.js';
|
||||
import { setConfig } from '../../diagram-api/diagramAPI.js';
|
||||
|
||||
@ -8,17 +7,11 @@ setConfig({
|
||||
});
|
||||
|
||||
describe('pie', () => {
|
||||
beforeAll(() => {
|
||||
parser.yy = db;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
parser.yy.clear();
|
||||
});
|
||||
beforeEach(() => db.clear());
|
||||
|
||||
describe('parse', () => {
|
||||
it('should handle very simple pie', () => {
|
||||
parser.parse(`pie
|
||||
it('should handle very simple pie', async () => {
|
||||
await parser.parse(`pie
|
||||
"ash": 100
|
||||
`);
|
||||
|
||||
@ -26,8 +19,8 @@ describe('pie', () => {
|
||||
expect(sections['ash']).toBe(100);
|
||||
});
|
||||
|
||||
it('should handle simple pie', () => {
|
||||
parser.parse(`pie
|
||||
it('should handle simple pie', async () => {
|
||||
await parser.parse(`pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
@ -37,8 +30,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with showData', () => {
|
||||
parser.parse(`pie showData
|
||||
it('should handle simple pie with showData', async () => {
|
||||
await parser.parse(`pie showData
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
@ -50,8 +43,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with comments', () => {
|
||||
parser.parse(`pie
|
||||
it('should handle simple pie with comments', async () => {
|
||||
await parser.parse(`pie
|
||||
%% comments
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
@ -62,8 +55,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a title', () => {
|
||||
parser.parse(`pie title a 60/40 pie
|
||||
it('should handle simple pie with a title', async () => {
|
||||
await parser.parse(`pie title a 60/40 pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
@ -75,8 +68,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with an acc title (accTitle)', () => {
|
||||
parser.parse(`pie title a neat chart
|
||||
it('should handle simple pie with an acc title (accTitle)', async () => {
|
||||
await parser.parse(`pie title a neat chart
|
||||
accTitle: a neat acc title
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
@ -91,8 +84,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with an acc description (accDescr)', () => {
|
||||
parser.parse(`pie title a neat chart
|
||||
it('should handle simple pie with an acc description (accDescr)', async () => {
|
||||
await parser.parse(`pie title a neat chart
|
||||
accDescr: a neat description
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
@ -107,8 +100,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a multiline acc description (accDescr)', () => {
|
||||
parser.parse(`pie title a neat chart
|
||||
it('should handle simple pie with a multiline acc description (accDescr)', async () => {
|
||||
await parser.parse(`pie title a neat chart
|
||||
accDescr {
|
||||
a neat description
|
||||
on multiple lines
|
||||
@ -126,8 +119,8 @@ describe('pie', () => {
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with positive decimal', () => {
|
||||
parser.parse(`pie
|
||||
it('should handle simple pie with positive decimal', async () => {
|
||||
await parser.parse(`pie
|
||||
"ash" : 60.67
|
||||
"bat" : 40
|
||||
`);
|
||||
@ -138,12 +131,12 @@ describe('pie', () => {
|
||||
});
|
||||
|
||||
it('should handle simple pie with negative decimal', () => {
|
||||
expect(() => {
|
||||
parser.parse(`pie
|
||||
expect(async () => {
|
||||
await parser.parse(`pie
|
||||
"ash" : -60.67
|
||||
"bat" : 40.12
|
||||
`);
|
||||
}).toThrowError();
|
||||
}).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
import { log } from '../../logger.js';
|
||||
import { getConfig as commonGetConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import { sanitizeText } from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
@ -10,7 +8,7 @@ import {
|
||||
setAccDescription,
|
||||
clear as commonClear,
|
||||
} from '../common/commonDb.js';
|
||||
import type { PieFields, PieDB, Sections } from './pieTypes.js';
|
||||
import type { PieFields, PieDB, Sections, D3Section } from './pieTypes.js';
|
||||
import type { RequiredDeep } from 'type-fest';
|
||||
import type { PieDiagramConfig } from '../../config.type.js';
|
||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||
@ -35,8 +33,7 @@ const clear = (): void => {
|
||||
commonClear();
|
||||
};
|
||||
|
||||
const addSection = (label: string, value: number): void => {
|
||||
label = sanitizeText(label, commonGetConfig());
|
||||
const addSection = ({ label, value }: D3Section): void => {
|
||||
if (sections[label] === undefined) {
|
||||
sections[label] = value;
|
||||
log.debug(`added new section: ${label}, with value: ${value}`);
|
||||
@ -45,13 +42,6 @@ const addSection = (label: string, value: number): void => {
|
||||
|
||||
const getSections = (): Sections => sections;
|
||||
|
||||
const cleanupValue = (value: string): number => {
|
||||
if (value.substring(0, 1) === ':') {
|
||||
value = value.substring(1).trim();
|
||||
}
|
||||
return Number(value.trim());
|
||||
};
|
||||
|
||||
const setShowData = (toggle: boolean): void => {
|
||||
showData = toggle;
|
||||
};
|
||||
@ -71,7 +61,6 @@ export const db: PieDB = {
|
||||
|
||||
addSection,
|
||||
getSections,
|
||||
cleanupValue,
|
||||
setShowData,
|
||||
getShowData,
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import parser from './parser/pie.jison';
|
||||
import { parser } from './pieParser.js';
|
||||
import { db } from './pieDb.js';
|
||||
import styles from './pieStyles.js';
|
||||
import { renderer } from './pieRenderer.js';
|
||||
|
21
packages/mermaid/src/diagrams/pie/pieParser.ts
Normal file
21
packages/mermaid/src/diagrams/pie/pieParser.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { Pie } from '@mermaid-js/parser';
|
||||
import { parse } from '@mermaid-js/parser';
|
||||
import { log } from '../../logger.js';
|
||||
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||
import { populateCommonDb } from '../common/populateCommonDb.js';
|
||||
import type { PieDB } from './pieTypes.js';
|
||||
import { db } from './pieDb.js';
|
||||
|
||||
const populateDb = (ast: Pie, db: PieDB) => {
|
||||
populateCommonDb(ast, db);
|
||||
db.setShowData(ast.showData);
|
||||
ast.sections.map(db.addSection);
|
||||
};
|
||||
|
||||
export const parser: ParserDefinition = {
|
||||
parse: async (input: string): Promise<void> => {
|
||||
const ast: Pie = await parse('pie', input);
|
||||
log.debug(ast);
|
||||
populateDb(ast, db);
|
||||
},
|
||||
};
|
@ -5,24 +5,24 @@ import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import { cleanAndMerge, parseFontSize } from '../../utils.js';
|
||||
import type { DrawDefinition, Group, SVG } from '../../diagram-api/types.js';
|
||||
import type { D3Sections, PieDB, Sections } from './pieTypes.js';
|
||||
import type { D3Section, PieDB, Sections } from './pieTypes.js';
|
||||
import type { MermaidConfig, PieDiagramConfig } from '../../config.type.js';
|
||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||
|
||||
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Sections>[] => {
|
||||
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Section>[] => {
|
||||
// Compute the position of each group on the pie:
|
||||
const pieData: D3Sections[] = Object.entries(sections)
|
||||
.map((element: [string, number]): D3Sections => {
|
||||
const pieData: D3Section[] = Object.entries(sections)
|
||||
.map((element: [string, number]): D3Section => {
|
||||
return {
|
||||
label: element[0],
|
||||
value: element[1],
|
||||
};
|
||||
})
|
||||
.sort((a: D3Sections, b: D3Sections): number => {
|
||||
.sort((a: D3Section, b: D3Section): number => {
|
||||
return b.value - a.value;
|
||||
});
|
||||
const pie: d3.Pie<unknown, D3Sections> = d3pie<D3Sections>().value(
|
||||
(d3Section: D3Sections): number => d3Section.value
|
||||
const pie: d3.Pie<unknown, D3Section> = d3pie<D3Section>().value(
|
||||
(d3Section: D3Section): number => d3Section.value
|
||||
);
|
||||
return pie(pieData);
|
||||
};
|
||||
@ -47,7 +47,6 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
const pieWidth: number = height;
|
||||
const svg: SVG = selectSvgElement(id);
|
||||
const group: Group = svg.append('g');
|
||||
const sections: Sections = db.getSections();
|
||||
group.attr('transform', 'translate(' + pieWidth / 2 + ',' + height / 2 + ')');
|
||||
|
||||
const { themeVariables } = globalConfig;
|
||||
@ -57,13 +56,11 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
const textPosition: number = pieConfig.textPosition;
|
||||
const radius: number = Math.min(pieWidth, height) / 2 - MARGIN;
|
||||
// Shape helper to build arcs:
|
||||
const arcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||
d3.PieArcDatum<D3Sections>
|
||||
>()
|
||||
const arcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Section>> = arc<d3.PieArcDatum<D3Section>>()
|
||||
.innerRadius(0)
|
||||
.outerRadius(radius);
|
||||
const labelArcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||
d3.PieArcDatum<D3Sections>
|
||||
const labelArcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Section>> = arc<
|
||||
d3.PieArcDatum<D3Section>
|
||||
>()
|
||||
.innerRadius(radius * textPosition)
|
||||
.outerRadius(radius * textPosition);
|
||||
@ -75,7 +72,8 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
.attr('r', radius + outerStrokeWidth / 2)
|
||||
.attr('class', 'pieOuterCircle');
|
||||
|
||||
const arcs: d3.PieArcDatum<D3Sections>[] = createPieArcs(sections);
|
||||
const sections: Sections = db.getSections();
|
||||
const arcs: d3.PieArcDatum<D3Section>[] = createPieArcs(sections);
|
||||
|
||||
const myGeneratedColors = [
|
||||
themeVariables.pie1,
|
||||
@ -101,7 +99,7 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', arcGenerator)
|
||||
.attr('fill', (datum: d3.PieArcDatum<D3Sections>) => {
|
||||
.attr('fill', (datum: d3.PieArcDatum<D3Section>) => {
|
||||
return color(datum.data.label);
|
||||
})
|
||||
.attr('class', 'pieCircle');
|
||||
@ -117,10 +115,10 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
.data(arcs)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text((datum: d3.PieArcDatum<D3Sections>): string => {
|
||||
.text((datum: d3.PieArcDatum<D3Section>): string => {
|
||||
return ((datum.data.value / sum) * 100).toFixed(0) + '%';
|
||||
})
|
||||
.attr('transform', (datum: d3.PieArcDatum<D3Sections>): string => {
|
||||
.attr('transform', (datum: d3.PieArcDatum<D3Section>): string => {
|
||||
return 'translate(' + labelArcGenerator.centroid(datum) + ')';
|
||||
})
|
||||
.style('text-anchor', 'middle')
|
||||
@ -160,7 +158,7 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
|
||||
.append('text')
|
||||
.attr('x', LEGEND_RECT_SIZE + LEGEND_SPACING)
|
||||
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
|
||||
.text((datum: d3.PieArcDatum<D3Sections>): string => {
|
||||
.text((datum: d3.PieArcDatum<D3Section>): string => {
|
||||
const { label, value } = datum.data;
|
||||
if (db.getShowData()) {
|
||||
return `${label} [${value}]`;
|
||||
|
@ -36,7 +36,7 @@ export interface PieStyleOptions {
|
||||
|
||||
export type Sections = Record<string, number>;
|
||||
|
||||
export interface D3Sections {
|
||||
export interface D3Section {
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
@ -55,9 +55,8 @@ export interface PieDB extends DiagramDB {
|
||||
getAccDescription: () => string;
|
||||
|
||||
// diagram db
|
||||
addSection: (label: string, value: number) => void;
|
||||
addSection: ({ label, value }: D3Section) => void;
|
||||
getSections: () => Sections;
|
||||
cleanupValue: (value: string) => number;
|
||||
setShowData: (toggle: boolean) => void;
|
||||
getShowData: () => boolean;
|
||||
}
|
||||
|
@ -239,18 +239,18 @@ In this example, the `mermaidAPI` is being called through the `CDN`:
|
||||
<body>
|
||||
Here is one mermaid diagram:
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
B --> D[Server2]
|
||||
</pre>
|
||||
|
||||
And here is another:
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
graph TD
|
||||
A[Client] -->|tcp_123| B
|
||||
B(Load Balancer)
|
||||
B -->|tcp_456| C[Server1]
|
||||
B(Load Balancer)
|
||||
B -->|tcp_456| C[Server1]
|
||||
B -->|tcp_456| D[Server2]
|
||||
</pre>
|
||||
|
||||
@ -271,15 +271,15 @@ In this example, `mermaid.js` is referenced in `src` as a separate JavaScript fi
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
graph LR
|
||||
A --- B
|
||||
B-->C[fa:fa-ban forbidden]
|
||||
graph LR
|
||||
A --- B
|
||||
B-->C[fa:fa-ban forbidden]
|
||||
B-->D(fa:fa-spinner);
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server1]
|
||||
B --> D[Server2]
|
||||
</pre>
|
||||
<script type="module">
|
||||
|
@ -10,6 +10,11 @@
|
||||
"id": "packet",
|
||||
"grammar": "src/language/packet/packet.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
},
|
||||
{
|
||||
"id": "pie",
|
||||
"grammar": "src/language/pie/pie.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
}
|
||||
],
|
||||
"mode": "production",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-parser.esm.mjs",
|
||||
"import": "./dist/mermaid-parser.core.mjs",
|
||||
"types": "./dist/src/index.d.ts"
|
||||
}
|
||||
},
|
||||
@ -34,10 +34,10 @@
|
||||
"ast"
|
||||
],
|
||||
"dependencies": {
|
||||
"langium": "2.0.1"
|
||||
"langium": "2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"langium-cli": "2.0.1"
|
||||
"chevrotain": "^11.0.3"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
|
@ -1,8 +1,7 @@
|
||||
import type { AstNode } from 'langium';
|
||||
|
||||
export type { Info, Packet } from './language/index.js';
|
||||
export { MermaidParseError, parse } from './parse.js';
|
||||
export type { DiagramAST } from './parse.js';
|
||||
export * from './language/index.js';
|
||||
export * from './parse.js';
|
||||
|
||||
/**
|
||||
* Exclude/omit all `AstNode` attributes recursively.
|
||||
|
@ -1,51 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type CustomPatternMatcherReturn = [string] & { payload?: any };
|
||||
|
||||
export type CustomPatternMatcherFunc = (
|
||||
text: string,
|
||||
offset: number,
|
||||
tokens: IToken[],
|
||||
groups: {
|
||||
[groupName: string]: IToken[];
|
||||
}
|
||||
) => CustomPatternMatcherReturn | RegExpExecArray | null;
|
||||
|
||||
interface ICustomPattern {
|
||||
exec: CustomPatternMatcherFunc;
|
||||
}
|
||||
|
||||
type TokenPattern = RegExp | string | CustomPatternMatcherFunc | ICustomPattern;
|
||||
|
||||
export interface IToken {
|
||||
image: string;
|
||||
startOffset: number;
|
||||
startLine?: number;
|
||||
startColumn?: number;
|
||||
endOffset?: number;
|
||||
endLine?: number;
|
||||
endColumn?: number;
|
||||
isInsertedInRecovery?: boolean;
|
||||
tokenTypeIdx: number;
|
||||
tokenType: TokenType;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
export interface TokenType {
|
||||
name: string;
|
||||
GROUP?: string;
|
||||
PATTERN?: TokenPattern;
|
||||
LABEL?: string;
|
||||
LONGER_ALT?: TokenType | TokenType[];
|
||||
POP_MODE?: boolean;
|
||||
PUSH_MODE?: string;
|
||||
LINE_BREAKS?: boolean;
|
||||
CATEGORIES?: TokenType[];
|
||||
tokenTypeIdx?: number;
|
||||
categoryMatches?: number[];
|
||||
categoryMatchesMap?: {
|
||||
[tokType: number]: boolean;
|
||||
};
|
||||
isParent?: boolean;
|
||||
START_CHARS_HINT?: (string | number)[];
|
||||
}
|
@ -5,15 +5,19 @@ interface Common {
|
||||
}
|
||||
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) NEWLINE+)+
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) EOL)+
|
||||
;
|
||||
|
||||
fragment EOL returns string:
|
||||
NEWLINE+ | EOF
|
||||
;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
terminal ACC_DESCR: /accDescr([\t ]*:[^\n\r]*(?=%%)|\s*{[^}]*})|accDescr([\t ]*:[^\n\r]*|\s*{[^}]*})/;
|
||||
terminal ACC_TITLE: /accTitle[\t ]*:[^\n\r]*(?=%%)|accTitle[\t ]*:[^\n\r]*/;
|
||||
terminal TITLE: /title([\t ][^\n\r]*|)(?=%%)|title([\t ][^\n\r]*|)/;
|
||||
terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/;
|
||||
terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/;
|
||||
terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/;
|
||||
|
||||
hidden terminal WHITESPACE: /[\t ]+/;
|
||||
hidden terminal YAML: /---[\t ]*\r?\n[\S\s]*?---[\t ]*(?!.)/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%\s*/;
|
||||
hidden terminal YAML: /---[\t ]*\r?\n(?:[\S\s]*?\r?\n)?---(?:\r?\n|(?!\S))/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%(?:\r?\n|(?!\S))/;
|
||||
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
|
||||
|
@ -1,3 +1,2 @@
|
||||
export * from './lexer.js';
|
||||
export * from './tokenBuilder.js';
|
||||
export { MermaidValueConverter } from './valueConverter.js';
|
||||
export * from './valueConverter.js';
|
||||
|
@ -1,8 +0,0 @@
|
||||
import type { LexerResult } from 'langium';
|
||||
import { DefaultLexer } from 'langium';
|
||||
|
||||
export class CommonLexer extends DefaultLexer {
|
||||
public override tokenize(text: string): LexerResult {
|
||||
return super.tokenize(text + '\n');
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
/**
|
||||
* Matches single and multiline accessible description
|
||||
* Matches single and multi line accessible description
|
||||
*/
|
||||
export const accessibilityDescrRegex = /accDescr(?:[\t ]*:[\t ]*([^\n\r]*)|\s*{([^}]*)})/;
|
||||
export const accessibilityDescrRegex = /accDescr(?:[\t ]*:([^\n\r]*)|\s*{([^}]*)})/;
|
||||
|
||||
/**
|
||||
* Matches single line accessible title
|
||||
*/
|
||||
export const accessibilityTitleRegex = /accTitle[\t ]*:[\t ]*([^\n\r]*)/;
|
||||
export const accessibilityTitleRegex = /accTitle[\t ]*:([^\n\r]*)/;
|
||||
|
||||
/**
|
||||
* Matches a single line title
|
||||
*/
|
||||
export const titleRegex = /title([\t ]+([^\n\r]*)|)/;
|
||||
export const titleRegex = /title([\t ][^\n\r]*|)/;
|
||||
|
@ -1,9 +1,9 @@
|
||||
import type { GrammarAST, Stream, TokenBuilderOptions } from 'langium';
|
||||
import type { TokenType } from '../chevrotainWrapper.js';
|
||||
import type { TokenType } from 'chevrotain';
|
||||
|
||||
import { DefaultTokenBuilder } from 'langium';
|
||||
|
||||
export abstract class MermaidTokenBuilder extends DefaultTokenBuilder {
|
||||
export abstract class AbstractMermaidTokenBuilder extends DefaultTokenBuilder {
|
||||
private keywords: Set<string>;
|
||||
|
||||
public constructor(keywords: string[]) {
|
||||
@ -20,9 +20,11 @@ export abstract class MermaidTokenBuilder extends DefaultTokenBuilder {
|
||||
// to restrict users, they mustn't have any non-whitespace characters after the keyword.
|
||||
tokenTypes.forEach((tokenType: TokenType): void => {
|
||||
if (this.keywords.has(tokenType.name) && tokenType.PATTERN !== undefined) {
|
||||
tokenType.PATTERN = new RegExp(tokenType.PATTERN.toString() + '(?!\\S)');
|
||||
tokenType.PATTERN = new RegExp(tokenType.PATTERN.toString() + '(?:(?=%%)|(?!\\S))');
|
||||
}
|
||||
});
|
||||
return tokenTypes;
|
||||
}
|
||||
}
|
||||
|
||||
export class CommonTokenBuilder extends AbstractMermaidTokenBuilder {}
|
||||
|
@ -10,7 +10,7 @@ const rulesRegexes: Record<string, RegExp> = {
|
||||
TITLE: titleRegex,
|
||||
};
|
||||
|
||||
export abstract class MermaidValueConverter extends DefaultValueConverter {
|
||||
export abstract class AbstractMermaidValueConverter extends DefaultValueConverter {
|
||||
/**
|
||||
* A method contains convert logic to be used by class.
|
||||
*
|
||||
@ -71,8 +71,8 @@ export abstract class MermaidValueConverter extends DefaultValueConverter {
|
||||
}
|
||||
}
|
||||
|
||||
export class CommonValueConverter extends MermaidValueConverter {
|
||||
protected runCustomConverter(
|
||||
export class CommonValueConverter extends AbstractMermaidValueConverter {
|
||||
protected override runCustomConverter(
|
||||
_rule: GrammarAST.AbstractRule,
|
||||
_input: string,
|
||||
_cstNode: CstNode
|
||||
|
@ -1,7 +1,25 @@
|
||||
export * from './generated/ast.js';
|
||||
export * from './generated/grammar.js';
|
||||
export * from './generated/module.js';
|
||||
export {
|
||||
Info,
|
||||
MermaidAstType,
|
||||
Packet,
|
||||
PacketBlock,
|
||||
Pie,
|
||||
PieSection,
|
||||
isCommon,
|
||||
isInfo,
|
||||
isPacket,
|
||||
isPacketBlock,
|
||||
isPie,
|
||||
isPieSection,
|
||||
} from './generated/ast.js';
|
||||
export {
|
||||
InfoGeneratedModule,
|
||||
MermaidGeneratedSharedModule,
|
||||
PacketGeneratedModule,
|
||||
PieGeneratedModule,
|
||||
} from './generated/module.js';
|
||||
|
||||
export * from './common/index.js';
|
||||
export * from './info/index.js';
|
||||
export * from './packet/index.js';
|
||||
export * from './pie/index.js';
|
||||
|
@ -7,8 +7,7 @@ import type {
|
||||
} from 'langium';
|
||||
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||
|
||||
import { CommonLexer } from '../common/lexer.js';
|
||||
import { CommonValueConverter } from '../common/valueConverter.js';
|
||||
import { CommonValueConverter } from '../common/index.js';
|
||||
import { InfoGeneratedModule, MermaidGeneratedSharedModule } from '../generated/module.js';
|
||||
import { InfoTokenBuilder } from './tokenBuilder.js';
|
||||
|
||||
@ -17,7 +16,6 @@ import { InfoTokenBuilder } from './tokenBuilder.js';
|
||||
*/
|
||||
type InfoAddedServices = {
|
||||
parser: {
|
||||
Lexer: CommonLexer;
|
||||
TokenBuilder: InfoTokenBuilder;
|
||||
ValueConverter: CommonValueConverter;
|
||||
};
|
||||
@ -34,7 +32,6 @@ export type InfoServices = LangiumServices & InfoAddedServices;
|
||||
*/
|
||||
export const InfoModule: Module<InfoServices, PartialLangiumServices & InfoAddedServices> = {
|
||||
parser: {
|
||||
Lexer: (services: InfoServices) => new CommonLexer(services),
|
||||
TokenBuilder: () => new InfoTokenBuilder(),
|
||||
ValueConverter: () => new CommonValueConverter(),
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MermaidTokenBuilder } from '../common/index.js';
|
||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class InfoTokenBuilder extends MermaidTokenBuilder {
|
||||
export class InfoTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['info', 'showInfo']);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import type {
|
||||
PartialLangiumServices,
|
||||
} from 'langium';
|
||||
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||
import { CommonLexer } from '../common/lexer.js';
|
||||
import { CommonValueConverter } from '../common/valueConverter.js';
|
||||
import { MermaidGeneratedSharedModule, PacketGeneratedModule } from '../generated/module.js';
|
||||
import { PacketTokenBuilder } from './tokenBuilder.js';
|
||||
@ -16,7 +15,6 @@ import { PacketTokenBuilder } from './tokenBuilder.js';
|
||||
*/
|
||||
type PacketAddedServices = {
|
||||
parser: {
|
||||
Lexer: CommonLexer;
|
||||
TokenBuilder: PacketTokenBuilder;
|
||||
ValueConverter: CommonValueConverter;
|
||||
};
|
||||
@ -33,7 +31,6 @@ export type PacketServices = LangiumServices & PacketAddedServices;
|
||||
*/
|
||||
export const PacketModule: Module<PacketServices, PartialLangiumServices & PacketAddedServices> = {
|
||||
parser: {
|
||||
Lexer: (services: PacketServices) => new CommonLexer(services),
|
||||
TokenBuilder: () => new PacketTokenBuilder(),
|
||||
ValueConverter: () => new CommonValueConverter(),
|
||||
},
|
||||
|
@ -4,15 +4,15 @@ import "../common/common";
|
||||
entry Packet:
|
||||
NEWLINE*
|
||||
"packet-beta"
|
||||
(
|
||||
NEWLINE* TitleAndAccessibilities blocks+=PacketBlock*
|
||||
| NEWLINE+ blocks+=PacketBlock+
|
||||
| NEWLINE*
|
||||
(
|
||||
NEWLINE* TitleAndAccessibilities blocks+=PacketBlock*
|
||||
| NEWLINE+ blocks+=PacketBlock+
|
||||
| NEWLINE*
|
||||
)
|
||||
;
|
||||
|
||||
PacketBlock:
|
||||
start=INT('-' end=INT)? ':' label=STRING NEWLINE+
|
||||
start=INT('-' end=INT)? ':' label=STRING EOL
|
||||
;
|
||||
|
||||
terminal INT returns number: /0|[1-9][0-9]*/;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MermaidTokenBuilder } from '../common/index.js';
|
||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class PacketTokenBuilder extends MermaidTokenBuilder {
|
||||
export class PacketTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['packet-beta']);
|
||||
}
|
||||
|
1
packages/parser/src/language/pie/index.ts
Normal file
1
packages/parser/src/language/pie/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './module.js';
|
65
packages/parser/src/language/pie/module.ts
Normal file
65
packages/parser/src/language/pie/module.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import type {
|
||||
DefaultSharedModuleContext,
|
||||
LangiumServices,
|
||||
LangiumSharedServices,
|
||||
Module,
|
||||
PartialLangiumServices,
|
||||
} from 'langium';
|
||||
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||
|
||||
import { MermaidGeneratedSharedModule, PieGeneratedModule } from '../generated/module.js';
|
||||
import { PieTokenBuilder } from './tokenBuilder.js';
|
||||
import { PieValueConverter } from './valueConverter.js';
|
||||
|
||||
/**
|
||||
* Declaration of `Pie` services.
|
||||
*/
|
||||
type PieAddedServices = {
|
||||
parser: {
|
||||
TokenBuilder: PieTokenBuilder;
|
||||
ValueConverter: PieValueConverter;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Union of Langium default services and `Pie` services.
|
||||
*/
|
||||
export type PieServices = LangiumServices & PieAddedServices;
|
||||
|
||||
/**
|
||||
* Dependency injection module that overrides Langium default services and
|
||||
* contributes the declared `Pie` services.
|
||||
*/
|
||||
export const PieModule: Module<PieServices, PartialLangiumServices & PieAddedServices> = {
|
||||
parser: {
|
||||
TokenBuilder: () => new PieTokenBuilder(),
|
||||
ValueConverter: () => new PieValueConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the full set of services required by Langium.
|
||||
*
|
||||
* First inject the shared services by merging two modules:
|
||||
* - Langium default shared services
|
||||
* - Services generated by langium-cli
|
||||
*
|
||||
* Then inject the language-specific services by merging three modules:
|
||||
* - Langium default language-specific services
|
||||
* - Services generated by langium-cli
|
||||
* - Services specified in this file
|
||||
* @param context - Optional module context with the LSP connection
|
||||
* @returns An object wrapping the shared services and the language-specific services
|
||||
*/
|
||||
export function createPieServices(context: DefaultSharedModuleContext = EmptyFileSystem): {
|
||||
shared: LangiumSharedServices;
|
||||
Pie: PieServices;
|
||||
} {
|
||||
const shared: LangiumSharedServices = inject(
|
||||
createDefaultSharedModule(context),
|
||||
MermaidGeneratedSharedModule
|
||||
);
|
||||
const Pie: PieServices = inject(createDefaultModule({ shared }), PieGeneratedModule, PieModule);
|
||||
shared.ServiceRegistry.register(Pie);
|
||||
return { shared, Pie };
|
||||
}
|
19
packages/parser/src/language/pie/pie.langium
Normal file
19
packages/parser/src/language/pie/pie.langium
Normal file
@ -0,0 +1,19 @@
|
||||
grammar Pie
|
||||
import "../common/common";
|
||||
|
||||
entry Pie:
|
||||
NEWLINE*
|
||||
"pie" showData?="showData"?
|
||||
(
|
||||
NEWLINE* TitleAndAccessibilities sections+=PieSection*
|
||||
| NEWLINE+ sections+=PieSection+
|
||||
| NEWLINE*
|
||||
)
|
||||
;
|
||||
|
||||
PieSection:
|
||||
label=PIE_SECTION_LABEL ":" value=PIE_SECTION_VALUE EOL
|
||||
;
|
||||
|
||||
terminal PIE_SECTION_LABEL: /"[^"]+"/;
|
||||
terminal PIE_SECTION_VALUE returns number: /(0|[1-9][0-9]*)(\.[0-9]+)?/;
|
7
packages/parser/src/language/pie/tokenBuilder.ts
Normal file
7
packages/parser/src/language/pie/tokenBuilder.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { AbstractMermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class PieTokenBuilder extends AbstractMermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['pie', 'showData']);
|
||||
}
|
||||
}
|
17
packages/parser/src/language/pie/valueConverter.ts
Normal file
17
packages/parser/src/language/pie/valueConverter.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||
|
||||
import { AbstractMermaidValueConverter } from '../common/index.js';
|
||||
|
||||
export class PieValueConverter extends AbstractMermaidValueConverter {
|
||||
protected runCustomConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
_cstNode: CstNode
|
||||
): ValueType | undefined {
|
||||
if (rule.name !== 'PIE_SECTION_LABEL') {
|
||||
return undefined;
|
||||
}
|
||||
return input.replace(/"/g, '').trim();
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
import type { Info, Packet } from './index.js';
|
||||
|
||||
export type DiagramAST = Info | Packet;
|
||||
import type { Info, Packet, Pie } from './index.js';
|
||||
|
||||
export type DiagramAST = Info | Packet | Pie;
|
||||
|
||||
const parsers: Record<string, LangiumParser> = {};
|
||||
|
||||
const initializers = {
|
||||
info: async () => {
|
||||
const { createInfoServices } = await import('./language/info/index.js');
|
||||
@ -16,9 +16,16 @@ const initializers = {
|
||||
const parser = createPacketServices().Packet.parser.LangiumParser;
|
||||
parsers['packet'] = parser;
|
||||
},
|
||||
pie: async () => {
|
||||
const { createPieServices } = await import('./language/pie/index.js');
|
||||
const parser = createPieServices().Pie.parser.LangiumParser;
|
||||
parsers['pie'] = parser;
|
||||
},
|
||||
} as const;
|
||||
|
||||
export async function parse(diagramType: 'info', text: string): Promise<Info>;
|
||||
export async function parse(diagramType: 'packet', text: string): Promise<Packet>;
|
||||
export async function parse(diagramType: 'pie', text: string): Promise<Pie>;
|
||||
export async function parse<T extends DiagramAST>(
|
||||
diagramType: keyof typeof initializers,
|
||||
text: string
|
||||
|
@ -1,26 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
|
||||
import type { InfoServices } from '../src/language/index.js';
|
||||
import { Info, createInfoServices } from '../src/language/index.js';
|
||||
import { noErrorsOrAlternatives } from './test-util.js';
|
||||
|
||||
const services: InfoServices = createInfoServices().Info;
|
||||
const parser: LangiumParser = services.parser.LangiumParser;
|
||||
function createInfoTestServices(): {
|
||||
services: InfoServices;
|
||||
parse: (input: string) => ParseResult<Info>;
|
||||
} {
|
||||
const parse = (input: string) => {
|
||||
return parser.parse<Info>(input);
|
||||
};
|
||||
|
||||
return { services, parse };
|
||||
}
|
||||
import { Info } from '../src/language/index.js';
|
||||
import { expectNoErrorsOrAlternatives, infoParse as parse } from './test-util.js';
|
||||
|
||||
describe('info', () => {
|
||||
const { parse } = createInfoTestServices();
|
||||
|
||||
it.each([
|
||||
`info`,
|
||||
`
|
||||
@ -32,26 +15,34 @@ describe('info', () => {
|
||||
`,
|
||||
])('should handle empty info', (context: string) => {
|
||||
const result = parse(context);
|
||||
noErrorsOrAlternatives(result);
|
||||
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Info);
|
||||
});
|
||||
|
||||
it.each([
|
||||
`info showInfo`,
|
||||
`info showInfo
|
||||
`,
|
||||
`
|
||||
info showInfo`,
|
||||
`info
|
||||
showInfo`,
|
||||
`info
|
||||
showInfo
|
||||
`,
|
||||
`
|
||||
info
|
||||
showInfo
|
||||
`,
|
||||
`
|
||||
info
|
||||
showInfo`,
|
||||
`
|
||||
info showInfo
|
||||
`,
|
||||
])('should handle showInfo', (context: string) => {
|
||||
const result = parse(context);
|
||||
noErrorsOrAlternatives(result);
|
||||
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Info);
|
||||
});
|
||||
});
|
||||
|
229
packages/parser/tests/pie.test.ts
Normal file
229
packages/parser/tests/pie.test.ts
Normal file
@ -0,0 +1,229 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { Pie } from '../src/language/index.js';
|
||||
import { expectNoErrorsOrAlternatives, pieParse as parse } from './test-util.js';
|
||||
|
||||
describe('pie', () => {
|
||||
it.each([
|
||||
`pie`,
|
||||
` pie `,
|
||||
`\tpie\t`,
|
||||
`
|
||||
\tpie
|
||||
`,
|
||||
])('should handle regular pie', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData`,
|
||||
` pie showData `,
|
||||
`\tpie\tshowData\t`,
|
||||
`
|
||||
pie\tshowData
|
||||
`,
|
||||
])('should handle regular showData', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie title sample title`,
|
||||
` pie title sample title `,
|
||||
`\tpie\ttitle sample title\t`,
|
||||
`pie
|
||||
\ttitle sample title
|
||||
`,
|
||||
])('should handle regular pie + title in same line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie
|
||||
title sample title`,
|
||||
`pie
|
||||
title sample title
|
||||
`,
|
||||
`pie
|
||||
title sample title`,
|
||||
`pie
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular pie + title in different line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { title } = result.value;
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData title sample title`,
|
||||
`pie showData title sample title
|
||||
`,
|
||||
])('should handle regular pie + showData + title', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, title } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
it.each([
|
||||
`pie showData
|
||||
title sample title`,
|
||||
`pie showData
|
||||
title sample title
|
||||
`,
|
||||
`pie showData
|
||||
title sample title`,
|
||||
`pie showData
|
||||
title sample title
|
||||
`,
|
||||
])('should handle regular showData + title in different line', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, title } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
expect(title).toBe('sample title');
|
||||
});
|
||||
|
||||
describe('sections', () => {
|
||||
describe('normal', () => {
|
||||
it.each([
|
||||
`pie
|
||||
"GitHub":100
|
||||
"GitLab":50`,
|
||||
`pie
|
||||
"GitHub" : 100
|
||||
"GitLab" : 50`,
|
||||
`pie
|
||||
"GitHub"\t:\t100
|
||||
"GitLab"\t:\t50`,
|
||||
`pie
|
||||
\t"GitHub" \t : \t 100
|
||||
\t"GitLab" \t : \t 50
|
||||
`,
|
||||
])('should handle regular secions', (context: string) => {
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { sections } = result.value;
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with showData', () => {
|
||||
const context = `pie showData
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { showData, sections } = result.value;
|
||||
expect(showData).toBeTruthy();
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with title', () => {
|
||||
const context = `pie title sample wow
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { title, sections } = result.value;
|
||||
expect(title).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with accTitle', () => {
|
||||
const context = `pie accTitle: sample wow
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { accTitle, sections } = result.value;
|
||||
expect(accTitle).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with single line accDescr', () => {
|
||||
const context = `pie accDescr: sample wow
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { accDescr, sections } = result.value;
|
||||
expect(accDescr).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
|
||||
it('should handle sections with multi line accDescr', () => {
|
||||
const context = `pie accDescr {
|
||||
sample wow
|
||||
}
|
||||
"GitHub": 100
|
||||
"GitLab": 50`;
|
||||
const result = parse(context);
|
||||
expectNoErrorsOrAlternatives(result);
|
||||
expect(result.value.$type).toBe(Pie);
|
||||
|
||||
const { accDescr, sections } = result.value;
|
||||
expect(accDescr).toBe('sample wow');
|
||||
|
||||
expect(sections[0].label).toBe('GitHub');
|
||||
expect(sections[0].value).toBe(100);
|
||||
|
||||
expect(sections[1].label).toBe('GitLab');
|
||||
expect(sections[1].value).toBe(50);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -1,5 +1,7 @@
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
import { expect, vi } from 'vitest';
|
||||
import type { ParseResult } from 'langium';
|
||||
import type { Info, InfoServices, Pie, PieServices } from '../src/language/index.js';
|
||||
import { createInfoServices, createPieServices } from '../src/language/index.js';
|
||||
|
||||
const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
@ -9,10 +11,32 @@ const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined)
|
||||
*
|
||||
* @param result - the result `parse` function.
|
||||
*/
|
||||
export function noErrorsOrAlternatives(result: ParseResult) {
|
||||
export function expectNoErrorsOrAlternatives(result: ParseResult) {
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
|
||||
expect(consoleMock).not.toHaveBeenCalled();
|
||||
consoleMock.mockReset();
|
||||
}
|
||||
|
||||
const infoServices: InfoServices = createInfoServices().Info;
|
||||
const infoParser: LangiumParser = infoServices.parser.LangiumParser;
|
||||
export function createInfoTestServices() {
|
||||
const parse = (input: string) => {
|
||||
return infoParser.parse<Info>(input);
|
||||
};
|
||||
|
||||
return { services: infoServices, parse };
|
||||
}
|
||||
export const infoParse = createInfoTestServices().parse;
|
||||
|
||||
const pieServices: PieServices = createPieServices().Pie;
|
||||
const pieParser: LangiumParser = pieServices.parser.LangiumParser;
|
||||
export function createPieTestServices() {
|
||||
const parse = (input: string) => {
|
||||
return pieParser.parse<Pie>(input);
|
||||
};
|
||||
|
||||
return { services: pieServices, parse };
|
||||
}
|
||||
export const pieParse = createPieTestServices().parse;
|
||||
|
77
pnpm-lock.yaml
generated
77
pnpm-lock.yaml
generated
@ -496,12 +496,12 @@ importers:
|
||||
packages/parser:
|
||||
dependencies:
|
||||
langium:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1
|
||||
specifier: 2.1.2
|
||||
version: 2.1.2
|
||||
devDependencies:
|
||||
langium-cli:
|
||||
specifier: 2.0.1
|
||||
version: 2.0.1
|
||||
chevrotain:
|
||||
specifier: ^11.0.3
|
||||
version: 11.0.3
|
||||
|
||||
tests/webpack:
|
||||
dependencies:
|
||||
@ -11968,20 +11968,6 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: true
|
||||
|
||||
/langium-cli@2.0.1:
|
||||
resolution: {integrity: sha512-dPPaHimIoCgELED4tvRGdU3i26tjWuyVwexXgPtTtTzp1MBdGCBLppLADXHkL8yFVdWM/PWlCq06YyqAT4eV3A==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
chalk: 5.3.0
|
||||
commander: 10.0.1
|
||||
fs-extra: 11.1.1
|
||||
jsonschema: 1.4.1
|
||||
langium: 2.0.1
|
||||
langium-railroad: 2.0.0
|
||||
lodash: 4.17.21
|
||||
dev: true
|
||||
|
||||
/langium-cli@2.1.0:
|
||||
resolution: {integrity: sha512-Gbj4CvfAc1gP/6ihxikd2Je95j1FWjXZu8bbji2/t2vQ6kEP+vs9Fx7kSGOM0AbU/hjZfy6E35bJPOdwsiyqTA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
@ -11991,37 +11977,20 @@ packages:
|
||||
commander: 11.0.0
|
||||
fs-extra: 11.1.1
|
||||
jsonschema: 1.4.1
|
||||
langium: 2.1.3
|
||||
langium: 2.1.2
|
||||
langium-railroad: 2.1.0
|
||||
lodash: 4.17.21
|
||||
dev: true
|
||||
|
||||
/langium-railroad@2.0.0:
|
||||
resolution: {integrity: sha512-g6y8vPh4i7ll/Q4D9aFrjk4UgtkuzkE6WGfiTHJHTFlDwHoiKrPSIIBZO4wjEb3XUF9P5vIt7aRjerTy7Jgm0g==}
|
||||
dependencies:
|
||||
langium: 2.0.1
|
||||
railroad-diagrams: 1.0.0
|
||||
dev: true
|
||||
|
||||
/langium-railroad@2.1.0:
|
||||
resolution: {integrity: sha512-2IeAIUSTQzbDjNnJA+0ql8tyN/mhCSN4FS50Mo9LOtLj523qUEBwHflDmCiOGZzW9iZdni6NXJgh8nLqjhTlDw==}
|
||||
dependencies:
|
||||
langium: 2.1.3
|
||||
langium: 2.1.2
|
||||
railroad-diagrams: 1.0.0
|
||||
dev: true
|
||||
|
||||
/langium@2.0.1:
|
||||
resolution: {integrity: sha512-EGi8NNN/5zxcUL//sA4kqpV9YVOZfDngwkkSxsZ/zfx4Wjdg9von71rWIMCV6kW1M40kPOKF6e8oMTyWeX92fg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
dependencies:
|
||||
chevrotain: 11.0.3
|
||||
chevrotain-allstar: 0.3.1(chevrotain@11.0.3)
|
||||
vscode-languageserver: 8.0.2
|
||||
vscode-languageserver-textdocument: 1.0.8
|
||||
vscode-uri: 3.0.7
|
||||
|
||||
/langium@2.1.3:
|
||||
resolution: {integrity: sha512-/WN1xHoNBg0mi1Jp9ydMFSHIv8Jhq7K+0stNVURdoG4NgZx4/06AfNeeixmmU8X842wBl9gFZJP5O93Ge5Oasw==}
|
||||
/langium@2.1.2:
|
||||
resolution: {integrity: sha512-1NDUmhm111xs6NLh1DzQ9YPrOhL6JqJryY9igPIGrG0AbKKGmGf3fahAiY1MUChwIYSec6Fvoj+igwKzvGXQog==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
dependencies:
|
||||
chevrotain: 11.0.3
|
||||
@ -12029,7 +11998,6 @@ packages:
|
||||
vscode-languageserver: 9.0.1
|
||||
vscode-languageserver-textdocument: 1.0.11
|
||||
vscode-uri: 3.0.8
|
||||
dev: true
|
||||
|
||||
/layout-base@1.0.2:
|
||||
resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==}
|
||||
@ -16502,37 +16470,22 @@ packages:
|
||||
vscode-uri: 3.0.7
|
||||
dev: true
|
||||
|
||||
/vscode-jsonrpc@8.0.2:
|
||||
resolution: {integrity: sha512-RY7HwI/ydoC1Wwg4gJ3y6LpU9FJRZAUnTYMXthqhFXXu77ErDd/xkREpGuk4MyYkk4a+XDWAMqe0S3KkelYQEQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
/vscode-jsonrpc@8.2.0:
|
||||
resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
dev: true
|
||||
|
||||
/vscode-languageserver-protocol@3.17.2:
|
||||
resolution: {integrity: sha512-8kYisQ3z/SQ2kyjlNeQxbkkTNmVFoQCqkmGrzLH6A9ecPlgTbp3wDTnUNqaUxYr4vlAcloxx8zwy7G5WdguYNg==}
|
||||
dependencies:
|
||||
vscode-jsonrpc: 8.0.2
|
||||
vscode-languageserver-types: 3.17.2
|
||||
|
||||
/vscode-languageserver-protocol@3.17.5:
|
||||
resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
|
||||
dependencies:
|
||||
vscode-jsonrpc: 8.2.0
|
||||
vscode-languageserver-types: 3.17.5
|
||||
dev: true
|
||||
|
||||
/vscode-languageserver-textdocument@1.0.11:
|
||||
resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
|
||||
dev: true
|
||||
|
||||
/vscode-languageserver-textdocument@1.0.8:
|
||||
resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==}
|
||||
|
||||
/vscode-languageserver-types@3.17.2:
|
||||
resolution: {integrity: sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA==}
|
||||
dev: true
|
||||
|
||||
/vscode-languageserver-types@3.17.3:
|
||||
resolution: {integrity: sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==}
|
||||
@ -16540,20 +16493,12 @@ packages:
|
||||
|
||||
/vscode-languageserver-types@3.17.5:
|
||||
resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
|
||||
dev: true
|
||||
|
||||
/vscode-languageserver@8.0.2:
|
||||
resolution: {integrity: sha512-bpEt2ggPxKzsAOZlXmCJ50bV7VrxwCS5BI4+egUmure/oI/t4OlFzi/YNtVvY24A2UDOZAgwFGgnZPwqSJubkA==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
vscode-languageserver-protocol: 3.17.2
|
||||
|
||||
/vscode-languageserver@9.0.1:
|
||||
resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
vscode-languageserver-protocol: 3.17.5
|
||||
dev: true
|
||||
|
||||
/vscode-nls@5.2.0:
|
||||
resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==}
|
||||
@ -16569,10 +16514,10 @@ packages:
|
||||
|
||||
/vscode-uri@3.0.7:
|
||||
resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==}
|
||||
dev: true
|
||||
|
||||
/vscode-uri@3.0.8:
|
||||
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
|
||||
dev: true
|
||||
|
||||
/vue-demi@0.13.11(vue@3.4.15):
|
||||
resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
|
||||
|
Loading…
x
Reference in New Issue
Block a user