brkraw 0.3.11__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
Files changed (113) hide show
  1. brkraw/__init__.py +9 -3
  2. brkraw/apps/__init__.py +12 -0
  3. brkraw/apps/addon/__init__.py +30 -0
  4. brkraw/apps/addon/core.py +35 -0
  5. brkraw/apps/addon/dependencies.py +402 -0
  6. brkraw/apps/addon/installation.py +500 -0
  7. brkraw/apps/addon/io.py +21 -0
  8. brkraw/apps/hook/__init__.py +25 -0
  9. brkraw/apps/hook/core.py +636 -0
  10. brkraw/apps/loader/__init__.py +10 -0
  11. brkraw/apps/loader/core.py +622 -0
  12. brkraw/apps/loader/formatter.py +288 -0
  13. brkraw/apps/loader/helper.py +797 -0
  14. brkraw/apps/loader/info/__init__.py +11 -0
  15. brkraw/apps/loader/info/scan.py +85 -0
  16. brkraw/apps/loader/info/scan.yaml +90 -0
  17. brkraw/apps/loader/info/study.py +69 -0
  18. brkraw/apps/loader/info/study.yaml +156 -0
  19. brkraw/apps/loader/info/transform.py +92 -0
  20. brkraw/apps/loader/types.py +220 -0
  21. brkraw/cli/__init__.py +5 -0
  22. brkraw/cli/commands/__init__.py +2 -0
  23. brkraw/cli/commands/addon.py +327 -0
  24. brkraw/cli/commands/config.py +205 -0
  25. brkraw/cli/commands/convert.py +903 -0
  26. brkraw/cli/commands/hook.py +348 -0
  27. brkraw/cli/commands/info.py +74 -0
  28. brkraw/cli/commands/init.py +214 -0
  29. brkraw/cli/commands/params.py +106 -0
  30. brkraw/cli/commands/prune.py +288 -0
  31. brkraw/cli/commands/session.py +371 -0
  32. brkraw/cli/hook_args.py +80 -0
  33. brkraw/cli/main.py +83 -0
  34. brkraw/cli/utils.py +60 -0
  35. brkraw/core/__init__.py +13 -0
  36. brkraw/core/config.py +380 -0
  37. brkraw/core/entrypoints.py +25 -0
  38. brkraw/core/formatter.py +367 -0
  39. brkraw/core/fs.py +495 -0
  40. brkraw/core/jcamp.py +600 -0
  41. brkraw/core/layout.py +451 -0
  42. brkraw/core/parameters.py +781 -0
  43. brkraw/core/zip.py +1121 -0
  44. brkraw/dataclasses/__init__.py +14 -0
  45. brkraw/dataclasses/node.py +139 -0
  46. brkraw/dataclasses/reco.py +33 -0
  47. brkraw/dataclasses/scan.py +61 -0
  48. brkraw/dataclasses/study.py +131 -0
  49. brkraw/default/__init__.py +3 -0
  50. brkraw/default/pruner_specs/deid4share.yaml +42 -0
  51. brkraw/default/rules/00_default.yaml +4 -0
  52. brkraw/default/specs/metadata_dicom.yaml +236 -0
  53. brkraw/default/specs/metadata_transforms.py +92 -0
  54. brkraw/resolver/__init__.py +7 -0
  55. brkraw/resolver/affine.py +539 -0
  56. brkraw/resolver/datatype.py +69 -0
  57. brkraw/resolver/fid.py +90 -0
  58. brkraw/resolver/helpers.py +36 -0
  59. brkraw/resolver/image.py +188 -0
  60. brkraw/resolver/nifti.py +370 -0
  61. brkraw/resolver/shape.py +235 -0
  62. brkraw/schema/__init__.py +3 -0
  63. brkraw/schema/context_map.yaml +62 -0
  64. brkraw/schema/meta.yaml +57 -0
  65. brkraw/schema/niftiheader.yaml +95 -0
  66. brkraw/schema/pruner.yaml +55 -0
  67. brkraw/schema/remapper.yaml +128 -0
  68. brkraw/schema/rules.yaml +154 -0
  69. brkraw/specs/__init__.py +10 -0
  70. brkraw/specs/hook/__init__.py +12 -0
  71. brkraw/specs/hook/logic.py +31 -0
  72. brkraw/specs/hook/validator.py +22 -0
  73. brkraw/specs/meta/__init__.py +5 -0
  74. brkraw/specs/meta/validator.py +156 -0
  75. brkraw/specs/pruner/__init__.py +15 -0
  76. brkraw/specs/pruner/logic.py +361 -0
  77. brkraw/specs/pruner/validator.py +119 -0
  78. brkraw/specs/remapper/__init__.py +27 -0
  79. brkraw/specs/remapper/logic.py +924 -0
  80. brkraw/specs/remapper/validator.py +314 -0
  81. brkraw/specs/rules/__init__.py +6 -0
  82. brkraw/specs/rules/logic.py +263 -0
  83. brkraw/specs/rules/validator.py +103 -0
  84. brkraw-0.5.0.dist-info/METADATA +81 -0
  85. brkraw-0.5.0.dist-info/RECORD +88 -0
  86. {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info}/WHEEL +1 -2
  87. brkraw-0.5.0.dist-info/entry_points.txt +13 -0
  88. brkraw/lib/__init__.py +0 -4
  89. brkraw/lib/backup.py +0 -641
  90. brkraw/lib/bids.py +0 -0
  91. brkraw/lib/errors.py +0 -125
  92. brkraw/lib/loader.py +0 -1220
  93. brkraw/lib/orient.py +0 -194
  94. brkraw/lib/parser.py +0 -48
  95. brkraw/lib/pvobj.py +0 -301
  96. brkraw/lib/reference.py +0 -245
  97. brkraw/lib/utils.py +0 -471
  98. brkraw/scripts/__init__.py +0 -0
  99. brkraw/scripts/brk_backup.py +0 -106
  100. brkraw/scripts/brkraw.py +0 -744
  101. brkraw/ui/__init__.py +0 -0
  102. brkraw/ui/config.py +0 -17
  103. brkraw/ui/main_win.py +0 -214
  104. brkraw/ui/previewer.py +0 -225
  105. brkraw/ui/scan_info.py +0 -72
  106. brkraw/ui/scan_list.py +0 -73
  107. brkraw/ui/subj_info.py +0 -128
  108. brkraw-0.3.11.dist-info/METADATA +0 -25
  109. brkraw-0.3.11.dist-info/RECORD +0 -28
  110. brkraw-0.3.11.dist-info/entry_points.txt +0 -3
  111. brkraw-0.3.11.dist-info/top_level.txt +0 -2
  112. tests/__init__.py +0 -0
  113. {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,371 @@
1
+ from __future__ import annotations
2
+
3
+ """Session command to manage BrkRaw environment defaults."""
4
+
5
+ import argparse
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Iterable, List, Tuple
9
+
10
+ from brkraw.apps.loader import BrukerLoader
11
+
12
+
13
+ def _format_export(name: str, value: str) -> str:
14
+ escaped = value.replace("\"", "\\\"")
15
+ return f'export {name}="{escaped}"'
16
+
17
+
18
+ def _format_scan_ids(scan_ids: Iterable[int]) -> str:
19
+ return ",".join(str(sid) for sid in scan_ids)
20
+
21
+
22
+ def _format_param_files(files: Iterable[str]) -> str:
23
+ return ",".join(str(item) for item in files)
24
+
25
+
26
+ def cmd_session(args: argparse.Namespace) -> int:
27
+ handler = getattr(args, "session_func", None)
28
+ if handler is None:
29
+ args.parser.print_help()
30
+ return 2
31
+ return handler(args)
32
+
33
+
34
+ def cmd_set(args: argparse.Namespace) -> int:
35
+ if (
36
+ not args.path
37
+ and not args.scan_id
38
+ and args.reco_id is None
39
+ and not args.param_key
40
+ and not args.param_file
41
+ and not args.convert_option
42
+ ):
43
+ parser = getattr(args, "parser", None)
44
+ if parser is not None:
45
+ print(_format_short_help(parser))
46
+ print("\nTip: run `brkraw init --shell-rc ~/.zshrc` (or ~/.bashrc)")
47
+ print("Then use `brkraw-set ...` and `brkraw-unset` in your shell.")
48
+ print("You can still use `eval \"$(brkraw session set ...)\"` directly.")
49
+ return 2
50
+ lines: List[str] = []
51
+ if args.path:
52
+ path = Path(args.path).expanduser()
53
+ if not path.exists():
54
+ print(f"error: path not found: {path}")
55
+ return 2
56
+ try:
57
+ BrukerLoader(path)
58
+ except Exception as exc:
59
+ print(f"error: failed to load dataset at {path}: {exc}")
60
+ return 2
61
+ lines.append(_format_export("BRKRAW_PATH", str(path.resolve())))
62
+ if args.scan_id:
63
+ lines.append(_format_export("BRKRAW_SCAN_ID", _format_scan_ids(args.scan_id)))
64
+ if args.reco_id is not None:
65
+ lines.append(_format_export("BRKRAW_RECO_ID", str(args.reco_id)))
66
+ if args.param_key:
67
+ lines.append(_format_export("BRKRAW_PARAM_KEY", args.param_key))
68
+ if args.param_file:
69
+ lines.append(_format_export("BRKRAW_PARAM_FILE", _format_param_files(args.param_file)))
70
+ if args.convert_option:
71
+ convert_items: List[str] = []
72
+ for item in args.convert_option:
73
+ if isinstance(item, list):
74
+ convert_items.extend(item)
75
+ else:
76
+ convert_items.append(item)
77
+ for key, value in _parse_convert_options(convert_items):
78
+ lines.append(_format_export(f"BRKRAW_CONVERT_{key}", value))
79
+ if lines:
80
+ print("\n".join(lines))
81
+ return 0
82
+
83
+
84
+ def cmd_unset(args: argparse.Namespace) -> int:
85
+ base_vars = [
86
+ "BRKRAW_PATH",
87
+ "BRKRAW_SCAN_ID",
88
+ "BRKRAW_RECO_ID",
89
+ "BRKRAW_PARAM_KEY",
90
+ "BRKRAW_PARAM_FILE",
91
+ ]
92
+ convert_vars = [
93
+ "BRKRAW_CONVERT_OUTPUT",
94
+ "BRKRAW_CONVERT_PREFIX",
95
+ "BRKRAW_CONVERT_SCAN_ID",
96
+ "BRKRAW_CONVERT_RECO_ID",
97
+ "BRKRAW_CONVERT_SIDECAR",
98
+ "BRKRAW_CONVERT_CONTEXT_MAP",
99
+ "BRKRAW_CONVERT_SPACE",
100
+ "BRKRAW_CONVERT_COMPRESS",
101
+ "BRKRAW_CONVERT_FLIP_X",
102
+ "BRKRAW_CONVERT_FLATTEN_FG",
103
+ "BRKRAW_CONVERT_OVERRIDE_SUBJECT_TYPE",
104
+ "BRKRAW_CONVERT_OVERRIDE_SUBJECT_POSE",
105
+ "BRKRAW_CONVERT_XYZ_UNITS",
106
+ "BRKRAW_CONVERT_T_UNITS",
107
+ "BRKRAW_CONVERT_HEADER",
108
+ "BRKRAW_CONVERT_FORMAT",
109
+ ]
110
+ targets: List[str] = []
111
+ if args.path:
112
+ targets.append("BRKRAW_PATH")
113
+ if args.scan_id:
114
+ targets.append("BRKRAW_SCAN_ID")
115
+ if args.reco_id:
116
+ targets.append("BRKRAW_RECO_ID")
117
+ if args.param_key:
118
+ targets.append("BRKRAW_PARAM_KEY")
119
+ if args.param_file:
120
+ targets.append("BRKRAW_PARAM_FILE")
121
+
122
+ if args.convert_option:
123
+ keys: List[str] = []
124
+ for item in args.convert_option:
125
+ if item is None or item == "*":
126
+ keys = ["*"]
127
+ break
128
+ keys.append(item)
129
+ if "*" in keys:
130
+ targets.extend(convert_vars)
131
+ else:
132
+ targets.extend(
133
+ [f"BRKRAW_CONVERT_{key.strip().upper().replace('-', '_')}" for key in keys]
134
+ )
135
+
136
+ if not targets:
137
+ targets = base_vars + convert_vars
138
+ print("unset " + " ".join(targets))
139
+ return 0
140
+
141
+
142
+ def cmd_env(_: argparse.Namespace) -> int:
143
+ path = os.environ.get("BRKRAW_PATH")
144
+ scan_id = os.environ.get("BRKRAW_SCAN_ID")
145
+ reco_id = os.environ.get("BRKRAW_RECO_ID")
146
+ param_key = os.environ.get("BRKRAW_PARAM_KEY")
147
+ param_file = os.environ.get("BRKRAW_PARAM_FILE")
148
+ convert_output = os.environ.get("BRKRAW_CONVERT_OUTPUT")
149
+ convert_prefix = os.environ.get("BRKRAW_CONVERT_PREFIX")
150
+ convert_scan_id = os.environ.get("BRKRAW_CONVERT_SCAN_ID")
151
+ convert_reco_id = os.environ.get("BRKRAW_CONVERT_RECO_ID")
152
+ convert_sidecar = os.environ.get("BRKRAW_CONVERT_SIDECAR")
153
+ convert_context_map = os.environ.get("BRKRAW_CONVERT_CONTEXT_MAP")
154
+ convert_compress = os.environ.get("BRKRAW_CONVERT_COMPRESS")
155
+ convert_space = os.environ.get("BRKRAW_CONVERT_SPACE")
156
+ convert_flip_x = os.environ.get("BRKRAW_CONVERT_FLIP_X")
157
+ convert_flatten_fg = os.environ.get("BRKRAW_CONVERT_FLATTEN_FG")
158
+ convert_subject_type = os.environ.get("BRKRAW_CONVERT_OVERRIDE_SUBJECT_TYPE")
159
+ convert_subject_pose = os.environ.get("BRKRAW_CONVERT_OVERRIDE_SUBJECT_POSE")
160
+ convert_xyz_units = os.environ.get("BRKRAW_CONVERT_XYZ_UNITS")
161
+ convert_t_units = os.environ.get("BRKRAW_CONVERT_T_UNITS")
162
+ convert_header = os.environ.get("BRKRAW_CONVERT_HEADER")
163
+ convert_format = os.environ.get("BRKRAW_CONVERT_FORMAT")
164
+ if (
165
+ path is None
166
+ and scan_id is None
167
+ and reco_id is None
168
+ and param_key is None
169
+ and param_file is None
170
+ and convert_output is None
171
+ and convert_prefix is None
172
+ and convert_scan_id is None
173
+ and convert_reco_id is None
174
+ and convert_sidecar is None
175
+ and convert_context_map is None
176
+ and convert_compress is None
177
+ and convert_space is None
178
+ and convert_flip_x is None
179
+ and convert_flatten_fg is None
180
+ and convert_subject_type is None
181
+ and convert_subject_pose is None
182
+ and convert_xyz_units is None
183
+ and convert_t_units is None
184
+ and convert_header is None
185
+ and convert_format is None
186
+ ):
187
+ print("(none)")
188
+ return 0
189
+ if path is not None:
190
+ print(f"BRKRAW_PATH={path}")
191
+ if scan_id is not None:
192
+ print(f"BRKRAW_SCAN_ID={scan_id}")
193
+ if reco_id is not None:
194
+ print(f"BRKRAW_RECO_ID={reco_id}")
195
+ if param_key is not None:
196
+ print(f"BRKRAW_PARAM_KEY={param_key}")
197
+ if param_file is not None:
198
+ print(f"BRKRAW_PARAM_FILE={param_file}")
199
+ if convert_output is not None:
200
+ print(f"BRKRAW_CONVERT_OUTPUT={convert_output}")
201
+ if convert_prefix is not None:
202
+ print(f"BRKRAW_CONVERT_PREFIX={convert_prefix}")
203
+ if convert_scan_id is not None:
204
+ print(f"BRKRAW_CONVERT_SCAN_ID={convert_scan_id}")
205
+ if convert_reco_id is not None:
206
+ print(f"BRKRAW_CONVERT_RECO_ID={convert_reco_id}")
207
+ if convert_sidecar is not None:
208
+ print(f"BRKRAW_CONVERT_SIDECAR={convert_sidecar}")
209
+ if convert_context_map is not None:
210
+ print(f"BRKRAW_CONVERT_CONTEXT_MAP={convert_context_map}")
211
+ if convert_space is not None:
212
+ print(f"BRKRAW_CONVERT_SPACE={convert_space}")
213
+ if convert_compress is not None:
214
+ print(f"BRKRAW_CONVERT_COMPRESS={convert_compress}")
215
+ if convert_flip_x is not None:
216
+ print(f"BRKRAW_CONVERT_FLIP_X={convert_flip_x}")
217
+ if convert_flatten_fg is not None:
218
+ print(f"BRKRAW_CONVERT_FLATTEN_FG={convert_flatten_fg}")
219
+ if convert_subject_type is not None:
220
+ print(f"BRKRAW_CONVERT_OVERRIDE_SUBJECT_TYPE={convert_subject_type}")
221
+ if convert_subject_pose is not None:
222
+ print(f"BRKRAW_CONVERT_OVERRIDE_SUBJECT_POSE={convert_subject_pose}")
223
+ if convert_xyz_units is not None:
224
+ print(f"BRKRAW_CONVERT_XYZ_UNITS={convert_xyz_units}")
225
+ if convert_t_units is not None:
226
+ print(f"BRKRAW_CONVERT_T_UNITS={convert_t_units}")
227
+ if convert_header is not None:
228
+ print(f"BRKRAW_CONVERT_HEADER={convert_header}")
229
+ if convert_format is not None:
230
+ print(f"BRKRAW_CONVERT_FORMAT={convert_format}")
231
+ return 0
232
+
233
+
234
+ def _format_short_help(parser: argparse.ArgumentParser) -> str:
235
+ formatter = parser._get_formatter()
236
+ formatter.add_usage(parser.usage, parser._actions, parser._mutually_exclusive_groups)
237
+ for action_group in parser._action_groups:
238
+ formatter.start_section(action_group.title)
239
+ actions = [
240
+ action
241
+ for action in action_group._group_actions
242
+ if "-h" not in action.option_strings and "--help" not in action.option_strings
243
+ ]
244
+ formatter.add_arguments(actions)
245
+ formatter.end_section()
246
+ return formatter.format_help()
247
+
248
+
249
+ def _parse_convert_options(items: List[str]) -> List[Tuple[str, str]]:
250
+ pairs: List[Tuple[str, str]] = []
251
+ for item in items:
252
+ if "=" not in item:
253
+ raise ValueError(f"Invalid convert option (expected KEY=VALUE): {item}")
254
+ key, value = item.split("=", 1)
255
+ key = key.strip().upper().replace("-", "_")
256
+ if not key:
257
+ raise ValueError(f"Invalid convert option key in: {item}")
258
+ pairs.append((key, value.strip()))
259
+ return pairs
260
+
261
+
262
+ def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[name-defined]
263
+ session_parser = subparsers.add_parser(
264
+ "session",
265
+ help="Manage BrkRaw environment defaults.",
266
+ )
267
+ session_parser.add_argument(
268
+ "--root",
269
+ help="Override config root directory (default: BRKRAW_CONFIG_HOME or ~/.brkraw).",
270
+ )
271
+ session_parser.set_defaults(func=cmd_session, parser=session_parser)
272
+ session_sub = session_parser.add_subparsers(dest="session_command")
273
+
274
+ set_parser = session_sub.add_parser(
275
+ "set",
276
+ help="Emit shell exports for BrkRaw environment defaults.",
277
+ )
278
+ set_parser.add_argument(
279
+ "-p",
280
+ "--path",
281
+ help="Default Bruker study path.",
282
+ )
283
+ set_parser.add_argument(
284
+ "-s",
285
+ "--scan-id",
286
+ nargs="*",
287
+ type=int,
288
+ help="Default scan id(s).",
289
+ )
290
+ set_parser.add_argument(
291
+ "-r",
292
+ "--reco-id",
293
+ type=int,
294
+ help="Default reco id.",
295
+ )
296
+ set_parser.add_argument(
297
+ "-k",
298
+ "--param-key",
299
+ help="Default parameter key for brkraw params.",
300
+ )
301
+ set_parser.add_argument(
302
+ "-f",
303
+ "--param-file",
304
+ nargs="*",
305
+ help="Default parameter file(s) for brkraw params.",
306
+ )
307
+ set_parser.add_argument(
308
+ "--convert-option",
309
+ action="append",
310
+ metavar="KEY=VALUE",
311
+ help=(
312
+ "Set BRKRAW_CONVERT_<OPTION> as KEY=VALUE (repeatable). "
313
+ "Keys: OUTPUT, PREFIX, SCAN_ID, RECO_ID, SIDECAR, CONTEXT_MAP, "
314
+ "COMPRESS, SPACE, FLIP_X, FLATTEN_FG, OVERRIDE_SUBJECT_TYPE, "
315
+ "OVERRIDE_SUBJECT_POSE, XYZ_UNITS, T_UNITS, HEADER, FORMAT."
316
+ ),
317
+ )
318
+ set_parser.set_defaults(session_func=cmd_set, parser=set_parser)
319
+
320
+ unset_parser = session_sub.add_parser(
321
+ "unset",
322
+ help="Emit shell unset commands for BrkRaw environment defaults.",
323
+ )
324
+ unset_parser.add_argument(
325
+ "-p",
326
+ "--path",
327
+ action="store_true",
328
+ help="Unset BRKRAW_PATH.",
329
+ )
330
+ unset_parser.add_argument(
331
+ "-s",
332
+ "--scan-id",
333
+ action="store_true",
334
+ help="Unset BRKRAW_SCAN_ID.",
335
+ )
336
+ unset_parser.add_argument(
337
+ "-r",
338
+ "--reco-id",
339
+ action="store_true",
340
+ help="Unset BRKRAW_RECO_ID.",
341
+ )
342
+ unset_parser.add_argument(
343
+ "-k",
344
+ "--param-key",
345
+ action="store_true",
346
+ help="Unset BRKRAW_PARAM_KEY.",
347
+ )
348
+ unset_parser.add_argument(
349
+ "-f",
350
+ "--param-file",
351
+ action="store_true",
352
+ help="Unset BRKRAW_PARAM_FILE.",
353
+ )
354
+ unset_parser.add_argument(
355
+ "--convert-option",
356
+ nargs="?",
357
+ action="append",
358
+ const="*",
359
+ metavar="KEY",
360
+ help=(
361
+ "Unset BRKRAW_CONVERT_<OPTION> by KEY (repeatable). "
362
+ "Use without KEY to unset all convert variables."
363
+ ),
364
+ )
365
+ unset_parser.set_defaults(session_func=cmd_unset)
366
+
367
+ env_parser = session_sub.add_parser(
368
+ "env",
369
+ help="Show current BrkRaw environment defaults.",
370
+ )
371
+ env_parser.set_defaults(session_func=cmd_env)
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Mapping
6
+
7
+ import yaml
8
+
9
+
10
+ def merge_hook_args(
11
+ base: Mapping[str, Mapping[str, Any]],
12
+ override: Mapping[str, Mapping[str, Any]],
13
+ ) -> Dict[str, Dict[str, Any]]:
14
+ merged: Dict[str, Dict[str, Any]] = {}
15
+ for hook_name, values in base.items():
16
+ if not isinstance(hook_name, str) or not hook_name:
17
+ continue
18
+ if not isinstance(values, Mapping):
19
+ continue
20
+ merged[hook_name] = dict(values)
21
+ for hook_name, values in override.items():
22
+ if not isinstance(hook_name, str) or not hook_name:
23
+ continue
24
+ if not isinstance(values, Mapping):
25
+ continue
26
+ merged.setdefault(hook_name, {}).update(dict(values))
27
+ return merged
28
+
29
+
30
+ def load_hook_args_yaml(paths: List[str]) -> Dict[str, Dict[str, Any]]:
31
+ """Load hook args mapping from YAML files.
32
+
33
+ Supported YAML formats:
34
+ - `{hooks: {hook_name: {key: value}}}`
35
+ - `{hook_name: {key: value}}`
36
+ """
37
+
38
+ merged: Dict[str, Dict[str, Any]] = {}
39
+
40
+ def normalize_doc(doc: Any, *, source: str) -> Dict[str, Dict[str, Any]]:
41
+ if doc is None:
42
+ return {}
43
+ hooks_obj = doc.get("hooks") if isinstance(doc, Mapping) else None
44
+ if hooks_obj is None and isinstance(doc, Mapping):
45
+ hooks_obj = doc
46
+ if not isinstance(hooks_obj, Mapping):
47
+ raise ValueError(f"Invalid hook args YAML in {source!r}: expected mapping.")
48
+
49
+ out: Dict[str, Dict[str, Any]] = {}
50
+ for hook_name, values in hooks_obj.items():
51
+ if not isinstance(hook_name, str) or not hook_name.strip():
52
+ continue
53
+ if values is None:
54
+ continue
55
+ if not isinstance(values, Mapping):
56
+ raise ValueError(
57
+ f"Invalid hook args YAML in {source!r}: hook {hook_name!r} must map to a dict."
58
+ )
59
+ out[hook_name.strip()] = dict(values)
60
+ return out
61
+
62
+ for raw in paths:
63
+ source = raw.strip()
64
+ if not source:
65
+ continue
66
+ if source == "-":
67
+ doc = yaml.safe_load(sys.stdin.read())
68
+ else:
69
+ path = Path(source).expanduser()
70
+ if not path.exists():
71
+ raise ValueError(f"Hook args YAML not found: {source}")
72
+ doc = yaml.safe_load(path.read_text(encoding="utf-8"))
73
+ parsed = normalize_doc(doc, source=source)
74
+ merged = merge_hook_args(merged, parsed)
75
+
76
+ return merged
77
+
78
+
79
+ __all__ = ["load_hook_args_yaml", "merge_hook_args"]
80
+
brkraw/cli/main.py ADDED
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from typing import Callable, List, Optional
5
+ from ..core.entrypoints import list_entry_points as _iter_entry_points
6
+
7
+ from brkraw import __version__
8
+ from brkraw.core import config as config_core
9
+
10
+ PLUGIN_GROUP = "brkraw.cli"
11
+
12
+
13
+ def _register_entry_point_commands(
14
+ subparsers: argparse._SubParsersAction, # type: ignore[name-defined]
15
+ ) -> None:
16
+ for ep in _iter_entry_points(PLUGIN_GROUP):
17
+ try:
18
+ register = ep.load()
19
+ except Exception as exc: # noqa: BLE001 - best-effort plugin load
20
+ print(f"warning: failed to load entry point {ep.name!r}: {exc}")
21
+ continue
22
+ if not callable(register):
23
+ raise TypeError("entry point must be callable (register(subparsers)).")
24
+ register(subparsers)
25
+
26
+ preferred = [
27
+ "init",
28
+ "config",
29
+ "session",
30
+ "info",
31
+ "params",
32
+ "convert",
33
+ "convert-batch",
34
+ "prune",
35
+ "addon",
36
+ "hook",
37
+ ]
38
+ preferred_set = set(preferred)
39
+ ordered = [name for name in preferred if name in subparsers.choices]
40
+ ordered += [name for name in subparsers.choices if name not in preferred_set]
41
+ subparsers.choices = {name: subparsers.choices[name] for name in ordered}
42
+ choices_actions = getattr(subparsers, "_choices_actions", None)
43
+ if choices_actions:
44
+ action_map = {action.dest: action for action in choices_actions}
45
+ ordered_actions = [action_map[name] for name in ordered if name in action_map]
46
+ ordered_actions += [
47
+ action for action in choices_actions if action.dest not in ordered
48
+ ]
49
+ subparsers._choices_actions = ordered_actions # type: ignore[attr-defined]
50
+
51
+
52
+ def main(argv: Optional[List[str]] = None) -> int:
53
+ config_core.configure_logging()
54
+ parser = argparse.ArgumentParser(
55
+ prog="brkraw",
56
+ description="BrkRaw command-line interface.",
57
+ )
58
+ parser.add_argument(
59
+ "-v", "--version", action="version", version="%(prog)s v{}".format(__version__)
60
+ )
61
+
62
+ subparsers = parser.add_subparsers(
63
+ title="Sub-commands",
64
+ description=(
65
+ "Choose one of the sub-commands below. For details on a specific "
66
+ "command, run: brkraw <command> -h."
67
+ ),
68
+ dest="command",
69
+ metavar="command",
70
+ )
71
+
72
+ _register_entry_point_commands(subparsers)
73
+
74
+ args = parser.parse_args(argv)
75
+ if not hasattr(args, "func"):
76
+ parser.print_help()
77
+ return 2
78
+ func: Callable[[argparse.Namespace], int] = args.func
79
+ return func(args)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ raise SystemExit(main())
brkraw/cli/utils.py ADDED
@@ -0,0 +1,60 @@
1
+ """Formatting helpers for CLI-style output.
2
+
3
+ Last updated: 2025-12-30
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import itertools
9
+ import logging
10
+ import threading
11
+ import time
12
+ from contextlib import contextmanager
13
+ from typing import Iterator, List
14
+
15
+ from brkraw.apps.loader import BrukerLoader
16
+
17
+ logger = logging.getLogger("brkraw")
18
+
19
+ @contextmanager
20
+ def spinner(prefix: str = "Loading") -> Iterator[None]:
21
+ """Display a simple CLI spinner while a block runs.
22
+
23
+ Args:
24
+ prefix: Text shown before the spinner glyph.
25
+
26
+ Yields:
27
+ None.
28
+ """
29
+ if logger.isEnabledFor(logging.DEBUG):
30
+ yield
31
+ return
32
+
33
+ stop_event = threading.Event()
34
+ seq = itertools.cycle("|/-\\")
35
+
36
+ def run() -> None:
37
+ while not stop_event.is_set():
38
+ print(f"\r{prefix} {next(seq)}", end="", flush=True)
39
+ time.sleep(0.08)
40
+
41
+ thread = threading.Thread(target=run, daemon=True)
42
+ thread.start()
43
+ try:
44
+ yield
45
+ finally:
46
+ stop_event.set()
47
+ thread.join()
48
+ print("\r" + " " * (len(prefix) + 2) + "\r", end="", flush=True)
49
+
50
+
51
+ def load(path, *, prefix: str = "Loading") -> BrukerLoader:
52
+ """Load a Bruker dataset with a CLI spinner."""
53
+ with spinner(prefix):
54
+ return BrukerLoader(path)
55
+
56
+
57
+ __all__ = ["spinner", "load"]
58
+
59
+ def __dir__() -> List[str]:
60
+ return sorted(__all__)
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ from . import fs, jcamp, parameters, zip
4
+ from .entrypoints import list_entry_points
5
+
6
+
7
+ __all__ = [
8
+ 'fs',
9
+ 'jcamp',
10
+ 'parameters',
11
+ 'zip',
12
+ 'list_entry_points',
13
+ ]