@cardenelabs/dragon 0.30.0 → 0.33.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/README.md +314 -12
- package/dist/index.cjs +6960 -1434
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2151 -115
- package/dist/index.d.ts +2151 -115
- package/dist/index.js +6944 -1436
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/compile/actor-ids.ts +6 -15
- package/src/compile/actor-option-notice.ts +379 -0
- package/src/compile/actors.ts +32 -3
- package/src/compile/c4.ts +0 -3
- package/src/compile/chain.ts +43 -4
- package/src/compile/chart-fields.ts +16 -1
- package/src/compile/direction.ts +50 -11
- package/src/compile/edge-option-notice.ts +112 -0
- package/src/compile/flowchart.ts +344 -0
- package/src/compile/funnel.ts +14 -2
- package/src/compile/gantt.ts +141 -36
- package/src/compile/generic.ts +473 -61
- package/src/compile/journey.ts +14 -5
- package/src/compile/kinds.ts +5 -13
- package/src/compile/lanes.ts +95 -13
- package/src/compile/metro.ts +271 -0
- package/src/compile/mind.ts +55 -5
- package/src/compile/notice.ts +51 -4
- package/src/compile/order.ts +62 -0
- package/src/compile/parts.ts +107 -33
- package/src/compile/placement-apply.ts +27 -11
- package/src/compile/quadrant.ts +171 -16
- package/src/compile/record.ts +75 -0
- package/src/compile/row-marks.ts +128 -0
- package/src/compile/sequence.ts +105 -26
- package/src/compile/slug.ts +9 -1
- package/src/compile/stages.ts +247 -0
- package/src/compile/subtitle.ts +15 -2
- package/src/compile/swimlane-marks.ts +87 -0
- package/src/compile/swimlane.ts +13 -14
- package/src/compile/timeline.ts +213 -0
- package/src/compile/topology.ts +3 -9
- package/src/compile/tree.ts +13 -4
- package/src/compile/value-chart.ts +179 -25
- package/src/compile.ts +1547 -370
- package/src/focus.ts +2 -2
- package/src/index.ts +34 -2
- package/src/json-parser.ts +1407 -48
- package/src/keywords.ts +255 -17
- package/src/notation-lint.ts +41 -64
- package/src/schemas/diagram.json +643 -20
- package/src/types.ts +336 -35
- package/src/v05/parser.ts +2451 -271
- package/src/compile/class.ts +0 -100
- package/src/compile/er.ts +0 -44
- package/src/compile/solidity.ts +0 -39
- package/src/compile/state.ts +0 -71
package/src/compile/quadrant.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { diagram } from "@cardenelabs/cdl";
|
|
2
2
|
import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
3
3
|
import type { DslDocument } from "../types";
|
|
4
|
-
import { 図表の大きさ, 語の欄から参照できる名前 } from "./chart-fields";
|
|
4
|
+
import { 図表の大きさ, 図表の大きさを読む, 数の欄から参照できる名前, 語の欄から参照できる名前 } from "./chart-fields";
|
|
5
5
|
import { 箱の題 } from "./node-title";
|
|
6
6
|
import type { CompileNotice } from "./notice";
|
|
7
7
|
import { slugify } from "./slug";
|
|
@@ -17,13 +17,58 @@ export function compileQuadrant(
|
|
|
17
17
|
onNotice?: (n: CompileNotice) => void,
|
|
18
18
|
): CdlDiagram {
|
|
19
19
|
const b = diagram(slugify(doc.title), { topic: doc.title, type: "quadrant" });
|
|
20
|
-
const { w: W, h: H } = 図表の大きさ.quadrant;
|
|
20
|
+
const { w: W, h: H } = 図表の大きさを読む(doc, 図表の大きさ.quadrant);
|
|
21
21
|
b.lane("chart", { width: W + 64 });
|
|
22
22
|
const items: NonNullable<CdlDiagram["nodes"][number]["quadrantData"]>["items"] = [];
|
|
23
23
|
const 読めない: string[] = [];
|
|
24
24
|
const 参照できる = 語の欄から参照できる名前(doc, 区画);
|
|
25
|
+
const 数を参照できる = 数の欄から参照できる名前(doc);
|
|
26
|
+
const 伝える = (a: DslDocument["actors"][number], message: string): void => {
|
|
27
|
+
onNotice?.({
|
|
28
|
+
kind: "chart-value-unreadable",
|
|
29
|
+
actor: a.name,
|
|
30
|
+
line: a.atPos?.line ?? a.pos?.line ?? 0,
|
|
31
|
+
message,
|
|
32
|
+
});
|
|
33
|
+
if (typeof console !== "undefined" && console.warn) console.warn(`[dragon] ${message}`);
|
|
34
|
+
};
|
|
25
35
|
for (const a of doc.actors) {
|
|
26
36
|
const 語 = (a.value ?? a.subtitle ?? "").trim();
|
|
37
|
+
if (a.at !== undefined) {
|
|
38
|
+
const 座標 = 四象限の座標(a.at, 数を参照できる);
|
|
39
|
+
if (座標 === null) {
|
|
40
|
+
伝える(
|
|
41
|
+
a,
|
|
42
|
+
`type: quadrant で ${a.name} の at (${a.at.raw}) が読めません (図に載せません)。 at: [0.2, 0.8] の形で 0..1 の数か {状態名} を 2 つ書いてください`,
|
|
43
|
+
);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const 範囲外 = [
|
|
47
|
+
座標.x,
|
|
48
|
+
座標.y,
|
|
49
|
+
...[座標.x, 座標.y].flatMap((v) =>
|
|
50
|
+
typeof v === "string" ? 状態が取る値(v, doc) : [],
|
|
51
|
+
),
|
|
52
|
+
].filter(
|
|
53
|
+
(v): v is number => typeof v === "number" && (v < 0 || v > 1),
|
|
54
|
+
);
|
|
55
|
+
if (範囲外.length > 0) {
|
|
56
|
+
伝える(
|
|
57
|
+
a,
|
|
58
|
+
`type: quadrant で ${a.name} の at (${a.at.raw}) が 0..1 の範囲外です (描画時に最寄りの端へ寄せます)`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
const q = 区画.get(語);
|
|
62
|
+
const 座標の区画 = 数の象限(座標);
|
|
63
|
+
if (q !== undefined && 座標の区画 !== undefined && q !== 座標の区画) {
|
|
64
|
+
伝える(
|
|
65
|
+
a,
|
|
66
|
+
`type: quadrant で ${a.name} の区画 (${語}) と at (${a.at.raw}) の象限が食い違います (at を採ります)`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
items.push({ id: slugify(a.name), title: 箱の題(a), at: 座標 });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
27
72
|
const 参照 = 語.match(/^\{(\w+)\}$/);
|
|
28
73
|
if (参照) {
|
|
29
74
|
if (!参照できる.has(参照[1]!)) {
|
|
@@ -65,10 +110,70 @@ export function compileQuadrant(
|
|
|
65
110
|
w: W,
|
|
66
111
|
h: H,
|
|
67
112
|
quadrantData: { ...軸と区画の名前(doc), items },
|
|
113
|
+
...(doc.quadrantPointLabelSide !== undefined
|
|
114
|
+
? { quadrantPointLabelSide: doc.quadrantPointLabelSide }
|
|
115
|
+
: {}),
|
|
116
|
+
...(doc.quadrantLabelPosition !== undefined ? { quadrantLabelPosition: doc.quadrantLabelPosition } : {}),
|
|
117
|
+
...(doc.quadrantSize !== undefined ? { quadrantSize: doc.quadrantSize } : {}),
|
|
68
118
|
});
|
|
69
119
|
return b.build();
|
|
70
120
|
}
|
|
71
121
|
|
|
122
|
+
function 四象限の座標(
|
|
123
|
+
at: NonNullable<DslDocument["actors"][number]["at"]>,
|
|
124
|
+
参照できる: ReadonlySet<string>,
|
|
125
|
+
): { x: number | string; y: number | string } | null {
|
|
126
|
+
if (!at.raw.startsWith("[") || !at.raw.endsWith("]") || at.x === undefined || at.y === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
if (at.raw.slice(1, -1).split(",").length !== 2) return null;
|
|
129
|
+
const 読める = (v: number | string): boolean => {
|
|
130
|
+
if (typeof v === "number") return Number.isFinite(v);
|
|
131
|
+
const m = v.match(/^\{([\w-]+)\}$/);
|
|
132
|
+
return m !== null && 参照できる.has(m[1]!);
|
|
133
|
+
};
|
|
134
|
+
return 読める(at.x) && 読める(at.y) ? { x: at.x, y: at.y } : null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** 座標の `{状態名}` が、記法に書かれた範囲で取り得る数を集める。 */
|
|
138
|
+
function 状態が取る値(参照: string, doc: DslDocument): number[] {
|
|
139
|
+
const m = 参照.match(/^\{([\w-]+)\}$/);
|
|
140
|
+
if (m === null) return [];
|
|
141
|
+
const 名前 = m[1]!;
|
|
142
|
+
const values: number[] = [];
|
|
143
|
+
const 数を加える = (value: unknown): void => {
|
|
144
|
+
if (typeof value === "number" && Number.isFinite(value)) values.push(value);
|
|
145
|
+
else if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)))
|
|
146
|
+
values.push(Number(value));
|
|
147
|
+
};
|
|
148
|
+
let 初期値: unknown;
|
|
149
|
+
let 初期値あり = false;
|
|
150
|
+
for (const state of doc.animate?.states ?? []) {
|
|
151
|
+
if (state.name !== 名前) continue;
|
|
152
|
+
初期値 = state.initial;
|
|
153
|
+
初期値あり = true;
|
|
154
|
+
}
|
|
155
|
+
if (初期値あり) 数を加える(初期値);
|
|
156
|
+
for (const phase of doc.animate?.phases ?? []) {
|
|
157
|
+
for (const tween of phase.tweens ?? []) {
|
|
158
|
+
if (tween.state !== 名前) continue;
|
|
159
|
+
数を加える(tween.from);
|
|
160
|
+
数を加える(tween.to);
|
|
161
|
+
}
|
|
162
|
+
for (const set of phase.sets ?? []) if (set.state === 名前) 数を加える(set.value);
|
|
163
|
+
}
|
|
164
|
+
return values;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function 数の象限(at: { x: number | string; y: number | string }):
|
|
168
|
+
| "topLeft"
|
|
169
|
+
| "topRight"
|
|
170
|
+
| "bottomLeft"
|
|
171
|
+
| "bottomRight"
|
|
172
|
+
| undefined {
|
|
173
|
+
if (typeof at.x !== "number" || typeof at.y !== "number") return undefined;
|
|
174
|
+
return `${at.y >= 0.5 ? "top" : "bottom"}${at.x >= 0.5 ? "Right" : "Left"}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
72
177
|
/** 軸を書かなかった時の名前。 何の軸か分からないため、位置をそのまま出す */
|
|
73
178
|
const 軸の既定 = {
|
|
74
179
|
xAxis: { left: "小さい", right: "大きい" },
|
|
@@ -87,24 +192,74 @@ const 軸の既定 = {
|
|
|
87
192
|
*
|
|
88
193
|
* 片側だけ書いた形では、書かなかった側は既定のままにする。 空文字を渡すと名前の無い軸が描かれる。
|
|
89
194
|
*/
|
|
90
|
-
function 軸と区画の名前(
|
|
195
|
+
function 軸と区画の名前(
|
|
196
|
+
doc: DslDocument,
|
|
197
|
+
): Omit<NonNullable<CdlDiagram["nodes"][number]["quadrantData"]>, "items"> {
|
|
198
|
+
const 端 = 軸の名前(doc);
|
|
199
|
+
const x = doc.axes?.x;
|
|
200
|
+
const y = doc.axes?.y;
|
|
201
|
+
const xAxis = x !== undefined && "direction" in x && x.direction === true
|
|
202
|
+
? { label: x.label, direction: true as const }
|
|
203
|
+
: 端.xAxis;
|
|
204
|
+
const yAxis = y !== undefined && "direction" in y && y.direction === true
|
|
205
|
+
? { label: y.label, direction: true as const }
|
|
206
|
+
: 端.yAxis;
|
|
207
|
+
return { xAxis, yAxis, quadrantLabels: 区画の名前(doc, 端) };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** 軸の両端の名前。 書かなかった側は既定のまま = 空文字を渡すと名前の無い軸が描かれる */
|
|
211
|
+
function 軸の名前(doc: DslDocument): {
|
|
91
212
|
xAxis: { left: string; right: string };
|
|
92
213
|
yAxis: { bottom: string; top: string };
|
|
93
|
-
quadrantLabels: { topLeft: string; topRight: string; bottomLeft: string; bottomRight: string };
|
|
94
214
|
} {
|
|
95
|
-
if (doc.axes === undefined) return
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const bottom = doc.axes.y?.bottom ?? 軸の既定.yAxis.bottom;
|
|
99
|
-
const top = doc.axes.y?.top ?? 軸の既定.yAxis.top;
|
|
215
|
+
if (doc.axes === undefined) return { xAxis: 軸の既定.xAxis, yAxis: 軸の既定.yAxis };
|
|
216
|
+
const x = doc.axes.x;
|
|
217
|
+
const y = doc.axes.y;
|
|
100
218
|
return {
|
|
101
|
-
xAxis: {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
219
|
+
xAxis: {
|
|
220
|
+
left: x !== undefined && "left" in x ? x.left ?? 軸の既定.xAxis.left : 軸の既定.xAxis.left,
|
|
221
|
+
right: x !== undefined && "right" in x ? x.right ?? 軸の既定.xAxis.right : 軸の既定.xAxis.right,
|
|
222
|
+
},
|
|
223
|
+
yAxis: {
|
|
224
|
+
bottom: y !== undefined && "bottom" in y ? y.bottom ?? 軸の既定.yAxis.bottom : 軸の既定.yAxis.bottom,
|
|
225
|
+
top: y !== undefined && "top" in y ? y.top ?? 軸の既定.yAxis.top : 軸の既定.yAxis.top,
|
|
108
226
|
},
|
|
109
227
|
};
|
|
110
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 区画の中に出す名前 (#2667)。
|
|
232
|
+
*
|
|
233
|
+
* 決め方は 3 段で、書いたものが勝つ。
|
|
234
|
+
*
|
|
235
|
+
* | 何を書いたか | 出る名前 |
|
|
236
|
+
* |---|---|
|
|
237
|
+
* | `regions:` にその区画を書いた | 書いた名前 |
|
|
238
|
+
* | `axes:` を書いた | 軸の掛け合わせ (`{上} × {左}`) |
|
|
239
|
+
* | どちらも書かない | 位置の名前 (`左上` 等) |
|
|
240
|
+
*
|
|
241
|
+
* **区画ごとに独立して決める**。 `regions:` を 1 つだけ書いた図で、残り 3 つが
|
|
242
|
+
* 位置の名前に戻ると「書いた所だけ変わる」 という読み方ができなくなる。
|
|
243
|
+
*/
|
|
244
|
+
function 区画の名前(
|
|
245
|
+
doc: DslDocument,
|
|
246
|
+
軸: { xAxis: { left: string; right: string }; yAxis: { bottom: string; top: string } },
|
|
247
|
+
): { topLeft: string; topRight: string; bottomLeft: string; bottomRight: string } {
|
|
248
|
+
const 掛け合わせ =
|
|
249
|
+
doc.axes === undefined
|
|
250
|
+
? 軸の既定.quadrantLabels
|
|
251
|
+
: {
|
|
252
|
+
topLeft: `${軸.yAxis.top} × ${軸.xAxis.left}`,
|
|
253
|
+
topRight: `${軸.yAxis.top} × ${軸.xAxis.right}`,
|
|
254
|
+
bottomLeft: `${軸.yAxis.bottom} × ${軸.xAxis.left}`,
|
|
255
|
+
bottomRight: `${軸.yAxis.bottom} × ${軸.xAxis.right}`,
|
|
256
|
+
};
|
|
257
|
+
const r = doc.regions;
|
|
258
|
+
if (r === undefined) return { ...掛け合わせ };
|
|
259
|
+
return {
|
|
260
|
+
topLeft: r.topLeft ?? 掛け合わせ.topLeft,
|
|
261
|
+
topRight: r.topRight ?? 掛け合わせ.topRight,
|
|
262
|
+
bottomLeft: r.bottomLeft ?? 掛け合わせ.bottomLeft,
|
|
263
|
+
bottomRight: r.bottomRight ?? 掛け合わせ.bottomRight,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { CLASS_RELATION_LOOK } from "@cardenelabs/cdl";
|
|
2
|
+
import type { CdlDiagram, CdlEdge, ClassRelationType, Tone } from "@cardenelabs/cdl";
|
|
3
|
+
import type { DslDocument, DslStep } from "../types";
|
|
4
|
+
|
|
5
|
+
import { ERの関係の指定を作る, ERの関係の矢印 } from "./er-relation";
|
|
6
|
+
import { compileGenericWithAnimate } from "./generic";
|
|
7
|
+
|
|
8
|
+
/** UML の関係を、印の形と同じ 3 群の色へ寄せる。描画側の classDiagram と同じ決まり。 */
|
|
9
|
+
const クラスの関係の色: Record<ClassRelationType, Tone> = {
|
|
10
|
+
extends: "accent",
|
|
11
|
+
implements: "accent",
|
|
12
|
+
aggregates: "teal",
|
|
13
|
+
composes: "teal",
|
|
14
|
+
associates: "success",
|
|
15
|
+
uses: "success",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 箱が行を持つ図の組み立て (#2782)。
|
|
20
|
+
*
|
|
21
|
+
* 畳む前は `er` と `state` の 2 つに分かれており、それぞれが **2 つの経路** を持っていた。
|
|
22
|
+
* 動きか縦列 (表の図) か種類 (移り変わりの図) を書いた図は共通の経路へ、書かない図は
|
|
23
|
+
* 組み立て器 (`er()` / `stateMachine()`) の経路へ落ちていた。
|
|
24
|
+
*
|
|
25
|
+
* **経路は共通の 1 本だけにする**。 組み立て器の経路は書いた行を捨てており
|
|
26
|
+
* (`rows: []` を渡していた)、記法に行を書いても図に出なかった。 動きを 1 段足すだけで
|
|
27
|
+
* 行が現れる形になっていたので、同じ記法が同じ図になるほうへ倒す。
|
|
28
|
+
*/
|
|
29
|
+
export function compileRecord(doc: DslDocument): CdlDiagram {
|
|
30
|
+
return compileGenericWithAnimate(doc, {
|
|
31
|
+
kind: "record",
|
|
32
|
+
laneWidth: 縦列の幅(doc),
|
|
33
|
+
edgeDefaults: 関係の既定,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* record の関係の既定 (#2783)。
|
|
39
|
+
*
|
|
40
|
+
* `relation` がある時だけ UML の関係として読み、`sub` は行き先、`tailSub` は出どころの
|
|
41
|
+
* 多重度にする。 relation の無い `sub` は従来どおり線の名前の下の行として残す。
|
|
42
|
+
* relation の有無を境にすることで、既存の record の補足を壊さず、class から移した本文も
|
|
43
|
+
* 同じ欄のまま同じ端へ置ける。
|
|
44
|
+
*/
|
|
45
|
+
function 関係の既定(s: DslStep): Partial<CdlEdge> {
|
|
46
|
+
if (s.relation === undefined) {
|
|
47
|
+
return ERの関係の矢印(ERの関係の指定を作る(s));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const 見た目 = CLASS_RELATION_LOOK[s.relation];
|
|
51
|
+
return {
|
|
52
|
+
label: s.label,
|
|
53
|
+
tone: クラスの関係の色[s.relation],
|
|
54
|
+
style: 見た目.style,
|
|
55
|
+
head: 見た目.head,
|
|
56
|
+
headFill: 見た目.fill,
|
|
57
|
+
tailHead: 見た目.tailHead,
|
|
58
|
+
tailHeadFill: 見た目.tailFill,
|
|
59
|
+
...(s.sub !== undefined ? { headLabel: s.sub } : {}),
|
|
60
|
+
...(s.tailSub !== undefined ? { tailLabel: s.tailSub } : {}),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 縦列の幅 (#2782)。
|
|
66
|
+
*
|
|
67
|
+
* **行を持つ箱があるかで決める**。 行を持つ箱は名前と型が左右に分かれて並ぶので幅が要り、
|
|
68
|
+
* 持たない箱は題だけなので要らない。 畳む前は図種ごとに持っていた値 (表の図 450 /
|
|
69
|
+
* 移り変わりの図 360) で、どちらも「行を持つか」 の言い換えだった。
|
|
70
|
+
*
|
|
71
|
+
* 書いた `lanes:` の幅はこの既定より強い (`compile/lanes.ts` が後から重ねる)。
|
|
72
|
+
*/
|
|
73
|
+
function 縦列の幅(doc: DslDocument): number {
|
|
74
|
+
return doc.actors.some((a) => (a.rows?.length ?? 0) > 0) ? 450 : 360;
|
|
75
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { RowMark } from "@cardenelabs/cdl";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 行頭の印を dragon の語で決める (#2782)。
|
|
5
|
+
*
|
|
6
|
+
* 描画側が持つ印は 3 軸しかない。 形 (四角 / 山形) と 塗り (塗る / 中空) と
|
|
7
|
+
* 名前の下線で、この 3 軸に 1 対 1 で当たる語を dragon 自身が持つ。
|
|
8
|
+
*
|
|
9
|
+
* | 語 | 軸 | 意味 |
|
|
10
|
+
* |---|---|---|
|
|
11
|
+
* | `鍵` | 名前に下線を引く | その行で箱を見分ける |
|
|
12
|
+
* | `外` | 行頭を山形にする | その行は箱の外とつながる |
|
|
13
|
+
* | `条件` | 行頭を中空にする | その行は条件が揃った時だけ効く |
|
|
14
|
+
*
|
|
15
|
+
* 畳む前は 図種ごとに語を持っていた (表の図は `pk` / `fk` / `opt`、移り変わりの図は
|
|
16
|
+
* `entry` / `exit` / `do` / `internal`)。 2 つの語彙は同じ 2 軸の格子を別の名前で
|
|
17
|
+
* 呼んでいただけで、格子の 4 隅は 1 対 1 に重なる。
|
|
18
|
+
*
|
|
19
|
+
* | | 塗る | 中空 |
|
|
20
|
+
* |---|---|---|
|
|
21
|
+
* | 四角 | 語なし / `do` | `条件` / `opt` / `internal` |
|
|
22
|
+
* | 山形 | `外` / `fk` / `entry` | `外 条件` / `fk opt` / `exit` |
|
|
23
|
+
*
|
|
24
|
+
* 古い語から dragon の語への読み替えは本 file の `行頭の語へ読み替える` が持ち、
|
|
25
|
+
* 読み取りの入口 (記法 / JSON の 2 経路) が通す。 印にする時点で古い語は 1 つも残っていない。
|
|
26
|
+
*/
|
|
27
|
+
export const 行頭の語 = ["鍵", "外", "条件"] as const;
|
|
28
|
+
|
|
29
|
+
export type 行頭の語 = (typeof 行頭の語)[number];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 行頭の印の古い語から dragon の 3 語への読み替え (#2782)。
|
|
33
|
+
*
|
|
34
|
+
* **ここは図種の別名と向きが違う**。 図種の `er` / `state` は綴りごと消したが (畳んだ先の
|
|
35
|
+
* `record` が別の名前なので、古い綴りを残すと「名前を無くす」 という目的が達たない)、
|
|
36
|
+
* 行頭の語は残す = 語が指す印は 1 つに決まっており、`pk` と `鍵` は同じ絵になる。
|
|
37
|
+
*
|
|
38
|
+
* 読み替え先は空白区切りの語。 `exit` だけが 2 語になる = 山形 + 中空 の 2 軸を同時に動かす。
|
|
39
|
+
*/
|
|
40
|
+
const 行頭の語の別名: ReadonlyMap<string, string> = new Map([
|
|
41
|
+
["pk", "鍵"],
|
|
42
|
+
["fk", "外"],
|
|
43
|
+
["opt", "条件"],
|
|
44
|
+
["entry", "外"],
|
|
45
|
+
["exit", "外 条件"],
|
|
46
|
+
["do", ""],
|
|
47
|
+
["internal", "条件"],
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 1 行分の印の語を dragon の 3 語に揃える (#2782)。
|
|
52
|
+
*
|
|
53
|
+
* 1 行に語を 2 つ以上書ける (`pk fk` の形) ので、語ごとに引いて繋ぎ直す。
|
|
54
|
+
* 知らない語はそのまま通す = 組み立て側が無視するため、書いた人が気付ける形は変わらない。
|
|
55
|
+
*
|
|
56
|
+
* **読み取りの入口は 3 経路ある** (縦に並べた形 / 中括弧の形 / JSON)。 3 つとも本 file の
|
|
57
|
+
* この関数を通す = 1 経路でも素通りさせると、その書き方だけ古い語が印を持たないまま通る
|
|
58
|
+
* (中括弧の形が実際にそうなっていた)。
|
|
59
|
+
*/
|
|
60
|
+
export function 行頭の語へ読み替える(値: string): string {
|
|
61
|
+
const 語 = 値.trim().split(/\s+/).filter(Boolean);
|
|
62
|
+
if (語.length === 0) return 値;
|
|
63
|
+
return 語
|
|
64
|
+
.map((w) => 行頭の語の別名.get(w) ?? w)
|
|
65
|
+
.filter((w) => w !== "")
|
|
66
|
+
.join(" ");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 書いた語を行頭の印にする (#2782)。
|
|
71
|
+
*
|
|
72
|
+
* **語を書かない行にも既定の印を付ける**。 四角 + 塗る + 下線なしで、箱の中で完結して
|
|
73
|
+
* 常に在る行を表す。
|
|
74
|
+
*
|
|
75
|
+
* 印なしにすると、書かなかった行だけ行頭が空いて **群の区切りと見分けが付かなくなる**
|
|
76
|
+
* (群の区切りは行頭の印が持っている)。 畳む前の表の図が同じ理由で既定を配っていた。
|
|
77
|
+
*/
|
|
78
|
+
export function 行頭の印にする(
|
|
79
|
+
marks: readonly (string | { mark: string; tone?: "primary" | "muted" })[],
|
|
80
|
+
行数: number,
|
|
81
|
+
): (RowMark | null)[] {
|
|
82
|
+
return Array.from({ length: 行数 }, (_, i) => {
|
|
83
|
+
const 値 = marks[i] ?? "";
|
|
84
|
+
const 語 = (typeof 値 === "string" ? 値 : 値.mark).trim().split(/\s+/).filter(Boolean);
|
|
85
|
+
return {
|
|
86
|
+
shape: 語.includes("外") ? ("chevron" as const) : ("square" as const),
|
|
87
|
+
filled: !語.includes("条件"),
|
|
88
|
+
...(typeof 値 === "object" && 値.tone !== undefined ? { tone: 値.tone } : {}),
|
|
89
|
+
...(語.includes("鍵") ? { underline: true } : {}),
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 箱を見分ける行を上にまとめる (#2782)。
|
|
96
|
+
*
|
|
97
|
+
* `鍵` を書いた行を先に、残りを後に置く。 畳む前の表の図が同じ並べ替えをしていた
|
|
98
|
+
* (組み立て器 `er()` と同じ形)。 `鍵` を 1 つも書かない図では並びが 1 行も動かない。
|
|
99
|
+
*
|
|
100
|
+
* **空の行では群を分けない**。 群の区切りは行頭の印が持っており、空の行を挟むと
|
|
101
|
+
* 行の間隔が不揃いになる (描画側が cdl 0.23.0 で空の行をやめた)。
|
|
102
|
+
*/
|
|
103
|
+
export function 鍵の行を上にまとめる(
|
|
104
|
+
rows: readonly string[],
|
|
105
|
+
印: readonly (RowMark | null)[],
|
|
106
|
+
): { rows: string[]; rowMarks: (RowMark | null)[] } {
|
|
107
|
+
const 鍵: number[] = [];
|
|
108
|
+
const 値: number[] = [];
|
|
109
|
+
rows.forEach((_, i) => ((印[i]?.underline === true ? 鍵 : 値).push(i)));
|
|
110
|
+
const 並び = [...鍵, ...値];
|
|
111
|
+
return {
|
|
112
|
+
rows: 並び.map((i) => rows[i] ?? ""),
|
|
113
|
+
rowMarks: 並び.map((i) => 印[i] ?? null),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 行頭の印を読む図種か (#2782)。
|
|
119
|
+
*
|
|
120
|
+
* **図種の名前を案内の文に写さない**。 読む図種を足した日に案内だけが古くなるため、
|
|
121
|
+
* 知らせを組む側はここに聞く (`compile.ts` の `行頭の印を読む図種`)。
|
|
122
|
+
*
|
|
123
|
+
* 畳む前は 2 図種 (表の図 / 移り変わりの図) が別の語彙で読んでいた。 クラス図は行を
|
|
124
|
+
* 組み立て器が作るので読まない。
|
|
125
|
+
*/
|
|
126
|
+
export function 行頭の印を読むか(type: string): boolean {
|
|
127
|
+
return type === "record";
|
|
128
|
+
}
|
package/src/compile/sequence.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { sequence, sequenceStepId } from "@cardenelabs/cdl";
|
|
2
2
|
import type { CdlDiagram } from "@cardenelabs/cdl";
|
|
3
|
-
import type { DslDocument } from "../types";
|
|
3
|
+
import type { DslActor, DslDocument } from "../types";
|
|
4
4
|
|
|
5
5
|
import { parseFocusEntry } from "../focus";
|
|
6
|
+
import { 並べ替えた文書 } from "./order";
|
|
6
7
|
import { 箱の題 } from "./node-title";
|
|
7
8
|
|
|
8
9
|
import { slugify } from "./slug";
|
|
9
10
|
|
|
10
11
|
export function compileSequence(doc: DslDocument): CdlDiagram {
|
|
12
|
+
// **書いた並び順を先に当てる** (#2655)。 以降は並べ替え済の箱で組む = 板の見出しも
|
|
13
|
+
// 端の番号も同じ並びから決まる。 書かなかった図は同じ文書がそのまま返る
|
|
14
|
+
doc = 並べ替えた文書(doc);
|
|
11
15
|
// v0.3 ... アニメーション 有無で経路を分岐。
|
|
12
16
|
// 有り = builder 直接経路で state / 複数 phase を注入。
|
|
13
17
|
/*
|
|
@@ -16,31 +20,24 @@ export function compileSequence(doc: DslDocument): CdlDiagram {
|
|
|
16
20
|
* 順序図は 1 つの箱が図を丸ごと描く形になり、言づては箱の中の行になった。 段ごとに
|
|
17
21
|
* 別経路で箱と縦線を組む形 (`compileSequenceWithAnimate`) では、その骨格が出ない。
|
|
18
22
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
...(s.side ? { side: s.side } : {}),
|
|
38
|
-
...(s.tone ? { tone: s.tone } : {}),
|
|
39
|
-
...(s.style ? { style: s.style } : {}),
|
|
40
|
-
...(s.msgKind ? { kind: s.msgKind } : {}),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
const built = seqBuilder.build();
|
|
23
|
+
/*
|
|
24
|
+
* **見出しと端の番号を別の組み立てで決める** (#2408)。
|
|
25
|
+
*
|
|
26
|
+
* 見出しに出すのは書いた題 (#1466)、 矢印の端と帯は名前で指す (`箱の題` の doc comment)。
|
|
27
|
+
* 組み立て器は渡された面の名前 1 つから「見出し」 と「端を引く表」 の両方を作るため、
|
|
28
|
+
* 題を渡すと名前で指した端が 1 度も見つからず、 番号 0 (先頭の面) に倒れる
|
|
29
|
+
* (実測 = 全ての面に題を付けると、 言づてが全て先頭の面の自分宛てになった)。
|
|
30
|
+
*
|
|
31
|
+
* 板の幅と高さは見出しの字幅と言づての文字と件数から決まり、 端の番号には依らない
|
|
32
|
+
* (組み立て器の `sequenceBoardMetrics`)。 そこで題で組んだ板を土台にし、 端の番号と帯だけを
|
|
33
|
+
* 名前で組んだ板から写す。 名前は図の中で 1 つに決まるので、 題が重なる図でも番号は正しい。
|
|
34
|
+
*
|
|
35
|
+
* 題と名前がどの面でも同じなら 2 回目は組まない = 題を書かない図は組み上がりが変わらない。
|
|
36
|
+
*/
|
|
37
|
+
const 見出しの板 = 板を組む(doc, 箱の題);
|
|
38
|
+
const built = doc.actors.some((a) => 箱の題(a) !== a.name)
|
|
39
|
+
? 端を写す(見出しの板, 板を組む(doc, (a) => a.name))
|
|
40
|
+
: 見出しの板;
|
|
44
41
|
const 段 = doc.animate?.phases ?? [];
|
|
45
42
|
if (段.length === 0) return built;
|
|
46
43
|
/*
|
|
@@ -79,6 +76,88 @@ export function compileSequence(doc: DslDocument): CdlDiagram {
|
|
|
79
76
|
};
|
|
80
77
|
}
|
|
81
78
|
|
|
79
|
+
/**
|
|
80
|
+
* 順序図の板を 1 枚組む。 面の名前として組み立て器へ渡すものだけを `面の名` で差し替える。
|
|
81
|
+
*
|
|
82
|
+
* 組み立て器はこの名前から見出しと「端を引く表」 を作る。 言づての端と帯は書いた名前のまま渡す。
|
|
83
|
+
*/
|
|
84
|
+
function 板を組む(doc: DslDocument, 面の名: (a: DslActor) => string): CdlDiagram {
|
|
85
|
+
const b = sequence({
|
|
86
|
+
id: slugify(doc.title),
|
|
87
|
+
topic: doc.title,
|
|
88
|
+
actors: doc.actors.map((a) =>
|
|
89
|
+
a.subtitle ? { name: 面の名(a), subtitle: a.subtitle } : 面の名(a),
|
|
90
|
+
),
|
|
91
|
+
...(doc.bands && doc.bands.length > 0 ? { bands: doc.bands } : {}),
|
|
92
|
+
...(doc.sequenceActorForm !== undefined ? { sequenceActorForm: doc.sequenceActorForm } : {}),
|
|
93
|
+
...(doc.sequenceLabelForm !== undefined ? { sequenceLabelForm: doc.sequenceLabelForm } : {}),
|
|
94
|
+
...(doc.sequenceReturnHead !== undefined ? { sequenceReturnHead: doc.sequenceReturnHead } : {}),
|
|
95
|
+
...(doc.sequenceKindLabels !== undefined ? { sequenceKindLabels: doc.sequenceKindLabels } : {}),
|
|
96
|
+
...(doc.sequenceRowGap !== undefined ? { sequenceRowGap: doc.sequenceRowGap } : {}),
|
|
97
|
+
...(doc.sequenceGutter !== undefined ? { sequenceGutter: doc.sequenceGutter } : {}),
|
|
98
|
+
...(doc.sequenceBandWidth !== undefined ? { sequenceBandWidth: doc.sequenceBandWidth } : {}),
|
|
99
|
+
...(doc.sequenceSelfLoop !== undefined ? { sequenceSelfLoop: doc.sequenceSelfLoop } : {}),
|
|
100
|
+
});
|
|
101
|
+
for (const s of doc.flow) {
|
|
102
|
+
b.step({
|
|
103
|
+
from: s.from,
|
|
104
|
+
to: s.to,
|
|
105
|
+
label: s.label,
|
|
106
|
+
...(s.sub ? { sub: s.sub } : {}),
|
|
107
|
+
...(s.side ? { side: s.side } : {}),
|
|
108
|
+
...(s.tone ? { tone: s.tone } : {}),
|
|
109
|
+
...(s.style ? { style: s.style } : {}),
|
|
110
|
+
...(s.msgKind ? { kind: s.msgKind } : {}),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const built = b.build();
|
|
114
|
+
if (doc.sequenceGutter === undefined || doc.viewport?.width === undefined) return built;
|
|
115
|
+
const canvasWidth = doc.viewport.width;
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
* `sequenceGutter` は見本の canvas 原点から最初の糸を決める。cdl の sequence preset は
|
|
119
|
+
* 板の左右へさらに lane padding を足すため、そのままでは gutter 610 の糸が 25 右へずれ、
|
|
120
|
+
* 1800 の canvas も 1948 まで広がる。幅を書いた図では lane の左右 25 ずつを除いた幅を
|
|
121
|
+
* 板へ渡し、layout 後の lane を canvas 幅に揃える。gutter + 列幅 / 2 は板の原点から測る。
|
|
122
|
+
* 幅を書かない従来図は cdl の自動幅を保つ。
|
|
123
|
+
*/
|
|
124
|
+
return {
|
|
125
|
+
...built,
|
|
126
|
+
lanes: built.lanes.map((lane) => lane.id === "seq" ? { ...lane, width: canvasWidth } : lane),
|
|
127
|
+
nodes: built.nodes.map((node) => node.kind === "sequence-board"
|
|
128
|
+
? { ...node, w: Math.max(1, canvasWidth - 50) }
|
|
129
|
+
: node),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 見出しで組んだ板へ、 名前で組んだ板の「言づての端」 と「帯」 を写す (#2408)。
|
|
135
|
+
*
|
|
136
|
+
* 写すのはこの 2 つだけ。 板の大きさ・見出し・言づての文字は見出しで組んだ板のものを使う。
|
|
137
|
+
* 帯は書かない時も組み立て器が言づての端から導くため、 端と一緒に写さないと導いた帯がずれる。
|
|
138
|
+
*/
|
|
139
|
+
function 端を写す(見出し: CdlDiagram, 名前: CdlDiagram): CdlDiagram {
|
|
140
|
+
const 名前の板 = 名前.nodes.find((n) => n.kind === "sequence-board")?.sequenceData;
|
|
141
|
+
if (!名前の板) return 見出し;
|
|
142
|
+
return {
|
|
143
|
+
...見出し,
|
|
144
|
+
nodes: 見出し.nodes.map((n) => {
|
|
145
|
+
if (n.kind !== "sequence-board" || !n.sequenceData) return n;
|
|
146
|
+
return {
|
|
147
|
+
...n,
|
|
148
|
+
sequenceData: {
|
|
149
|
+
...n.sequenceData,
|
|
150
|
+
messages: n.sequenceData.messages.map((m, i) => {
|
|
151
|
+
const 正 = 名前の板.messages[i];
|
|
152
|
+
return 正 ? { ...m, from: 正.from, to: 正.to } : m;
|
|
153
|
+
}),
|
|
154
|
+
bands: 名前の板.bands,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
82
161
|
/** 名前が同じ箱を指すか。 書いた名前のほか、 id の形 (`api-gateway`) で書いた指定も受ける */
|
|
83
162
|
function 同じ箱(a: string, b: string): boolean {
|
|
84
163
|
return a === b || slugify(a) === slugify(b);
|
package/src/compile/slug.ts
CHANGED
|
@@ -16,13 +16,21 @@ export const ID_MAX = 64;
|
|
|
16
16
|
*
|
|
17
17
|
* 英数字と日本語と `-` `_` だけを残し、それ以外は `-` にまとめる。 前後の `-` は落とす。
|
|
18
18
|
* 何も残らなければ `n` を返す = 空の id は図の参照先にできないため。
|
|
19
|
+
*
|
|
20
|
+
* **カタカナは伸ばす音 (`ー`) まで残す** (#2431)。 `ヶ` (U+30F6) で切ると `ー` (U+30FC) が
|
|
21
|
+
* 範囲の外へ落ち、`フロー` が `フロ-` になる。 読めない字が一覧に出るだけでなく、
|
|
22
|
+
* `ユーザー` と `ユーザ` が同じ `ユ-ザ` に潰れる (末尾の `-` も落ちるため完全に一致する)。
|
|
23
|
+
* 矢印は id で端を指すので、潰れると別の箱へ向かう。
|
|
24
|
+
*
|
|
25
|
+
* **区切りに使う記号は残さない**。 `・` (U+30FB) と `ヽヾ` (U+30FD-E) は範囲の外に置く =
|
|
26
|
+
* `東京・大阪` の `・` は名前の区切りで、字として残すと 1 つの名前に見える。
|
|
19
27
|
*/
|
|
20
28
|
export function slugify(s: string): string {
|
|
21
29
|
return (
|
|
22
30
|
s
|
|
23
31
|
.toLowerCase()
|
|
24
32
|
.normalize("NFKC")
|
|
25
|
-
.replace(/[^a-z0-9
|
|
33
|
+
.replace(/[^a-z0-9ぁ-んァ-ヺー一-龯\-_]+/g, "-")
|
|
26
34
|
.replace(/^-+|-+$/g, "")
|
|
27
35
|
.slice(0, ID_MAX) || "n"
|
|
28
36
|
);
|