@weborigami/async-tree 0.0.71-beta.1 → 0.0.71

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/main.js CHANGED
@@ -5,6 +5,7 @@ export { default as FileTree } from "./src/FileTree.js";
5
5
  export { default as FunctionTree } from "./src/FunctionTree.js";
6
6
  export { default as MapTree } from "./src/MapTree.js";
7
7
  // Skip BrowserFileTree.js, which is browser-only.
8
+ export { default as calendarTree } from "./src/calendarTree.js";
8
9
  export { default as DeepMapTree } from "./src/DeepMapTree.js";
9
10
  export { default as ExplorableSiteTree } from "./src/ExplorableSiteTree.js";
10
11
  export { DeepObjectTree, ObjectTree, Tree } from "./src/internal.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.0.71-beta.1",
3
+ "version": "0.0.71",
4
4
  "description": "Asynchronous tree drivers based on standard JavaScript classes",
5
5
  "type": "module",
6
6
  "main": "./main.js",
@@ -11,7 +11,7 @@
11
11
  "typescript": "5.6.2"
12
12
  },
13
13
  "dependencies": {
14
- "@weborigami/types": "0.0.71-beta.1"
14
+ "@weborigami/types": "0.0.71"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
package/src/SiteTree.js CHANGED
@@ -87,17 +87,15 @@ export default class SiteTree {
87
87
  const { type, subtype } = match.groups;
88
88
  if (type === "text") {
89
89
  return true;
90
- } else if (type === "application") {
91
- return (
92
- subtype === "json" ||
93
- subtype.endsWith("+json") ||
94
- subtype.endsWith(".json") ||
95
- subtype === "xml" ||
96
- subtype.endsWith("+xml") ||
97
- subtype.endsWith(".xml")
98
- );
99
90
  }
100
- return false;
91
+ return (
92
+ subtype === "json" ||
93
+ subtype.endsWith("+json") ||
94
+ subtype.endsWith(".json") ||
95
+ subtype === "xml" ||
96
+ subtype.endsWith("+xml") ||
97
+ subtype.endsWith(".xml")
98
+ );
101
99
  }
102
100
 
103
101
  get path() {
@@ -1,3 +1,5 @@
1
+ import * as trailingSlash from "./trailingSlash.js";
2
+
1
3
  /**
2
4
  * Return a tree of years, months, and days from a start date to an end date.
3
5
  *
@@ -10,68 +12,163 @@
10
12
  *
11
13
  * If a start date is omitted, today will be used, likewise for the end date.
12
14
  *
13
- * @param {string} [start] - Start date in "YYYY-MM-DD", "YYYY-MM", or "YYYY"
14
- * format
15
- * @param {string} [end] - End date in "YYYY-MM-DD", "YYYY-MM", or "YYYY" format
15
+ * @typedef {string|undefined} CalendarOptionsDate
16
+ * @typedef {( year: string, month: string, day: string ) => any} CalendarOptionsFn
17
+ * @param {{ end?: CalendarOptionsDate, start?: CalendarOptionsDate, value: CalendarOptionsFn }} options
16
18
  */
17
- export default function calendarTree(start, end) {
18
- const startParts = start?.split("-") ?? [];
19
- const endParts = end?.split("-") ?? [];
19
+ export default function calendarTree(options) {
20
+ const start = dateParts(options.start);
21
+ const end = dateParts(options.end);
22
+ const valueFn = options.value;
20
23
 
24
+ // Fill in the missing parts of the start and end dates.
21
25
  const today = new Date();
22
26
 
23
- const startYear = startParts[0]
24
- ? parseInt(startParts[0])
25
- : today.getFullYear();
26
- const startMonth = startParts[1]
27
- ? parseInt(startParts[1])
28
- : startParts[0]
29
- ? 1
30
- : today.getMonth() + 1;
31
- const startDay = startParts[2]
32
- ? parseInt(startParts[2])
33
- : startParts[1]
34
- ? 1
35
- : today.getDate();
36
-
37
- const endYear = endParts[0] ? parseInt(endParts[0]) : today.getFullYear();
38
- const endMonth = endParts[1]
39
- ? parseInt(endParts[1])
40
- : endParts[0]
41
- ? 12
42
- : today.getMonth() + 1;
43
- const endDay = endParts[2]
44
- ? parseInt(endParts[2])
45
- : endParts[1]
46
- ? daysInMonth(endYear, endMonth)
47
- : today.getDate();
48
-
49
- let years = {};
50
- for (let year = startYear; year <= endYear; year++) {
51
- let months = new Map();
52
- const firstMonth = year === startYear ? startMonth : 1;
53
- const lastMonth = year === endYear ? endMonth : 12;
54
- for (let month = firstMonth; month <= lastMonth; month++) {
55
- const monthPadded = month.toString().padStart(2, "0");
56
- let days = new Map();
57
- const firstDay =
58
- year === startYear && month === startMonth ? startDay : 1;
59
- const lastDay =
60
- year === endYear && month === endMonth
61
- ? endDay
62
- : daysInMonth(year, month);
63
- for (let day = firstDay; day <= lastDay; day++) {
64
- const dayPadded = day.toString().padStart(2, "0");
65
- days.set(dayPadded, null);
66
- }
67
- months.set(monthPadded, days);
68
- }
69
- years[year] = months;
27
+ if (start.day === undefined) {
28
+ start.day = start.year ? 1 : today.getDate();
29
+ }
30
+ if (start.month === undefined) {
31
+ start.month = start.year ? 1 : today.getMonth() + 1;
32
+ }
33
+ if (start.year === undefined) {
34
+ start.year = today.getFullYear();
35
+ }
36
+
37
+ if (end.day === undefined) {
38
+ end.day = end.month
39
+ ? daysInMonth(end.year, end.month)
40
+ : end.year
41
+ ? 31 // Last day of December
42
+ : today.getDate();
43
+ }
44
+ if (end.month === undefined) {
45
+ end.month = end.year ? 12 : today.getMonth() + 1;
70
46
  }
47
+ if (end.year === undefined) {
48
+ end.year = today.getFullYear();
49
+ }
50
+
51
+ return yearsTree(start, end, valueFn);
52
+ }
71
53
 
72
- return years;
54
+ function dateParts(date) {
55
+ let year;
56
+ let month;
57
+ let day;
58
+ if (typeof date === "string") {
59
+ const parts = date.split("-");
60
+ year = parts[0] ? parseInt(parts[0]) : undefined;
61
+ month = parts[1] ? parseInt(parts[1]) : undefined;
62
+ day = parts[2] ? parseInt(parts[2]) : undefined;
63
+ }
64
+ return { year, month, day };
65
+ }
66
+
67
+ function daysForMonthTree(year, month, start, end, valueFn) {
68
+ return {
69
+ async get(day) {
70
+ day = parseInt(trailingSlash.remove(day));
71
+ return this.inRange(day)
72
+ ? valueFn(year.toString(), twoDigits(month), twoDigits(day))
73
+ : undefined;
74
+ },
75
+
76
+ inRange(day) {
77
+ if (year === start.year && year === end.year) {
78
+ if (month === start.month && month === end.month) {
79
+ return day >= start.day && day <= end.day;
80
+ } else if (month === start.month) {
81
+ return day >= start.day;
82
+ } else if (month === end.month) {
83
+ return day <= end.day;
84
+ } else {
85
+ return true;
86
+ }
87
+ } else if (year === start.year) {
88
+ if (month === start.month) {
89
+ return day >= start.day;
90
+ } else {
91
+ return month > start.month;
92
+ }
93
+ } else if (year === end.year) {
94
+ if (month === end.month) {
95
+ return day <= end.day;
96
+ } else {
97
+ return month < end.month;
98
+ }
99
+ } else {
100
+ return true;
101
+ }
102
+ },
103
+
104
+ async keys() {
105
+ const days = Array.from(
106
+ { length: daysInMonth(year, month) },
107
+ (_, i) => i + 1
108
+ );
109
+ return days
110
+ .filter((day) => this.inRange(day))
111
+ .map((day) => twoDigits(day));
112
+ },
113
+ };
73
114
  }
74
115
 
75
116
  function daysInMonth(year, month) {
76
117
  return new Date(year, month, 0).getDate();
77
118
  }
119
+
120
+ function monthsForYearTree(year, start, end, valueFn) {
121
+ return {
122
+ async get(month) {
123
+ month = parseInt(trailingSlash.remove(month));
124
+ return this.inRange(month)
125
+ ? daysForMonthTree(year, month, start, end, valueFn)
126
+ : undefined;
127
+ },
128
+
129
+ inRange(month) {
130
+ if (year === start.year && year === end.year) {
131
+ return month >= start.month && month <= end.month;
132
+ } else if (year === start.year) {
133
+ return month >= start.month;
134
+ } else if (year === end.year) {
135
+ return month <= end.month;
136
+ } else {
137
+ return true;
138
+ }
139
+ },
140
+
141
+ async keys() {
142
+ const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
143
+ return months
144
+ .filter((month) => this.inRange(month))
145
+ .map((month) => twoDigits(month));
146
+ },
147
+ };
148
+ }
149
+
150
+ function twoDigits(number) {
151
+ return number.toString().padStart(2, "0");
152
+ }
153
+
154
+ function yearsTree(start, end, valueFn) {
155
+ return {
156
+ async get(year) {
157
+ year = parseInt(trailingSlash.remove(year));
158
+ return this.inRange(year)
159
+ ? monthsForYearTree(year, start, end, valueFn)
160
+ : undefined;
161
+ },
162
+
163
+ inRange(year) {
164
+ return year >= start.year && year <= end.year;
165
+ },
166
+
167
+ async keys() {
168
+ return Array.from(
169
+ { length: end.year - start.year + 1 },
170
+ (_, i) => start.year + i
171
+ );
172
+ },
173
+ };
174
+ }
@@ -5,7 +5,9 @@ import { toPlainValue } from "../src/utilities.js";
5
5
 
6
6
  describe("calendarTree", () => {
7
7
  test("without a start or end, returns a tree for today", async () => {
8
- const tree = calendarTree();
8
+ const tree = calendarTree({
9
+ value: (year, month, day) => `${year}-${month}-${day}`,
10
+ });
9
11
  const plain = await toPlainValue(tree);
10
12
  const today = new Date();
11
13
  const year = today.getFullYear();
@@ -14,96 +16,104 @@ describe("calendarTree", () => {
14
16
  assert.deepEqual(plain, {
15
17
  [year]: {
16
18
  [month]: {
17
- [day]: null,
19
+ [day]: `${year}-${month}-${day}`,
18
20
  },
19
21
  },
20
22
  });
21
23
  });
22
24
 
23
25
  test("returns a tree for a month range", async () => {
24
- const tree = calendarTree("2025-01", "2025-02");
26
+ const tree = calendarTree({
27
+ start: "2025-01",
28
+ end: "2025-02",
29
+ value: (year, month, day) => `${year}-${month}-${day}`,
30
+ });
25
31
  const plain = await toPlainValue(tree);
26
32
  assert.deepEqual(plain, {
27
33
  2025: {
28
34
  "01": {
29
- "01": null,
30
- "02": null,
31
- "03": null,
32
- "04": null,
33
- "05": null,
34
- "06": null,
35
- "07": null,
36
- "08": null,
37
- "09": null,
38
- 10: null,
39
- 11: null,
40
- 12: null,
41
- 13: null,
42
- 14: null,
43
- 15: null,
44
- 16: null,
45
- 17: null,
46
- 18: null,
47
- 19: null,
48
- 20: null,
49
- 21: null,
50
- 22: null,
51
- 23: null,
52
- 24: null,
53
- 25: null,
54
- 26: null,
55
- 27: null,
56
- 28: null,
57
- 29: null,
58
- 30: null,
59
- 31: null,
35
+ "01": "2025-01-01",
36
+ "02": "2025-01-02",
37
+ "03": "2025-01-03",
38
+ "04": "2025-01-04",
39
+ "05": "2025-01-05",
40
+ "06": "2025-01-06",
41
+ "07": "2025-01-07",
42
+ "08": "2025-01-08",
43
+ "09": "2025-01-09",
44
+ 10: "2025-01-10",
45
+ 11: "2025-01-11",
46
+ 12: "2025-01-12",
47
+ 13: "2025-01-13",
48
+ 14: "2025-01-14",
49
+ 15: "2025-01-15",
50
+ 16: "2025-01-16",
51
+ 17: "2025-01-17",
52
+ 18: "2025-01-18",
53
+ 19: "2025-01-19",
54
+ 20: "2025-01-20",
55
+ 21: "2025-01-21",
56
+ 22: "2025-01-22",
57
+ 23: "2025-01-23",
58
+ 24: "2025-01-24",
59
+ 25: "2025-01-25",
60
+ 26: "2025-01-26",
61
+ 27: "2025-01-27",
62
+ 28: "2025-01-28",
63
+ 29: "2025-01-29",
64
+ 30: "2025-01-30",
65
+ 31: "2025-01-31",
60
66
  },
61
67
  "02": {
62
- "01": null,
63
- "02": null,
64
- "03": null,
65
- "04": null,
66
- "05": null,
67
- "06": null,
68
- "07": null,
69
- "08": null,
70
- "09": null,
71
- 10: null,
72
- 11: null,
73
- 12: null,
74
- 13: null,
75
- 14: null,
76
- 15: null,
77
- 16: null,
78
- 17: null,
79
- 18: null,
80
- 19: null,
81
- 20: null,
82
- 21: null,
83
- 22: null,
84
- 23: null,
85
- 24: null,
86
- 25: null,
87
- 26: null,
88
- 27: null,
89
- 28: null,
68
+ "01": "2025-02-01",
69
+ "02": "2025-02-02",
70
+ "03": "2025-02-03",
71
+ "04": "2025-02-04",
72
+ "05": "2025-02-05",
73
+ "06": "2025-02-06",
74
+ "07": "2025-02-07",
75
+ "08": "2025-02-08",
76
+ "09": "2025-02-09",
77
+ 10: "2025-02-10",
78
+ 11: "2025-02-11",
79
+ 12: "2025-02-12",
80
+ 13: "2025-02-13",
81
+ 14: "2025-02-14",
82
+ 15: "2025-02-15",
83
+ 16: "2025-02-16",
84
+ 17: "2025-02-17",
85
+ 18: "2025-02-18",
86
+ 19: "2025-02-19",
87
+ 20: "2025-02-20",
88
+ 21: "2025-02-21",
89
+ 22: "2025-02-22",
90
+ 23: "2025-02-23",
91
+ 24: "2025-02-24",
92
+ 25: "2025-02-25",
93
+ 26: "2025-02-26",
94
+ 27: "2025-02-27",
95
+ 28: "2025-02-28",
90
96
  },
91
97
  },
92
98
  });
93
99
  });
94
100
 
95
101
  test("returns a tree for a day range", async () => {
96
- const tree = calendarTree("2025-02-27", "2025-03-02");
102
+ const tree = calendarTree({
103
+ start: "2025-02-27",
104
+ end: "2025-03-02",
105
+ value: (year, month, day) => `${year}-${month}-${day}`,
106
+ });
97
107
  const plain = await toPlainValue(tree);
98
108
  assert.deepEqual(plain, {
99
109
  2025: {
100
110
  "02": {
101
- 27: null,
102
- 28: null,
111
+ 27: "2025-02-27",
112
+ 28: "2025-02-28",
103
113
  },
104
114
  "03": {
105
- "01": null,
106
- "02": null,
115
+ "01": "2025-03-01",
116
+ "02": "2025-03-02",
107
117
  },
108
118
  },
109
119
  });