@diagrammo/dgmo 0.8.4 → 0.8.5
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/.claude/commands/dgmo.md +267 -0
- package/dist/cli.cjs +188 -185
- package/dist/editor.cjs +2 -0
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +2 -0
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +560 -0
- package/dist/highlight.cjs.map +1 -0
- package/dist/highlight.d.cts +32 -0
- package/dist/highlight.d.ts +32 -0
- package/dist/highlight.js +530 -0
- package/dist/highlight.js.map +1 -0
- package/dist/index.cjs +117 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +116 -9
- package/dist/index.js.map +1 -1
- package/docs/language-reference.md +17 -9
- package/gallery/fixtures/slope.dgmo +7 -6
- package/package.json +26 -6
- package/src/cli.ts +43 -1
- package/src/d3.ts +161 -19
- package/src/editor/highlight-api.ts +444 -0
- package/src/editor/keywords.ts +2 -0
- package/src/index.ts +96 -31
|
@@ -1143,20 +1143,27 @@ Thousands commas supported.
|
|
|
1143
1143
|
|
|
1144
1144
|
## 16. Visualizations
|
|
1145
1145
|
|
|
1146
|
-
### 16.1 Slope Charts
|
|
1146
|
+
### 16.1 Slope Charts
|
|
1147
1147
|
|
|
1148
1148
|
```
|
|
1149
1149
|
slope Fleet Strength
|
|
1150
1150
|
|
|
1151
|
-
1715 1725
|
|
1151
|
+
period 1715 1725
|
|
1152
1152
|
|
|
1153
|
-
Blackbeard
|
|
1154
|
-
Roberts
|
|
1153
|
+
Blackbeard 40 4
|
|
1154
|
+
Roberts 12 52
|
|
1155
1155
|
```
|
|
1156
1156
|
|
|
1157
|
-
- Period
|
|
1158
|
-
|
|
1159
|
-
|
|
1157
|
+
- Period directive required: `period Label1 Label2` (one-line) or indented block for multi-token labels:
|
|
1158
|
+
```
|
|
1159
|
+
period
|
|
1160
|
+
Before COVID
|
|
1161
|
+
After COVID
|
|
1162
|
+
```
|
|
1163
|
+
- Data rows: `Label value1 value2` — space-separated, no colons, no commas between values
|
|
1164
|
+
- Thousands commas within values supported (e.g., `1,000`)
|
|
1165
|
+
- Color annotations: `Label (color) value1 value2`
|
|
1166
|
+
- Minimum 2 periods required
|
|
1160
1167
|
|
|
1161
1168
|
### 16.2 Wordcloud
|
|
1162
1169
|
|
|
@@ -1239,7 +1246,6 @@ Navigator 0.85, 0.8
|
|
|
1239
1246
|
| Class field types | class | `+ name: string` |
|
|
1240
1247
|
| Class method returns | class | `+ sail(): void` |
|
|
1241
1248
|
| Function expressions | function | `f(x): x^2 + 1` |
|
|
1242
|
-
| Slope data rows | slope | `Blackbeard: 40 4` |
|
|
1243
1249
|
| Hide tag values | initiative-status | `hide phase:Planning` |
|
|
1244
1250
|
|
|
1245
1251
|
### Colons OPTIONAL
|
|
@@ -1265,13 +1271,15 @@ Navigator 0.85, 0.8
|
|
|
1265
1271
|
| Section dividers | sequence | `== Phase ==` |
|
|
1266
1272
|
| Comments | all | `// comment` |
|
|
1267
1273
|
| Wordcloud data | wordcloud | `swordsmanship 95` |
|
|
1274
|
+
| Slope data rows | slope | `Blackbeard 40 4` |
|
|
1275
|
+
| Slope period directive | slope | `period 1715 1725` |
|
|
1268
1276
|
| Venn intersections | venn | `sw + nav Sea Raiders` |
|
|
1269
1277
|
|
|
1270
1278
|
### The Rule
|
|
1271
1279
|
|
|
1272
1280
|
**Colons appear in two contexts:**
|
|
1273
1281
|
1. **Value assignment** — `key: value` in pipe metadata, indented tag/metadata assignment (org, c4), and hide directives
|
|
1274
|
-
2. **Type/expression separation** — where labels can contain spaces and a delimiter is needed (function expressions,
|
|
1282
|
+
2. **Type/expression separation** — where labels can contain spaces and a delimiter is needed (function expressions, class members)
|
|
1275
1283
|
|
|
1276
1284
|
**Exception**: Known-schema properties (infra node properties, ER columns) remain space-separated even though they are indented. The colon rule applies to open-ended metadata, not fixed property schemas.
|
|
1277
1285
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
slope Programming Language Popularity
|
|
2
2
|
|
|
3
|
-
2020
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
period 2020 2022 2025
|
|
4
|
+
|
|
5
|
+
Python (blue) 3 1 1
|
|
6
|
+
JavaScript (yellow) 1 2 2
|
|
7
|
+
TypeScript (cyan) 7 4 3
|
|
8
|
+
Rust (orange) 18 12 5
|
|
9
|
+
Go (green) 10 8 7
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@diagrammo/dgmo",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.5",
|
|
4
4
|
"description": "DGMO diagram markup language — parser, renderer, and color system",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,16 @@
|
|
|
30
30
|
"types": "./dist/editor.d.cts",
|
|
31
31
|
"default": "./dist/editor.cjs"
|
|
32
32
|
}
|
|
33
|
+
},
|
|
34
|
+
"./highlight": {
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/highlight.d.ts",
|
|
37
|
+
"default": "./dist/highlight.js"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/highlight.d.cts",
|
|
41
|
+
"default": "./dist/highlight.cjs"
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
},
|
|
35
45
|
"files": [
|
|
@@ -116,10 +126,20 @@
|
|
|
116
126
|
"@lezer/lr": "^1.4.8"
|
|
117
127
|
},
|
|
118
128
|
"peerDependenciesMeta": {
|
|
119
|
-
"@codemirror/language": {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
"@
|
|
123
|
-
|
|
129
|
+
"@codemirror/language": {
|
|
130
|
+
"optional": true
|
|
131
|
+
},
|
|
132
|
+
"@codemirror/state": {
|
|
133
|
+
"optional": true
|
|
134
|
+
},
|
|
135
|
+
"@lezer/common": {
|
|
136
|
+
"optional": true
|
|
137
|
+
},
|
|
138
|
+
"@lezer/highlight": {
|
|
139
|
+
"optional": true
|
|
140
|
+
},
|
|
141
|
+
"@lezer/lr": {
|
|
142
|
+
"optional": true
|
|
143
|
+
}
|
|
124
144
|
}
|
|
125
145
|
}
|
package/src/cli.ts
CHANGED
|
@@ -486,6 +486,7 @@ Full reference: call \`get_language_reference\` MCP tool or visit diagrammo.app/
|
|
|
486
486
|
function printHelp(): void {
|
|
487
487
|
console.log(`Usage: dgmo <input> [options]
|
|
488
488
|
cat input.dgmo | dgmo [options]
|
|
489
|
+
dgmo cat <file> Display file with syntax highlighting
|
|
489
490
|
|
|
490
491
|
Render a .dgmo file to PNG (default) or SVG.
|
|
491
492
|
|
|
@@ -534,6 +535,8 @@ function parseArgs(argv: string[]): {
|
|
|
534
535
|
copy: boolean;
|
|
535
536
|
json: boolean;
|
|
536
537
|
chartTypes: boolean;
|
|
538
|
+
cat: boolean;
|
|
539
|
+
noColor: boolean;
|
|
537
540
|
installClaudeSkill: boolean;
|
|
538
541
|
installClaudeCodeIntegration: boolean;
|
|
539
542
|
installCodexIntegration: boolean;
|
|
@@ -553,6 +556,8 @@ function parseArgs(argv: string[]): {
|
|
|
553
556
|
copy: false,
|
|
554
557
|
json: false,
|
|
555
558
|
chartTypes: false,
|
|
559
|
+
cat: false,
|
|
560
|
+
noColor: false,
|
|
556
561
|
installClaudeSkill: false,
|
|
557
562
|
installClaudeCodeIntegration: false,
|
|
558
563
|
installCodexIntegration: false,
|
|
@@ -572,7 +577,13 @@ function parseArgs(argv: string[]): {
|
|
|
572
577
|
while (i < args.length) {
|
|
573
578
|
const arg = args[i];
|
|
574
579
|
|
|
575
|
-
if (arg === '
|
|
580
|
+
if (arg === 'cat' && !result.cat && !result.input) {
|
|
581
|
+
result.cat = true;
|
|
582
|
+
i++;
|
|
583
|
+
} else if (arg === '--no-color') {
|
|
584
|
+
result.noColor = true;
|
|
585
|
+
i++;
|
|
586
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
576
587
|
result.help = true;
|
|
577
588
|
i++;
|
|
578
589
|
} else if (arg === '--version' || arg === '-v') {
|
|
@@ -746,6 +757,37 @@ async function main(): Promise<void> {
|
|
|
746
757
|
return;
|
|
747
758
|
}
|
|
748
759
|
|
|
760
|
+
if (opts.cat) {
|
|
761
|
+
const useColor =
|
|
762
|
+
!opts.noColor && !process.env.NO_COLOR && process.stdout.isTTY === true;
|
|
763
|
+
|
|
764
|
+
let catContent: string;
|
|
765
|
+
if (opts.input && opts.input !== '-') {
|
|
766
|
+
const inputPath = resolve(opts.input);
|
|
767
|
+
try {
|
|
768
|
+
catContent = readFileSync(inputPath, 'utf-8');
|
|
769
|
+
} catch {
|
|
770
|
+
console.error(`Error: Cannot read file "${inputPath}"`);
|
|
771
|
+
process.exit(1);
|
|
772
|
+
}
|
|
773
|
+
} else {
|
|
774
|
+
// Read from stdin
|
|
775
|
+
try {
|
|
776
|
+
catContent = readFileSync(0, 'utf-8');
|
|
777
|
+
} catch {
|
|
778
|
+
console.error('Error: No input file specified');
|
|
779
|
+
console.error('Usage: dgmo cat <file>');
|
|
780
|
+
process.exit(1);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const { highlightDgmo, renderAnsi } =
|
|
785
|
+
await import('./editor/highlight-api');
|
|
786
|
+
const tokens = highlightDgmo(catContent);
|
|
787
|
+
process.stdout.write(renderAnsi(tokens, useColor));
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
|
|
749
791
|
if (opts.installClaudeCodeIntegration) {
|
|
750
792
|
const claudeDir = join(homedir(), '.claude');
|
|
751
793
|
if (!existsSync(claudeDir)) {
|
package/src/d3.ts
CHANGED
|
@@ -509,6 +509,7 @@ export function parseVisualization(
|
|
|
509
509
|
let timelineEraBlockIndent = 0;
|
|
510
510
|
let inTimelineMarkerBlock = false;
|
|
511
511
|
let timelineMarkerBlockIndent = 0;
|
|
512
|
+
let inSlopePeriodBlock = false;
|
|
512
513
|
const timelineAliasMap = new Map<string, string>();
|
|
513
514
|
const VALID_D3_TYPES = new Set([
|
|
514
515
|
'slope',
|
|
@@ -1099,6 +1100,164 @@ export function parseVisualization(
|
|
|
1099
1100
|
}
|
|
1100
1101
|
}
|
|
1101
1102
|
|
|
1103
|
+
// ── Slope chart: period directive + right-scan data rows ──
|
|
1104
|
+
if (result.type === 'slope') {
|
|
1105
|
+
// Period block: indented lines inside `period` block
|
|
1106
|
+
// (blank lines are pre-filtered at loop top, so only non-indented lines close the block)
|
|
1107
|
+
if (inSlopePeriodBlock) {
|
|
1108
|
+
if (indent > 0) {
|
|
1109
|
+
result.periods.push(line);
|
|
1110
|
+
continue;
|
|
1111
|
+
}
|
|
1112
|
+
// Non-indented line → close block, fall through to process normally
|
|
1113
|
+
inSlopePeriodBlock = false;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// Period directive: `period Label1 Label2` or bare `period` (block open)
|
|
1117
|
+
// Only accept before data rows start (F4: prevent keyword shadowing labels)
|
|
1118
|
+
if (result.data.length === 0) {
|
|
1119
|
+
const periodMatch = line.match(/^period\b(.*)$/i);
|
|
1120
|
+
if (periodMatch) {
|
|
1121
|
+
if (result.periods.length > 0 && !inSlopePeriodBlock) {
|
|
1122
|
+
// F5: warn on duplicate period directives
|
|
1123
|
+
warn(
|
|
1124
|
+
lineNumber,
|
|
1125
|
+
`Duplicate 'period' directive — periods are already defined`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
const rest = periodMatch[1].trim();
|
|
1129
|
+
if (rest) {
|
|
1130
|
+
// One-line: `period 1715 1725`
|
|
1131
|
+
const periodLabels = rest.split(/\s+/);
|
|
1132
|
+
result.periods.push(...periodLabels);
|
|
1133
|
+
} else {
|
|
1134
|
+
// Block open: bare `period`
|
|
1135
|
+
inSlopePeriodBlock = true;
|
|
1136
|
+
}
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// Migration error: bare period line (old syntax — comma-separated, no keyword)
|
|
1142
|
+
// F1: Only fire when ALL comma-separated tokens are short (≤20 chars) and non-empty
|
|
1143
|
+
if (
|
|
1144
|
+
result.periods.length === 0 &&
|
|
1145
|
+
line.includes(',') &&
|
|
1146
|
+
!line.includes(':')
|
|
1147
|
+
) {
|
|
1148
|
+
const tokens = line
|
|
1149
|
+
.split(',')
|
|
1150
|
+
.map((t) => t.trim())
|
|
1151
|
+
.filter(Boolean);
|
|
1152
|
+
const looksLikePeriods =
|
|
1153
|
+
tokens.length >= 2 && tokens.every((t) => t.length <= 20);
|
|
1154
|
+
if (looksLikePeriods) {
|
|
1155
|
+
return fail(
|
|
1156
|
+
lineNumber,
|
|
1157
|
+
`Period lines require the 'period' keyword — use 'period ${tokens.join(' ')}'`
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Migration error: old colon syntax in data rows
|
|
1163
|
+
// F2: Only fire when content after colon is predominantly numeric (old "Label: val1, val2" pattern)
|
|
1164
|
+
if (line.includes(':')) {
|
|
1165
|
+
const colonPos = line.indexOf(':');
|
|
1166
|
+
const afterColon = line.substring(colonPos + 1).trim();
|
|
1167
|
+
const numericTokens = afterColon
|
|
1168
|
+
.split(/[,\s]+/)
|
|
1169
|
+
.filter((v) => /^-?\d/.test(v));
|
|
1170
|
+
// Only trigger if most tokens after the colon are numeric (old data pattern)
|
|
1171
|
+
if (numericTokens.length >= 1) {
|
|
1172
|
+
const allTokens = afterColon.split(/[,\s]+/).filter(Boolean);
|
|
1173
|
+
if (numericTokens.length >= allTokens.length * 0.5) {
|
|
1174
|
+
const label = line.substring(0, colonPos).trim();
|
|
1175
|
+
return fail(
|
|
1176
|
+
lineNumber,
|
|
1177
|
+
`Colons are no longer used in slope data rows — use '${label} ${numericTokens.join(' ')}'`
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Right-scan data row parsing (requires periods to be known)
|
|
1184
|
+
if (result.periods.length >= 2) {
|
|
1185
|
+
const P = result.periods.length;
|
|
1186
|
+
const tokens = line.split(/\s+/);
|
|
1187
|
+
const values: number[] = [];
|
|
1188
|
+
|
|
1189
|
+
// Scan from right, capped at P values
|
|
1190
|
+
let rightIdx = tokens.length - 1;
|
|
1191
|
+
while (rightIdx >= 0 && values.length < P) {
|
|
1192
|
+
const raw = tokens[rightIdx].replace(/,/g, '');
|
|
1193
|
+
const num = parseFloat(raw);
|
|
1194
|
+
if (!isNaN(num) && /^-?\d/.test(raw)) {
|
|
1195
|
+
values.unshift(num);
|
|
1196
|
+
rightIdx--;
|
|
1197
|
+
} else {
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (values.length < P) {
|
|
1203
|
+
warn(
|
|
1204
|
+
lineNumber,
|
|
1205
|
+
`Data row has ${values.length} numeric value(s) but ${P} period(s) are defined — expected ${P} values`
|
|
1206
|
+
);
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Remaining left tokens = label
|
|
1211
|
+
const labelTokens = tokens.slice(0, rightIdx + 1);
|
|
1212
|
+
const joinedLabel = labelTokens.join(' ');
|
|
1213
|
+
|
|
1214
|
+
if (!joinedLabel) {
|
|
1215
|
+
warn(
|
|
1216
|
+
lineNumber,
|
|
1217
|
+
`Data row has no label — add a label before the numeric values`
|
|
1218
|
+
);
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Color annotation: `Label (color)` → extract color
|
|
1223
|
+
const colorMatch = joinedLabel.match(/^(.+?)\(([^)]+)\)\s*$/);
|
|
1224
|
+
const labelPart = colorMatch ? colorMatch[1].trim() : joinedLabel;
|
|
1225
|
+
const colorPart = colorMatch
|
|
1226
|
+
? resolveColor(colorMatch[2].trim(), palette)
|
|
1227
|
+
: null;
|
|
1228
|
+
|
|
1229
|
+
if (!labelPart) {
|
|
1230
|
+
warn(
|
|
1231
|
+
lineNumber,
|
|
1232
|
+
`Data row has no label — add a label before the numeric values`
|
|
1233
|
+
);
|
|
1234
|
+
continue;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// F3: Warn on purely numeric labels — likely a mistake
|
|
1238
|
+
if (/^\d[\d,.]*$/.test(labelPart)) {
|
|
1239
|
+
warn(
|
|
1240
|
+
lineNumber,
|
|
1241
|
+
`Label '${labelPart}' looks numeric — this may indicate too many values or a missing label`
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
result.data.push({
|
|
1246
|
+
label: labelPart,
|
|
1247
|
+
values,
|
|
1248
|
+
color: colorPart,
|
|
1249
|
+
lineNumber,
|
|
1250
|
+
});
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// If we get here in a slope chart, it's an unrecognized line
|
|
1255
|
+
if (firstLineParsed) {
|
|
1256
|
+
warn(lineNumber, `Unexpected line: '${line}'.`);
|
|
1257
|
+
}
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1102
1261
|
// ── Colon-separated metadata / options (legacy + data lines) ──
|
|
1103
1262
|
const colonIndex = line.indexOf(':');
|
|
1104
1263
|
|
|
@@ -1222,23 +1381,6 @@ export function parseVisualization(
|
|
|
1222
1381
|
continue;
|
|
1223
1382
|
}
|
|
1224
1383
|
|
|
1225
|
-
// Period line: comma-separated labels with no colon before first comma
|
|
1226
|
-
// e.g., "2020, 2024" or "Q1 2023, Q2 2023, Q3 2023"
|
|
1227
|
-
if (
|
|
1228
|
-
result.periods.length === 0 &&
|
|
1229
|
-
line.includes(',') &&
|
|
1230
|
-
!line.includes(':')
|
|
1231
|
-
) {
|
|
1232
|
-
const periods = line
|
|
1233
|
-
.split(',')
|
|
1234
|
-
.map((p) => p.trim())
|
|
1235
|
-
.filter(Boolean);
|
|
1236
|
-
if (periods.length >= 2) {
|
|
1237
|
-
result.periods = periods;
|
|
1238
|
-
continue;
|
|
1239
|
-
}
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
1384
|
// Catch-all: nothing matched this line
|
|
1243
1385
|
// Skip on first line — chart type suggestion is handled post-loop
|
|
1244
1386
|
if (firstLineParsed) {
|
|
@@ -1408,14 +1550,14 @@ export function parseVisualization(
|
|
|
1408
1550
|
if (result.periods.length < 2) {
|
|
1409
1551
|
return fail(
|
|
1410
1552
|
1,
|
|
1411
|
-
|
|
1553
|
+
"Missing 'period' directive. Add 'period 2020 2024' before data rows (minimum 2 periods required)"
|
|
1412
1554
|
);
|
|
1413
1555
|
}
|
|
1414
1556
|
|
|
1415
1557
|
if (result.data.length === 0) {
|
|
1416
1558
|
warn(
|
|
1417
1559
|
1,
|
|
1418
|
-
|
|
1560
|
+
"No data lines found. Add data as 'Label value1 value2' (e.g., 'Blackbeard 40 4')"
|
|
1419
1561
|
);
|
|
1420
1562
|
}
|
|
1421
1563
|
|