@jarenjs/mermaid 0.46.4 → 0.49.2
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 +63 -3
- package/dist/types/gantt-calendar.d.ts +99 -0
- package/dist/types/index.d.ts +7 -1
- package/dist/types/layout/gantt.d.ts +31 -0
- package/dist/types/parser/gantt-grammar.d.ts +174 -0
- package/dist/types/parser/gantt.d.ts +35 -5
- package/dist/types/parser/index.d.ts +5 -1
- package/dist/types/render/gantt.d.ts +26 -0
- package/dist/types/render/misc.d.ts +1 -1
- package/docs/MERMAID-FORMAT.md +106 -4
- package/package.json +4 -4
- package/schemas/jaren-mermaid-ast.schema.json +379 -37
- package/src/gantt-calendar.js +152 -0
- package/src/index.js +5 -1
- package/src/layout/gantt.js +269 -0
- package/src/parser/gantt-grammar.js +298 -0
- package/src/parser/gantt.js +456 -8
- package/src/parser/index.js +5 -2
- package/src/render/gantt.js +169 -0
- package/src/render/index.js +6 -2
- package/src/render/misc.js +5 -13
- package/styles/mermaid.css +21 -0
|
@@ -4,74 +4,416 @@
|
|
|
4
4
|
"title": "Jaren Mermaid diagram document",
|
|
5
5
|
"description": "The DiagramDocument envelope and per-type AST vocabulary of @jarenjs/mermaid (normative prose in docs/MERMAID-FORMAT.md). Draft-neutral: no $ref siblings, no unevaluated*, no $dynamic*. The AST is geometry-free — layout is a separate pass.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": [
|
|
7
|
+
"required": [
|
|
8
|
+
"$mermaid",
|
|
9
|
+
"diagram",
|
|
10
|
+
"config",
|
|
11
|
+
"ast",
|
|
12
|
+
"meta"
|
|
13
|
+
],
|
|
8
14
|
"properties": {
|
|
9
|
-
"$mermaid": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
15
|
+
"$mermaid": {
|
|
16
|
+
"const": "0.1"
|
|
17
|
+
},
|
|
18
|
+
"diagram": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"config": {
|
|
22
|
+
"type": "object"
|
|
23
|
+
},
|
|
24
|
+
"ast": {
|
|
25
|
+
"type": "object"
|
|
26
|
+
},
|
|
13
27
|
"meta": {
|
|
14
28
|
"type": "object",
|
|
15
|
-
"required": [
|
|
29
|
+
"required": [
|
|
30
|
+
"hash",
|
|
31
|
+
"direction",
|
|
32
|
+
"title"
|
|
33
|
+
],
|
|
16
34
|
"properties": {
|
|
17
|
-
"hash": {
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
"hash": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
"direction": {
|
|
39
|
+
"type": [
|
|
40
|
+
"string",
|
|
41
|
+
"null"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"title": {
|
|
45
|
+
"type": [
|
|
46
|
+
"string",
|
|
47
|
+
"null"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
20
50
|
}
|
|
21
51
|
}
|
|
22
52
|
},
|
|
23
53
|
"$defs": {
|
|
24
54
|
"flowNode": {
|
|
25
55
|
"type": "object",
|
|
26
|
-
"required": [
|
|
56
|
+
"required": [
|
|
57
|
+
"id",
|
|
58
|
+
"label",
|
|
59
|
+
"shape"
|
|
60
|
+
],
|
|
27
61
|
"properties": {
|
|
28
|
-
"id": {
|
|
29
|
-
|
|
30
|
-
|
|
62
|
+
"id": {
|
|
63
|
+
"type": "string"
|
|
64
|
+
},
|
|
65
|
+
"label": {
|
|
66
|
+
"type": "string"
|
|
67
|
+
},
|
|
68
|
+
"shape": {
|
|
69
|
+
"type": "string"
|
|
70
|
+
}
|
|
31
71
|
}
|
|
32
72
|
},
|
|
33
73
|
"flowEdge": {
|
|
34
74
|
"type": "object",
|
|
35
|
-
"required": [
|
|
75
|
+
"required": [
|
|
76
|
+
"from",
|
|
77
|
+
"to",
|
|
78
|
+
"stroke",
|
|
79
|
+
"head",
|
|
80
|
+
"tail",
|
|
81
|
+
"length",
|
|
82
|
+
"label"
|
|
83
|
+
],
|
|
36
84
|
"properties": {
|
|
37
|
-
"from": {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
85
|
+
"from": {
|
|
86
|
+
"type": "string"
|
|
87
|
+
},
|
|
88
|
+
"to": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
},
|
|
91
|
+
"stroke": {
|
|
92
|
+
"enum": [
|
|
93
|
+
"solid",
|
|
94
|
+
"thick",
|
|
95
|
+
"dotted"
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
"head": {
|
|
99
|
+
"enum": [
|
|
100
|
+
"none",
|
|
101
|
+
"arrow",
|
|
102
|
+
"circle",
|
|
103
|
+
"cross"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"tail": {
|
|
107
|
+
"enum": [
|
|
108
|
+
"none",
|
|
109
|
+
"arrow",
|
|
110
|
+
"circle",
|
|
111
|
+
"cross"
|
|
112
|
+
]
|
|
113
|
+
},
|
|
114
|
+
"length": {
|
|
115
|
+
"type": "integer"
|
|
116
|
+
},
|
|
117
|
+
"label": {
|
|
118
|
+
"type": [
|
|
119
|
+
"string",
|
|
120
|
+
"null"
|
|
121
|
+
]
|
|
122
|
+
}
|
|
44
123
|
}
|
|
45
124
|
},
|
|
46
125
|
"flowchartAst": {
|
|
47
126
|
"type": "object",
|
|
48
|
-
"required": [
|
|
127
|
+
"required": [
|
|
128
|
+
"direction",
|
|
129
|
+
"nodes",
|
|
130
|
+
"edges",
|
|
131
|
+
"subgraphs",
|
|
132
|
+
"classDefs",
|
|
133
|
+
"classes",
|
|
134
|
+
"styles"
|
|
135
|
+
],
|
|
49
136
|
"properties": {
|
|
50
|
-
"direction": {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
137
|
+
"direction": {
|
|
138
|
+
"type": "string"
|
|
139
|
+
},
|
|
140
|
+
"nodes": {
|
|
141
|
+
"type": "array",
|
|
142
|
+
"items": {
|
|
143
|
+
"$ref": "#/$defs/flowNode"
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"edges": {
|
|
147
|
+
"type": "array",
|
|
148
|
+
"items": {
|
|
149
|
+
"$ref": "#/$defs/flowEdge"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"subgraphs": {
|
|
153
|
+
"type": "array"
|
|
154
|
+
},
|
|
155
|
+
"classDefs": {
|
|
156
|
+
"type": "array"
|
|
157
|
+
},
|
|
158
|
+
"classes": {
|
|
159
|
+
"type": "array"
|
|
160
|
+
},
|
|
161
|
+
"styles": {
|
|
162
|
+
"type": "array"
|
|
163
|
+
}
|
|
57
164
|
}
|
|
58
165
|
},
|
|
59
166
|
"seqParticipant": {
|
|
60
167
|
"type": "object",
|
|
61
|
-
"required": [
|
|
168
|
+
"required": [
|
|
169
|
+
"id",
|
|
170
|
+
"label",
|
|
171
|
+
"kind"
|
|
172
|
+
],
|
|
62
173
|
"properties": {
|
|
63
|
-
"id": {
|
|
64
|
-
|
|
65
|
-
|
|
174
|
+
"id": {
|
|
175
|
+
"type": "string"
|
|
176
|
+
},
|
|
177
|
+
"label": {
|
|
178
|
+
"type": "string"
|
|
179
|
+
},
|
|
180
|
+
"kind": {
|
|
181
|
+
"enum": [
|
|
182
|
+
"participant",
|
|
183
|
+
"actor"
|
|
184
|
+
]
|
|
185
|
+
}
|
|
66
186
|
}
|
|
67
187
|
},
|
|
68
188
|
"sequenceAst": {
|
|
69
189
|
"type": "object",
|
|
70
|
-
"required": [
|
|
190
|
+
"required": [
|
|
191
|
+
"participants",
|
|
192
|
+
"statements",
|
|
193
|
+
"autonumber"
|
|
194
|
+
],
|
|
195
|
+
"properties": {
|
|
196
|
+
"participants": {
|
|
197
|
+
"type": "array",
|
|
198
|
+
"items": {
|
|
199
|
+
"$ref": "#/$defs/seqParticipant"
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
"statements": {
|
|
203
|
+
"type": "array"
|
|
204
|
+
},
|
|
205
|
+
"autonumber": {
|
|
206
|
+
"type": "boolean"
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"ganttTask": {
|
|
211
|
+
"description": "A resolved task row. `info` is the raw metadata string the printer round-trips; the rest is the schedule the layout draws. `start`/`end` are epoch milliseconds and the interval is half-open, so `end` is the first instant the task no longer occupies — except for a milestone, which is the instant `start === end`.",
|
|
212
|
+
"type": "object",
|
|
213
|
+
"required": [
|
|
214
|
+
"name",
|
|
215
|
+
"info",
|
|
216
|
+
"id",
|
|
217
|
+
"flags",
|
|
218
|
+
"start",
|
|
219
|
+
"end",
|
|
220
|
+
"duration",
|
|
221
|
+
"after",
|
|
222
|
+
"line"
|
|
223
|
+
],
|
|
224
|
+
"properties": {
|
|
225
|
+
"name": {
|
|
226
|
+
"type": "string"
|
|
227
|
+
},
|
|
228
|
+
"info": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"id": {
|
|
232
|
+
"type": "string",
|
|
233
|
+
"minLength": 1
|
|
234
|
+
},
|
|
235
|
+
"flags": {
|
|
236
|
+
"type": "array",
|
|
237
|
+
"items": {
|
|
238
|
+
"enum": [
|
|
239
|
+
"done",
|
|
240
|
+
"active",
|
|
241
|
+
"crit",
|
|
242
|
+
"milestone"
|
|
243
|
+
]
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
"start": {
|
|
247
|
+
"type": "number"
|
|
248
|
+
},
|
|
249
|
+
"end": {
|
|
250
|
+
"type": "number"
|
|
251
|
+
},
|
|
252
|
+
"duration": {
|
|
253
|
+
"type": "number",
|
|
254
|
+
"minimum": 0
|
|
255
|
+
},
|
|
256
|
+
"after": {
|
|
257
|
+
"type": "array",
|
|
258
|
+
"items": {
|
|
259
|
+
"type": "string"
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
"line": {
|
|
263
|
+
"type": "integer",
|
|
264
|
+
"minimum": 0
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"ganttRules": {
|
|
269
|
+
"description": "The header directives, interpreted. `dateFormat` and `axisFormat` keep their Mermaid spelling (dayjs and strftime respectively, not LDML); `tick` is the parsed `tickInterval`; `weekStart` and `weekendStart` are ISO weekdays, 1 is Monday.",
|
|
270
|
+
"type": "object",
|
|
271
|
+
"required": [
|
|
272
|
+
"dateFormat",
|
|
273
|
+
"axisFormat",
|
|
274
|
+
"tick",
|
|
275
|
+
"weekStart",
|
|
276
|
+
"weekendStart",
|
|
277
|
+
"excludes",
|
|
278
|
+
"todayMarker"
|
|
279
|
+
],
|
|
280
|
+
"properties": {
|
|
281
|
+
"dateFormat": {
|
|
282
|
+
"type": "string"
|
|
283
|
+
},
|
|
284
|
+
"axisFormat": {
|
|
285
|
+
"type": "string"
|
|
286
|
+
},
|
|
287
|
+
"tick": {
|
|
288
|
+
"type": [
|
|
289
|
+
"object",
|
|
290
|
+
"null"
|
|
291
|
+
],
|
|
292
|
+
"required": [
|
|
293
|
+
"amount",
|
|
294
|
+
"unit"
|
|
295
|
+
],
|
|
296
|
+
"properties": {
|
|
297
|
+
"amount": {
|
|
298
|
+
"type": "integer",
|
|
299
|
+
"minimum": 1
|
|
300
|
+
},
|
|
301
|
+
"unit": {
|
|
302
|
+
"enum": [
|
|
303
|
+
"millisecond",
|
|
304
|
+
"second",
|
|
305
|
+
"minute",
|
|
306
|
+
"hour",
|
|
307
|
+
"day",
|
|
308
|
+
"week",
|
|
309
|
+
"month"
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"weekStart": {
|
|
315
|
+
"type": "integer",
|
|
316
|
+
"minimum": 1,
|
|
317
|
+
"maximum": 7
|
|
318
|
+
},
|
|
319
|
+
"weekendStart": {
|
|
320
|
+
"type": "integer",
|
|
321
|
+
"minimum": 1,
|
|
322
|
+
"maximum": 7
|
|
323
|
+
},
|
|
324
|
+
"excludes": {
|
|
325
|
+
"type": "object",
|
|
326
|
+
"required": [
|
|
327
|
+
"weekends",
|
|
328
|
+
"weekdays",
|
|
329
|
+
"days"
|
|
330
|
+
],
|
|
331
|
+
"properties": {
|
|
332
|
+
"weekends": {
|
|
333
|
+
"type": "boolean"
|
|
334
|
+
},
|
|
335
|
+
"weekdays": {
|
|
336
|
+
"type": "array",
|
|
337
|
+
"items": {
|
|
338
|
+
"type": "integer",
|
|
339
|
+
"minimum": 1,
|
|
340
|
+
"maximum": 7
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"days": {
|
|
344
|
+
"type": "array",
|
|
345
|
+
"items": {
|
|
346
|
+
"type": "integer"
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"todayMarker": {
|
|
352
|
+
"type": [
|
|
353
|
+
"string",
|
|
354
|
+
"null"
|
|
355
|
+
]
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
"ganttAst": {
|
|
360
|
+
"description": "gantt — a resolved schedule. `meta` holds the header lines verbatim so the printer stays a fixed point; `rules` holds the same directives interpreted, and `domain` is the half-open span every task falls inside.",
|
|
361
|
+
"type": "object",
|
|
362
|
+
"required": [
|
|
363
|
+
"meta",
|
|
364
|
+
"rules",
|
|
365
|
+
"sections",
|
|
366
|
+
"domain"
|
|
367
|
+
],
|
|
71
368
|
"properties": {
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
369
|
+
"meta": {
|
|
370
|
+
"type": "object",
|
|
371
|
+
"additionalProperties": {
|
|
372
|
+
"type": "string"
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
"rules": {
|
|
376
|
+
"$ref": "#/$defs/ganttRules"
|
|
377
|
+
},
|
|
378
|
+
"sections": {
|
|
379
|
+
"type": "array",
|
|
380
|
+
"items": {
|
|
381
|
+
"type": "object",
|
|
382
|
+
"required": [
|
|
383
|
+
"name",
|
|
384
|
+
"tasks"
|
|
385
|
+
],
|
|
386
|
+
"properties": {
|
|
387
|
+
"name": {
|
|
388
|
+
"type": [
|
|
389
|
+
"string",
|
|
390
|
+
"null"
|
|
391
|
+
]
|
|
392
|
+
},
|
|
393
|
+
"tasks": {
|
|
394
|
+
"type": "array",
|
|
395
|
+
"items": {
|
|
396
|
+
"$ref": "#/$defs/ganttTask"
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
"domain": {
|
|
403
|
+
"type": "object",
|
|
404
|
+
"required": [
|
|
405
|
+
"start",
|
|
406
|
+
"end"
|
|
407
|
+
],
|
|
408
|
+
"properties": {
|
|
409
|
+
"start": {
|
|
410
|
+
"type": "number"
|
|
411
|
+
},
|
|
412
|
+
"end": {
|
|
413
|
+
"type": "number"
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
75
417
|
}
|
|
76
418
|
}
|
|
77
419
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file The working calendar behind `excludes`: which whole days a
|
|
4
|
+
* schedule skips, and what a duration therefore means.
|
|
5
|
+
*
|
|
6
|
+
* Two implementations of one answer live here on purpose. The exported
|
|
7
|
+
* one expresses an excluded stretch as half-open day intervals and
|
|
8
|
+
* measures it with `@jarenjs/core/series` (`mergeIntervals`,
|
|
9
|
+
* `coverageOf`) — the suite's one interval algebra, so a weekend here
|
|
10
|
+
* and an unavailability there are the same kind of thing. The other,
|
|
11
|
+
* {@link pushEndDayByDay}, is the naive day-stepping loop the shipped
|
|
12
|
+
* Mermaid renderer runs; it exists so the fast answer has an
|
|
13
|
+
* independent oracle to be differential-tested against, and it is the
|
|
14
|
+
* definition when the two disagree.
|
|
15
|
+
*
|
|
16
|
+
* The rule is Mermaid's, not an invention: an excluded day pushes a
|
|
17
|
+
* task's END out by a whole day, the task's own START is left where the
|
|
18
|
+
* document put it, and an end that was written as an explicit DATE is
|
|
19
|
+
* never pushed at all — only one derived from a duration is. Days are
|
|
20
|
+
* UTC days, because this engine has no local zone and no clock.
|
|
21
|
+
*
|
|
22
|
+
* This is not a holiday service. A schedule skips exactly the weekends,
|
|
23
|
+
* weekday names and dates its own `excludes` line lists.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { mergeIntervals, coverageOf } from '@jarenjs/core/series';
|
|
27
|
+
import { isoWeekdayFromDays } from '@jarenjs/core/dates';
|
|
28
|
+
|
|
29
|
+
/** Milliseconds in a UTC day. */
|
|
30
|
+
export const DAY_MS = 86400000;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Mermaid's own guard: a schedule whose exclusions swallow every day
|
|
34
|
+
* would push an end forever, so the walk stops and the caller reports a
|
|
35
|
+
* `JM` error instead of hanging.
|
|
36
|
+
*/
|
|
37
|
+
export const MAX_PUSH_DAYS = 10000;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The index of the UTC day holding `ms`, counting from 1970-01-01.
|
|
41
|
+
* @param {number} ms
|
|
42
|
+
* @returns {number}
|
|
43
|
+
*/
|
|
44
|
+
export function dayIndexOf(ms) {
|
|
45
|
+
return Math.floor(ms / DAY_MS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} Excluder
|
|
50
|
+
* @property {boolean} any whether the document excludes anything at all
|
|
51
|
+
* @property {(dayIndex: number) => boolean} isExcludedDay
|
|
52
|
+
* @property {(fromDay: number, toDay: number) => { start: number, end: number }[]}
|
|
53
|
+
* intervalsIn merged half-open day spans over `[fromDay, toDay)`
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the working calendar a document's `excludes`, `weekend` and
|
|
58
|
+
* `dateFormat` describe.
|
|
59
|
+
*
|
|
60
|
+
* @param {{ weekends: boolean, weekdays: number[], days: number[] }} rules -
|
|
61
|
+
* `weekdays` are ISO weekday numbers, `days` are day indexes
|
|
62
|
+
* @param {number} weekendStart - ISO weekday the weekend starts on
|
|
63
|
+
* @returns {Excluder}
|
|
64
|
+
*/
|
|
65
|
+
export function createExcluder(rules, weekendStart) {
|
|
66
|
+
const weekdays = new Set(rules.weekdays);
|
|
67
|
+
if (rules.weekends) {
|
|
68
|
+
weekdays.add(weekendStart);
|
|
69
|
+
// the weekend is two days: friday+saturday, or saturday+sunday
|
|
70
|
+
weekdays.add(weekendStart === 7 ? 1 : weekendStart + 1);
|
|
71
|
+
}
|
|
72
|
+
const days = new Set(rules.days);
|
|
73
|
+
const any = weekdays.size > 0 || days.size > 0;
|
|
74
|
+
|
|
75
|
+
/** @param {number} dayIndex @returns {boolean} */
|
|
76
|
+
const isExcludedDay = (dayIndex) => days.has(dayIndex)
|
|
77
|
+
|| weekdays.has(isoWeekdayFromDays(dayIndex));
|
|
78
|
+
|
|
79
|
+
/** @param {number} fromDay @param {number} toDay */
|
|
80
|
+
const intervalsIn = (fromDay, toDay) => {
|
|
81
|
+
if (!any || toDay <= fromDay) return [];
|
|
82
|
+
const spans = [];
|
|
83
|
+
for (let d = fromDay; d < toDay; d++) {
|
|
84
|
+
if (isExcludedDay(d))
|
|
85
|
+
spans.push({ start: d * DAY_MS, end: (d + 1) * DAY_MS });
|
|
86
|
+
}
|
|
87
|
+
// touching days merge by default (core/series D4), so a weekend is
|
|
88
|
+
// one two-day span rather than two abutting ones
|
|
89
|
+
return spans.length === 0 ? [] : mergeIntervals(spans);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return { any, isExcludedDay, intervalsIn };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Push a task's end past the excluded days its span crosses.
|
|
97
|
+
*
|
|
98
|
+
* The probes are the instants `start + k days` for `k` from 1, which is
|
|
99
|
+
* what makes this a *day* rule rather than a millisecond one: a task
|
|
100
|
+
* that starts at noon on Friday and lasts a day ends at noon on Monday
|
|
101
|
+
* when the weekend is excluded, not at midnight.
|
|
102
|
+
*
|
|
103
|
+
* @param {number} start - epoch milliseconds
|
|
104
|
+
* @param {number} end - epoch milliseconds, at or after `start`
|
|
105
|
+
* @param {Excluder} excluder
|
|
106
|
+
* @returns {number} the pushed end, or `NaN` when the exclusions never
|
|
107
|
+
* let the task finish
|
|
108
|
+
*/
|
|
109
|
+
export function pushEndPastExclusions(start, end, excluder) {
|
|
110
|
+
if (!excluder.any || end <= start) return end;
|
|
111
|
+
const firstDay = dayIndexOf(start) + 1;
|
|
112
|
+
let out = end;
|
|
113
|
+
// the iteration is monotone (each round can only find more excluded
|
|
114
|
+
// days), so it reaches the least fixed point from below
|
|
115
|
+
for (let round = 0; round < MAX_PUSH_DAYS; round++) {
|
|
116
|
+
const spanDays = Math.floor((out - start) / DAY_MS);
|
|
117
|
+
if (spanDays < 1) return out;
|
|
118
|
+
if (spanDays > MAX_PUSH_DAYS) return NaN;
|
|
119
|
+
const window = { start: firstDay * DAY_MS, end: (firstDay + spanDays) * DAY_MS };
|
|
120
|
+
const excluded = coverageOf(excluder.intervalsIn(firstDay, firstDay + spanDays), window);
|
|
121
|
+
const next = end + (excluded / DAY_MS) * DAY_MS;
|
|
122
|
+
if (next === out) return out;
|
|
123
|
+
out = next;
|
|
124
|
+
}
|
|
125
|
+
return NaN;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* The oracle: Mermaid's own day-stepping loop, written plainly. Kept as
|
|
130
|
+
* a test double rather than as the implementation because it walks
|
|
131
|
+
* every day of a span one at a time, but it is the definition of the
|
|
132
|
+
* answer {@link pushEndPastExclusions} computes.
|
|
133
|
+
*
|
|
134
|
+
* @param {number} start - epoch milliseconds
|
|
135
|
+
* @param {number} end - epoch milliseconds
|
|
136
|
+
* @param {Excluder} excluder
|
|
137
|
+
* @returns {number} the pushed end, or `NaN` when it never finishes
|
|
138
|
+
*/
|
|
139
|
+
export function pushEndDayByDay(start, end, excluder) {
|
|
140
|
+
if (!excluder.any || end <= start) return end;
|
|
141
|
+
let out = end;
|
|
142
|
+
let probe = start + DAY_MS;
|
|
143
|
+
let steps = 0;
|
|
144
|
+
while (probe <= out) {
|
|
145
|
+
if (excluder.isExcludedDay(dayIndexOf(probe))) {
|
|
146
|
+
out += DAY_MS;
|
|
147
|
+
if (++steps > MAX_PUSH_DAYS) return NaN;
|
|
148
|
+
}
|
|
149
|
+
probe += DAY_MS;
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
package/src/index.js
CHANGED
|
@@ -39,7 +39,11 @@ export {
|
|
|
39
39
|
/**
|
|
40
40
|
* Convenience: parse → layout → render, error-safe, in one call.
|
|
41
41
|
* @param {string} source
|
|
42
|
-
* @param {{ theme?: any
|
|
42
|
+
* @param {{ theme?: any, dateNames?: import('@jarenjs/core/dates').DateNames,
|
|
43
|
+
* parseFrontmatter?: (text: string) => any }} [options] - `dateNames`
|
|
44
|
+
* is the locale-name record a Gantt's `dateFormat`/`axisFormat` needs
|
|
45
|
+
* for a month or weekday token (`compileDateLocale(pack).names`);
|
|
46
|
+
* this engine ships none of its own
|
|
43
47
|
* @returns {any} an SVG vnode
|
|
44
48
|
*/
|
|
45
49
|
export function renderMermaid(source, options = {}) {
|