@ifc-lite/cli 0.26.0 → 0.28.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 +1 -1
- package/dist/commands/ask.d.ts +8 -0
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +10 -9
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/bsdd.d.ts.map +1 -1
- package/dist/commands/bsdd.js +49 -13
- package/dist/commands/bsdd.js.map +1 -1
- package/dist/commands/delivery-checks.d.ts +56 -0
- package/dist/commands/delivery-checks.d.ts.map +1 -0
- package/dist/commands/delivery-checks.js +222 -0
- package/dist/commands/delivery-checks.js.map +1 -0
- package/dist/commands/delivery-recipe.d.ts +23 -0
- package/dist/commands/delivery-recipe.d.ts.map +1 -0
- package/dist/commands/delivery-recipe.js +104 -0
- package/dist/commands/delivery-recipe.js.map +1 -0
- package/dist/commands/delivery-report.d.ts +51 -0
- package/dist/commands/delivery-report.d.ts.map +1 -0
- package/dist/commands/delivery-report.js +114 -0
- package/dist/commands/delivery-report.js.map +1 -0
- package/dist/commands/delivery.d.ts +2 -0
- package/dist/commands/delivery.d.ts.map +1 -0
- package/dist/commands/delivery.js +127 -0
- package/dist/commands/delivery.js.map +1 -0
- package/dist/commands/diff-classification-label.d.ts +12 -0
- package/dist/commands/diff-classification-label.d.ts.map +1 -0
- package/dist/commands/diff-classification-label.js +20 -0
- package/dist/commands/diff-classification-label.js.map +1 -0
- package/dist/commands/diff-engine.d.ts.map +1 -1
- package/dist/commands/diff-engine.js +33 -11
- package/dist/commands/diff-engine.js.map +1 -1
- package/dist/commands/diff-test-helpers.d.ts +12 -0
- package/dist/commands/diff-test-helpers.d.ts.map +1 -1
- package/dist/commands/diff-test-helpers.js +68 -0
- package/dist/commands/diff-test-helpers.js.map +1 -1
- package/dist/commands/diff-unit-scale-fixtures.d.ts +34 -0
- package/dist/commands/diff-unit-scale-fixtures.d.ts.map +1 -0
- package/dist/commands/diff-unit-scale-fixtures.js +94 -0
- package/dist/commands/diff-unit-scale-fixtures.js.map +1 -0
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +9 -14
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/gym.js +2 -2
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +8 -0
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/query-aggregation.d.ts +27 -4
- package/dist/commands/query-aggregation.d.ts.map +1 -1
- package/dist/commands/query-aggregation.js +67 -6
- package/dist/commands/query-aggregation.js.map +1 -1
- package/dist/commands/query-output.d.ts +7 -0
- package/dist/commands/query-output.d.ts.map +1 -1
- package/dist/commands/query-output.js +63 -27
- package/dist/commands/query-output.js.map +1 -1
- package/dist/commands/query-storey.d.ts +24 -0
- package/dist/commands/query-storey.d.ts.map +1 -0
- package/dist/commands/query-storey.js +71 -0
- package/dist/commands/query-storey.js.map +1 -0
- package/dist/commands/query.d.ts +2 -25
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +39 -158
- package/dist/commands/query.js.map +1 -1
- package/dist/commands/schedule-aggregate.d.ts +51 -0
- package/dist/commands/schedule-aggregate.d.ts.map +1 -0
- package/dist/commands/schedule-aggregate.js +121 -0
- package/dist/commands/schedule-aggregate.js.map +1 -0
- package/dist/commands/schedule-columns.d.ts +15 -0
- package/dist/commands/schedule-columns.d.ts.map +1 -0
- package/dist/commands/schedule-columns.js +63 -0
- package/dist/commands/schedule-columns.js.map +1 -0
- package/dist/commands/schedule-group.d.ts +65 -0
- package/dist/commands/schedule-group.d.ts.map +1 -0
- package/dist/commands/schedule-group.js +0 -0
- package/dist/commands/schedule-group.js.map +1 -0
- package/dist/commands/schedule-presets.d.ts +21 -0
- package/dist/commands/schedule-presets.d.ts.map +1 -0
- package/dist/commands/schedule-presets.js +62 -0
- package/dist/commands/schedule-presets.js.map +1 -0
- package/dist/commands/schedule-render-html.d.ts +13 -0
- package/dist/commands/schedule-render-html.d.ts.map +1 -0
- package/dist/commands/schedule-render-html.js +100 -0
- package/dist/commands/schedule-render-html.js.map +1 -0
- package/dist/commands/schedule-render-md.d.ts +12 -0
- package/dist/commands/schedule-render-md.d.ts.map +1 -0
- package/dist/commands/schedule-render-md.js +77 -0
- package/dist/commands/schedule-render-md.js.map +1 -0
- package/dist/commands/schedule-render.d.ts +51 -0
- package/dist/commands/schedule-render.d.ts.map +1 -0
- package/dist/commands/schedule-render.js +205 -0
- package/dist/commands/schedule-render.js.map +1 -0
- package/dist/commands/schedule-spec.d.ts +29 -0
- package/dist/commands/schedule-spec.d.ts.map +1 -0
- package/dist/commands/schedule-spec.js +111 -0
- package/dist/commands/schedule-spec.js.map +1 -0
- package/dist/commands/schedule.d.ts +23 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +249 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +192 -17
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/stats-aggregation.d.ts +31 -6
- package/dist/commands/stats-aggregation.d.ts.map +1 -1
- package/dist/commands/stats-aggregation.js +79 -11
- package/dist/commands/stats-aggregation.js.map +1 -1
- package/dist/commands/stats.d.ts.map +1 -1
- package/dist/commands/stats.js +4 -4
- package/dist/commands/stats.js.map +1 -1
- package/dist/commands/validate.d.ts +7 -4
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +9 -6
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/where-filter.d.ts +42 -0
- package/dist/commands/where-filter.d.ts.map +1 -0
- package/dist/commands/where-filter.js +88 -0
- package/dist/commands/where-filter.js.map +1 -0
- package/dist/energy-export.d.ts.map +1 -1
- package/dist/energy-export.js +12 -2
- package/dist/energy-export.js.map +1 -1
- package/dist/geometry-report.d.ts.map +1 -1
- package/dist/geometry-report.js +15 -3
- package/dist/geometry-report.js.map +1 -1
- package/dist/headless-backend.d.ts +20 -5
- package/dist/headless-backend.d.ts.map +1 -1
- package/dist/headless-backend.js +131 -90
- package/dist/headless-backend.js.map +1 -1
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -1
- package/dist/output.d.ts +13 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +21 -0
- package/dist/output.js.map +1 -1
- package/dist/property-filter-match.d.ts +12 -0
- package/dist/property-filter-match.d.ts.map +1 -0
- package/dist/property-filter-match.js +17 -0
- package/dist/property-filter-match.js.map +1 -0
- package/dist/query-overlay-relations.d.ts +29 -0
- package/dist/query-overlay-relations.d.ts.map +1 -0
- package/dist/query-overlay-relations.js +88 -0
- package/dist/query-overlay-relations.js.map +1 -0
- package/dist/query-overlay.d.ts +44 -0
- package/dist/query-overlay.d.ts.map +1 -0
- package/dist/query-overlay.js +100 -0
- package/dist/query-overlay.js.map +1 -0
- package/package.json +21 -21
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* CSV / JSON rendering for `ifc-lite schedule`.
|
|
6
|
+
*
|
|
7
|
+
* A schedule is a list of header names plus one raw value per (row, column).
|
|
8
|
+
* CSV routes every cell through `escapeCsvCell` (RFC 4180 quoting + the
|
|
9
|
+
* spreadsheet-formula-injection guard) shared with the `export` command; a
|
|
10
|
+
* missing value renders as an empty CSV cell and as JSON `null`.
|
|
11
|
+
*/
|
|
12
|
+
import { escapeCsvCell } from '@ifc-lite/export';
|
|
13
|
+
import { columnValueToCsv } from './export.js';
|
|
14
|
+
/**
|
|
15
|
+
* Render the schedule as RFC-4180 CSV. Headers and cells both go through the
|
|
16
|
+
* shared escaper so a header or value containing a comma, quote, newline, or a
|
|
17
|
+
* leading formula trigger is correctly quoted/neutralised.
|
|
18
|
+
*/
|
|
19
|
+
export function renderScheduleCsv(columns, rows) {
|
|
20
|
+
const sep = ',';
|
|
21
|
+
const headerLine = columns.map(c => escapeCsvCell(c.header, { delimiter: sep })).join(sep);
|
|
22
|
+
const dataLines = rows.map(row => row.map(value => escapeCsvCell(columnValueToCsv(value), { delimiter: sep })).join(sep));
|
|
23
|
+
return [headerLine, ...dataLines].join('\n');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Render the schedule as a JSON array of row objects keyed by header. Raw
|
|
27
|
+
* values keep their type; a missing value is `null`.
|
|
28
|
+
*/
|
|
29
|
+
export function renderScheduleJson(columns, rows) {
|
|
30
|
+
return rows.map(row => scheduleRowToJson(columns, row));
|
|
31
|
+
}
|
|
32
|
+
/** One data row as a header-keyed object; a missing value is `null`. */
|
|
33
|
+
function scheduleRowToJson(columns, row) {
|
|
34
|
+
const obj = {};
|
|
35
|
+
columns.forEach((col, i) => {
|
|
36
|
+
const value = row[i];
|
|
37
|
+
obj[col.header] = value == null ? null : value;
|
|
38
|
+
});
|
|
39
|
+
return obj;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The subtotal/total label for a group: `Subtotal (H1=v1, H2=v2)` or `Total`,
|
|
43
|
+
* suffixed with `: <n>` when a `count` aggregation is present (the row count of
|
|
44
|
+
* the group / whole schedule).
|
|
45
|
+
*/
|
|
46
|
+
function subtotalLabel(data) {
|
|
47
|
+
const base = data.kind === 'total'
|
|
48
|
+
? 'Total'
|
|
49
|
+
: `Subtotal (${data.groupValues.map(g => `${g.header}=${g.value == null ? '' : String(g.value)}`).join(', ')})`;
|
|
50
|
+
const count = data.values.find(v => v.spec === 'count');
|
|
51
|
+
return count ? `${base}: ${count.value}` : base;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Pick the column that carries the subtotal/total label. A forced choice
|
|
55
|
+
* (the first group column, or column 0 for the grand total) can collide with
|
|
56
|
+
* a `--subtotals` aggregation targeting that same column — `sum:Area` when
|
|
57
|
+
* `Area` is the first declared column, or the first group-by header — and a
|
|
58
|
+
* fixed-width row array has no spare slot to hold both a text label and a
|
|
59
|
+
* numeric value in the same cell. Prefer, in order: the first group column
|
|
60
|
+
* that is not itself an aggregation target (keeps the label meaningfully
|
|
61
|
+
* tied to a group value), then the first column overall that is not a
|
|
62
|
+
* target, and only when every column is a target — no free slot exists at
|
|
63
|
+
* all — fall back to the historical group/0 choice (that one column's
|
|
64
|
+
* aggregation is what the label displaces; every other aggregation still
|
|
65
|
+
* lands in its own column).
|
|
66
|
+
*/
|
|
67
|
+
function chooseLabelCol(columns, data) {
|
|
68
|
+
const targetIdx = new Set();
|
|
69
|
+
for (const v of data.values) {
|
|
70
|
+
if (v.spec === 'count')
|
|
71
|
+
continue;
|
|
72
|
+
const header = v.spec.slice(v.spec.indexOf(':') + 1);
|
|
73
|
+
const idx = columns.findIndex(c => c.header === header);
|
|
74
|
+
if (idx !== -1)
|
|
75
|
+
targetIdx.add(idx);
|
|
76
|
+
}
|
|
77
|
+
for (const g of data.groupValues) {
|
|
78
|
+
if (!targetIdx.has(g.index))
|
|
79
|
+
return g.index;
|
|
80
|
+
}
|
|
81
|
+
for (let i = 0; i < columns.length; i++) {
|
|
82
|
+
if (!targetIdx.has(i))
|
|
83
|
+
return i;
|
|
84
|
+
}
|
|
85
|
+
return data.groupValues.length > 0 ? data.groupValues[0].index : 0;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A subtotal/total row as a full-width cell array. The label sits in a
|
|
89
|
+
* column none of this row's aggregations target (see `chooseLabelCol`); each
|
|
90
|
+
* numeric aggregation fills its own target column; every other column is
|
|
91
|
+
* blank.
|
|
92
|
+
*
|
|
93
|
+
* `--subtotals` allows more than one aggregation on the same `--columns`
|
|
94
|
+
* header (`sum:Area,avg:Area` is a reasonable request, and the JSON renderer
|
|
95
|
+
* already keys those by `spec` without collision — see `subtotalToJson`).
|
|
96
|
+
* The tabular formats have only one cell per column, so when two or more
|
|
97
|
+
* aggregations target the same column their values are joined into one cell
|
|
98
|
+
* as `"sum: 30, avg: 15"` instead of the second one silently overwriting the
|
|
99
|
+
* first. A single aggregation on a column keeps rendering as its bare value
|
|
100
|
+
* (a number, same as before) so the common case is unaffected.
|
|
101
|
+
*
|
|
102
|
+
* When every declared column is an aggregation target there is no free slot
|
|
103
|
+
* for the label at all (`--columns Area --subtotals sum:Area`), so the label
|
|
104
|
+
* takes a targeted column. Those displaced aggregations are appended to the
|
|
105
|
+
* label cell as `Total (sum: 30, avg: 15)` rather than dropped, which is what
|
|
106
|
+
* used to happen: the tabular formats printed a bare `Total` while JSON still
|
|
107
|
+
* reported the values.
|
|
108
|
+
*
|
|
109
|
+
* Exported so the Markdown/HTML renderers (`schedule-render-md.ts`,
|
|
110
|
+
* `schedule-render-html.ts`) lay out subtotal/total rows identically to CSV —
|
|
111
|
+
* same label, same column placement — instead of re-deriving the shape.
|
|
112
|
+
*/
|
|
113
|
+
export function subtotalCells(columns, data) {
|
|
114
|
+
const cells = columns.map(() => '');
|
|
115
|
+
const labelCol = chooseLabelCol(columns, data);
|
|
116
|
+
// Group every non-count aggregation by its target column index so a
|
|
117
|
+
// collision can be detected before any cell is written. Aggregations
|
|
118
|
+
// targeting the label's own column are kept too (under `labelCol`), to be
|
|
119
|
+
// folded into the label text rather than dropped.
|
|
120
|
+
const byIdx = new Map();
|
|
121
|
+
for (const v of data.values) {
|
|
122
|
+
if (v.spec === 'count')
|
|
123
|
+
continue;
|
|
124
|
+
const header = v.spec.slice(v.spec.indexOf(':') + 1);
|
|
125
|
+
const idx = columns.findIndex(c => c.header === header);
|
|
126
|
+
if (idx === -1)
|
|
127
|
+
continue; // header is not a declared column: nowhere to put it
|
|
128
|
+
const bucket = byIdx.get(idx);
|
|
129
|
+
if (bucket)
|
|
130
|
+
bucket.push(v);
|
|
131
|
+
else
|
|
132
|
+
byIdx.set(idx, [v]);
|
|
133
|
+
}
|
|
134
|
+
const label = subtotalLabel(data);
|
|
135
|
+
const displaced = byIdx.get(labelCol);
|
|
136
|
+
cells[labelCol] = displaced ? `${label} (${joinAggValues(displaced)})` : label;
|
|
137
|
+
for (const [idx, values] of byIdx) {
|
|
138
|
+
if (idx === labelCol)
|
|
139
|
+
continue; // already folded into the label cell
|
|
140
|
+
if (values.length === 1) {
|
|
141
|
+
cells[idx] = values[0].value == null ? '' : values[0].value;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Two-plus aggregations on one column: join as "mode: value" pairs so
|
|
145
|
+
// every value survives instead of the last one winning.
|
|
146
|
+
cells[idx] = joinAggValues(values);
|
|
147
|
+
}
|
|
148
|
+
return cells;
|
|
149
|
+
}
|
|
150
|
+
/** `sum:Area`/`avg:Area` values as the shared `"sum: 30, avg: 15"` cell text. */
|
|
151
|
+
function joinAggValues(values) {
|
|
152
|
+
return values
|
|
153
|
+
.map(v => `${v.spec.slice(0, v.spec.indexOf(':'))}: ${v.value == null ? '' : v.value}`)
|
|
154
|
+
.join(', ');
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Render a schedule with `--subtotals`. Grouped output emits each contiguous
|
|
158
|
+
* group's rows followed by its subtotal row; the grand total closes the table.
|
|
159
|
+
* Without `--group-by` only the flat rows and the grand total are emitted.
|
|
160
|
+
*/
|
|
161
|
+
export function renderScheduleCsvWithSubtotals(columns, plan) {
|
|
162
|
+
const sep = ',';
|
|
163
|
+
const escapeRow = (row) => row.map(value => escapeCsvCell(columnValueToCsv(value), { delimiter: sep })).join(sep);
|
|
164
|
+
const lines = [columns.map(c => escapeCsvCell(c.header, { delimiter: sep })).join(sep)];
|
|
165
|
+
if (plan.groups.length > 0) {
|
|
166
|
+
for (const group of plan.groups) {
|
|
167
|
+
for (const row of group.rows)
|
|
168
|
+
lines.push(escapeRow(row));
|
|
169
|
+
lines.push(escapeRow(subtotalCells(columns, group.subtotal)));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
for (const row of plan.rows)
|
|
174
|
+
lines.push(escapeRow(row));
|
|
175
|
+
}
|
|
176
|
+
lines.push(escapeRow(subtotalCells(columns, plan.total)));
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
}
|
|
179
|
+
/** The JSON form of a subtotal/total row: `__row` marker + group + aggregated fields. */
|
|
180
|
+
function subtotalToJson(data) {
|
|
181
|
+
const obj = { __row: data.kind };
|
|
182
|
+
for (const g of data.groupValues)
|
|
183
|
+
obj[g.header] = g.value == null ? null : g.value;
|
|
184
|
+
for (const v of data.values)
|
|
185
|
+
obj[v.spec] = v.value;
|
|
186
|
+
return obj;
|
|
187
|
+
}
|
|
188
|
+
/** Render a schedule with `--subtotals` as flat JSON rows plus `__row`-marked subtotal/total objects. */
|
|
189
|
+
export function renderScheduleJsonWithSubtotals(columns, plan) {
|
|
190
|
+
const out = [];
|
|
191
|
+
if (plan.groups.length > 0) {
|
|
192
|
+
for (const group of plan.groups) {
|
|
193
|
+
for (const row of group.rows)
|
|
194
|
+
out.push(scheduleRowToJson(columns, row));
|
|
195
|
+
out.push(subtotalToJson(group.subtotal));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
for (const row of plan.rows)
|
|
200
|
+
out.push(scheduleRowToJson(columns, row));
|
|
201
|
+
}
|
|
202
|
+
out.push(subtotalToJson(plan.total));
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=schedule-render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule-render.js","sourceRoot":"","sources":["../../src/commands/schedule-render.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** The reusable definition persisted by `--save` and consumed by `--spec`. */
|
|
2
|
+
export interface ScheduleSpec {
|
|
3
|
+
preset?: string;
|
|
4
|
+
type?: string;
|
|
5
|
+
columns?: string;
|
|
6
|
+
where?: string;
|
|
7
|
+
sort?: string;
|
|
8
|
+
groupBy?: string;
|
|
9
|
+
subtotals?: string;
|
|
10
|
+
format?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Load a `--spec` file. Fatal (not a thrown exception the top-level handler
|
|
14
|
+
* reformats) on every way a spec can fail to be a usable schedule definition:
|
|
15
|
+
* the file doesn't exist or isn't readable, its content isn't valid JSON,
|
|
16
|
+
* isn't a JSON object, or has a declared field that isn't a string — each
|
|
17
|
+
* would otherwise surface only once `schedule.ts` tries to use the bad value
|
|
18
|
+
* (or, for a non-object/wrong-type field, might silently coerce into an empty
|
|
19
|
+
* `--columns`/`--type`, producing an empty schedule with no error at all).
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadScheduleSpec(path: string): Promise<ScheduleSpec>;
|
|
22
|
+
/**
|
|
23
|
+
* Write the schedule definition actually used by this invocation (after
|
|
24
|
+
* `--preset` defaults are folded in, so a saved spec is self-contained and
|
|
25
|
+
* `--spec`-loadable without also needing the original `--preset`) to `path`.
|
|
26
|
+
* Undefined fields are omitted rather than written as `null`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function saveScheduleSpec(path: string, spec: ScheduleSpec): Promise<void>;
|
|
29
|
+
//# sourceMappingURL=schedule-spec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule-spec.d.ts","sourceRoot":"","sources":["../../src/commands/schedule-spec.ts"],"names":[],"mappings":"AA6BA,8EAA8E;AAC9E,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwD1E;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtF"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* `--save`/`--spec` reusable schedule definitions for `ifc-lite schedule`.
|
|
6
|
+
*
|
|
7
|
+
* A spec is a small JSON file holding exactly the flag strings the CLI itself
|
|
8
|
+
* accepts — `--type`, `--columns`, `--where`, `--sort`, `--group-by`,
|
|
9
|
+
* `--subtotals`, `--format` — plus an optional `preset` field naming a
|
|
10
|
+
* `--preset` to start from. It is a plain file at whatever path the caller
|
|
11
|
+
* gives `--spec`/`--save`, not a config-directory convention: `--save` writes
|
|
12
|
+
* one, `--spec` reads one back.
|
|
13
|
+
*
|
|
14
|
+
* Precedence when both a spec and an explicit flag are given (`schedule.ts`):
|
|
15
|
+
* explicit CLI flag > spec field > preset field (whether `--preset` on the
|
|
16
|
+
* command line or `preset` inside the spec) — the same "most specific wins"
|
|
17
|
+
* rule `--preset` already uses against explicit flags.
|
|
18
|
+
*
|
|
19
|
+
* A spec naming an unresolvable `preset`, or missing/malformed in a way that
|
|
20
|
+
* would otherwise silently resolve to an empty schedule, is a `fatal(...)`:
|
|
21
|
+
* failing loudly beats emitting a table with no rows or columns.
|
|
22
|
+
*/
|
|
23
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
24
|
+
import { fatal } from '../output.js';
|
|
25
|
+
import { logger } from '../logger.js';
|
|
26
|
+
import { parseWhereFilter } from './where-filter.js';
|
|
27
|
+
const SPEC_FIELDS = [
|
|
28
|
+
'preset', 'type', 'columns', 'where', 'sort', 'groupBy', 'subtotals', 'format',
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Load a `--spec` file. Fatal (not a thrown exception the top-level handler
|
|
32
|
+
* reformats) on every way a spec can fail to be a usable schedule definition:
|
|
33
|
+
* the file doesn't exist or isn't readable, its content isn't valid JSON,
|
|
34
|
+
* isn't a JSON object, or has a declared field that isn't a string — each
|
|
35
|
+
* would otherwise surface only once `schedule.ts` tries to use the bad value
|
|
36
|
+
* (or, for a non-object/wrong-type field, might silently coerce into an empty
|
|
37
|
+
* `--columns`/`--type`, producing an empty schedule with no error at all).
|
|
38
|
+
*/
|
|
39
|
+
export async function loadScheduleSpec(path) {
|
|
40
|
+
let content;
|
|
41
|
+
try {
|
|
42
|
+
content = await readFile(path, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
fatal(`--spec "${path}" could not be read: ${err.message}`);
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(content);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
fatal(`--spec "${path}" is not valid JSON: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
55
|
+
fatal(`--spec "${path}" must be a JSON object with schedule fields (type, columns, where, sort, groupBy, subtotals, format, preset), got ${Array.isArray(parsed) ? 'an array' : typeof parsed}.`);
|
|
56
|
+
}
|
|
57
|
+
const obj = parsed;
|
|
58
|
+
// An unrecognised key (a mistyped "group-by" instead of "groupBy", say) used
|
|
59
|
+
// to be silently ignored by the loop below, which only ever reads the
|
|
60
|
+
// fields it already knows about — the mistyped key had no effect and the
|
|
61
|
+
// command exited 0 with whatever the OTHER, correctly-spelled fields
|
|
62
|
+
// produced (an ungrouped schedule, for a mistyped "groupBy"), never
|
|
63
|
+
// surfacing that the field was dropped. Reject it up front instead.
|
|
64
|
+
const unknownKeys = Object.keys(obj).filter(k => !SPEC_FIELDS.includes(k));
|
|
65
|
+
if (unknownKeys.length > 0) {
|
|
66
|
+
fatal(`--spec "${path}" has unrecognised field(s): ${unknownKeys.join(', ')}. Valid fields: ${SPEC_FIELDS.join(', ')}.`);
|
|
67
|
+
}
|
|
68
|
+
const spec = {};
|
|
69
|
+
for (const field of SPEC_FIELDS) {
|
|
70
|
+
if (!(field in obj))
|
|
71
|
+
continue;
|
|
72
|
+
const value = obj[field];
|
|
73
|
+
if (typeof value !== 'string') {
|
|
74
|
+
fatal(`--spec "${path}": field "${field}" must be a string, got ${Array.isArray(value) ? 'an array' : typeof value}.`);
|
|
75
|
+
}
|
|
76
|
+
spec[field] = value;
|
|
77
|
+
}
|
|
78
|
+
if (spec.preset === undefined && spec.type === undefined) {
|
|
79
|
+
fatal(`--spec "${path}" declares neither "preset" nor "type" — nothing to schedule. Expected at least one, plus "columns" unless a preset supplies it.`);
|
|
80
|
+
}
|
|
81
|
+
// Validate `where` the same way an explicit --where flag is (schedule.ts
|
|
82
|
+
// calls the identical parseWhereFilter once it applies the filter) —
|
|
83
|
+
// eagerly here, rather than deferring the same fatal(...) until the run
|
|
84
|
+
// gets around to using it (by which point --save, if also given, may
|
|
85
|
+
// already have written a spec built from this same broken value).
|
|
86
|
+
if (spec.where !== undefined) {
|
|
87
|
+
parseWhereFilter(spec.where);
|
|
88
|
+
}
|
|
89
|
+
return spec;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Write the schedule definition actually used by this invocation (after
|
|
93
|
+
* `--preset` defaults are folded in, so a saved spec is self-contained and
|
|
94
|
+
* `--spec`-loadable without also needing the original `--preset`) to `path`.
|
|
95
|
+
* Undefined fields are omitted rather than written as `null`.
|
|
96
|
+
*/
|
|
97
|
+
export async function saveScheduleSpec(path, spec) {
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const field of SPEC_FIELDS) {
|
|
100
|
+
if (spec[field] !== undefined)
|
|
101
|
+
out[field] = spec[field];
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
await writeFile(path, JSON.stringify(out, null, 2) + '\n');
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
fatal(`--save "${path}" could not be written: ${err.message}`);
|
|
108
|
+
}
|
|
109
|
+
logger.info(`Schedule spec written to ${path}`);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=schedule-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule-spec.js","sourceRoot":"","sources":["../../src/commands/schedule-spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ScheduleColumn } from './schedule-columns.js';
|
|
2
|
+
import { type ScheduleRow } from './schedule-render.js';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve one column path against one entity.
|
|
5
|
+
*
|
|
6
|
+
* A `Set.Member` path (and the five native attributes) goes through the shared
|
|
7
|
+
* `resolveColumnValue` the `export` command uses, so schedule columns inherit
|
|
8
|
+
* its type-inheritance / same-named-pset / complex-property behaviour and agree
|
|
9
|
+
* with `--where`. A plain attribute name the shared resolver does not treat as
|
|
10
|
+
* native (e.g. `Tag`, `PredefinedType`) is then read from the canonical
|
|
11
|
+
* entity-attribute list — the same surface `query --attributes` exposes — so a
|
|
12
|
+
* missing value stays `null`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveScheduleValue(entity: any, path: string, bim: any): unknown;
|
|
15
|
+
/**
|
|
16
|
+
* Build one row per entity, resolving every declared column. Reads
|
|
17
|
+
* properties/quantities/attributes/materials at most once per entity
|
|
18
|
+
* (see `memoizedColumnAccessors`) — measured 5.6x on FM_ARC_DigitalHub,
|
|
19
|
+
* 6 columns: 40.6ms -> 7.2ms per row build.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildScheduleRows(entities: any[], columns: ScheduleColumn[], bim: any): ScheduleRow[];
|
|
22
|
+
export declare function scheduleCommand(args: string[]): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=schedule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../../src/commands/schedule.ts"],"names":[],"mappings":"AAmDA,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,sBAAsB,CAAC;AAW9B;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CA2DjF;AAgCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,GAAG,EAAE,GAAG,GAAG,WAAW,EAAE,CAGrG;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiHnE"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* ifc-lite schedule <file.ifc> [--preset <name>] --type <IfcClass>
|
|
6
|
+
* --columns "<Header>=<path>[, ...]"
|
|
7
|
+
* [--where "<expr>"] [--sort "<Header>[:asc|desc][, ...]"]
|
|
8
|
+
* [--group-by "<Header>[, ...]"]
|
|
9
|
+
* [--subtotals "<agg>[, ...]"] [--format csv|json|md|html]
|
|
10
|
+
* [--spec <file.json>] [--save <file.json>]
|
|
11
|
+
*
|
|
12
|
+
* `--preset door|window|space|wall|material-takeoff` supplies a default
|
|
13
|
+
* `--type` and `--columns` (and, for some presets, a default group-by/sort/
|
|
14
|
+
* subtotals) so a common schedule runs with no other flags. An explicit flag of
|
|
15
|
+
* the same name overrides the preset's default; `--where`/`--format` apply as
|
|
16
|
+
* normal. See `schedule-presets.ts`.
|
|
17
|
+
*
|
|
18
|
+
* `--spec <file.json>` loads a saved schedule definition (see
|
|
19
|
+
* `schedule-spec.ts`) supplying the same defaults a preset would (lower
|
|
20
|
+
* priority than an explicit flag, higher priority than `--preset`); `--save
|
|
21
|
+
* <file.json>` writes the definition this invocation resolved to — after any
|
|
22
|
+
* `--preset`/`--spec` defaults are folded in — so it can be re-run later with
|
|
23
|
+
* `--spec` alone. A spec that names an unresolvable preset, isn't valid JSON,
|
|
24
|
+
* or is missing both `type` and `preset` is a `fatal(...)`, never a silent
|
|
25
|
+
* empty schedule.
|
|
26
|
+
*
|
|
27
|
+
* Produce a tabular schedule of one IFC class: pick the entities of `--type`,
|
|
28
|
+
* (optionally) narrow them with the same `--where` filter `query` uses, and
|
|
29
|
+
* emit one row per entity with the requested columns. A column path is a plain
|
|
30
|
+
* attribute name, a `PsetName.PropName`, or a `QtoName.QtyName`, resolved
|
|
31
|
+
* through the SAME resolver the `export` command uses so schedule columns and
|
|
32
|
+
* query filters agree on type-inheritance / same-named-pset / complex-property
|
|
33
|
+
* behaviour. `--format md` emits a GFM table; `--format html` emits a
|
|
34
|
+
* standalone HTML document — both lay out `--group-by`/`--subtotals` rows
|
|
35
|
+
* identically to CSV/JSON (see `schedule-render-md.ts`/`schedule-render-html.ts`)
|
|
36
|
+
* and escape cell text for their format (`|`/newline for Markdown;
|
|
37
|
+
* `&`/`<`/`>`/quotes for HTML — model text is untrusted).
|
|
38
|
+
*
|
|
39
|
+
* `--sort`, `--group-by` and `--subtotals` all name column HEADERS (never
|
|
40
|
+
* paths); a header that is not among `--columns` is a `fatal(...)`. Rows are
|
|
41
|
+
* ordered by group first (so each group is contiguous) then by the remaining
|
|
42
|
+
* sort keys, with the original entity order as the stable tiebreaker.
|
|
43
|
+
* `--subtotals` emits a subtotal row after each group and a grand-total row;
|
|
44
|
+
* without `--group-by` it emits only the grand total.
|
|
45
|
+
*/
|
|
46
|
+
import { createHeadlessContext } from '../loader.js';
|
|
47
|
+
import { getFlag, fatal, printJson, firstNonBlank, isBlank } from '../output.js';
|
|
48
|
+
import { normalizeTypeName, parseWhereFilter, applyWhereFilter } from './query.js';
|
|
49
|
+
import { resolveColumnValue } from './export.js';
|
|
50
|
+
import { parseColumnSpec } from './schedule-columns.js';
|
|
51
|
+
import { renderScheduleCsv, renderScheduleJson, renderScheduleCsvWithSubtotals, renderScheduleJsonWithSubtotals, } from './schedule-render.js';
|
|
52
|
+
import { renderScheduleMarkdown, renderScheduleMarkdownWithSubtotals } from './schedule-render-md.js';
|
|
53
|
+
import { renderScheduleHtml, renderScheduleHtmlWithSubtotals } from './schedule-render-html.js';
|
|
54
|
+
import { parseSortSpec, parseGroupBySpec, orderRows } from './schedule-group.js';
|
|
55
|
+
import { parseSubtotalsSpec, buildSubtotalPlan } from './schedule-aggregate.js';
|
|
56
|
+
import { resolvePreset } from './schedule-presets.js';
|
|
57
|
+
import { loadScheduleSpec, saveScheduleSpec } from './schedule-spec.js';
|
|
58
|
+
const FORMATS = ['csv', 'json', 'md', 'html'];
|
|
59
|
+
/**
|
|
60
|
+
* Resolve one column path against one entity.
|
|
61
|
+
*
|
|
62
|
+
* A `Set.Member` path (and the five native attributes) goes through the shared
|
|
63
|
+
* `resolveColumnValue` the `export` command uses, so schedule columns inherit
|
|
64
|
+
* its type-inheritance / same-named-pset / complex-property behaviour and agree
|
|
65
|
+
* with `--where`. A plain attribute name the shared resolver does not treat as
|
|
66
|
+
* native (e.g. `Tag`, `PredefinedType`) is then read from the canonical
|
|
67
|
+
* entity-attribute list — the same surface `query --attributes` exposes — so a
|
|
68
|
+
* missing value stays `null`.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveScheduleValue(entity, path, bim) {
|
|
71
|
+
// `Material` is a pseudo-column: resolve the element's associated material
|
|
72
|
+
// name through the same fallback chain `packages/mcp/src/material-naming.ts`
|
|
73
|
+
// `materialFallbackName` applies (duplicated locally the same way
|
|
74
|
+
// `firstNonBlank`/`isBlank` already are in `output.ts` — a cross-package
|
|
75
|
+
// import would widen `@ifc-lite/mcp`'s public surface for one helper). A
|
|
76
|
+
// plain `.name`/`.materials[0]` pair (the pre-fix behaviour) only covers a
|
|
77
|
+
// bare `IfcMaterial` or `IfcMaterialList`: the normal `IfcWall` shape is a
|
|
78
|
+
// `MaterialLayerSet` with no top-level `LayerSetName`, whose real name
|
|
79
|
+
// lives on `layers[]`/`profiles[]`/`constituents[]` — reading only
|
|
80
|
+
// `.name`/`.materials[0]` returns `null` for every such wall, and
|
|
81
|
+
// `--preset material-takeoff`'s default `--group-by Material` collapses
|
|
82
|
+
// them into one unlabelled group instead of one group per real material.
|
|
83
|
+
// A whitespace-only leaf name falls through the same way (#3714's family),
|
|
84
|
+
// and a fully blank result renders `(no material)` — the same placeholder
|
|
85
|
+
// `query --group-by material` uses — rather than a silent empty cell, so a
|
|
86
|
+
// group-by/subtotal on `Material` buckets blank elements together under a
|
|
87
|
+
// readable label instead of splitting on `null` vs `''` vs whitespace.
|
|
88
|
+
if (path === 'Material') {
|
|
89
|
+
const mat = bim.materials(entity.ref);
|
|
90
|
+
if (!mat)
|
|
91
|
+
return '(no material)';
|
|
92
|
+
return (firstNonBlank(mat.name, mat.materials?.[0]?.name, mat.layers?.find(l => !isBlank(l.materialName))?.materialName, mat.profiles?.find(p => !isBlank(p.materialName))?.materialName, mat.constituents?.find(c => !isBlank(c.materialName))?.materialName) ?? '(no material)');
|
|
93
|
+
}
|
|
94
|
+
// A dot-free path checks the canonical entity-attribute list FIRST.
|
|
95
|
+
// resolveColumnValue's own bare-name fallback (below) sweeps EVERY property
|
|
96
|
+
// and quantity set for a member with that literal name — a user-authored
|
|
97
|
+
// pset property that happens to share a schema attribute's name (e.g. a
|
|
98
|
+
// "Tag" property in some custom Pset) would otherwise shadow the real
|
|
99
|
+
// IfcDoor.Tag attribute that a column like --preset door's `Mark=Tag`
|
|
100
|
+
// means. A dotted `Pset.Prop`/`Qto.Qty` path is unambiguous and always
|
|
101
|
+
// goes straight through resolveColumnValue.
|
|
102
|
+
if (!path.includes('.')) {
|
|
103
|
+
const attrs = bim.attributes(entity.ref);
|
|
104
|
+
const hit = attrs.find(a => a.name === path);
|
|
105
|
+
if (hit && hit.value != null)
|
|
106
|
+
return hit.value;
|
|
107
|
+
}
|
|
108
|
+
const viaColumn = resolveColumnValue(entity, path, bim);
|
|
109
|
+
if (viaColumn != null)
|
|
110
|
+
return viaColumn;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/** Wrap a single-argument function so a repeated call with the SAME argument (by `===`) is served from cache instead of re-invoked. */
|
|
114
|
+
function memoize(fn) {
|
|
115
|
+
const cache = new Map();
|
|
116
|
+
return (arg) => {
|
|
117
|
+
const hit = cache.get(arg);
|
|
118
|
+
if (hit !== undefined || cache.has(arg))
|
|
119
|
+
return hit;
|
|
120
|
+
const result = fn(arg);
|
|
121
|
+
cache.set(arg, result);
|
|
122
|
+
return result;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Wrap `bim` so `properties`/`quantities`/`attributes`/`materials` — the only
|
|
127
|
+
* four methods `resolveScheduleValue` (and the `resolveColumnValue` it
|
|
128
|
+
* delegates to) ever call — are read at most ONCE per entity ref for the
|
|
129
|
+
* whole schedule, however many `--columns` request them. Resolving each
|
|
130
|
+
* column independently means an N-column row calls e.g. `bim.properties(ref)`
|
|
131
|
+
* N times for the identical entity, re-walking the same property-set list
|
|
132
|
+
* from scratch each time.
|
|
133
|
+
*/
|
|
134
|
+
function memoizedColumnAccessors(bim) {
|
|
135
|
+
return {
|
|
136
|
+
properties: memoize((ref) => bim.properties(ref)),
|
|
137
|
+
quantities: memoize((ref) => bim.quantities(ref)),
|
|
138
|
+
attributes: memoize((ref) => bim.attributes(ref)),
|
|
139
|
+
materials: memoize((ref) => bim.materials(ref)),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build one row per entity, resolving every declared column. Reads
|
|
144
|
+
* properties/quantities/attributes/materials at most once per entity
|
|
145
|
+
* (see `memoizedColumnAccessors`) — measured 5.6x on FM_ARC_DigitalHub,
|
|
146
|
+
* 6 columns: 40.6ms -> 7.2ms per row build.
|
|
147
|
+
*/
|
|
148
|
+
export function buildScheduleRows(entities, columns, bim) {
|
|
149
|
+
const cached = memoizedColumnAccessors(bim);
|
|
150
|
+
return entities.map((e) => columns.map(col => resolveScheduleValue(e, col.path, cached)));
|
|
151
|
+
}
|
|
152
|
+
export async function scheduleCommand(args) {
|
|
153
|
+
const filePath = args.find(a => !a.startsWith('-'));
|
|
154
|
+
if (!filePath) {
|
|
155
|
+
fatal('Usage: ifc-lite schedule <file.ifc> --type IfcDoor --columns "Name=Name, Mark=Pset_DoorCommon.Reference" [--where PsetName.Prop=Value] [--format csv|json]');
|
|
156
|
+
}
|
|
157
|
+
// --spec loads a saved definition (see schedule-spec.ts); its fields sit
|
|
158
|
+
// between an explicit flag (wins) and a --preset default (loses) in
|
|
159
|
+
// precedence, whether the preset comes from --preset or the spec's own
|
|
160
|
+
// "preset" field.
|
|
161
|
+
const specPath = getFlag(args, '--spec');
|
|
162
|
+
const spec = specPath ? await loadScheduleSpec(specPath) : undefined;
|
|
163
|
+
// --preset supplies default type/columns (and optionally group-by/sort/
|
|
164
|
+
// subtotals); an explicit flag of the same name overrides the preset default.
|
|
165
|
+
const presetName = getFlag(args, '--preset') ?? spec?.preset;
|
|
166
|
+
const preset = presetName ? resolvePreset(presetName) : undefined;
|
|
167
|
+
const typeRaw = getFlag(args, '--type') ?? spec?.type ?? preset?.type;
|
|
168
|
+
if (!typeRaw) {
|
|
169
|
+
fatal('--type is required (or pass --preset/--spec), e.g. --type IfcDoor or --preset door');
|
|
170
|
+
}
|
|
171
|
+
const type = normalizeTypeName(typeRaw);
|
|
172
|
+
const columnsSpec = getFlag(args, '--columns') ?? spec?.columns ?? preset?.columns;
|
|
173
|
+
const columns = parseColumnSpec(columnsSpec);
|
|
174
|
+
const format = (getFlag(args, '--format') ?? spec?.format ?? 'csv').toLowerCase();
|
|
175
|
+
if (!FORMATS.includes(format)) {
|
|
176
|
+
fatal(`Unknown --format "${format}". Expected ${FORMATS.join(', ')}.`);
|
|
177
|
+
}
|
|
178
|
+
const whereFilter = getFlag(args, '--where') ?? spec?.where;
|
|
179
|
+
// --sort / --group-by / --subtotals name declared column HEADERS; an unknown
|
|
180
|
+
// header is a fatal(...) with the valid headers listed (raised during parse).
|
|
181
|
+
const sortSpec = getFlag(args, '--sort') ?? spec?.sort ?? preset?.sort;
|
|
182
|
+
const groupBySpec = getFlag(args, '--group-by') ?? spec?.groupBy ?? preset?.groupBy;
|
|
183
|
+
const subtotalsSpec = getFlag(args, '--subtotals') ?? spec?.subtotals ?? preset?.subtotals;
|
|
184
|
+
const sortKeys = parseSortSpec(sortSpec, columns);
|
|
185
|
+
const groupKeys = parseGroupBySpec(groupBySpec, columns);
|
|
186
|
+
const subtotalAggs = parseSubtotalsSpec(subtotalsSpec, columns);
|
|
187
|
+
// --save's target path is captured here (so --save itself is validated up
|
|
188
|
+
// front, same as every other flag) but the file is written only once this
|
|
189
|
+
// run has actually succeeded — see below.
|
|
190
|
+
const savePath = getFlag(args, '--save');
|
|
191
|
+
const { bim } = await createHeadlessContext(filePath);
|
|
192
|
+
let entities = bim.query().byType(...type.split(',')).toArray();
|
|
193
|
+
// --where reuses the exact query filter (property sets + quantity fallback).
|
|
194
|
+
if (whereFilter) {
|
|
195
|
+
const parsed = parseWhereFilter(whereFilter);
|
|
196
|
+
entities = applyWhereFilter(entities, parsed, bim);
|
|
197
|
+
}
|
|
198
|
+
// One row per entity, in entity order — the stable tiebreaker for ordering.
|
|
199
|
+
let rows = buildScheduleRows(entities, columns, bim);
|
|
200
|
+
// Order once, after row assembly: groups contiguous, then remaining sort keys.
|
|
201
|
+
if (sortKeys.length > 0 || groupKeys.length > 0) {
|
|
202
|
+
rows = orderRows(rows, sortKeys, groupKeys);
|
|
203
|
+
}
|
|
204
|
+
// --save persists the definition this invocation actually resolved to
|
|
205
|
+
// (after --preset/--spec defaults are folded in), so the written file is
|
|
206
|
+
// self-contained and reloadable with --spec alone. Written only here, once
|
|
207
|
+
// --type/--where/--sort/--group-by/--subtotals have all parsed AND the
|
|
208
|
+
// input file has loaded successfully — writing it any earlier (its
|
|
209
|
+
// original position, before --where was even parsed and before the input
|
|
210
|
+
// file was opened) could persist a broken spec despite the run's own
|
|
211
|
+
// non-zero exit.
|
|
212
|
+
if (savePath) {
|
|
213
|
+
await saveScheduleSpec(savePath, {
|
|
214
|
+
type, columns: columnsSpec, where: whereFilter,
|
|
215
|
+
sort: sortSpec, groupBy: groupBySpec, subtotals: subtotalsSpec, format,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (subtotalAggs.length > 0) {
|
|
219
|
+
const plan = buildSubtotalPlan(rows, groupKeys, subtotalAggs);
|
|
220
|
+
if (format === 'json') {
|
|
221
|
+
printJson(renderScheduleJsonWithSubtotals(columns, plan));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (format === 'md') {
|
|
225
|
+
process.stdout.write(renderScheduleMarkdownWithSubtotals(columns, plan) + '\n');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (format === 'html') {
|
|
229
|
+
process.stdout.write(renderScheduleHtmlWithSubtotals(columns, plan));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
process.stdout.write(renderScheduleCsvWithSubtotals(columns, plan) + '\n');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (format === 'json') {
|
|
236
|
+
printJson(renderScheduleJson(columns, rows));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (format === 'md') {
|
|
240
|
+
process.stdout.write(renderScheduleMarkdown(columns, rows) + '\n');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (format === 'html') {
|
|
244
|
+
process.stdout.write(renderScheduleHtml(columns, rows));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
process.stdout.write(renderScheduleCsv(columns, rows) + '\n');
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=schedule.js.map
|