mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-10 02:49:40 +02:00
feat: Add packet diagram
This commit is contained in:
@@ -23,6 +23,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
|
|||||||
'gitGraph',
|
'gitGraph',
|
||||||
'c4',
|
'c4',
|
||||||
'sankey',
|
'sankey',
|
||||||
|
'packet',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,9 +9,10 @@ import { generateLangium } from '../.build/generateLangium.js';
|
|||||||
const parserCtx = await context(
|
const parserCtx = await context(
|
||||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'parser' })
|
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'parser' })
|
||||||
);
|
);
|
||||||
const mermaidCtx = await context(
|
const mermaidCtx = await context({
|
||||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' })
|
...getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' }),
|
||||||
);
|
sourcemap: 'linked',
|
||||||
|
});
|
||||||
const mermaidIIFECtx = await context(
|
const mermaidIIFECtx = await context(
|
||||||
getBuildConfig({
|
getBuildConfig({
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
|
47
demos/packet.html
Normal file
47
demos/packet.html
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<title>Mermaid Quick Test Page</title>
|
||||||
|
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||||
|
<style>
|
||||||
|
div.mermaid {
|
||||||
|
font-family: 'Courier New', Courier, monospace !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<h1>Packet diagram demo</h1>
|
||||||
|
<pre class="mermaid">
|
||||||
|
packet-beta
|
||||||
|
0-15: "Source Port"
|
||||||
|
16-31: "Destination Port"
|
||||||
|
32-63: "Sequence Number"
|
||||||
|
64-95: "Acknowledgment Number"
|
||||||
|
96-99: "Data Offset"
|
||||||
|
100-105: "Reserved"
|
||||||
|
106: "URG"
|
||||||
|
107: "ACK"
|
||||||
|
108: "PSH"
|
||||||
|
109: "RST"
|
||||||
|
110: "SYN"
|
||||||
|
111: "FIN"
|
||||||
|
112-127: "Window"
|
||||||
|
128-143: "Checksum"
|
||||||
|
144-159: "Urgent Pointer"
|
||||||
|
160-191: "(Options and Padding)"
|
||||||
|
192-223: "data"
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from '/mermaid.esm.mjs';
|
||||||
|
mermaid.initialize({
|
||||||
|
theme: 'forest',
|
||||||
|
logLevel: 3,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#### Defined in
|
#### Defined in
|
||||||
|
|
||||||
[defaultConfig.ts:268](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L268)
|
[defaultConfig.ts:275](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L275)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@@ -140,10 +140,43 @@ export interface MermaidConfig {
|
|||||||
gitGraph?: GitGraphDiagramConfig;
|
gitGraph?: GitGraphDiagramConfig;
|
||||||
c4?: C4DiagramConfig;
|
c4?: C4DiagramConfig;
|
||||||
sankey?: SankeyDiagramConfig;
|
sankey?: SankeyDiagramConfig;
|
||||||
|
packet?: PacketDiagramConfig;
|
||||||
dompurifyConfig?: DOMPurifyConfiguration;
|
dompurifyConfig?: DOMPurifyConfiguration;
|
||||||
wrap?: boolean;
|
wrap?: boolean;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* The object containing configurations specific for packet diagrams.
|
||||||
|
*
|
||||||
|
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||||
|
* via the `definition` "PacketDiagramConfig".
|
||||||
|
*/
|
||||||
|
export interface PacketDiagramConfig extends BaseDiagramConfig {
|
||||||
|
/**
|
||||||
|
* The height of each row in the packet diagram.
|
||||||
|
*/
|
||||||
|
rowHeight?: number;
|
||||||
|
/**
|
||||||
|
* The width of each bit in the packet diagram.
|
||||||
|
*/
|
||||||
|
bitWidth?: number;
|
||||||
|
/**
|
||||||
|
* The number of bits to display per row.
|
||||||
|
*/
|
||||||
|
bitsPerRow?: number;
|
||||||
|
/**
|
||||||
|
* Toggle to display or hide bit numbers.
|
||||||
|
*/
|
||||||
|
showBits?: boolean;
|
||||||
|
/**
|
||||||
|
* The horizontal padding between the blocks in a row.
|
||||||
|
*/
|
||||||
|
paddingX?: number;
|
||||||
|
/**
|
||||||
|
* The vertical padding between the rows.
|
||||||
|
*/
|
||||||
|
paddingY?: number;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||||
* via the `definition` "BaseDiagramConfig".
|
* via the `definition` "BaseDiagramConfig".
|
||||||
|
@@ -253,6 +253,13 @@ const config: RequiredDeep<MermaidConfig> = {
|
|||||||
// TODO: can we make this default to `true` instead?
|
// TODO: can we make this default to `true` instead?
|
||||||
useMaxWidth: false,
|
useMaxWidth: false,
|
||||||
},
|
},
|
||||||
|
packet: {
|
||||||
|
...defaultConfigJson.packet,
|
||||||
|
useWidth: undefined,
|
||||||
|
// this is false, unlike every other diagram (other than gitGraph)
|
||||||
|
// TODO: can we make this default to `true` instead?
|
||||||
|
useMaxWidth: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyify = (obj: any, prefix = ''): string[] =>
|
const keyify = (obj: any, prefix = ''): string[] =>
|
||||||
|
@@ -19,6 +19,7 @@ import flowchartElk from '../diagrams/flowchart/elk/detector.js';
|
|||||||
import timeline from '../diagrams/timeline/detector.js';
|
import timeline from '../diagrams/timeline/detector.js';
|
||||||
import mindmap from '../diagrams/mindmap/detector.js';
|
import mindmap from '../diagrams/mindmap/detector.js';
|
||||||
import sankey from '../diagrams/sankey/sankeyDetector.js';
|
import sankey from '../diagrams/sankey/sankeyDetector.js';
|
||||||
|
import { packet } from '../diagrams/packet/detector.js';
|
||||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||||
import { registerDiagram } from './diagramAPI.js';
|
import { registerDiagram } from './diagramAPI.js';
|
||||||
|
|
||||||
@@ -84,6 +85,7 @@ export const addDiagrams = () => {
|
|||||||
state,
|
state,
|
||||||
journey,
|
journey,
|
||||||
quadrantChart,
|
quadrantChart,
|
||||||
sankey
|
sankey,
|
||||||
|
packet
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
101
packages/mermaid/src/diagrams/packet/db.ts
Normal file
101
packages/mermaid/src/diagrams/packet/db.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import type { Block, PacketDB, Word } from './types.js';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import type { PacketDiagramConfig } from '../../config.type.js';
|
||||||
|
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||||
|
import { getConfig as commonGetConfig } from '../../config.js';
|
||||||
|
|
||||||
|
interface PacketData {
|
||||||
|
packet: Word[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPacketData: PacketData = {
|
||||||
|
packet: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
let data: PacketData = structuredClone(defaultPacketData);
|
||||||
|
export const DEFAULT_PACKET_CONFIG: Required<PacketDiagramConfig> = DEFAULT_CONFIG.packet;
|
||||||
|
|
||||||
|
export const getConfig = (): Required<PacketDiagramConfig> => {
|
||||||
|
return structuredClone({
|
||||||
|
...DEFAULT_PACKET_CONFIG,
|
||||||
|
...commonGetConfig().packet,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPacket = (): Word[] => data.packet;
|
||||||
|
|
||||||
|
export const getNextFittingBlock = (
|
||||||
|
block: Block,
|
||||||
|
row: number,
|
||||||
|
bitsPerRow: number
|
||||||
|
): [Block, Block | undefined] => {
|
||||||
|
block.end = block.end ?? block.start;
|
||||||
|
|
||||||
|
if (block.start > block.end) {
|
||||||
|
throw new Error(`Block start ${block.start} is greater than block end ${block.end}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.end + 1 <= row * bitsPerRow) {
|
||||||
|
return [block, undefined];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
start: block.start,
|
||||||
|
end: row * bitsPerRow - 1,
|
||||||
|
label: block.label,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: row * bitsPerRow,
|
||||||
|
end: block.end,
|
||||||
|
label: block.label,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const populate = ({ blocks }: { blocks: Block[] }) => {
|
||||||
|
let lastByte = -1;
|
||||||
|
let word: Block[] = [];
|
||||||
|
data.packet = [];
|
||||||
|
let row = 1;
|
||||||
|
const { bitsPerRow } = getConfig();
|
||||||
|
for (let { start, end, label } of blocks) {
|
||||||
|
if (end < start) {
|
||||||
|
throw new Error(`Packet block ${start} - ${end} is invalid. End must be greater than start.`);
|
||||||
|
}
|
||||||
|
if (start != lastByte + 1) {
|
||||||
|
throw new Error(
|
||||||
|
`Packet block ${start} - ${end} is not contiguous. It should start from ${lastByte + 1}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
lastByte = end ?? start;
|
||||||
|
log.debug(`Packet block ${start} - ${lastByte} with label ${label}`);
|
||||||
|
|
||||||
|
while (word.length <= bitsPerRow + 1 && data.packet.length < 10_000) {
|
||||||
|
const [block, nextBlock] = getNextFittingBlock({ start, end, label }, row, bitsPerRow);
|
||||||
|
word.push(block);
|
||||||
|
if (block.end + 1 === row * bitsPerRow) {
|
||||||
|
data.packet.push(word);
|
||||||
|
word = [];
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
if (!nextBlock) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
({ start, end, label } = nextBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (word.length > 0) {
|
||||||
|
data.packet.push(word);
|
||||||
|
}
|
||||||
|
log.debug(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const clear = () => {
|
||||||
|
data = structuredClone(defaultPacketData);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const db: PacketDB = {
|
||||||
|
getPacket,
|
||||||
|
getConfig,
|
||||||
|
};
|
22
packages/mermaid/src/diagrams/packet/detector.ts
Normal file
22
packages/mermaid/src/diagrams/packet/detector.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import type {
|
||||||
|
DiagramDetector,
|
||||||
|
DiagramLoader,
|
||||||
|
ExternalDiagramDefinition,
|
||||||
|
} from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
const id = 'packet';
|
||||||
|
|
||||||
|
const detector: DiagramDetector = (txt) => {
|
||||||
|
return /^\s*packet-beta/.test(txt);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loader: DiagramLoader = async () => {
|
||||||
|
const { diagram } = await import('./diagram.js');
|
||||||
|
return { id, diagram };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const packet: ExternalDiagramDefinition = {
|
||||||
|
id,
|
||||||
|
detector,
|
||||||
|
loader,
|
||||||
|
};
|
12
packages/mermaid/src/diagrams/packet/diagram.ts
Normal file
12
packages/mermaid/src/diagrams/packet/diagram.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
|
import { parser } from './parser.js';
|
||||||
|
import { db } from './db.js';
|
||||||
|
import { renderer } from './renderer.js';
|
||||||
|
import { styles } from './styles.js';
|
||||||
|
|
||||||
|
export const diagram: DiagramDefinition = {
|
||||||
|
parser,
|
||||||
|
db,
|
||||||
|
renderer,
|
||||||
|
styles,
|
||||||
|
};
|
31
packages/mermaid/src/diagrams/packet/packet.spec.ts
Normal file
31
packages/mermaid/src/diagrams/packet/packet.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { parser } from './parser.js';
|
||||||
|
|
||||||
|
describe('info', () => {
|
||||||
|
it('should handle an info definition', () => {
|
||||||
|
const str = `info`;
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an info definition with showInfo', () => {
|
||||||
|
const str = `info showInfo`;
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw because of unsupported info grammar', () => {
|
||||||
|
const str = `info unsupported`;
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw because of unsupported info grammar', () => {
|
||||||
|
const str = `info unsupported`;
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||||
|
});
|
||||||
|
});
|
14
packages/mermaid/src/diagrams/packet/parser.ts
Normal file
14
packages/mermaid/src/diagrams/packet/parser.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import type { Packet } from 'mermaid-parser';
|
||||||
|
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
import { parse } from 'mermaid-parser';
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import { populate } from './db.js';
|
||||||
|
|
||||||
|
export const parser: ParserDefinition = {
|
||||||
|
parse: (input: string): void => {
|
||||||
|
const ast: Packet = parse('packet', input);
|
||||||
|
log.debug(ast);
|
||||||
|
populate(ast);
|
||||||
|
},
|
||||||
|
};
|
81
packages/mermaid/src/diagrams/packet/renderer.ts
Normal file
81
packages/mermaid/src/diagrams/packet/renderer.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||||
|
import type { DrawDefinition, Group, SVG } from '../../diagram-api/types.js';
|
||||||
|
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||||
|
import type { PacketDB, Word } from './types.js';
|
||||||
|
import type { PacketDiagramConfig } from '../../config.type.js';
|
||||||
|
import type { Diagram } from '../../Diagram.js';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const draw: DrawDefinition = (_text, id, _version, diagram: Diagram) => {
|
||||||
|
const db = diagram.db as PacketDB;
|
||||||
|
const config = db.getConfig?.() as Required<PacketDiagramConfig>;
|
||||||
|
const { rowHeight, paddingY, bitWidth, bitsPerRow } = config;
|
||||||
|
const words = db.getPacket();
|
||||||
|
const svgHeight = (rowHeight + paddingY) * words.length + paddingY;
|
||||||
|
const svgWidth = bitWidth * bitsPerRow + 2;
|
||||||
|
|
||||||
|
const svg: SVG = selectSvgElement(id);
|
||||||
|
configureSvgSize(svg, svgHeight, svgWidth, true);
|
||||||
|
svg.attr('height', svgHeight + 'px');
|
||||||
|
|
||||||
|
for (const [row, packet] of words.entries()) {
|
||||||
|
drawWord(svg, packet, row, config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawWord = (
|
||||||
|
svg: SVG,
|
||||||
|
word: Word,
|
||||||
|
row: number,
|
||||||
|
{ rowHeight, paddingX, paddingY, bitWidth, bitsPerRow }: Required<PacketDiagramConfig>
|
||||||
|
) => {
|
||||||
|
const group: Group = svg.append('g');
|
||||||
|
const wordY = row * (rowHeight + paddingY) + paddingY;
|
||||||
|
for (const block of word) {
|
||||||
|
const blockX = (block.start % bitsPerRow) * bitWidth + 1;
|
||||||
|
const width = (block.end - block.start + 1) * bitWidth - paddingX;
|
||||||
|
// Block rectangle
|
||||||
|
group
|
||||||
|
.append('rect')
|
||||||
|
.attr('x', blockX)
|
||||||
|
.attr('y', wordY)
|
||||||
|
.attr('width', width)
|
||||||
|
.attr('height', rowHeight)
|
||||||
|
.attr('class', 'block');
|
||||||
|
|
||||||
|
// Block label
|
||||||
|
group
|
||||||
|
.append('text')
|
||||||
|
.attr('x', blockX + width / 2)
|
||||||
|
.attr('y', wordY + rowHeight / 2)
|
||||||
|
.attr('class', 'label')
|
||||||
|
.attr('dominant-baseline', 'middle')
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.text(block.label);
|
||||||
|
|
||||||
|
// Start byte count
|
||||||
|
const isSingleBlock = block.end === block.start;
|
||||||
|
const byteNumberY = wordY - 2;
|
||||||
|
group
|
||||||
|
.append('text')
|
||||||
|
.attr('x', blockX + (isSingleBlock ? width / 2 : 0))
|
||||||
|
.attr('y', byteNumberY)
|
||||||
|
.attr('class', 'byte start')
|
||||||
|
.attr('dominant-baseline', 'auto')
|
||||||
|
.attr('text-anchor', isSingleBlock ? 'middle' : 'start')
|
||||||
|
.text(block.start);
|
||||||
|
|
||||||
|
// Draw end byte count if it is not the same as start byte count
|
||||||
|
if (!isSingleBlock) {
|
||||||
|
group
|
||||||
|
.append('text')
|
||||||
|
.attr('x', blockX + width)
|
||||||
|
.attr('y', byteNumberY)
|
||||||
|
.attr('class', 'byte end')
|
||||||
|
.attr('dominant-baseline', 'auto')
|
||||||
|
.attr('text-anchor', 'end')
|
||||||
|
.text(block.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const renderer = { draw };
|
27
packages/mermaid/src/diagrams/packet/styles.ts
Normal file
27
packages/mermaid/src/diagrams/packet/styles.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { log } from '../../logger.js';
|
||||||
|
|
||||||
|
export const styles = (options: any = {}) => {
|
||||||
|
log.debug({ options });
|
||||||
|
return `
|
||||||
|
.byte {
|
||||||
|
font-size: ${options.packet?.byteFontSize ?? '10px'};
|
||||||
|
}
|
||||||
|
.byte.start {
|
||||||
|
fill: ${options.packet?.startByteColor ?? 'black'};
|
||||||
|
}
|
||||||
|
.byte.end {
|
||||||
|
fill: ${options.packet?.endByteColor ?? 'black'};
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
fill: ${options.packet?.labelColor ?? 'black'};
|
||||||
|
font-size: ${options.packet?.labelFontSize ?? '12px'};
|
||||||
|
}
|
||||||
|
.block {
|
||||||
|
stroke: ${options.packet?.blockStrokeColor ?? 'black'};
|
||||||
|
stroke-width: ${options.packet?.blockStrokeWidth ?? '1'};
|
||||||
|
fill: ${options.packet?.blockFillColor ?? '#efefef'};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default styles;
|
10
packages/mermaid/src/diagrams/packet/types.ts
Normal file
10
packages/mermaid/src/diagrams/packet/types.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Packet } from 'mermaid-parser';
|
||||||
|
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
export type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
|
||||||
|
export type Block = Pick<ArrayElement<Packet['blocks']>, 'start' | 'end' | 'label'>;
|
||||||
|
export type Word = Block[];
|
||||||
|
|
||||||
|
export interface PacketDB extends DiagramDB {
|
||||||
|
getPacket: () => Word[];
|
||||||
|
}
|
@@ -48,6 +48,7 @@ required:
|
|||||||
- gitGraph
|
- gitGraph
|
||||||
- c4
|
- c4
|
||||||
- sankey
|
- sankey
|
||||||
|
- packet
|
||||||
properties:
|
properties:
|
||||||
theme:
|
theme:
|
||||||
description: |
|
description: |
|
||||||
@@ -201,6 +202,8 @@ properties:
|
|||||||
$ref: '#/$defs/C4DiagramConfig'
|
$ref: '#/$defs/C4DiagramConfig'
|
||||||
sankey:
|
sankey:
|
||||||
$ref: '#/$defs/SankeyDiagramConfig'
|
$ref: '#/$defs/SankeyDiagramConfig'
|
||||||
|
packet:
|
||||||
|
$ref: '#/$defs/PacketDiagramConfig'
|
||||||
dompurifyConfig:
|
dompurifyConfig:
|
||||||
title: DOM Purify Configuration
|
title: DOM Purify Configuration
|
||||||
description: Configuration options to pass to the `dompurify` library.
|
description: Configuration options to pass to the `dompurify` library.
|
||||||
@@ -1853,6 +1856,32 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
|||||||
type: string
|
type: string
|
||||||
default: ''
|
default: ''
|
||||||
|
|
||||||
|
PacketDiagramConfig:
|
||||||
|
title: Packet Diagram Config
|
||||||
|
allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }]
|
||||||
|
description: The object containing configurations specific for packet diagrams.
|
||||||
|
type: object
|
||||||
|
unevaluatedProperties: false
|
||||||
|
properties:
|
||||||
|
rowHeight:
|
||||||
|
description: The height of each row in the packet diagram.
|
||||||
|
default: 32
|
||||||
|
bitWidth:
|
||||||
|
description: The width of each bit in the packet diagram.
|
||||||
|
default: 32
|
||||||
|
bitsPerRow:
|
||||||
|
description: The number of bits to display per row.
|
||||||
|
default: 32
|
||||||
|
showBits:
|
||||||
|
description: Toggle to display or hide bit numbers.
|
||||||
|
default: true
|
||||||
|
paddingX:
|
||||||
|
description: The horizontal padding between the blocks in a row.
|
||||||
|
default: 5
|
||||||
|
paddingY:
|
||||||
|
description: The vertical padding between the rows.
|
||||||
|
default: 15
|
||||||
|
|
||||||
FontCalculator:
|
FontCalculator:
|
||||||
title: Font Calculator
|
title: Font Calculator
|
||||||
description: |
|
description: |
|
||||||
|
@@ -28,6 +28,7 @@ import state from './diagrams/state/styles.js';
|
|||||||
import journey from './diagrams/user-journey/styles.js';
|
import journey from './diagrams/user-journey/styles.js';
|
||||||
import timeline from './diagrams/timeline/styles.js';
|
import timeline from './diagrams/timeline/styles.js';
|
||||||
import mindmap from './diagrams/mindmap/styles.js';
|
import mindmap from './diagrams/mindmap/styles.js';
|
||||||
|
import packet from './diagrams/packet/styles.js';
|
||||||
import themes from './themes/index.js';
|
import themes from './themes/index.js';
|
||||||
|
|
||||||
async function checkValidStylisCSSStyleSheet(stylisString: string) {
|
async function checkValidStylisCSSStyleSheet(stylisString: string) {
|
||||||
@@ -96,6 +97,7 @@ describe('styles', () => {
|
|||||||
sequence,
|
sequence,
|
||||||
state,
|
state,
|
||||||
timeline,
|
timeline,
|
||||||
|
packet,
|
||||||
})) {
|
})) {
|
||||||
test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => {
|
test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => {
|
||||||
const { default: getStyles, addStylesForDiagram } = await import('./styles.js');
|
const { default: getStyles, addStylesForDiagram } = await import('./styles.js');
|
||||||
|
@@ -5,6 +5,11 @@
|
|||||||
"id": "info",
|
"id": "info",
|
||||||
"grammar": "src/language/info/info.langium",
|
"grammar": "src/language/info/info.langium",
|
||||||
"fileExtensions": [".mmd", ".mermaid"]
|
"fileExtensions": [".mmd", ".mermaid"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "packet",
|
||||||
|
"grammar": "src/language/packet/packet.langium",
|
||||||
|
"fileExtensions": [".mmd", ".mermaid"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"mode": "production",
|
"mode": "production",
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
export type { Info } from './language/index.js';
|
export type { Info, Packet } from './language/index.js';
|
||||||
export type { DiagramAST } from './parse.js';
|
export type { DiagramAST } from './parse.js';
|
||||||
export { parse, MermaidParseError } from './parse.js';
|
export { parse, MermaidParseError } from './parse.js';
|
||||||
|
@@ -4,3 +4,4 @@ export * from './generated/module.js';
|
|||||||
|
|
||||||
export * from './common/index.js';
|
export * from './common/index.js';
|
||||||
export * from './info/index.js';
|
export * from './info/index.js';
|
||||||
|
export * from './packet/index.js';
|
||||||
|
1
packages/parser/src/language/packet/index.ts
Normal file
1
packages/parser/src/language/packet/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './module.js';
|
72
packages/parser/src/language/packet/module.ts
Normal file
72
packages/parser/src/language/packet/module.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import type {
|
||||||
|
DefaultSharedModuleContext,
|
||||||
|
LangiumServices,
|
||||||
|
LangiumSharedServices,
|
||||||
|
Module,
|
||||||
|
PartialLangiumServices,
|
||||||
|
} from 'langium';
|
||||||
|
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||||
|
|
||||||
|
import { CommonLexer } from '../common/lexer.js';
|
||||||
|
import { MermaidGeneratedSharedModule, PacketGeneratedModule } from '../generated/module.js';
|
||||||
|
import { PacketTokenBuilder } from './tokenBuilder.js';
|
||||||
|
import { CommonValueConverter } from '../common/valueConverter.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration of `Packet` services.
|
||||||
|
*/
|
||||||
|
type PacketAddedServices = {
|
||||||
|
parser: {
|
||||||
|
Lexer: CommonLexer;
|
||||||
|
TokenBuilder: PacketTokenBuilder;
|
||||||
|
ValueConverter: CommonValueConverter;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union of Langium default services and `Packet` services.
|
||||||
|
*/
|
||||||
|
export type PacketServices = LangiumServices & PacketAddedServices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency injection module that overrides Langium default services and
|
||||||
|
* contributes the declared `Packet` services.
|
||||||
|
*/
|
||||||
|
export const PacketModule: Module<PacketServices, PartialLangiumServices & PacketAddedServices> = {
|
||||||
|
parser: {
|
||||||
|
Lexer: (services: PacketServices) => new CommonLexer(services),
|
||||||
|
TokenBuilder: () => new PacketTokenBuilder(),
|
||||||
|
ValueConverter: () => new CommonValueConverter(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 createPacketServices(context: DefaultSharedModuleContext = EmptyFileSystem): {
|
||||||
|
shared: LangiumSharedServices;
|
||||||
|
Packet: PacketServices;
|
||||||
|
} {
|
||||||
|
const shared: LangiumSharedServices = inject(
|
||||||
|
createDefaultSharedModule(context),
|
||||||
|
MermaidGeneratedSharedModule
|
||||||
|
);
|
||||||
|
const Packet: PacketServices = inject(
|
||||||
|
createDefaultModule({ shared }),
|
||||||
|
PacketGeneratedModule,
|
||||||
|
PacketModule
|
||||||
|
);
|
||||||
|
shared.ServiceRegistry.register(Packet);
|
||||||
|
return { shared, Packet };
|
||||||
|
}
|
14
packages/parser/src/language/packet/packet.langium
Normal file
14
packages/parser/src/language/packet/packet.langium
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
grammar Packet
|
||||||
|
import "../common/common";
|
||||||
|
|
||||||
|
entry Packet:
|
||||||
|
"packet-beta" NEWLINE*
|
||||||
|
TitleAndAccessibilities?
|
||||||
|
(blocks+=Block)*;
|
||||||
|
|
||||||
|
Block:
|
||||||
|
start=INT ('-' end=INT)? ':' label=STRING;
|
||||||
|
|
||||||
|
hidden terminal WS: /\s+/;
|
||||||
|
terminal INT returns number: /[0-9]+/;
|
||||||
|
terminal STRING: /"[^"]*"|'[^']*'/;
|
7
packages/parser/src/language/packet/tokenBuilder.ts
Normal file
7
packages/parser/src/language/packet/tokenBuilder.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { MermaidTokenBuilder } from '../common/index.js';
|
||||||
|
|
||||||
|
export class PacketTokenBuilder extends MermaidTokenBuilder {
|
||||||
|
public constructor() {
|
||||||
|
super(['packet-beta']);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,8 +1,8 @@
|
|||||||
import type { LangiumParser, ParseResult } from 'langium';
|
import type { LangiumParser, ParseResult } from 'langium';
|
||||||
import type { Info } from './index.js';
|
import type { Info, Packet } from './index.js';
|
||||||
import { createInfoServices } from './language/index.js';
|
import { createInfoServices, createPacketServices } from './language/index.js';
|
||||||
|
|
||||||
export type DiagramAST = Info;
|
export type DiagramAST = Info | Packet;
|
||||||
|
|
||||||
const parsers: Record<string, LangiumParser> = {};
|
const parsers: Record<string, LangiumParser> = {};
|
||||||
|
|
||||||
@@ -13,8 +13,13 @@ const initializers = {
|
|||||||
const parser = createInfoServices().Info.parser.LangiumParser;
|
const parser = createInfoServices().Info.parser.LangiumParser;
|
||||||
parsers['info'] = parser;
|
parsers['info'] = parser;
|
||||||
},
|
},
|
||||||
|
packet: () => {
|
||||||
|
const parser = createPacketServices().Packet.parser.LangiumParser;
|
||||||
|
parsers['packet'] = parser;
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
export function parse(diagramType: 'info', text: string): Info;
|
export function parse(diagramType: 'info', text: string): Info;
|
||||||
|
export function parse(diagramType: 'packet', text: string): Packet;
|
||||||
export function parse<T extends DiagramAST>(
|
export function parse<T extends DiagramAST>(
|
||||||
diagramType: keyof typeof initializers,
|
diagramType: keyof typeof initializers,
|
||||||
text: string
|
text: string
|
||||||
|
Reference in New Issue
Block a user