@styx-api/core 0.2.0 → 0.4.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/index.cjs +521 -107
- package/dist/index.d.cts +1 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +521 -107
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/frontend/detect-format.ts +10 -1
- package/src/frontend/mrtrix/index.ts +1 -0
- package/src/frontend/mrtrix/parser.ts +455 -0
- package/src/index.ts +4 -1
package/dist/index.mjs
CHANGED
|
@@ -8,20 +8,20 @@ function findDeepName$1(node) {
|
|
|
8
8
|
if (name) return name;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
-
function isObject$
|
|
11
|
+
function isObject$3(x) {
|
|
12
12
|
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
13
13
|
}
|
|
14
|
-
function isString$
|
|
14
|
+
function isString$3(x) {
|
|
15
15
|
return typeof x === "string";
|
|
16
16
|
}
|
|
17
|
-
function isNumber$
|
|
17
|
+
function isNumber$2(x) {
|
|
18
18
|
return typeof x === "number";
|
|
19
19
|
}
|
|
20
|
-
function isArray$
|
|
20
|
+
function isArray$3(x) {
|
|
21
21
|
return Array.isArray(x);
|
|
22
22
|
}
|
|
23
23
|
function isArgparseMarker(x) {
|
|
24
|
-
return isObject$
|
|
24
|
+
return isObject$3(x) && isString$3(x.__argparse__);
|
|
25
25
|
}
|
|
26
26
|
function isSuppressed(x) {
|
|
27
27
|
return isArgparseMarker(x) && x.__argparse__ === "SUPPRESS";
|
|
@@ -55,7 +55,7 @@ var ArgdumpParser = class {
|
|
|
55
55
|
this.error(e instanceof SyntaxError ? e.message : "Invalid JSON");
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
-
if (!isObject$
|
|
58
|
+
if (!isObject$3(parsed)) {
|
|
59
59
|
this.error("JSON source is not an object");
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
@@ -63,11 +63,11 @@ var ArgdumpParser = class {
|
|
|
63
63
|
}
|
|
64
64
|
buildAppMeta(descriptor) {
|
|
65
65
|
const prog = descriptor.prog;
|
|
66
|
-
if (!isString$
|
|
66
|
+
if (!isString$3(prog)) return void 0;
|
|
67
67
|
let id = prog || void 0;
|
|
68
68
|
if (!id) {
|
|
69
69
|
const desc = descriptor.description;
|
|
70
|
-
if (isString$
|
|
70
|
+
if (isString$3(desc)) {
|
|
71
71
|
const match = desc.match(/^(\S+)/);
|
|
72
72
|
if (match) id = match[1].replace(/[:;,]+$/, "") || void 0;
|
|
73
73
|
}
|
|
@@ -77,8 +77,8 @@ var ArgdumpParser = class {
|
|
|
77
77
|
const epilog = descriptor.epilog;
|
|
78
78
|
let versionStr;
|
|
79
79
|
const actions = descriptor.actions;
|
|
80
|
-
if (isArray$
|
|
81
|
-
for (const action of actions) if (isObject$
|
|
80
|
+
if (isArray$3(actions)) {
|
|
81
|
+
for (const action of actions) if (isObject$3(action) && action.action_type === "version" && isString$3(action.version)) {
|
|
82
82
|
versionStr = action.version.replace(/%%?\(prog\)s\s*/g, "").trim();
|
|
83
83
|
if (!versionStr) versionStr = void 0;
|
|
84
84
|
break;
|
|
@@ -87,9 +87,9 @@ var ArgdumpParser = class {
|
|
|
87
87
|
return {
|
|
88
88
|
id,
|
|
89
89
|
...versionStr && { version: versionStr },
|
|
90
|
-
...(isString$
|
|
91
|
-
...isString$
|
|
92
|
-
...isString$
|
|
90
|
+
...(isString$3(description) || isString$3(epilog)) && { doc: {
|
|
91
|
+
...isString$3(description) && { description },
|
|
92
|
+
...isString$3(epilog) && { comment: epilog }
|
|
93
93
|
} }
|
|
94
94
|
};
|
|
95
95
|
}
|
|
@@ -97,9 +97,9 @@ var ArgdumpParser = class {
|
|
|
97
97
|
const typeInfo = action.type_info;
|
|
98
98
|
const fileTypeInfo = action.file_type_info;
|
|
99
99
|
const choices = action.choices;
|
|
100
|
-
if (isArray$
|
|
100
|
+
if (isArray$3(choices) && choices.length > 0) {
|
|
101
101
|
const alts = [];
|
|
102
|
-
for (const choice of choices) if (isString$
|
|
102
|
+
for (const choice of choices) if (isString$3(choice) || isNumber$2(choice)) alts.push({
|
|
103
103
|
kind: "literal",
|
|
104
104
|
attrs: { str: String(choice) }
|
|
105
105
|
});
|
|
@@ -110,13 +110,13 @@ var ArgdumpParser = class {
|
|
|
110
110
|
attrs: { alts }
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
|
-
if (isObject$
|
|
113
|
+
if (isObject$3(fileTypeInfo)) return {
|
|
114
114
|
kind: "path",
|
|
115
115
|
attrs: {}
|
|
116
116
|
};
|
|
117
|
-
if (isObject$
|
|
117
|
+
if (isObject$3(typeInfo)) {
|
|
118
118
|
const name = typeInfo.name;
|
|
119
|
-
if (!isString$
|
|
119
|
+
if (!isString$3(name)) return this.inferFromSamples(action) ?? {
|
|
120
120
|
kind: "str",
|
|
121
121
|
attrs: {}
|
|
122
122
|
};
|
|
@@ -165,7 +165,7 @@ var ArgdumpParser = class {
|
|
|
165
165
|
};
|
|
166
166
|
}
|
|
167
167
|
resolveByModule(mod) {
|
|
168
|
-
if (!isString$
|
|
168
|
+
if (!isString$3(mod)) return null;
|
|
169
169
|
if (mod === "pathlib" || mod === "os.path" || mod.includes("path")) return {
|
|
170
170
|
kind: "path",
|
|
171
171
|
attrs: {}
|
|
@@ -180,7 +180,7 @@ var ArgdumpParser = class {
|
|
|
180
180
|
inferFromSamples(action) {
|
|
181
181
|
const samples = [];
|
|
182
182
|
const def = action.default;
|
|
183
|
-
if (isArray$
|
|
183
|
+
if (isArray$3(def)) samples.push(...def);
|
|
184
184
|
else samples.push(def);
|
|
185
185
|
samples.push(action.const);
|
|
186
186
|
let sawNumber = false;
|
|
@@ -232,7 +232,7 @@ var ArgdumpParser = class {
|
|
|
232
232
|
countMin: 1
|
|
233
233
|
}
|
|
234
234
|
};
|
|
235
|
-
if (isNumber$
|
|
235
|
+
if (isNumber$2(nargs) && Number.isInteger(nargs) && nargs >= 0) {
|
|
236
236
|
if (nargs === 1) return node;
|
|
237
237
|
return {
|
|
238
238
|
kind: "repeat",
|
|
@@ -249,10 +249,10 @@ var ArgdumpParser = class {
|
|
|
249
249
|
const dest = action.dest;
|
|
250
250
|
const help = action.help;
|
|
251
251
|
const defaultVal = action.default;
|
|
252
|
-
const name = this.preferredName(action) ?? (isString$
|
|
252
|
+
const name = this.preferredName(action) ?? (isString$3(dest) ? dest : void 0);
|
|
253
253
|
const hasName = name !== void 0;
|
|
254
|
-
const hasHelp = isString$
|
|
255
|
-
const hasDefault = (isString$
|
|
254
|
+
const hasHelp = isString$3(help) && !isSuppressed(help);
|
|
255
|
+
const hasDefault = (isString$3(defaultVal) || isNumber$2(defaultVal) || typeof defaultVal === "boolean") && !isSuppressed(defaultVal);
|
|
256
256
|
if (!hasName && !hasHelp && !hasDefault) return void 0;
|
|
257
257
|
return {
|
|
258
258
|
...hasName && { name },
|
|
@@ -262,20 +262,20 @@ var ArgdumpParser = class {
|
|
|
262
262
|
}
|
|
263
263
|
getOptionFlag(action) {
|
|
264
264
|
const optionStrings = action.option_strings;
|
|
265
|
-
if (!isArray$
|
|
266
|
-
for (const opt of optionStrings) if (isString$
|
|
265
|
+
if (!isArray$3(optionStrings) || optionStrings.length === 0) return null;
|
|
266
|
+
for (const opt of optionStrings) if (isString$3(opt) && opt.startsWith("--")) return opt;
|
|
267
267
|
const first = optionStrings[0];
|
|
268
|
-
return isString$
|
|
268
|
+
return isString$3(first) ? first : null;
|
|
269
269
|
}
|
|
270
270
|
/** Prefer the first --long flag (without leading dashes) over dest. */
|
|
271
271
|
preferredName(action) {
|
|
272
272
|
const optionStrings = action.option_strings;
|
|
273
|
-
if (!isArray$
|
|
274
|
-
for (const opt of optionStrings) if (isString$
|
|
273
|
+
if (!isArray$3(optionStrings)) return void 0;
|
|
274
|
+
for (const opt of optionStrings) if (isString$3(opt) && opt.startsWith("--") && opt.length > 2) return opt.slice(2);
|
|
275
275
|
}
|
|
276
276
|
isPositional(action) {
|
|
277
277
|
const optionStrings = action.option_strings;
|
|
278
|
-
return !isArray$
|
|
278
|
+
return !isArray$3(optionStrings) || optionStrings.length === 0;
|
|
279
279
|
}
|
|
280
280
|
buildAction(action) {
|
|
281
281
|
const actionType = action.action_type ?? "store";
|
|
@@ -293,7 +293,7 @@ var ArgdumpParser = class {
|
|
|
293
293
|
case "help":
|
|
294
294
|
case "version": return null;
|
|
295
295
|
case "unknown":
|
|
296
|
-
this.warn(`Unknown action type for '${action.dest}'` + (isString$
|
|
296
|
+
this.warn(`Unknown action type for '${action.dest}'` + (isString$3(action.custom_action_class) ? ` (custom class: ${action.custom_action_class})` : "") + ", treating as store");
|
|
297
297
|
return this.buildStore(action);
|
|
298
298
|
default:
|
|
299
299
|
this.warn(`Unrecognized action_type '${actionType}' for '${action.dest}', skipping`);
|
|
@@ -405,20 +405,20 @@ var ArgdumpParser = class {
|
|
|
405
405
|
}
|
|
406
406
|
buildBooleanOptional(action) {
|
|
407
407
|
const optionStrings = action.option_strings;
|
|
408
|
-
if (!isArray$
|
|
408
|
+
if (!isArray$3(optionStrings) || optionStrings.length === 0) {
|
|
409
409
|
this.error(`boolean_optional action '${action.dest}' has no option strings`);
|
|
410
410
|
return null;
|
|
411
411
|
}
|
|
412
412
|
let posFlag = null;
|
|
413
413
|
let negFlag = null;
|
|
414
414
|
for (const opt of optionStrings) {
|
|
415
|
-
if (!isString$
|
|
415
|
+
if (!isString$3(opt)) continue;
|
|
416
416
|
if (opt.startsWith("--no-")) negFlag = opt;
|
|
417
417
|
else if (opt.startsWith("--")) posFlag = opt;
|
|
418
418
|
}
|
|
419
419
|
if (!posFlag) {
|
|
420
|
-
posFlag = isString$
|
|
421
|
-
negFlag = isString$
|
|
420
|
+
posFlag = isString$3(optionStrings[0]) ? optionStrings[0] : null;
|
|
421
|
+
negFlag = isString$3(optionStrings[1]) ? optionStrings[1] : null;
|
|
422
422
|
}
|
|
423
423
|
if (!posFlag) {
|
|
424
424
|
this.error(`boolean_optional action '${action.dest}' has no valid option strings`);
|
|
@@ -545,14 +545,14 @@ var ArgdumpParser = class {
|
|
|
545
545
|
}
|
|
546
546
|
buildSubparsers(action) {
|
|
547
547
|
const subparsers = action.subparsers;
|
|
548
|
-
if (!isObject$
|
|
548
|
+
if (!isObject$3(subparsers)) {
|
|
549
549
|
this.error(`parsers action '${action.dest}' has no subparsers`);
|
|
550
550
|
return null;
|
|
551
551
|
}
|
|
552
|
-
const aliases = isObject$
|
|
552
|
+
const aliases = isObject$3(action.subparsers_aliases) ? action.subparsers_aliases : {};
|
|
553
553
|
const alts = [];
|
|
554
554
|
for (const [name, parserInfo] of Object.entries(subparsers)) {
|
|
555
|
-
if (!isObject$
|
|
555
|
+
if (!isObject$3(parserInfo)) {
|
|
556
556
|
this.warn(`Skipping non-object subparser '${name}'`);
|
|
557
557
|
continue;
|
|
558
558
|
}
|
|
@@ -566,8 +566,8 @@ var ArgdumpParser = class {
|
|
|
566
566
|
}, ...subExpr.attrs.nodes] }
|
|
567
567
|
};
|
|
568
568
|
const subAliases = aliases[name];
|
|
569
|
-
const aliasDoc = isArray$
|
|
570
|
-
const description = isString$
|
|
569
|
+
const aliasDoc = isArray$3(subAliases) && subAliases.length > 0 ? ` (aliases: ${subAliases.filter(isString$3).join(", ")})` : "";
|
|
570
|
+
const description = isString$3(parserInfo.description) ? parserInfo.description : void 0;
|
|
571
571
|
const docStr = description ? description + aliasDoc : aliasDoc ? aliasDoc.slice(1) : void 0;
|
|
572
572
|
seq.meta = {
|
|
573
573
|
name,
|
|
@@ -600,11 +600,11 @@ var ArgdumpParser = class {
|
|
|
600
600
|
applyMutualExclusion(actionsByDest, groups, nodes, nodesByDest) {
|
|
601
601
|
const excluded = /* @__PURE__ */ new Set();
|
|
602
602
|
for (const group of groups) {
|
|
603
|
-
if (!isObject$
|
|
603
|
+
if (!isObject$3(group)) continue;
|
|
604
604
|
const groupActions = group.actions;
|
|
605
|
-
if (!isArray$
|
|
605
|
+
if (!isArray$3(groupActions)) continue;
|
|
606
606
|
const memberDests = [];
|
|
607
|
-
for (const dest of groupActions) if (isString$
|
|
607
|
+
for (const dest of groupActions) if (isString$3(dest) && nodesByDest.has(dest)) memberDests.push(dest);
|
|
608
608
|
if (memberDests.length < 2) continue;
|
|
609
609
|
const altMembers = [];
|
|
610
610
|
for (const dest of memberDests) {
|
|
@@ -634,7 +634,7 @@ var ArgdumpParser = class {
|
|
|
634
634
|
kind: "optional",
|
|
635
635
|
attrs: { node: alt }
|
|
636
636
|
};
|
|
637
|
-
const groupName = (isString$
|
|
637
|
+
const groupName = (isString$3(group.title) ? group.title : void 0) ?? (memberDests.length === 2 ? `${memberDests[0]}_or_${memberDests[1]}` : `${memberDests[0]}_choice`);
|
|
638
638
|
groupNode.meta = {
|
|
639
639
|
...groupNode.meta,
|
|
640
640
|
name: groupName
|
|
@@ -651,7 +651,7 @@ var ArgdumpParser = class {
|
|
|
651
651
|
}
|
|
652
652
|
parseParserInfo(descriptor) {
|
|
653
653
|
const actions = descriptor.actions;
|
|
654
|
-
if (!isArray$
|
|
654
|
+
if (!isArray$3(actions)) return {
|
|
655
655
|
kind: "sequence",
|
|
656
656
|
attrs: { nodes: [] }
|
|
657
657
|
};
|
|
@@ -660,11 +660,11 @@ var ArgdumpParser = class {
|
|
|
660
660
|
const actionsByDest = /* @__PURE__ */ new Map();
|
|
661
661
|
const nodesByDest = /* @__PURE__ */ new Map();
|
|
662
662
|
for (const rawAction of actions) {
|
|
663
|
-
if (!isObject$
|
|
663
|
+
if (!isObject$3(rawAction)) continue;
|
|
664
664
|
const node = this.buildAction(rawAction);
|
|
665
665
|
if (!node) continue;
|
|
666
666
|
const dest = rawAction.dest;
|
|
667
|
-
if (isString$
|
|
667
|
+
if (isString$3(dest)) {
|
|
668
668
|
actionsByDest.set(dest, rawAction);
|
|
669
669
|
nodesByDest.set(dest, node);
|
|
670
670
|
}
|
|
@@ -673,7 +673,7 @@ var ArgdumpParser = class {
|
|
|
673
673
|
}
|
|
674
674
|
let allNodes = [...positionals, ...optionals];
|
|
675
675
|
const mutexGroups = descriptor.mutually_exclusive_groups;
|
|
676
|
-
if (isArray$
|
|
676
|
+
if (isArray$3(mutexGroups) && mutexGroups.length > 0) allNodes = this.applyMutualExclusion(actionsByDest, mutexGroups, allNodes, nodesByDest);
|
|
677
677
|
return {
|
|
678
678
|
kind: "sequence",
|
|
679
679
|
attrs: { nodes: allNodes }
|
|
@@ -715,7 +715,7 @@ var ArgdumpParser = class {
|
|
|
715
715
|
};
|
|
716
716
|
}
|
|
717
717
|
const prog = descriptor.prog;
|
|
718
|
-
if (isString$
|
|
718
|
+
if (isString$3(prog) && prog) expr.attrs.nodes.unshift({
|
|
719
719
|
kind: "literal",
|
|
720
720
|
attrs: { str: prog }
|
|
721
721
|
});
|
|
@@ -847,16 +847,16 @@ function boutiquesSplitCommand(command) {
|
|
|
847
847
|
|
|
848
848
|
//#endregion
|
|
849
849
|
//#region src/frontend/boutiques/parser.ts
|
|
850
|
-
function isObject$
|
|
850
|
+
function isObject$2(x) {
|
|
851
851
|
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
852
852
|
}
|
|
853
|
-
function isString$
|
|
853
|
+
function isString$2(x) {
|
|
854
854
|
return typeof x === "string";
|
|
855
855
|
}
|
|
856
|
-
function isNumber(x) {
|
|
856
|
+
function isNumber$1(x) {
|
|
857
857
|
return typeof x === "number";
|
|
858
858
|
}
|
|
859
|
-
function isArray$
|
|
859
|
+
function isArray$2(x) {
|
|
860
860
|
return Array.isArray(x);
|
|
861
861
|
}
|
|
862
862
|
var InputTypePrimitive = /* @__PURE__ */ function(InputTypePrimitive) {
|
|
@@ -898,7 +898,7 @@ var BoutiquesParser = class {
|
|
|
898
898
|
this.error(e instanceof SyntaxError ? e.message : "Invalid JSON");
|
|
899
899
|
return null;
|
|
900
900
|
}
|
|
901
|
-
if (!isObject$
|
|
901
|
+
if (!isObject$2(parsed)) {
|
|
902
902
|
this.error("JSON source is not an object");
|
|
903
903
|
return null;
|
|
904
904
|
}
|
|
@@ -910,9 +910,9 @@ var BoutiquesParser = class {
|
|
|
910
910
|
this.error(`type is missing for input: '${btInput.id}'`);
|
|
911
911
|
return null;
|
|
912
912
|
}
|
|
913
|
-
if (isObject$
|
|
914
|
-
if (isArray$
|
|
915
|
-
const typeName = isString$
|
|
913
|
+
if (isObject$2(btType)) return InputTypePrimitive.SubCommand;
|
|
914
|
+
if (isArray$2(btType)) return InputTypePrimitive.SubCommandUnion;
|
|
915
|
+
const typeName = isString$2(btType) ? btType : String(btType);
|
|
916
916
|
switch (typeName) {
|
|
917
917
|
case "String": return InputTypePrimitive.String;
|
|
918
918
|
case "File": return InputTypePrimitive.File;
|
|
@@ -951,43 +951,43 @@ var BoutiquesParser = class {
|
|
|
951
951
|
const title = btInput.name;
|
|
952
952
|
const description = btInput.description;
|
|
953
953
|
const defaultValue = btInput["default-value"];
|
|
954
|
-
const hasDefault = isString$
|
|
955
|
-
if (!isString$
|
|
954
|
+
const hasDefault = isString$2(defaultValue) || isNumber$1(defaultValue) || typeof defaultValue === "boolean";
|
|
955
|
+
if (!isString$2(name) && !isString$2(title) && !isString$2(description) && !hasDefault) return;
|
|
956
956
|
return {
|
|
957
|
-
...isString$
|
|
958
|
-
...(isString$
|
|
959
|
-
...isString$
|
|
960
|
-
...isString$
|
|
957
|
+
...isString$2(name) && { name },
|
|
958
|
+
...(isString$2(title) || isString$2(description)) && { doc: {
|
|
959
|
+
...isString$2(title) && { title },
|
|
960
|
+
...isString$2(description) && { description }
|
|
961
961
|
} },
|
|
962
962
|
...hasDefault && { defaultValue }
|
|
963
963
|
};
|
|
964
964
|
}
|
|
965
965
|
buildStreamMeta(bt) {
|
|
966
966
|
const id = bt.id;
|
|
967
|
-
if (!isString$
|
|
967
|
+
if (!isString$2(id)) return void 0;
|
|
968
968
|
const name = bt.name;
|
|
969
969
|
const description = bt.description;
|
|
970
970
|
return {
|
|
971
971
|
name: id,
|
|
972
|
-
...(isString$
|
|
973
|
-
...isString$
|
|
974
|
-
...isString$
|
|
972
|
+
...(isString$2(name) || isString$2(description)) && { doc: {
|
|
973
|
+
...isString$2(name) && { title: name },
|
|
974
|
+
...isString$2(description) && { description }
|
|
975
975
|
} }
|
|
976
976
|
};
|
|
977
977
|
}
|
|
978
978
|
buildOutput(out, lookup, idOptional) {
|
|
979
979
|
const id = out.id;
|
|
980
|
-
if (!isString$
|
|
980
|
+
if (!isString$2(id)) {
|
|
981
981
|
this.error("output-files entry missing id");
|
|
982
982
|
return null;
|
|
983
983
|
}
|
|
984
984
|
const template = out["path-template"];
|
|
985
|
-
if (!isString$
|
|
985
|
+
if (!isString$2(template)) {
|
|
986
986
|
this.error(`output-files entry '${id}' missing path-template`);
|
|
987
987
|
return null;
|
|
988
988
|
}
|
|
989
989
|
const stripRaw = out["path-template-stripped-extensions"];
|
|
990
|
-
const stripExtensions = isArray$
|
|
990
|
+
const stripExtensions = isArray$2(stripRaw) && stripRaw.every(isString$2) && stripRaw.length > 0 ? stripRaw : void 0;
|
|
991
991
|
const tokens = destructTemplate(template, lookup).map((part) => {
|
|
992
992
|
if (typeof part === "string") return {
|
|
993
993
|
kind: "literal",
|
|
@@ -1006,9 +1006,9 @@ var BoutiquesParser = class {
|
|
|
1006
1006
|
name: id,
|
|
1007
1007
|
tokens
|
|
1008
1008
|
};
|
|
1009
|
-
if (isString$
|
|
1010
|
-
...isString$
|
|
1011
|
-
...isString$
|
|
1009
|
+
if (isString$2(title) || isString$2(description)) output.doc = {
|
|
1010
|
+
...isString$2(title) && { title },
|
|
1011
|
+
...isString$2(description) && { description }
|
|
1012
1012
|
};
|
|
1013
1013
|
return { output };
|
|
1014
1014
|
}
|
|
@@ -1019,18 +1019,18 @@ var BoutiquesParser = class {
|
|
|
1019
1019
|
*/
|
|
1020
1020
|
attachOutputs(rootSeq, bt) {
|
|
1021
1021
|
const outputFiles = bt["output-files"];
|
|
1022
|
-
if (!isArray$
|
|
1022
|
+
if (!isArray$2(outputFiles)) return;
|
|
1023
1023
|
const lookup = {};
|
|
1024
1024
|
const idOptional = /* @__PURE__ */ new Set();
|
|
1025
1025
|
const inputs = bt["inputs"];
|
|
1026
|
-
if (isArray$
|
|
1027
|
-
for (const input of inputs) if (isObject$
|
|
1026
|
+
if (isArray$2(inputs)) {
|
|
1027
|
+
for (const input of inputs) if (isObject$2(input) && isString$2(input["value-key"]) && isString$2(input.id)) {
|
|
1028
1028
|
lookup[input["value-key"]] = nodeRef(input.id);
|
|
1029
1029
|
if (input.optional === true) idOptional.add(input.id);
|
|
1030
1030
|
}
|
|
1031
1031
|
}
|
|
1032
1032
|
for (const out of outputFiles) {
|
|
1033
|
-
if (!isObject$
|
|
1033
|
+
if (!isObject$2(out)) {
|
|
1034
1034
|
this.warn("Skipping non-object output-files entry");
|
|
1035
1035
|
continue;
|
|
1036
1036
|
}
|
|
@@ -1042,7 +1042,7 @@ var BoutiquesParser = class {
|
|
|
1042
1042
|
}
|
|
1043
1043
|
buildAppMeta(bt) {
|
|
1044
1044
|
const id = bt.id ?? bt.name;
|
|
1045
|
-
if (!isString$
|
|
1045
|
+
if (!isString$2(id)) return void 0;
|
|
1046
1046
|
const name = bt.name;
|
|
1047
1047
|
const description = bt.description;
|
|
1048
1048
|
const version = bt["tool-version"];
|
|
@@ -1052,30 +1052,30 @@ var BoutiquesParser = class {
|
|
|
1052
1052
|
const stdout = bt["stdout-output"];
|
|
1053
1053
|
const stderr = bt["stderr-output"];
|
|
1054
1054
|
const doc = {
|
|
1055
|
-
...isString$
|
|
1056
|
-
...isString$
|
|
1057
|
-
...isString$
|
|
1058
|
-
...isString$
|
|
1055
|
+
...isString$2(name) && { title: name },
|
|
1056
|
+
...isString$2(description) && { description },
|
|
1057
|
+
...isString$2(author) && { authors: [author] },
|
|
1058
|
+
...isString$2(url) && { urls: [url] }
|
|
1059
1059
|
};
|
|
1060
1060
|
return {
|
|
1061
1061
|
id,
|
|
1062
|
-
...isString$
|
|
1062
|
+
...isString$2(version) && { version },
|
|
1063
1063
|
...Object.keys(doc).length > 0 && { doc },
|
|
1064
|
-
...isObject$
|
|
1064
|
+
...isObject$2(container) && isString$2(container.image) && { container: {
|
|
1065
1065
|
image: container.image,
|
|
1066
|
-
...isString$
|
|
1066
|
+
...isString$2(container.type) && { type: container.type }
|
|
1067
1067
|
} },
|
|
1068
|
-
...isObject$
|
|
1069
|
-
...isObject$
|
|
1068
|
+
...isObject$2(stdout) && { stdout: this.buildStreamMeta(stdout) },
|
|
1069
|
+
...isObject$2(stderr) && { stderr: this.buildStreamMeta(stderr) }
|
|
1070
1070
|
};
|
|
1071
1071
|
}
|
|
1072
1072
|
buildEnumAlternative(choices, meta) {
|
|
1073
1073
|
const alts = [];
|
|
1074
|
-
for (const choice of choices) if (isString$
|
|
1074
|
+
for (const choice of choices) if (isString$2(choice)) alts.push({
|
|
1075
1075
|
kind: "literal",
|
|
1076
1076
|
attrs: { str: choice }
|
|
1077
1077
|
});
|
|
1078
|
-
else if (isNumber(choice)) alts.push({
|
|
1078
|
+
else if (isNumber$1(choice)) alts.push({
|
|
1079
1079
|
kind: "literal",
|
|
1080
1080
|
attrs: { str: String(choice) }
|
|
1081
1081
|
});
|
|
@@ -1092,7 +1092,7 @@ var BoutiquesParser = class {
|
|
|
1092
1092
|
const meta = this.buildNodeMeta(btInput);
|
|
1093
1093
|
if (inputType.isEnum) {
|
|
1094
1094
|
const choices = btInput["value-choices"];
|
|
1095
|
-
if (!isArray$
|
|
1095
|
+
if (!isArray$2(choices)) {
|
|
1096
1096
|
this.error(`Invalid value-choices for '${btInput.id}'`);
|
|
1097
1097
|
return null;
|
|
1098
1098
|
}
|
|
@@ -1112,11 +1112,11 @@ var BoutiquesParser = class {
|
|
|
1112
1112
|
kind: "int",
|
|
1113
1113
|
attrs: {}
|
|
1114
1114
|
};
|
|
1115
|
-
if (isNumber(btInput.minimum)) {
|
|
1115
|
+
if (isNumber$1(btInput.minimum)) {
|
|
1116
1116
|
node.attrs.minValue = Math.floor(btInput.minimum);
|
|
1117
1117
|
if (btInput["exclusive-minimum"] === true) node.attrs.minValue += 1;
|
|
1118
1118
|
}
|
|
1119
|
-
if (isNumber(btInput.maximum)) {
|
|
1119
|
+
if (isNumber$1(btInput.maximum)) {
|
|
1120
1120
|
node.attrs.maxValue = Math.floor(btInput.maximum);
|
|
1121
1121
|
if (btInput["exclusive-maximum"] === true) node.attrs.maxValue -= 1;
|
|
1122
1122
|
}
|
|
@@ -1128,8 +1128,8 @@ var BoutiquesParser = class {
|
|
|
1128
1128
|
kind: "float",
|
|
1129
1129
|
attrs: {}
|
|
1130
1130
|
};
|
|
1131
|
-
if (isNumber(btInput.minimum)) node.attrs.minValue = btInput.minimum;
|
|
1132
|
-
if (isNumber(btInput.maximum)) node.attrs.maxValue = btInput.maximum;
|
|
1131
|
+
if (isNumber$1(btInput.minimum)) node.attrs.minValue = btInput.minimum;
|
|
1132
|
+
if (isNumber$1(btInput.maximum)) node.attrs.maxValue = btInput.maximum;
|
|
1133
1133
|
if (meta) node.meta = meta;
|
|
1134
1134
|
return node;
|
|
1135
1135
|
}
|
|
@@ -1146,7 +1146,7 @@ var BoutiquesParser = class {
|
|
|
1146
1146
|
}
|
|
1147
1147
|
case InputTypePrimitive.Flag: {
|
|
1148
1148
|
const flag = btInput["command-line-flag"];
|
|
1149
|
-
if (!isString$
|
|
1149
|
+
if (!isString$2(flag)) {
|
|
1150
1150
|
this.error(`Flag input '${btInput.id}' missing command-line-flag`);
|
|
1151
1151
|
return null;
|
|
1152
1152
|
}
|
|
@@ -1164,7 +1164,7 @@ var BoutiquesParser = class {
|
|
|
1164
1164
|
}
|
|
1165
1165
|
case InputTypePrimitive.SubCommand: {
|
|
1166
1166
|
const nested = btInput.type;
|
|
1167
|
-
if (!isObject$
|
|
1167
|
+
if (!isObject$2(nested)) {
|
|
1168
1168
|
this.error(`Invalid subcommand type for '${btInput.id}'`);
|
|
1169
1169
|
return null;
|
|
1170
1170
|
}
|
|
@@ -1174,14 +1174,14 @@ var BoutiquesParser = class {
|
|
|
1174
1174
|
}
|
|
1175
1175
|
case InputTypePrimitive.SubCommandUnion: {
|
|
1176
1176
|
const alts = btInput.type;
|
|
1177
|
-
if (!isArray$
|
|
1177
|
+
if (!isArray$2(alts)) {
|
|
1178
1178
|
this.error(`Invalid subcommand union type for '${btInput.id}'`);
|
|
1179
1179
|
return null;
|
|
1180
1180
|
}
|
|
1181
1181
|
const parsedAlts = [];
|
|
1182
1182
|
const usedTags = /* @__PURE__ */ new Set();
|
|
1183
1183
|
for (const alt of alts) {
|
|
1184
|
-
if (!isObject$
|
|
1184
|
+
if (!isObject$2(alt)) {
|
|
1185
1185
|
this.warn("Skipping non-object subcommand alternative");
|
|
1186
1186
|
continue;
|
|
1187
1187
|
}
|
|
@@ -1233,15 +1233,15 @@ var BoutiquesParser = class {
|
|
|
1233
1233
|
kind: "repeat",
|
|
1234
1234
|
attrs: {
|
|
1235
1235
|
node,
|
|
1236
|
-
...isString$
|
|
1237
|
-
...isNumber(btInput["min-list-entries"]) && { countMin: btInput["min-list-entries"] },
|
|
1238
|
-
...isNumber(btInput["max-list-entries"]) && { countMax: btInput["max-list-entries"] }
|
|
1236
|
+
...isString$2(btInput["list-separator"]) && { join: btInput["list-separator"] },
|
|
1237
|
+
...isNumber$1(btInput["min-list-entries"]) && { countMin: btInput["min-list-entries"] },
|
|
1238
|
+
...isNumber$1(btInput["max-list-entries"]) && { countMax: btInput["max-list-entries"] }
|
|
1239
1239
|
}
|
|
1240
1240
|
};
|
|
1241
1241
|
}
|
|
1242
1242
|
wrapWithFlag(node, btInput) {
|
|
1243
1243
|
const flag = btInput["command-line-flag"];
|
|
1244
|
-
if (!isString$
|
|
1244
|
+
if (!isString$2(flag)) return node;
|
|
1245
1245
|
return {
|
|
1246
1246
|
kind: "sequence",
|
|
1247
1247
|
attrs: { nodes: [{
|
|
@@ -1288,11 +1288,11 @@ var BoutiquesParser = class {
|
|
|
1288
1288
|
parseDescriptor(bt) {
|
|
1289
1289
|
const inputs = bt["inputs"];
|
|
1290
1290
|
const inputsLookup = /* @__PURE__ */ new Map();
|
|
1291
|
-
if (isArray$
|
|
1292
|
-
for (const input of inputs) if (isObject$
|
|
1291
|
+
if (isArray$2(inputs)) {
|
|
1292
|
+
for (const input of inputs) if (isObject$2(input) && isString$2(input["value-key"])) inputsLookup.set(input["value-key"], input);
|
|
1293
1293
|
}
|
|
1294
1294
|
const commandLine = bt["command-line"];
|
|
1295
|
-
const segments = isString$
|
|
1295
|
+
const segments = isString$2(commandLine) ? this.parseCommandLineTemplate(commandLine, inputsLookup) : [];
|
|
1296
1296
|
const rootSeq = {
|
|
1297
1297
|
kind: "sequence",
|
|
1298
1298
|
attrs: { nodes: [] }
|
|
@@ -1305,7 +1305,7 @@ var BoutiquesParser = class {
|
|
|
1305
1305
|
join: ""
|
|
1306
1306
|
}
|
|
1307
1307
|
};
|
|
1308
|
-
for (const elem of segment) if (isObject$
|
|
1308
|
+
for (const elem of segment) if (isObject$2(elem)) {
|
|
1309
1309
|
const inputType = this.getInputType(elem);
|
|
1310
1310
|
if (inputType === null) continue;
|
|
1311
1311
|
let node = this.buildTerminal(elem, inputType);
|
|
@@ -1477,6 +1477,419 @@ function camelCase(s) {
|
|
|
1477
1477
|
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
1478
1478
|
}
|
|
1479
1479
|
|
|
1480
|
+
//#endregion
|
|
1481
|
+
//#region src/frontend/mrtrix/parser.ts
|
|
1482
|
+
function isObject$1(x) {
|
|
1483
|
+
return typeof x === "object" && x !== null && !Array.isArray(x);
|
|
1484
|
+
}
|
|
1485
|
+
function isString$1(x) {
|
|
1486
|
+
return typeof x === "string";
|
|
1487
|
+
}
|
|
1488
|
+
function isNumber(x) {
|
|
1489
|
+
return typeof x === "number" && Number.isFinite(x);
|
|
1490
|
+
}
|
|
1491
|
+
function isArray$1(x) {
|
|
1492
|
+
return Array.isArray(x);
|
|
1493
|
+
}
|
|
1494
|
+
/** Coerce a possibly-missing list field to an array. */
|
|
1495
|
+
function asArray$1(x) {
|
|
1496
|
+
return isArray$1(x) ? x : [];
|
|
1497
|
+
}
|
|
1498
|
+
/** Input file types: each maps to a plain path terminal. */
|
|
1499
|
+
const INPUT_TYPES = new Set([
|
|
1500
|
+
"file in",
|
|
1501
|
+
"image in",
|
|
1502
|
+
"directory in",
|
|
1503
|
+
"tracks in"
|
|
1504
|
+
]);
|
|
1505
|
+
/** Output file types: each maps to a `str` (the user-supplied filename) + an `Output`. */
|
|
1506
|
+
const OUTPUT_TYPES = new Set([
|
|
1507
|
+
"file out",
|
|
1508
|
+
"image out",
|
|
1509
|
+
"directory out",
|
|
1510
|
+
"tracks out"
|
|
1511
|
+
]);
|
|
1512
|
+
/** A fresh empty root sequence for error returns (IR passes mutate in place,
|
|
1513
|
+
* so callers must not share a single instance). */
|
|
1514
|
+
function emptyExpr$1() {
|
|
1515
|
+
return {
|
|
1516
|
+
kind: "sequence",
|
|
1517
|
+
attrs: { nodes: [] }
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* Parser for MRtrix3 C++ command definitions (`mrtrix.json`), as dumped by the
|
|
1522
|
+
* `__print_usage_json__` hook (see niwrap `extraction/mrtrix/`).
|
|
1523
|
+
*
|
|
1524
|
+
* The format is flat: positional `arguments`, plus `option_groups[].options`
|
|
1525
|
+
* where each option is a single-dash switch (`-id`) carrying 0..N positional
|
|
1526
|
+
* `arguments`. There are no unions, conditionals, or nested options. We lower
|
|
1527
|
+
* it onto the same `Expr` shapes the Boutiques/Workbench parsers emit:
|
|
1528
|
+
*
|
|
1529
|
+
* <command> <positionals...> [-option ...] ...
|
|
1530
|
+
*
|
|
1531
|
+
* - argument (input type) -> typed terminal
|
|
1532
|
+
* - argument (`* out`) -> str terminal + an Output entry referencing it
|
|
1533
|
+
* - option, 0 args -> opt(lit(-switch)) (bool flag)
|
|
1534
|
+
* - option, 1 arg -> opt(seq(lit(-switch), value)) (flat optional)
|
|
1535
|
+
* - option, >1 arg / multi -> opt|rep(seq(lit(-switch), ...)) (sub-struct)
|
|
1536
|
+
*
|
|
1537
|
+
* Type mapping mirrors the v1 `mrt2bt.js` converter's `set_type`, plus `choice`
|
|
1538
|
+
* values: when the dump carries `choices` it lowers to an enum (an alternative
|
|
1539
|
+
* of literals), else a plain string (older dumps that omit the values - as v1
|
|
1540
|
+
* always did). Per-command quirks v1 hand-coded that the flat dump cannot
|
|
1541
|
+
* express (e.g. dwi2fod/mtnormalise paired in/out args) are intentionally NOT
|
|
1542
|
+
* special-cased here - they are patched on the niwrap side post-dump, keeping
|
|
1543
|
+
* this frontend format-general.
|
|
1544
|
+
*/
|
|
1545
|
+
var MrtrixParser = class {
|
|
1546
|
+
name = "mrtrix";
|
|
1547
|
+
extensions = ["json"];
|
|
1548
|
+
errors = [];
|
|
1549
|
+
warnings = [];
|
|
1550
|
+
usedNames = /* @__PURE__ */ new Set();
|
|
1551
|
+
reset() {
|
|
1552
|
+
this.errors = [];
|
|
1553
|
+
this.warnings = [];
|
|
1554
|
+
this.usedNames = /* @__PURE__ */ new Set();
|
|
1555
|
+
}
|
|
1556
|
+
/** Reserve a unique sibling name, suffixing `_2`, `_3`, ... on collision. */
|
|
1557
|
+
uniqueName(base) {
|
|
1558
|
+
if (!this.usedNames.has(base)) {
|
|
1559
|
+
this.usedNames.add(base);
|
|
1560
|
+
return base;
|
|
1561
|
+
}
|
|
1562
|
+
let n = 2;
|
|
1563
|
+
while (this.usedNames.has(`${base}_${n}`)) n++;
|
|
1564
|
+
const name = `${base}_${n}`;
|
|
1565
|
+
this.usedNames.add(name);
|
|
1566
|
+
this.warn(`Duplicate id '${base}'; renamed a sibling binding to '${name}'`);
|
|
1567
|
+
return name;
|
|
1568
|
+
}
|
|
1569
|
+
error(message, location) {
|
|
1570
|
+
this.errors.push({
|
|
1571
|
+
message,
|
|
1572
|
+
location
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
warn(message, location) {
|
|
1576
|
+
this.warnings.push({
|
|
1577
|
+
message,
|
|
1578
|
+
location
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
parseJSON(source) {
|
|
1582
|
+
let parsed;
|
|
1583
|
+
try {
|
|
1584
|
+
parsed = JSON.parse(source);
|
|
1585
|
+
} catch (e) {
|
|
1586
|
+
this.error(e instanceof SyntaxError ? e.message : "Invalid JSON");
|
|
1587
|
+
return null;
|
|
1588
|
+
}
|
|
1589
|
+
if (!isObject$1(parsed)) {
|
|
1590
|
+
this.error("JSON source is not an object");
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
return parsed;
|
|
1594
|
+
}
|
|
1595
|
+
docFrom(description) {
|
|
1596
|
+
return isString$1(description) && description.length > 0 ? { description } : void 0;
|
|
1597
|
+
}
|
|
1598
|
+
buildAppMeta(cmd) {
|
|
1599
|
+
const name = cmd.name;
|
|
1600
|
+
if (!isString$1(name) || name.length === 0) {
|
|
1601
|
+
this.error("MRtrix descriptor missing required 'name' string");
|
|
1602
|
+
return;
|
|
1603
|
+
}
|
|
1604
|
+
const synopsis = cmd.synopsis;
|
|
1605
|
+
const paragraphs = asArray$1(cmd.description).filter(isString$1).join("\n\n");
|
|
1606
|
+
const author = cmd.author;
|
|
1607
|
+
const references = asArray$1(cmd.references).filter(isString$1);
|
|
1608
|
+
const version = cmd.version;
|
|
1609
|
+
const doc = {
|
|
1610
|
+
...isString$1(synopsis) && synopsis.length > 0 && { title: synopsis },
|
|
1611
|
+
...paragraphs.length > 0 && { description: paragraphs },
|
|
1612
|
+
...isString$1(author) && author.length > 0 && { authors: [author] },
|
|
1613
|
+
...references.length > 0 && { literature: references },
|
|
1614
|
+
urls: [`https://mrtrix.readthedocs.io/en/latest/reference/commands/${name}.html`]
|
|
1615
|
+
};
|
|
1616
|
+
return {
|
|
1617
|
+
id: name,
|
|
1618
|
+
...isString$1(version) && version.length > 0 && { version },
|
|
1619
|
+
doc
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Integer/float bounds, when the dump carries them. The C++ hook serializes
|
|
1624
|
+
* `Argument::limits.{i,f}.{min,max}` as `min`/`max`, omitting the unbounded
|
|
1625
|
+
* sentinels - so a present value is a real, tool-enforced bound.
|
|
1626
|
+
*/
|
|
1627
|
+
numericBounds(arg) {
|
|
1628
|
+
const lo = arg.min;
|
|
1629
|
+
const hi = arg.max;
|
|
1630
|
+
return {
|
|
1631
|
+
...isNumber(lo) && { minValue: lo },
|
|
1632
|
+
...isNumber(hi) && { maxValue: hi }
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Lower one MRtrix argument to its terminal node (carrying name + doc) and,
|
|
1637
|
+
* for output types, an accompanying `Output`.
|
|
1638
|
+
*/
|
|
1639
|
+
buildArgTerminal(arg, meta) {
|
|
1640
|
+
const argType = arg.type;
|
|
1641
|
+
if (!isString$1(argType)) {
|
|
1642
|
+
this.error(`MRtrix argument '${String(arg.id)}' missing 'type'`);
|
|
1643
|
+
return null;
|
|
1644
|
+
}
|
|
1645
|
+
const name = meta.name;
|
|
1646
|
+
switch (argType) {
|
|
1647
|
+
case "integer": {
|
|
1648
|
+
const node = int(meta);
|
|
1649
|
+
Object.assign(node.attrs, this.numericBounds(arg));
|
|
1650
|
+
return { node };
|
|
1651
|
+
}
|
|
1652
|
+
case "float": {
|
|
1653
|
+
const node = float(meta);
|
|
1654
|
+
Object.assign(node.attrs, this.numericBounds(arg));
|
|
1655
|
+
return { node };
|
|
1656
|
+
}
|
|
1657
|
+
case "text":
|
|
1658
|
+
case "undefined":
|
|
1659
|
+
case "boolean": return { node: str(meta) };
|
|
1660
|
+
case "choice": {
|
|
1661
|
+
const choices = arg.choices;
|
|
1662
|
+
if (isArray$1(choices)) {
|
|
1663
|
+
const alts = choices.filter(isString$1).map((c) => lit(c));
|
|
1664
|
+
if (alts.length > 0) {
|
|
1665
|
+
const node = alt(...alts);
|
|
1666
|
+
node.meta = meta;
|
|
1667
|
+
return { node };
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return { node: str(meta) };
|
|
1671
|
+
}
|
|
1672
|
+
case "int seq": {
|
|
1673
|
+
const node = repJoin(",", int());
|
|
1674
|
+
node.meta = meta;
|
|
1675
|
+
return { node };
|
|
1676
|
+
}
|
|
1677
|
+
case "float seq": {
|
|
1678
|
+
const node = repJoin(",", float());
|
|
1679
|
+
node.meta = meta;
|
|
1680
|
+
return { node };
|
|
1681
|
+
}
|
|
1682
|
+
case "various": {
|
|
1683
|
+
const stringArm = seq(str({ name: "obj" }));
|
|
1684
|
+
stringArm.meta = {
|
|
1685
|
+
name: "VariousString",
|
|
1686
|
+
variantTag: "VariousString"
|
|
1687
|
+
};
|
|
1688
|
+
const fileArm = seq(path({ name: "obj" }));
|
|
1689
|
+
fileArm.meta = {
|
|
1690
|
+
name: "VariousFile",
|
|
1691
|
+
variantTag: "VariousFile"
|
|
1692
|
+
};
|
|
1693
|
+
const node = alt(stringArm, fileArm);
|
|
1694
|
+
node.meta = meta;
|
|
1695
|
+
return { node };
|
|
1696
|
+
}
|
|
1697
|
+
default:
|
|
1698
|
+
if (INPUT_TYPES.has(argType)) return { node: path(meta) };
|
|
1699
|
+
if (OUTPUT_TYPES.has(argType)) return {
|
|
1700
|
+
node: str(meta),
|
|
1701
|
+
output: {
|
|
1702
|
+
name,
|
|
1703
|
+
...meta.doc && { doc: meta.doc },
|
|
1704
|
+
tokens: [{
|
|
1705
|
+
kind: "ref",
|
|
1706
|
+
target: nodeRef(name)
|
|
1707
|
+
}],
|
|
1708
|
+
mediaTypes: [`mrtrix/${argType}`]
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
this.error(`Unknown MRtrix type '${argType}' for '${name}'`);
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
/**
|
|
1716
|
+
* A positional argument: typed terminal, wrapped for cardinality. Output-type
|
|
1717
|
+
* positionals push their `Output` onto the root's `outputs` collector.
|
|
1718
|
+
*/
|
|
1719
|
+
buildPositional(arg, rootOutputs) {
|
|
1720
|
+
if (!isObject$1(arg)) {
|
|
1721
|
+
this.warn("Skipping non-object argument");
|
|
1722
|
+
return null;
|
|
1723
|
+
}
|
|
1724
|
+
const id = arg.id;
|
|
1725
|
+
if (!isString$1(id)) {
|
|
1726
|
+
this.error("MRtrix argument missing 'id'");
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
const meta = {
|
|
1730
|
+
name: this.uniqueName(snakeCase(id)),
|
|
1731
|
+
...this.docMeta(arg.description)
|
|
1732
|
+
};
|
|
1733
|
+
const built = this.buildArgTerminal(arg, meta);
|
|
1734
|
+
if (!built) return null;
|
|
1735
|
+
if (built.output) rootOutputs.push(built.output);
|
|
1736
|
+
return this.applyCardinality(built.node, arg, built.output !== void 0);
|
|
1737
|
+
}
|
|
1738
|
+
/** Wrap a terminal for `allow_multiple` (repeat) and `optional` (optional). */
|
|
1739
|
+
applyCardinality(node, arg, isOutput) {
|
|
1740
|
+
let result = node;
|
|
1741
|
+
if (arg.allow_multiple === true && !isOutput && result.kind !== "repeat") result = rep(result);
|
|
1742
|
+
else if (arg.allow_multiple === true && isOutput) this.warn(`Output argument '${String(arg.id)}' is allow_multiple; treating as single`);
|
|
1743
|
+
if (arg.optional === true) result = opt(result);
|
|
1744
|
+
return result;
|
|
1745
|
+
}
|
|
1746
|
+
docMeta(description) {
|
|
1747
|
+
const doc = this.docFrom(description);
|
|
1748
|
+
return doc ? { doc } : {};
|
|
1749
|
+
}
|
|
1750
|
+
/**
|
|
1751
|
+
* An option `-{id}` with 0..N arguments. Returns the node plus any outputs
|
|
1752
|
+
* that must live on the ROOT (collapsing single-value options); sub-struct
|
|
1753
|
+
* options carry their own outputs on the struct sequence's meta.
|
|
1754
|
+
*/
|
|
1755
|
+
buildOption(option) {
|
|
1756
|
+
if (!isObject$1(option)) {
|
|
1757
|
+
this.warn("Skipping non-object option");
|
|
1758
|
+
return null;
|
|
1759
|
+
}
|
|
1760
|
+
const id = option.id;
|
|
1761
|
+
if (!isString$1(id)) {
|
|
1762
|
+
this.error("MRtrix option missing 'id'");
|
|
1763
|
+
return null;
|
|
1764
|
+
}
|
|
1765
|
+
const flag = `-${id}`;
|
|
1766
|
+
const name = this.uniqueName(snakeCase(id));
|
|
1767
|
+
const optDoc = this.docFrom(option.description);
|
|
1768
|
+
const args = asArray$1(option.arguments);
|
|
1769
|
+
const required = option.optional === false;
|
|
1770
|
+
if (args.length === 0) {
|
|
1771
|
+
if (option.allow_multiple === true) return {
|
|
1772
|
+
node: rep(lit(flag), {
|
|
1773
|
+
name,
|
|
1774
|
+
...optDoc && { doc: optDoc }
|
|
1775
|
+
}),
|
|
1776
|
+
rootOutputs: []
|
|
1777
|
+
};
|
|
1778
|
+
const meta = {
|
|
1779
|
+
name,
|
|
1780
|
+
...optDoc && { doc: optDoc },
|
|
1781
|
+
defaultValue: false
|
|
1782
|
+
};
|
|
1783
|
+
return {
|
|
1784
|
+
node: opt(lit(flag), meta),
|
|
1785
|
+
rootOutputs: []
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
if (args.length === 1 && option.allow_multiple !== true) {
|
|
1789
|
+
const arg = args[0];
|
|
1790
|
+
if (!isObject$1(arg)) {
|
|
1791
|
+
this.error(`MRtrix option '${id}' has a non-object argument`);
|
|
1792
|
+
return null;
|
|
1793
|
+
}
|
|
1794
|
+
const meta = {
|
|
1795
|
+
name,
|
|
1796
|
+
...optDoc && { doc: optDoc }
|
|
1797
|
+
};
|
|
1798
|
+
const built = this.buildArgTerminal(arg, meta);
|
|
1799
|
+
if (!built) return null;
|
|
1800
|
+
const valueNode = this.applyCardinality(built.node, arg, built.output !== void 0);
|
|
1801
|
+
const flagSeq = seq(lit(flag), valueNode);
|
|
1802
|
+
return {
|
|
1803
|
+
node: required ? flagSeq : opt(flagSeq),
|
|
1804
|
+
rootOutputs: built.output ? [built.output] : []
|
|
1805
|
+
};
|
|
1806
|
+
}
|
|
1807
|
+
const inner = [lit(flag)];
|
|
1808
|
+
const structOutputs = [];
|
|
1809
|
+
for (const rawArg of args) {
|
|
1810
|
+
if (!isObject$1(rawArg)) {
|
|
1811
|
+
this.warn(`Skipping non-object argument of option '${id}'`);
|
|
1812
|
+
continue;
|
|
1813
|
+
}
|
|
1814
|
+
const argId = rawArg.id;
|
|
1815
|
+
if (!isString$1(argId)) {
|
|
1816
|
+
this.error(`MRtrix option '${id}' has an argument missing 'id'`);
|
|
1817
|
+
continue;
|
|
1818
|
+
}
|
|
1819
|
+
const argDoc = this.docFrom(rawArg.description) ?? optDoc;
|
|
1820
|
+
const meta = {
|
|
1821
|
+
name: snakeCase(argId),
|
|
1822
|
+
...argDoc && { doc: argDoc }
|
|
1823
|
+
};
|
|
1824
|
+
const built = this.buildArgTerminal(rawArg, meta);
|
|
1825
|
+
if (!built) continue;
|
|
1826
|
+
const valueNode = this.applyCardinality(built.node, rawArg, built.output !== void 0);
|
|
1827
|
+
inner.push(valueNode);
|
|
1828
|
+
if (built.output) structOutputs.push(built.output);
|
|
1829
|
+
}
|
|
1830
|
+
const structSeq = seq(...inner);
|
|
1831
|
+
structSeq.meta = {
|
|
1832
|
+
name,
|
|
1833
|
+
...structOutputs.length > 0 && { outputs: structOutputs }
|
|
1834
|
+
};
|
|
1835
|
+
const wrapperMeta = optDoc ? { doc: optDoc } : void 0;
|
|
1836
|
+
let node;
|
|
1837
|
+
if (option.allow_multiple === true) node = rep(structSeq, wrapperMeta);
|
|
1838
|
+
else if (required) {
|
|
1839
|
+
if (optDoc) structSeq.meta = {
|
|
1840
|
+
...structSeq.meta,
|
|
1841
|
+
doc: optDoc
|
|
1842
|
+
};
|
|
1843
|
+
node = structSeq;
|
|
1844
|
+
} else node = opt(structSeq, wrapperMeta);
|
|
1845
|
+
return {
|
|
1846
|
+
node,
|
|
1847
|
+
rootOutputs: []
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1850
|
+
parse(source, _filename) {
|
|
1851
|
+
this.reset();
|
|
1852
|
+
const cmd = this.parseJSON(source);
|
|
1853
|
+
if (!cmd) return {
|
|
1854
|
+
expr: emptyExpr$1(),
|
|
1855
|
+
errors: this.errors,
|
|
1856
|
+
warnings: this.warnings
|
|
1857
|
+
};
|
|
1858
|
+
const meta = this.buildAppMeta(cmd);
|
|
1859
|
+
if (!meta || !isString$1(cmd.name)) return {
|
|
1860
|
+
expr: emptyExpr$1(),
|
|
1861
|
+
errors: this.errors,
|
|
1862
|
+
warnings: this.warnings
|
|
1863
|
+
};
|
|
1864
|
+
const nodes = [lit(cmd.name)];
|
|
1865
|
+
const rootOutputs = [];
|
|
1866
|
+
for (const arg of asArray$1(cmd.arguments)) {
|
|
1867
|
+
const node = this.buildPositional(arg, rootOutputs);
|
|
1868
|
+
if (node) nodes.push(node);
|
|
1869
|
+
}
|
|
1870
|
+
for (const group of asArray$1(cmd.option_groups)) {
|
|
1871
|
+
if (!isObject$1(group)) continue;
|
|
1872
|
+
for (const option of asArray$1(group.options)) {
|
|
1873
|
+
const built = this.buildOption(option);
|
|
1874
|
+
if (!built) continue;
|
|
1875
|
+
nodes.push(built.node);
|
|
1876
|
+
rootOutputs.push(...built.rootOutputs);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
const rootSeq = seq(...nodes);
|
|
1880
|
+
rootSeq.meta = {
|
|
1881
|
+
name: meta.id,
|
|
1882
|
+
...rootOutputs.length > 0 && { outputs: rootOutputs }
|
|
1883
|
+
};
|
|
1884
|
+
return {
|
|
1885
|
+
meta,
|
|
1886
|
+
expr: rootSeq,
|
|
1887
|
+
errors: this.errors,
|
|
1888
|
+
warnings: this.warnings
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
|
|
1480
1893
|
//#endregion
|
|
1481
1894
|
//#region src/frontend/workbench/parser.ts
|
|
1482
1895
|
function isObject(x) {
|
|
@@ -1808,6 +2221,7 @@ function detectFormat(source) {
|
|
|
1808
2221
|
const obj = parsed;
|
|
1809
2222
|
if (typeof obj.$schema === "string" && obj.$schema.includes("argdump")) return "argdump";
|
|
1810
2223
|
if (typeof obj.command === "string" && typeof obj.short_description === "string") return "workbench";
|
|
2224
|
+
if (typeof obj.synopsis === "string" && Array.isArray(obj.option_groups) && Array.isArray(obj.arguments)) return "mrtrix";
|
|
1811
2225
|
if ("command-line" in obj || Array.isArray(obj.inputs) && "name" in obj) return "boutiques";
|
|
1812
2226
|
if (Array.isArray(obj.actions) && "prog" in obj) return "argdump";
|
|
1813
2227
|
return null;
|
|
@@ -7869,7 +8283,7 @@ function compile(source, filenameOrOptions) {
|
|
|
7869
8283
|
errors: [{ message: "Could not detect input format. Specify format explicitly." }],
|
|
7870
8284
|
warnings: []
|
|
7871
8285
|
};
|
|
7872
|
-
return (format === "argdump" ? new ArgdumpParser() : format === "workbench" ? new WorkbenchParser() : new BoutiquesParser()).parse(source, options.filename);
|
|
8286
|
+
return (format === "argdump" ? new ArgdumpParser() : format === "workbench" ? new WorkbenchParser() : format === "mrtrix" ? new MrtrixParser() : new BoutiquesParser()).parse(source, options.filename);
|
|
7873
8287
|
}
|
|
7874
8288
|
|
|
7875
8289
|
//#endregion
|