@fnndsc/brasa 0.23.2 → 0.25.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/dist/buildinfo.json +1 -1
- package/dist/builtins/fs/pull.args.d.ts +1 -1
- package/dist/builtins/fs/pull.args.js +5 -3
- package/dist/builtins/fs/pull.args.js.map +1 -1
- package/dist/builtins/fs/pull.js +2 -2
- package/dist/builtins/fs/touch.js +1 -1
- package/dist/builtins/fs/touch.js.map +1 -1
- package/dist/builtins/help.js +134 -10
- package/dist/builtins/help.js.map +1 -1
- package/dist/builtins/index.d.ts +6 -0
- package/dist/builtins/index.js +6 -0
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/net/query.csv.js +14 -0
- package/dist/builtins/net/query.csv.js.map +1 -1
- package/dist/builtins/net/query.js +3 -0
- package/dist/builtins/net/query.js.map +1 -1
- package/dist/builtins/pluginExecute.js +28 -3
- package/dist/builtins/pluginExecute.js.map +1 -1
- package/dist/builtins/proc.js +32 -2
- package/dist/builtins/proc.js.map +1 -1
- package/dist/builtins/res/dicom.d.ts +35 -1
- package/dist/builtins/res/dicom.js +15 -2
- package/dist/builtins/res/dicom.js.map +1 -1
- package/dist/builtins/res/expect.d.ts +40 -0
- package/dist/builtins/res/expect.js +447 -0
- package/dist/builtins/res/expect.js.map +1 -0
- package/dist/builtins/res/gather.d.ts +10 -0
- package/dist/builtins/res/gather.js +224 -0
- package/dist/builtins/res/gather.js.map +1 -0
- package/dist/builtins/res/gather.store.d.ts +131 -0
- package/dist/builtins/res/gather.store.js +225 -0
- package/dist/builtins/res/gather.store.js.map +1 -0
- package/dist/builtins/res/pipeline.args.d.ts +5 -2
- package/dist/builtins/res/pipeline.args.js +8 -3
- package/dist/builtins/res/pipeline.args.js.map +1 -1
- package/dist/builtins/res/pipeline.js +23 -0
- package/dist/builtins/res/pipeline.js.map +1 -1
- package/dist/builtins/res/play.d.ts +70 -0
- package/dist/builtins/res/play.js +334 -0
- package/dist/builtins/res/play.js.map +1 -0
- package/dist/builtins/res/plugin.js +21 -6
- package/dist/builtins/res/plugin.js.map +1 -1
- package/dist/builtins/res/record.d.ts +49 -0
- package/dist/builtins/res/record.js +150 -0
- package/dist/builtins/res/record.js.map +1 -0
- package/dist/builtins/res/runWait.d.ts +49 -0
- package/dist/builtins/res/runWait.js +193 -0
- package/dist/builtins/res/runWait.js.map +1 -0
- package/dist/builtins/sys/motd.d.ts +41 -0
- package/dist/builtins/sys/motd.js +139 -0
- package/dist/builtins/sys/motd.js.map +1 -0
- package/dist/builtins/sys/weather.d.ts +132 -0
- package/dist/builtins/sys/weather.js +297 -0
- package/dist/builtins/sys/weather.js.map +1 -0
- package/dist/command-keys.js +4 -0
- package/dist/command-keys.js.map +1 -1
- package/dist/core/connect.d.ts +31 -1
- package/dist/core/connect.js +32 -1
- package/dist/core/connect.js.map +1 -1
- package/dist/core/dispatch.d.ts +0 -14
- package/dist/core/dispatch.js +85 -27
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/engine.d.ts +10 -0
- package/dist/core/engine.js +9 -0
- package/dist/core/engine.js.map +1 -1
- package/dist/core/expansion.d.ts +70 -0
- package/dist/core/expansion.js +289 -0
- package/dist/core/expansion.js.map +1 -0
- package/dist/core/promptContext.js +5 -1
- package/dist/core/promptContext.js.map +1 -1
- package/dist/lib/duration.d.ts +17 -0
- package/dist/lib/duration.js +25 -0
- package/dist/lib/duration.js.map +1 -0
- package/dist/lib/parser.d.ts +50 -0
- package/dist/lib/parser.js +119 -0
- package/dist/lib/parser.js.map +1 -1
- package/dist/logo/brain.d.ts +6 -27
- package/dist/logo/brain.js +5 -250
- package/dist/logo/brain.js.map +1 -1
- package/dist/session/answer.d.ts +141 -0
- package/dist/session/answer.js +163 -0
- package/dist/session/answer.js.map +1 -0
- package/dist/session/answerAdapters.d.ts +4 -0
- package/dist/session/answerAdapters.js +106 -0
- package/dist/session/answerAdapters.js.map +1 -0
- package/dist/session/recent.d.ts +68 -0
- package/dist/session/recent.js +99 -0
- package/dist/session/recent.js.map +1 -0
- package/dist/session/recorder.d.ts +61 -0
- package/dist/session/recorder.js +95 -0
- package/dist/session/recorder.js.map +1 -0
- package/package.json +5 -5
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Builtin gather command: the session's cohort as a kernel subject.
|
|
3
|
+
*
|
|
4
|
+
* Subcommands: list (the default), add, remove, clear, name.
|
|
5
|
+
*
|
|
6
|
+
* Gathering was a surface gesture before it was a verb, which meant the set
|
|
7
|
+
* a session was working on could only be built by pressing on it. As a
|
|
8
|
+
* subject it is scriptable: a line adds to it, a manifest builds one, and
|
|
9
|
+
* the surface becomes one client of the cohort rather than the only place
|
|
10
|
+
* it exists.
|
|
11
|
+
*
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { envelope_ok, envelope_error } from '@fnndsc/cumin';
|
|
17
|
+
import { table_render } from '@fnndsc/chili/screen/screen.js';
|
|
18
|
+
import { commandArgs_process, path_resolve } from '../utils.js';
|
|
19
|
+
import { cohort_read, cohort_write, member_key, members_drop, members_merge, pathKind_determine, seriesUID_ofPath, } from './gather.store.js';
|
|
20
|
+
/** The model kind under which a cohort travels to a surface. */
|
|
21
|
+
export const GATHER_MODEL_KIND = 'gather.cohort';
|
|
22
|
+
/**
|
|
23
|
+
* Renders a refusal.
|
|
24
|
+
*
|
|
25
|
+
* @param message - What the operator needs to know.
|
|
26
|
+
* @returns An error envelope, with the process marked failed.
|
|
27
|
+
*/
|
|
28
|
+
function gather_error(message) {
|
|
29
|
+
process.exitCode = 1;
|
|
30
|
+
return envelope_error('', undefined, `${chalk.red(message)}\n`);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* What to call a member in a listing.
|
|
34
|
+
*
|
|
35
|
+
* @param member - The member to name.
|
|
36
|
+
* @returns Its description, else the leaf of its path.
|
|
37
|
+
*/
|
|
38
|
+
function member_label(member) {
|
|
39
|
+
const described = member.description;
|
|
40
|
+
if (typeof described === 'string' && described.trim().length > 0)
|
|
41
|
+
return described;
|
|
42
|
+
return path.posix.basename(member.vfsPath);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Where a member IS: home in CUBE, or still at the source.
|
|
46
|
+
*
|
|
47
|
+
* A series is home once CUBE has said where it landed; a place gathered
|
|
48
|
+
* from the filesystem was never anywhere else.
|
|
49
|
+
*
|
|
50
|
+
* @param member - The member to place.
|
|
51
|
+
* @returns A one-word state for the listing.
|
|
52
|
+
*/
|
|
53
|
+
function member_state(member) {
|
|
54
|
+
if (member.kind !== 'series')
|
|
55
|
+
return 'in CUBE';
|
|
56
|
+
const landed = member.folderPath;
|
|
57
|
+
return typeof landed === 'string' && landed.length > 0 ? 'in CUBE' : 'at PACS';
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Renders the cohort as a table, with its name and counts.
|
|
61
|
+
*
|
|
62
|
+
* @param state - The cohort to show.
|
|
63
|
+
* @returns A success envelope carrying the rendered table and the model.
|
|
64
|
+
*/
|
|
65
|
+
function cohort_render(state) {
|
|
66
|
+
const model = { kind: GATHER_MODEL_KIND, data: state };
|
|
67
|
+
if (state.series.length === 0) {
|
|
68
|
+
return envelope_ok(`${chalk.gray('The cohort is empty. Gather something with `gather add <path>`.')}\n`, model);
|
|
69
|
+
}
|
|
70
|
+
const rows = state.series.map((member, index) => ({
|
|
71
|
+
'#': index + 1,
|
|
72
|
+
kind: member.kind,
|
|
73
|
+
member: member_label(member),
|
|
74
|
+
state: member_state(member),
|
|
75
|
+
path: member.vfsPath,
|
|
76
|
+
}));
|
|
77
|
+
const seriesCount = state.series.filter((member) => member.kind === 'series').length;
|
|
78
|
+
const placeCount = state.series.length - seriesCount;
|
|
79
|
+
const parts = [];
|
|
80
|
+
if (seriesCount > 0)
|
|
81
|
+
parts.push(`${seriesCount} ${seriesCount === 1 ? 'series' : 'series'}`);
|
|
82
|
+
if (placeCount > 0)
|
|
83
|
+
parts.push(`${placeCount} ${placeCount === 1 ? 'place' : 'places'}`);
|
|
84
|
+
const title = state.name === null ? 'Cohort' : `Cohort · ${state.name}`;
|
|
85
|
+
return envelope_ok(`${table_render(rows, ['#', 'kind', 'member', 'state', 'path'], {
|
|
86
|
+
title: { title, justification: 'center' },
|
|
87
|
+
})}${chalk.gray(parts.join(' · '))}\n`, model);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Takes paths into the cohort.
|
|
91
|
+
*
|
|
92
|
+
* Each operand is resolved against the session's cwd, so `gather add .` and
|
|
93
|
+
* a relative series path mean what they say. What a path IS is asked of the
|
|
94
|
+
* filesystem rather than guessed from its spelling.
|
|
95
|
+
*
|
|
96
|
+
* @param operands - Paths to gather.
|
|
97
|
+
* @returns The cohort after the change.
|
|
98
|
+
*/
|
|
99
|
+
async function gather_add(operands) {
|
|
100
|
+
if (operands.length === 0)
|
|
101
|
+
return gather_error('Usage: gather add <path> [path...]');
|
|
102
|
+
const incoming = [];
|
|
103
|
+
for (const operand of operands) {
|
|
104
|
+
const resolved = await path_resolve(operand);
|
|
105
|
+
const kind = await pathKind_determine(resolved);
|
|
106
|
+
// A path that names nothing is refused by name, before anything is
|
|
107
|
+
// taken: a cohort holding a typo is a cohort a run will be refused on.
|
|
108
|
+
if (kind === null)
|
|
109
|
+
return gather_error(`gather: ${resolved}: nothing is there.`);
|
|
110
|
+
const member = { vfsPath: resolved, kind };
|
|
111
|
+
if (member.kind === 'series') {
|
|
112
|
+
const uid = seriesUID_ofPath(resolved);
|
|
113
|
+
if (uid !== undefined)
|
|
114
|
+
member.seriesUID = uid;
|
|
115
|
+
member.imagery = true;
|
|
116
|
+
}
|
|
117
|
+
incoming.push(member);
|
|
118
|
+
}
|
|
119
|
+
const state = await cohort_read();
|
|
120
|
+
const { members, added, merged } = members_merge(state.series, incoming);
|
|
121
|
+
state.series = members;
|
|
122
|
+
await cohort_write(state);
|
|
123
|
+
const noted = [];
|
|
124
|
+
if (added > 0)
|
|
125
|
+
noted.push(`${added} gathered`);
|
|
126
|
+
if (merged > 0)
|
|
127
|
+
noted.push(`${merged} already in the cohort`);
|
|
128
|
+
const envelope = cohort_render(state);
|
|
129
|
+
return envelope_ok(`${chalk.green(`gather: ${noted.join(', ')}.`)}\n${envelope.rendered}`, envelope.model);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Drops members from the cohort, by path or by listing index.
|
|
133
|
+
*
|
|
134
|
+
* An index is how the operator reads the cohort back, so `gather remove 2`
|
|
135
|
+
* removes the second row rather than refusing because 2 is not a path.
|
|
136
|
+
*
|
|
137
|
+
* @param operands - Paths or `#`-style indices to drop.
|
|
138
|
+
* @returns The cohort after the change.
|
|
139
|
+
*/
|
|
140
|
+
async function gather_remove(operands) {
|
|
141
|
+
if (operands.length === 0)
|
|
142
|
+
return gather_error('Usage: gather remove <path|index> [path|index...]');
|
|
143
|
+
const state = await cohort_read();
|
|
144
|
+
if (state.series.length === 0)
|
|
145
|
+
return gather_error('gather: the cohort is empty.');
|
|
146
|
+
const keys = [];
|
|
147
|
+
for (const operand of operands) {
|
|
148
|
+
const asIndex = Number.parseInt(operand.replace(/^#/, ''), 10);
|
|
149
|
+
if (!Number.isNaN(asIndex) && `${asIndex}` === operand.replace(/^#/, '')) {
|
|
150
|
+
const member = state.series[asIndex - 1];
|
|
151
|
+
if (member === undefined)
|
|
152
|
+
return gather_error(`gather: no member ${operand} — the cohort holds ${state.series.length}.`);
|
|
153
|
+
keys.push(member_key(member));
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
keys.push(await path_resolve(operand));
|
|
157
|
+
}
|
|
158
|
+
const { members, dropped, missing } = members_drop(state.series, keys);
|
|
159
|
+
if (missing.length > 0) {
|
|
160
|
+
return gather_error(`gather: not in the cohort: ${missing.join(', ')}`);
|
|
161
|
+
}
|
|
162
|
+
state.series = members;
|
|
163
|
+
await cohort_write(state);
|
|
164
|
+
const envelope = cohort_render(state);
|
|
165
|
+
return envelope_ok(`${chalk.green(`gather: ${dropped} removed.`)}\n${envelope.rendered}`, envelope.model);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Empties the cohort, keeping nothing — not its name, not its feed.
|
|
169
|
+
*
|
|
170
|
+
* @returns The empty cohort.
|
|
171
|
+
*/
|
|
172
|
+
async function gather_clear() {
|
|
173
|
+
const state = await cohort_read();
|
|
174
|
+
const held = state.series.length;
|
|
175
|
+
const emptied = { version: state.version, name: null, feed: null, series: [] };
|
|
176
|
+
await cohort_write(emptied);
|
|
177
|
+
const envelope = cohort_render(emptied);
|
|
178
|
+
return envelope_ok(`${chalk.green(`gather: cleared ${held} ${held === 1 ? 'member' : 'members'}.`)}\n${envelope.rendered}`, envelope.model);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Reads or sets the cohort's name.
|
|
182
|
+
*
|
|
183
|
+
* @param operands - The new name, or nothing to read the current one.
|
|
184
|
+
* @returns The cohort, named.
|
|
185
|
+
*/
|
|
186
|
+
async function gather_name(operands) {
|
|
187
|
+
const state = await cohort_read();
|
|
188
|
+
if (operands.length === 0) {
|
|
189
|
+
if (state.name === null)
|
|
190
|
+
return envelope_ok(`${chalk.gray('The cohort has no name.')}\n`);
|
|
191
|
+
return envelope_ok(`${state.name}\n`);
|
|
192
|
+
}
|
|
193
|
+
state.name = operands.join(' ');
|
|
194
|
+
await cohort_write(state);
|
|
195
|
+
return envelope_ok(`${chalk.green(`gather: the cohort is "${state.name}".`)}\n`);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Builtin handler for the `gather` command.
|
|
199
|
+
*
|
|
200
|
+
* @param args - Parsed command arguments.
|
|
201
|
+
* @returns An envelope carrying the cohort, or a usage refusal.
|
|
202
|
+
*/
|
|
203
|
+
export async function builtin_gather(args) {
|
|
204
|
+
const parsed = commandArgs_process(args);
|
|
205
|
+
const subcommand = parsed._[0];
|
|
206
|
+
const operands = parsed._.slice(1);
|
|
207
|
+
try {
|
|
208
|
+
if (subcommand === undefined || subcommand === 'list')
|
|
209
|
+
return cohort_render(await cohort_read());
|
|
210
|
+
if (subcommand === 'add')
|
|
211
|
+
return await gather_add(operands);
|
|
212
|
+
if (subcommand === 'remove' || subcommand === 'rm')
|
|
213
|
+
return await gather_remove(operands);
|
|
214
|
+
if (subcommand === 'clear')
|
|
215
|
+
return await gather_clear();
|
|
216
|
+
if (subcommand === 'name')
|
|
217
|
+
return await gather_name(operands);
|
|
218
|
+
}
|
|
219
|
+
catch (e) {
|
|
220
|
+
return gather_error(`gather: ${e instanceof Error ? e.message : String(e)}`);
|
|
221
|
+
}
|
|
222
|
+
return gather_error(`Unknown subcommand: ${subcommand}. Usage: gather <list|add|remove|clear|name>`);
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=gather.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gather.js","sourceRoot":"","sources":["../../../src/builtins/res/gather.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/** Where the session keeps the cohort it is working on. */
|
|
2
|
+
export declare const COHORT_FILE: string;
|
|
3
|
+
/** The directory that holds it, created on first write. */
|
|
4
|
+
export declare const COHORT_DIR: string;
|
|
5
|
+
/** What a member IS, which decides which verbs can act on it. */
|
|
6
|
+
export type GatherKind = 'series' | 'dir' | 'file';
|
|
7
|
+
/**
|
|
8
|
+
* One gathered thing.
|
|
9
|
+
*
|
|
10
|
+
* `vfsPath` is the identity and the only field the kernel requires; the
|
|
11
|
+
* rest is what a sighting happened to know. The index signature is not
|
|
12
|
+
* laxity — it is the promise that a surface's richer record survives a
|
|
13
|
+
* kernel rewrite untouched.
|
|
14
|
+
*/
|
|
15
|
+
export interface GatherMember {
|
|
16
|
+
/** The member's address, and its identity within the cohort. */
|
|
17
|
+
vfsPath: string;
|
|
18
|
+
/** Series, directory or file. */
|
|
19
|
+
kind: GatherKind;
|
|
20
|
+
/** The series UID, when the member is one. */
|
|
21
|
+
seriesUID?: string;
|
|
22
|
+
/** What the member is called where it came from. */
|
|
23
|
+
description?: string;
|
|
24
|
+
/** The modality, for a series. */
|
|
25
|
+
modality?: string;
|
|
26
|
+
/** The patient it came from, for a series. */
|
|
27
|
+
patient?: string;
|
|
28
|
+
/** The study it came from, for a series. */
|
|
29
|
+
study?: string;
|
|
30
|
+
/** Whether the member is imagery: a series always is, a place sometimes. */
|
|
31
|
+
imagery?: boolean;
|
|
32
|
+
/** Its ChRIS folder once CUBE has said where it landed. */
|
|
33
|
+
folderPath?: string;
|
|
34
|
+
/** How many files it holds, when the source said. */
|
|
35
|
+
files?: number;
|
|
36
|
+
/** Anything a surface knows and the kernel does not, kept across a write. */
|
|
37
|
+
[extra: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
/** The feed a cohort was rooted in: its id, and the root a run appends to. */
|
|
40
|
+
export interface GatherFeed {
|
|
41
|
+
feedId: number;
|
|
42
|
+
rootInstanceId: number;
|
|
43
|
+
path: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The whole cohort as it is kept on disk.
|
|
47
|
+
*
|
|
48
|
+
* The member array is named `series` because that is what the surface
|
|
49
|
+
* wrote before members had kinds, and a rename would strand every cohort
|
|
50
|
+
* a session is currently holding for no gain.
|
|
51
|
+
*/
|
|
52
|
+
export interface GatherState {
|
|
53
|
+
version: number;
|
|
54
|
+
name: string | null;
|
|
55
|
+
feed: GatherFeed | null;
|
|
56
|
+
series: GatherMember[];
|
|
57
|
+
}
|
|
58
|
+
/** An empty cohort: what a session that has gathered nothing holds. */
|
|
59
|
+
export declare function cohortState_empty(): GatherState;
|
|
60
|
+
/**
|
|
61
|
+
* The identity of one member.
|
|
62
|
+
*
|
|
63
|
+
* Path first, falling back to the series UID for a record written before
|
|
64
|
+
* membership was path-keyed, so an older cohort still de-duplicates.
|
|
65
|
+
*
|
|
66
|
+
* @param member - The member to key.
|
|
67
|
+
* @returns Its identity within the cohort.
|
|
68
|
+
*/
|
|
69
|
+
export declare function member_key(member: GatherMember): string;
|
|
70
|
+
/**
|
|
71
|
+
* Reads the cohort the session is working on.
|
|
72
|
+
*
|
|
73
|
+
* An unreadable or unparseable file answers an EMPTY cohort rather than an
|
|
74
|
+
* error: this is a working file the operator may have edited, and a cohort
|
|
75
|
+
* that cannot be read is not a reason to refuse the command.
|
|
76
|
+
*
|
|
77
|
+
* @returns The cohort, normalized.
|
|
78
|
+
*/
|
|
79
|
+
export declare function cohort_read(): Promise<GatherState>;
|
|
80
|
+
/**
|
|
81
|
+
* Writes the cohort back, and tells the listing cache the folder changed.
|
|
82
|
+
*
|
|
83
|
+
* Without the invalidation an `ls ~/gather` right after a gather serves the
|
|
84
|
+
* listing from before the write and the operator is told their cohort does
|
|
85
|
+
* not exist — the same staleness a CSV export once showed.
|
|
86
|
+
*
|
|
87
|
+
* @param state - The cohort to keep.
|
|
88
|
+
*/
|
|
89
|
+
export declare function cohort_write(state: GatherState): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Says what a path IS, by asking rather than by guessing at its spelling.
|
|
92
|
+
*
|
|
93
|
+
* A PACS projection is a series: the query answer's own addresses are the
|
|
94
|
+
* only things in the tree that name one. Everything else is asked of the
|
|
95
|
+
* filesystem, and what is not a directory is a file.
|
|
96
|
+
*
|
|
97
|
+
* @param address - A resolved VFS path.
|
|
98
|
+
* @returns The member kind to record, or null when nothing is at the path.
|
|
99
|
+
*/
|
|
100
|
+
export declare function pathKind_determine(address: string): Promise<GatherKind | null>;
|
|
101
|
+
/**
|
|
102
|
+
* The series UID a PACS projection path names, when it names one.
|
|
103
|
+
*
|
|
104
|
+
* @param address - A resolved VFS path.
|
|
105
|
+
* @returns The UID, or undefined when the path is not a series projection.
|
|
106
|
+
*/
|
|
107
|
+
export declare function seriesUID_ofPath(address: string): string | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Takes members into a cohort, merging what a later sighting knows.
|
|
110
|
+
*
|
|
111
|
+
* @param held - The members already in the cohort, in gather order.
|
|
112
|
+
* @param incoming - The members being taken.
|
|
113
|
+
* @returns The new membership, and how many were new rather than merged.
|
|
114
|
+
*/
|
|
115
|
+
export declare function members_merge(held: GatherMember[], incoming: GatherMember[]): {
|
|
116
|
+
members: GatherMember[];
|
|
117
|
+
added: number;
|
|
118
|
+
merged: number;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Drops members by identity.
|
|
122
|
+
*
|
|
123
|
+
* @param held - The current membership.
|
|
124
|
+
* @param keys - Identities to drop.
|
|
125
|
+
* @returns The remaining members, and the identities that matched nothing.
|
|
126
|
+
*/
|
|
127
|
+
export declare function members_drop(held: GatherMember[], keys: string[]): {
|
|
128
|
+
members: GatherMember[];
|
|
129
|
+
dropped: number;
|
|
130
|
+
missing: string[];
|
|
131
|
+
};
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The session's cohort, as the kernel holds it.
|
|
3
|
+
*
|
|
4
|
+
* A cohort is the set of things a session is working on — a PACS series
|
|
5
|
+
* still at the modality, a directory of uploads, one file — gathered so
|
|
6
|
+
* that one act can be taken over all of them. It belongs to the SESSION,
|
|
7
|
+
* not to a surface: it outlives a page refresh, a second surface attached
|
|
8
|
+
* to the same session sees the same one, and a command line reaches it
|
|
9
|
+
* without a browser in the room.
|
|
10
|
+
*
|
|
11
|
+
* Membership is keyed by PATH. A series, a directory and a file are all
|
|
12
|
+
* addressable, so one identity rule covers every kind, gathering the same
|
|
13
|
+
* thing twice merges rather than doubles, and `gather remove` takes the
|
|
14
|
+
* same operand whatever it is removing.
|
|
15
|
+
*
|
|
16
|
+
* The cohort lives as a working file in the ChRIS filesystem, in the shape
|
|
17
|
+
* the surface already wrote, so the two read each other's cohorts without
|
|
18
|
+
* a migration. Fields this layer does not understand are preserved across
|
|
19
|
+
* a rewrite: the surface knows things about a series that the kernel has
|
|
20
|
+
* no name for, and a kernel write must not quietly forget them.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import { listCache_get } from '@fnndsc/cumin';
|
|
26
|
+
import { fileContent_get, files_path_isDirectory } from '@fnndsc/salsa';
|
|
27
|
+
import { files_touch as chefs_touch_cmd } from '@fnndsc/chili/commands/fs/touch.js';
|
|
28
|
+
import { files_mkdir as chefs_mkdir_cmd } from '@fnndsc/chili/commands/fs/mkdir.js';
|
|
29
|
+
import { path_resolve } from '../utils.js';
|
|
30
|
+
/** Where the session keeps the cohort it is working on. */
|
|
31
|
+
export const COHORT_FILE = '~/gather/current.json';
|
|
32
|
+
/** The directory that holds it, created on first write. */
|
|
33
|
+
export const COHORT_DIR = '~/gather';
|
|
34
|
+
/** An empty cohort: what a session that has gathered nothing holds. */
|
|
35
|
+
export function cohortState_empty() {
|
|
36
|
+
return { version: 1, name: null, feed: null, series: [] };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The identity of one member.
|
|
40
|
+
*
|
|
41
|
+
* Path first, falling back to the series UID for a record written before
|
|
42
|
+
* membership was path-keyed, so an older cohort still de-duplicates.
|
|
43
|
+
*
|
|
44
|
+
* @param member - The member to key.
|
|
45
|
+
* @returns Its identity within the cohort.
|
|
46
|
+
*/
|
|
47
|
+
export function member_key(member) {
|
|
48
|
+
const address = member.vfsPath;
|
|
49
|
+
if (typeof address === 'string' && address.length > 0)
|
|
50
|
+
return address;
|
|
51
|
+
const uid = member.seriesUID;
|
|
52
|
+
return typeof uid === 'string' ? uid : '';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Fills in what an older or partial record left out.
|
|
56
|
+
*
|
|
57
|
+
* A record written before kinds existed is a series, which is what every
|
|
58
|
+
* member was then; a record written before paths were identity carries its
|
|
59
|
+
* address in `seriesUID`.
|
|
60
|
+
*
|
|
61
|
+
* @param raw - One entry as the file held it.
|
|
62
|
+
* @returns The same entry, normalized.
|
|
63
|
+
*/
|
|
64
|
+
function member_normalize(raw) {
|
|
65
|
+
const normalized = { ...raw };
|
|
66
|
+
if (typeof normalized.vfsPath !== 'string' || normalized.vfsPath.length === 0) {
|
|
67
|
+
normalized.vfsPath = typeof raw.seriesUID === 'string' ? raw.seriesUID : '';
|
|
68
|
+
}
|
|
69
|
+
if (normalized.kind !== 'dir' && normalized.kind !== 'file' && normalized.kind !== 'series') {
|
|
70
|
+
normalized.kind = 'series';
|
|
71
|
+
}
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reads the cohort the session is working on.
|
|
76
|
+
*
|
|
77
|
+
* An unreadable or unparseable file answers an EMPTY cohort rather than an
|
|
78
|
+
* error: this is a working file the operator may have edited, and a cohort
|
|
79
|
+
* that cannot be read is not a reason to refuse the command.
|
|
80
|
+
*
|
|
81
|
+
* @returns The cohort, normalized.
|
|
82
|
+
*/
|
|
83
|
+
export async function cohort_read() {
|
|
84
|
+
try {
|
|
85
|
+
const resolved = await path_resolve(COHORT_FILE);
|
|
86
|
+
const content = await fileContent_get(resolved);
|
|
87
|
+
if (!content.ok)
|
|
88
|
+
return cohortState_empty();
|
|
89
|
+
const held = JSON.parse(content.value);
|
|
90
|
+
const members = Array.isArray(held.series)
|
|
91
|
+
? held.series.map(member_normalize).filter((m) => m.vfsPath.length > 0)
|
|
92
|
+
: [];
|
|
93
|
+
return {
|
|
94
|
+
version: typeof held.version === 'number' ? held.version : 1,
|
|
95
|
+
name: typeof held.name === 'string' ? held.name : null,
|
|
96
|
+
feed: (held.feed ?? null),
|
|
97
|
+
series: members,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return cohortState_empty();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Writes the cohort back, and tells the listing cache the folder changed.
|
|
106
|
+
*
|
|
107
|
+
* Without the invalidation an `ls ~/gather` right after a gather serves the
|
|
108
|
+
* listing from before the write and the operator is told their cohort does
|
|
109
|
+
* not exist — the same staleness a CSV export once showed.
|
|
110
|
+
*
|
|
111
|
+
* @param state - The cohort to keep.
|
|
112
|
+
*/
|
|
113
|
+
export async function cohort_write(state) {
|
|
114
|
+
const directory = await path_resolve(COHORT_DIR);
|
|
115
|
+
await chefs_mkdir_cmd(directory);
|
|
116
|
+
const target = await path_resolve(COHORT_FILE);
|
|
117
|
+
await chefs_touch_cmd(target, { withContents: JSON.stringify(state) });
|
|
118
|
+
const cache = listCache_get();
|
|
119
|
+
cache.cache_invalidate(path.posix.dirname(target));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Says what a path IS, by asking rather than by guessing at its spelling.
|
|
123
|
+
*
|
|
124
|
+
* A PACS projection is a series: the query answer's own addresses are the
|
|
125
|
+
* only things in the tree that name one. Everything else is asked of the
|
|
126
|
+
* filesystem, and what is not a directory is a file.
|
|
127
|
+
*
|
|
128
|
+
* @param address - A resolved VFS path.
|
|
129
|
+
* @returns The member kind to record, or null when nothing is at the path.
|
|
130
|
+
*/
|
|
131
|
+
export async function pathKind_determine(address) {
|
|
132
|
+
if (address.startsWith('/net/pacs/'))
|
|
133
|
+
return 'series';
|
|
134
|
+
try {
|
|
135
|
+
if (await files_path_isDirectory(address))
|
|
136
|
+
return 'dir';
|
|
137
|
+
// Not a directory is not the same as a file: a path that names nothing
|
|
138
|
+
// must not be gathered as one, or a typo becomes a member a run would
|
|
139
|
+
// be handed and refused on. Its parent is asked whether it is there.
|
|
140
|
+
const parent = path.posix.dirname(address);
|
|
141
|
+
const leaf = path.posix.basename(address);
|
|
142
|
+
const listed = await folderEntries_list(parent);
|
|
143
|
+
if (!listed.ok || !listed.value.includes(leaf))
|
|
144
|
+
return null;
|
|
145
|
+
return 'file';
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* The names a folder holds, files and folders alike.
|
|
153
|
+
*
|
|
154
|
+
* @param folder - The folder to list.
|
|
155
|
+
* @returns Its entries' names, or a refusal when it cannot be listed.
|
|
156
|
+
*/
|
|
157
|
+
async function folderEntries_list(folder) {
|
|
158
|
+
const { vfsDispatcher } = await import('@fnndsc/salsa');
|
|
159
|
+
const listed = await vfsDispatcher.list(folder);
|
|
160
|
+
if (!listed.ok)
|
|
161
|
+
return { ok: false };
|
|
162
|
+
return { ok: true, value: listed.value.map((item) => item.name) };
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The series UID a PACS projection path names, when it names one.
|
|
166
|
+
*
|
|
167
|
+
* @param address - A resolved VFS path.
|
|
168
|
+
* @returns The UID, or undefined when the path is not a series projection.
|
|
169
|
+
*/
|
|
170
|
+
export function seriesUID_ofPath(address) {
|
|
171
|
+
const leaf = path.posix.basename(address);
|
|
172
|
+
return /^[0-9]+(\.[0-9]+)+$/.test(leaf) ? leaf : undefined;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Takes members into a cohort, merging what a later sighting knows.
|
|
176
|
+
*
|
|
177
|
+
* @param held - The members already in the cohort, in gather order.
|
|
178
|
+
* @param incoming - The members being taken.
|
|
179
|
+
* @returns The new membership, and how many were new rather than merged.
|
|
180
|
+
*/
|
|
181
|
+
export function members_merge(held, incoming) {
|
|
182
|
+
const byKey = new Map(held.map((member) => [member_key(member), member]));
|
|
183
|
+
let added = 0;
|
|
184
|
+
let merged = 0;
|
|
185
|
+
for (const member of incoming) {
|
|
186
|
+
const key = member_key(member);
|
|
187
|
+
if (key.length === 0)
|
|
188
|
+
continue;
|
|
189
|
+
const existing = byKey.get(key);
|
|
190
|
+
if (existing === undefined) {
|
|
191
|
+
byKey.set(key, member);
|
|
192
|
+
added += 1;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
byKey.set(key, { ...existing, ...member });
|
|
196
|
+
merged += 1;
|
|
197
|
+
}
|
|
198
|
+
return { members: [...byKey.values()], added, merged };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Drops members by identity.
|
|
202
|
+
*
|
|
203
|
+
* @param held - The current membership.
|
|
204
|
+
* @param keys - Identities to drop.
|
|
205
|
+
* @returns The remaining members, and the identities that matched nothing.
|
|
206
|
+
*/
|
|
207
|
+
export function members_drop(held, keys) {
|
|
208
|
+
const wanted = new Set(keys);
|
|
209
|
+
const kept = [];
|
|
210
|
+
const hit = new Set();
|
|
211
|
+
for (const member of held) {
|
|
212
|
+
const key = member_key(member);
|
|
213
|
+
if (wanted.has(key)) {
|
|
214
|
+
hit.add(key);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
kept.push(member);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
members: kept,
|
|
221
|
+
dropped: held.length - kept.length,
|
|
222
|
+
missing: keys.filter((key) => !hit.has(key)),
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=gather.store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gather.store.js","sourceRoot":"","sources":["../../../src/builtins/res/gather.store.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,11 +14,14 @@ export interface PipelineRunOverrides {
|
|
|
14
14
|
previousOverride: number | undefined;
|
|
15
15
|
paramFile: string | undefined;
|
|
16
16
|
bindings: PipelineInvocationBinding[];
|
|
17
|
+
/** Whether the operator asked for the handle rather than the outcome. */
|
|
18
|
+
detach: boolean;
|
|
17
19
|
parseError: string | null;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
|
-
* Parses `--compute <resource
|
|
21
|
-
* `pipeline run <name|id> ...` arguments (scanning from
|
|
22
|
+
* Parses `--compute <resource>`, `--previous <inst_id>`, `--paramFile <path>`
|
|
23
|
+
* and `--detach` from `pipeline run <name|id> ...` arguments (scanning from
|
|
24
|
+
* index 2). Anything else in `--node.field value` form is a binding.
|
|
22
25
|
*
|
|
23
26
|
* @param args - The full `pipeline run` argument list.
|
|
24
27
|
* @returns The parsed overrides.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { executableArguments_parse } from '../argumentTokens.js';
|
|
2
2
|
/**
|
|
3
|
-
* Parses `--compute <resource
|
|
4
|
-
* `pipeline run <name|id> ...` arguments (scanning from
|
|
3
|
+
* Parses `--compute <resource>`, `--previous <inst_id>`, `--paramFile <path>`
|
|
4
|
+
* and `--detach` from `pipeline run <name|id> ...` arguments (scanning from
|
|
5
|
+
* index 2). Anything else in `--node.field value` form is a binding.
|
|
5
6
|
*
|
|
6
7
|
* @param args - The full `pipeline run` argument list.
|
|
7
8
|
* @returns The parsed overrides.
|
|
@@ -11,6 +12,7 @@ export function pipelineRunArgs_parse(args) {
|
|
|
11
12
|
let previousOverride;
|
|
12
13
|
let paramFile;
|
|
13
14
|
const bindings = [];
|
|
15
|
+
let detach = false;
|
|
14
16
|
let parseError = null;
|
|
15
17
|
for (let i = 2; i < args.length; i++) {
|
|
16
18
|
const token = args[i];
|
|
@@ -32,6 +34,9 @@ export function pipelineRunArgs_parse(args) {
|
|
|
32
34
|
previousOverride = Number(value);
|
|
33
35
|
i++;
|
|
34
36
|
}
|
|
37
|
+
else if (token === '--detach') {
|
|
38
|
+
detach = true;
|
|
39
|
+
}
|
|
35
40
|
else if (token === '--paramFile') {
|
|
36
41
|
const value = args[i + 1];
|
|
37
42
|
if (paramFile !== undefined) {
|
|
@@ -74,6 +79,6 @@ export function pipelineRunArgs_parse(args) {
|
|
|
74
79
|
break;
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
|
-
return { computeOverride, previousOverride, paramFile, bindings, parseError };
|
|
82
|
+
return { computeOverride, previousOverride, paramFile, bindings, detach, parseError };
|
|
78
83
|
}
|
|
79
84
|
//# sourceMappingURL=pipeline.args.js.map
|