@diagrammo/dgmo 0.8.17 → 0.8.19

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.
@@ -17,7 +17,7 @@ import {
17
17
  MULTIPLE_PIPE_ERROR,
18
18
  parseFirstLine,
19
19
  } from '../utils/parsing';
20
- import { parseOffset } from '../utils/duration';
20
+ import { parseOffset, parseDuration } from '../utils/duration';
21
21
  import type { PaletteColors } from '../palettes';
22
22
  import { getSeriesColors } from '../palettes';
23
23
  import type {
@@ -35,14 +35,14 @@ import type {
35
35
  // ── Regexes ─────────────────────────────────────────────────
36
36
 
37
37
  /** Duration task: `30d Label`, `1.5w Label`, `10bd? Label`, `2h Label`, `90min Label` */
38
- const DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
38
+ const DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)(\?)?\s+(.+)$/;
39
39
 
40
40
  /** Explicit date task: `2024-01-15 Label` or `2024-01-15 14:30 Label` */
41
41
  const EXPLICIT_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s+(.+)$/;
42
42
 
43
43
  /** Timeline migration syntax: `2024-01-15 -> 30d Label` or `2024-01-15 14:30 -> 2h Label` */
44
44
  const TIMELINE_DURATION_RE =
45
- /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s*(?:->|\u2013>)\s*(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
45
+ /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s*(?:->|\u2013>)\s*(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)(\?)?\s+(.+)$/;
46
46
 
47
47
  /** Group container: `[GroupName]` with optional pipe metadata */
48
48
  const GROUP_RE = /^\[(.+?)\]\s*(.*)$/;
@@ -138,6 +138,10 @@ export function parseGantt(
138
138
  activeTag: null,
139
139
  optionLineNumbers: {},
140
140
  holidaysLineNumber: null,
141
+ sprintLength: null,
142
+ sprintNumber: null,
143
+ sprintStart: null,
144
+ sprintMode: null,
141
145
  },
142
146
  diagnostics,
143
147
  error: null,
@@ -650,6 +654,57 @@ export function parseGantt(
650
654
  case 'active-tag':
651
655
  result.options.activeTag = value;
652
656
  break;
657
+ case 'sprint-length': {
658
+ const dur = parseDuration(value);
659
+ if (!dur) {
660
+ warn(
661
+ lineNumber,
662
+ `Invalid sprint-length value: "${value}". Expected a duration like "2w" or "10d".`
663
+ );
664
+ } else if (dur.unit !== 'd' && dur.unit !== 'w') {
665
+ warn(
666
+ lineNumber,
667
+ `sprint-length only accepts "d" or "w" units, got "${dur.unit}".`
668
+ );
669
+ } else if (dur.amount <= 0) {
670
+ warn(lineNumber, `sprint-length must be greater than 0.`);
671
+ } else if (
672
+ !Number.isInteger(dur.amount * (dur.unit === 'w' ? 7 : 1))
673
+ ) {
674
+ warn(
675
+ lineNumber,
676
+ `sprint-length must resolve to a whole number of days.`
677
+ );
678
+ } else {
679
+ result.options.sprintLength = dur;
680
+ }
681
+ break;
682
+ }
683
+ case 'sprint-number': {
684
+ const n = Number(value);
685
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) {
686
+ warn(
687
+ lineNumber,
688
+ `sprint-number must be a positive integer, got "${value}".`
689
+ );
690
+ } else {
691
+ result.options.sprintNumber = n;
692
+ }
693
+ break;
694
+ }
695
+ case 'sprint-start': {
696
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
697
+ warn(
698
+ lineNumber,
699
+ `sprint-start requires a full date (YYYY-MM-DD), got "${value}".`
700
+ );
701
+ } else if (Number.isNaN(new Date(value + 'T00:00:00').getTime())) {
702
+ warn(lineNumber, `sprint-start is not a valid date: "${value}".`);
703
+ } else {
704
+ result.options.sprintStart = value;
705
+ }
706
+ break;
707
+ }
653
708
  }
654
709
  continue;
655
710
  }
@@ -879,6 +934,31 @@ export function parseGantt(
879
934
 
880
935
  validateTagGroupNames(result.tagGroups, warn);
881
936
 
937
+ // ── Sprint mode detection ──────────────────────────────
938
+ const hasSprintOption =
939
+ result.options.sprintLength !== null ||
940
+ result.options.sprintNumber !== null ||
941
+ result.options.sprintStart !== null;
942
+
943
+ const hasSprintUnit = hasSprintDurationUnit(result.nodes);
944
+
945
+ if (hasSprintOption) {
946
+ result.options.sprintMode = 'explicit';
947
+ } else if (hasSprintUnit) {
948
+ result.options.sprintMode = 'auto';
949
+ }
950
+
951
+ // Apply defaults when sprint mode is active
952
+ if (result.options.sprintMode) {
953
+ if (!result.options.sprintLength) {
954
+ result.options.sprintLength = { amount: 2, unit: 'w' };
955
+ }
956
+ if (result.options.sprintNumber === null) {
957
+ result.options.sprintNumber = 1;
958
+ }
959
+ // sprintStart defaults to chart start or today — handled in calculator
960
+ }
961
+
882
962
  return result;
883
963
 
884
964
  // ── Helper: create a task ───────────────────────────────
@@ -1041,6 +1121,9 @@ const KNOWN_OPTIONS = new Set([
1041
1121
  'chart',
1042
1122
  'sort',
1043
1123
  'active-tag',
1124
+ 'sprint-length',
1125
+ 'sprint-number',
1126
+ 'sprint-start',
1044
1127
  ]);
1045
1128
 
1046
1129
  /** Boolean options that can appear as bare keywords or with `no-` prefix. */
@@ -1053,3 +1136,15 @@ const KNOWN_BOOLEANS = new Set([
1053
1136
  function isKnownOption(key: string): boolean {
1054
1137
  return KNOWN_OPTIONS.has(key);
1055
1138
  }
1139
+
1140
+ /** Check if any task in the tree uses the `s` (sprint) duration unit. */
1141
+ function hasSprintDurationUnit(nodes: GanttNode[]): boolean {
1142
+ for (const node of nodes) {
1143
+ if (node.kind === 'task') {
1144
+ if (node.duration?.unit === 's') return true;
1145
+ } else if (node.kind === 'group' || node.kind === 'parallel') {
1146
+ if (hasSprintDurationUnit(node.children)) return true;
1147
+ }
1148
+ }
1149
+ return false;
1150
+ }