@diagrammo/dgmo 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
+ "bin": {
8
+ "dgmo": "./dist/cli.cjs"
9
+ },
7
10
  "main": "./dist/index.cjs",
8
11
  "module": "./dist/index.js",
9
12
  "types": "./dist/index.d.ts",
@@ -37,9 +40,11 @@
37
40
  "d3-scale": "^4.0.2",
38
41
  "d3-selection": "^3.0.0",
39
42
  "d3-shape": "^3.2.0",
40
- "echarts": "^5.6.0"
43
+ "echarts": "^5.6.0",
44
+ "jsdom": "^26.0.0"
41
45
  },
42
46
  "devDependencies": {
47
+ "@types/jsdom": "^21.1.7",
43
48
  "@types/d3-array": "^3.2.1",
44
49
  "@types/d3-cloud": "^1.2.9",
45
50
  "@types/d3-scale": "^4.0.8",
package/src/cli.ts ADDED
@@ -0,0 +1,189 @@
1
+ import { readFileSync, writeFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { JSDOM } from 'jsdom';
4
+ import { renderD3ForExport } from './d3';
5
+ import { getPalette } from './palettes/registry';
6
+
7
+ const PALETTES = [
8
+ 'nord',
9
+ 'solarized',
10
+ 'catppuccin',
11
+ 'rose-pine',
12
+ 'gruvbox',
13
+ 'tokyo-night',
14
+ 'one-dark',
15
+ 'bold',
16
+ ];
17
+
18
+ const THEMES = ['light', 'dark', 'transparent'] as const;
19
+
20
+ function printHelp(): void {
21
+ console.log(`Usage: dgmo render <input> [options]
22
+
23
+ Commands:
24
+ render <input> Render a .dgmo file to SVG
25
+
26
+ Options:
27
+ -o <file> Write SVG to file (default: stdout)
28
+ --theme <theme> Theme: ${THEMES.join(', ')} (default: light)
29
+ --palette <name> Palette: ${PALETTES.join(', ')} (default: nord)
30
+ --help Show this help
31
+ --version Show version`);
32
+ }
33
+
34
+ function printVersion(): void {
35
+ const pkg = JSON.parse(
36
+ readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8')
37
+ );
38
+ console.log(pkg.version);
39
+ }
40
+
41
+ function parseArgs(argv: string[]): {
42
+ command: string | undefined;
43
+ input: string | undefined;
44
+ output: string | undefined;
45
+ theme: (typeof THEMES)[number];
46
+ palette: string;
47
+ help: boolean;
48
+ version: boolean;
49
+ } {
50
+ const result = {
51
+ command: undefined as string | undefined,
52
+ input: undefined as string | undefined,
53
+ output: undefined as string | undefined,
54
+ theme: 'light' as (typeof THEMES)[number],
55
+ palette: 'nord',
56
+ help: false,
57
+ version: false,
58
+ };
59
+
60
+ const args = argv.slice(2); // skip node + script
61
+ let i = 0;
62
+
63
+ while (i < args.length) {
64
+ const arg = args[i];
65
+
66
+ if (arg === '--help' || arg === '-h') {
67
+ result.help = true;
68
+ i++;
69
+ } else if (arg === '--version' || arg === '-v') {
70
+ result.version = true;
71
+ i++;
72
+ } else if (arg === '-o') {
73
+ result.output = args[++i];
74
+ i++;
75
+ } else if (arg === '--theme') {
76
+ const val = args[++i];
77
+ if (!THEMES.includes(val as (typeof THEMES)[number])) {
78
+ console.error(
79
+ `Error: Invalid theme "${val}". Valid themes: ${THEMES.join(', ')}`
80
+ );
81
+ process.exit(1);
82
+ }
83
+ result.theme = val as (typeof THEMES)[number];
84
+ i++;
85
+ } else if (arg === '--palette') {
86
+ const val = args[++i];
87
+ if (!PALETTES.includes(val)) {
88
+ console.error(
89
+ `Error: Unknown palette "${val}". Valid palettes: ${PALETTES.join(', ')}`
90
+ );
91
+ process.exit(1);
92
+ }
93
+ result.palette = val;
94
+ i++;
95
+ } else if (!result.command) {
96
+ result.command = arg;
97
+ i++;
98
+ } else if (!result.input) {
99
+ result.input = arg;
100
+ i++;
101
+ } else {
102
+ console.error(`Error: Unexpected argument "${arg}"`);
103
+ process.exit(1);
104
+ }
105
+ }
106
+
107
+ return result;
108
+ }
109
+
110
+ function setupDom(): void {
111
+ const dom = new JSDOM('<!DOCTYPE html><html><body></body></html>');
112
+ const win = dom.window;
113
+
114
+ // Expose DOM globals needed by d3-selection and renderers
115
+ Object.defineProperty(globalThis, 'document', { value: win.document, configurable: true });
116
+ Object.defineProperty(globalThis, 'window', { value: win, configurable: true });
117
+ Object.defineProperty(globalThis, 'navigator', { value: win.navigator, configurable: true });
118
+ Object.defineProperty(globalThis, 'HTMLElement', { value: win.HTMLElement, configurable: true });
119
+ Object.defineProperty(globalThis, 'SVGElement', { value: win.SVGElement, configurable: true });
120
+ }
121
+
122
+ async function main(): Promise<void> {
123
+ const opts = parseArgs(process.argv);
124
+
125
+ if (opts.help) {
126
+ printHelp();
127
+ return;
128
+ }
129
+
130
+ if (opts.version) {
131
+ printVersion();
132
+ return;
133
+ }
134
+
135
+ if (opts.command !== 'render') {
136
+ if (opts.command) {
137
+ console.error(`Error: Unknown command "${opts.command}"`);
138
+ } else {
139
+ console.error('Error: No command specified');
140
+ }
141
+ console.error('Run "dgmo --help" for usage');
142
+ process.exit(1);
143
+ }
144
+
145
+ if (!opts.input) {
146
+ console.error('Error: No input file specified');
147
+ console.error('Usage: dgmo render <input> [-o output.svg]');
148
+ process.exit(1);
149
+ }
150
+
151
+ const inputPath = resolve(opts.input);
152
+ let content: string;
153
+ try {
154
+ content = readFileSync(inputPath, 'utf-8');
155
+ } catch {
156
+ console.error(`Error: Cannot read file "${inputPath}"`);
157
+ process.exit(1);
158
+ }
159
+
160
+ // Set up jsdom before any d3/renderer code runs
161
+ setupDom();
162
+
163
+ const isDark = opts.theme === 'dark';
164
+ const paletteColors = isDark
165
+ ? getPalette(opts.palette).dark
166
+ : getPalette(opts.palette).light;
167
+
168
+ const svg = await renderD3ForExport(content, opts.theme, paletteColors);
169
+
170
+ if (!svg) {
171
+ console.error(
172
+ 'Error: Failed to render diagram. The input may be empty, invalid, or use an unsupported chart type (e.g. Chart.js/ECharts charts require a browser).'
173
+ );
174
+ process.exit(1);
175
+ }
176
+
177
+ if (opts.output) {
178
+ const outputPath = resolve(opts.output);
179
+ writeFileSync(outputPath, svg, 'utf-8');
180
+ console.error(`Wrote ${outputPath}`);
181
+ } else {
182
+ process.stdout.write(svg);
183
+ }
184
+ }
185
+
186
+ main().catch((err: Error) => {
187
+ console.error(`Error: ${err.message}`);
188
+ process.exit(1);
189
+ });
package/src/index.ts CHANGED
@@ -85,8 +85,14 @@ export {
85
85
  computeActivations,
86
86
  applyPositionOverrides,
87
87
  applyGroupOrdering,
88
+ groupMessagesBySection,
89
+ } from './sequence/renderer';
90
+ export type {
91
+ RenderStep,
92
+ Activation,
93
+ SectionMessageGroup,
94
+ SequenceRenderOptions,
88
95
  } from './sequence/renderer';
89
- export type { RenderStep, Activation } from './sequence/renderer';
90
96
 
91
97
  // ============================================================
92
98
  // Colors & Palettes
@@ -247,8 +247,15 @@ export function parseSequenceDgmo(content: string): ParsedSequenceDgmo {
247
247
  if (trimmed.startsWith('#') || trimmed.startsWith('//')) continue;
248
248
 
249
249
  // Parse section dividers — "== Label ==" or "== Label(color) =="
250
+ // Close blocks first — sections at indent 0 should not nest inside blocks
250
251
  const sectionMatch = trimmed.match(SECTION_PATTERN);
251
252
  if (sectionMatch) {
253
+ const sectionIndent = measureIndent(raw);
254
+ while (blockStack.length > 0) {
255
+ const top = blockStack[blockStack.length - 1];
256
+ if (sectionIndent > top.indent) break;
257
+ blockStack.pop();
258
+ }
252
259
  const labelRaw = sectionMatch[1].trim();
253
260
  const colorMatch = labelRaw.match(/^(.+?)\((\w+)\)$/);
254
261
  const section: SequenceSection = {