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.
- brkraw/__init__.py +9 -3
- brkraw/apps/__init__.py +12 -0
- brkraw/apps/addon/__init__.py +30 -0
- brkraw/apps/addon/core.py +35 -0
- brkraw/apps/addon/dependencies.py +402 -0
- brkraw/apps/addon/installation.py +500 -0
- brkraw/apps/addon/io.py +21 -0
- brkraw/apps/hook/__init__.py +25 -0
- brkraw/apps/hook/core.py +636 -0
- brkraw/apps/loader/__init__.py +10 -0
- brkraw/apps/loader/core.py +622 -0
- brkraw/apps/loader/formatter.py +288 -0
- brkraw/apps/loader/helper.py +797 -0
- brkraw/apps/loader/info/__init__.py +11 -0
- brkraw/apps/loader/info/scan.py +85 -0
- brkraw/apps/loader/info/scan.yaml +90 -0
- brkraw/apps/loader/info/study.py +69 -0
- brkraw/apps/loader/info/study.yaml +156 -0
- brkraw/apps/loader/info/transform.py +92 -0
- brkraw/apps/loader/types.py +220 -0
- brkraw/cli/__init__.py +5 -0
- brkraw/cli/commands/__init__.py +2 -0
- brkraw/cli/commands/addon.py +327 -0
- brkraw/cli/commands/config.py +205 -0
- brkraw/cli/commands/convert.py +903 -0
- brkraw/cli/commands/hook.py +348 -0
- brkraw/cli/commands/info.py +74 -0
- brkraw/cli/commands/init.py +214 -0
- brkraw/cli/commands/params.py +106 -0
- brkraw/cli/commands/prune.py +288 -0
- brkraw/cli/commands/session.py +371 -0
- brkraw/cli/hook_args.py +80 -0
- brkraw/cli/main.py +83 -0
- brkraw/cli/utils.py +60 -0
- brkraw/core/__init__.py +13 -0
- brkraw/core/config.py +380 -0
- brkraw/core/entrypoints.py +25 -0
- brkraw/core/formatter.py +367 -0
- brkraw/core/fs.py +495 -0
- brkraw/core/jcamp.py +600 -0
- brkraw/core/layout.py +451 -0
- brkraw/core/parameters.py +781 -0
- brkraw/core/zip.py +1121 -0
- brkraw/dataclasses/__init__.py +14 -0
- brkraw/dataclasses/node.py +139 -0
- brkraw/dataclasses/reco.py +33 -0
- brkraw/dataclasses/scan.py +61 -0
- brkraw/dataclasses/study.py +131 -0
- brkraw/default/__init__.py +3 -0
- brkraw/default/pruner_specs/deid4share.yaml +42 -0
- brkraw/default/rules/00_default.yaml +4 -0
- brkraw/default/specs/metadata_dicom.yaml +236 -0
- brkraw/default/specs/metadata_transforms.py +92 -0
- brkraw/resolver/__init__.py +7 -0
- brkraw/resolver/affine.py +539 -0
- brkraw/resolver/datatype.py +69 -0
- brkraw/resolver/fid.py +90 -0
- brkraw/resolver/helpers.py +36 -0
- brkraw/resolver/image.py +188 -0
- brkraw/resolver/nifti.py +370 -0
- brkraw/resolver/shape.py +235 -0
- brkraw/schema/__init__.py +3 -0
- brkraw/schema/context_map.yaml +62 -0
- brkraw/schema/meta.yaml +57 -0
- brkraw/schema/niftiheader.yaml +95 -0
- brkraw/schema/pruner.yaml +55 -0
- brkraw/schema/remapper.yaml +128 -0
- brkraw/schema/rules.yaml +154 -0
- brkraw/specs/__init__.py +10 -0
- brkraw/specs/hook/__init__.py +12 -0
- brkraw/specs/hook/logic.py +31 -0
- brkraw/specs/hook/validator.py +22 -0
- brkraw/specs/meta/__init__.py +5 -0
- brkraw/specs/meta/validator.py +156 -0
- brkraw/specs/pruner/__init__.py +15 -0
- brkraw/specs/pruner/logic.py +361 -0
- brkraw/specs/pruner/validator.py +119 -0
- brkraw/specs/remapper/__init__.py +27 -0
- brkraw/specs/remapper/logic.py +924 -0
- brkraw/specs/remapper/validator.py +314 -0
- brkraw/specs/rules/__init__.py +6 -0
- brkraw/specs/rules/logic.py +263 -0
- brkraw/specs/rules/validator.py +103 -0
- brkraw-0.5.0.dist-info/METADATA +81 -0
- brkraw-0.5.0.dist-info/RECORD +88 -0
- {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info}/WHEEL +1 -2
- brkraw-0.5.0.dist-info/entry_points.txt +13 -0
- brkraw/lib/__init__.py +0 -4
- brkraw/lib/backup.py +0 -641
- brkraw/lib/bids.py +0 -0
- brkraw/lib/errors.py +0 -125
- brkraw/lib/loader.py +0 -1220
- brkraw/lib/orient.py +0 -194
- brkraw/lib/parser.py +0 -48
- brkraw/lib/pvobj.py +0 -301
- brkraw/lib/reference.py +0 -245
- brkraw/lib/utils.py +0 -471
- brkraw/scripts/__init__.py +0 -0
- brkraw/scripts/brk_backup.py +0 -106
- brkraw/scripts/brkraw.py +0 -744
- brkraw/ui/__init__.py +0 -0
- brkraw/ui/config.py +0 -17
- brkraw/ui/main_win.py +0 -214
- brkraw/ui/previewer.py +0 -225
- brkraw/ui/scan_info.py +0 -72
- brkraw/ui/scan_list.py +0 -73
- brkraw/ui/subj_info.py +0 -128
- brkraw-0.3.11.dist-info/METADATA +0 -25
- brkraw-0.3.11.dist-info/RECORD +0 -28
- brkraw-0.3.11.dist-info/entry_points.txt +0 -3
- brkraw-0.3.11.dist-info/top_level.txt +0 -2
- tests/__init__.py +0 -0
- {brkraw-0.3.11.dist-info → brkraw-0.5.0.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Parameter search command for BrkRaw.
|
|
4
|
+
|
|
5
|
+
Last updated: 2025-12-30
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import yaml
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from brkraw.cli.utils import load
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("brkraw")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cmd_params(args: argparse.Namespace) -> int:
|
|
21
|
+
if args.path is None:
|
|
22
|
+
args.path = os.environ.get("BRKRAW_PATH")
|
|
23
|
+
if args.path is None:
|
|
24
|
+
args.parser.print_help()
|
|
25
|
+
return 2
|
|
26
|
+
if args.key is None:
|
|
27
|
+
args.key = os.environ.get("BRKRAW_PARAM_KEY")
|
|
28
|
+
if args.key is None:
|
|
29
|
+
logger.error("Missing --key (or BRKRAW_PARAM_KEY).")
|
|
30
|
+
return 2
|
|
31
|
+
if args.scan_id is None:
|
|
32
|
+
env_scan = os.environ.get("BRKRAW_SCAN_ID")
|
|
33
|
+
if env_scan:
|
|
34
|
+
try:
|
|
35
|
+
args.scan_id = int(env_scan)
|
|
36
|
+
except ValueError:
|
|
37
|
+
logger.error("Invalid BRKRAW_SCAN_ID: %s", env_scan)
|
|
38
|
+
return 2
|
|
39
|
+
if args.reco_id is None:
|
|
40
|
+
env_reco = os.environ.get("BRKRAW_RECO_ID")
|
|
41
|
+
if env_reco:
|
|
42
|
+
try:
|
|
43
|
+
args.reco_id = int(env_reco)
|
|
44
|
+
except ValueError:
|
|
45
|
+
logger.error("Invalid BRKRAW_RECO_ID: %s", env_reco)
|
|
46
|
+
return 2
|
|
47
|
+
if args.file is None:
|
|
48
|
+
env_file = os.environ.get("BRKRAW_PARAM_FILE")
|
|
49
|
+
if env_file:
|
|
50
|
+
args.file = [p.strip() for p in env_file.split(",") if p.strip()]
|
|
51
|
+
if not Path(args.path).exists():
|
|
52
|
+
logger.error("Path not found: %s", args.path)
|
|
53
|
+
return 2
|
|
54
|
+
loader = load(args.path, prefix="Loading")
|
|
55
|
+
result = loader.search_params(
|
|
56
|
+
args.key,
|
|
57
|
+
file=args.file,
|
|
58
|
+
scan_id=args.scan_id,
|
|
59
|
+
reco_id=args.reco_id,
|
|
60
|
+
)
|
|
61
|
+
if result is None:
|
|
62
|
+
logger.info("(none)")
|
|
63
|
+
return 0
|
|
64
|
+
def _to_yaml_safe(value):
|
|
65
|
+
if isinstance(value, dict):
|
|
66
|
+
return {k: _to_yaml_safe(v) for k, v in value.items()}
|
|
67
|
+
if isinstance(value, (list, tuple)):
|
|
68
|
+
return [_to_yaml_safe(v) for v in value]
|
|
69
|
+
if isinstance(value, np.ndarray):
|
|
70
|
+
return value.tolist()
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
logger.info("%s", yaml.safe_dump(_to_yaml_safe(result), sort_keys=False))
|
|
74
|
+
return 0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[name-defined]
|
|
78
|
+
params_parser = subparsers.add_parser(
|
|
79
|
+
"params",
|
|
80
|
+
help="Search parameter files for key matches.",
|
|
81
|
+
)
|
|
82
|
+
params_parser.add_argument("path", nargs="?", help="Path to the Bruker study.")
|
|
83
|
+
params_parser.add_argument(
|
|
84
|
+
"-k",
|
|
85
|
+
"--key",
|
|
86
|
+
help="Parameter key to search for.",
|
|
87
|
+
)
|
|
88
|
+
params_parser.add_argument(
|
|
89
|
+
"-s",
|
|
90
|
+
"--scan-id",
|
|
91
|
+
type=int,
|
|
92
|
+
help="Scan id to search (required for study-level search).",
|
|
93
|
+
)
|
|
94
|
+
params_parser.add_argument(
|
|
95
|
+
"-r",
|
|
96
|
+
"--reco-id",
|
|
97
|
+
type=int,
|
|
98
|
+
help="Reco id to search within a scan.",
|
|
99
|
+
)
|
|
100
|
+
params_parser.add_argument(
|
|
101
|
+
"-f",
|
|
102
|
+
"--file",
|
|
103
|
+
nargs="*",
|
|
104
|
+
help="Parameter file(s) to search (method, acqp, visu_pars, reco).",
|
|
105
|
+
)
|
|
106
|
+
params_parser.set_defaults(func=cmd_params, parser=params_parser)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""Create a pruned dataset zip using a prune spec."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional, Union
|
|
10
|
+
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from brkraw.cli.utils import spinner
|
|
14
|
+
from brkraw.core import config as config_core
|
|
15
|
+
from brkraw.specs.pruner import prune_dataset_to_zip_from_spec
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger("brkraw")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def cmd_prune(args: argparse.Namespace) -> int:
|
|
21
|
+
output = args.output
|
|
22
|
+
root_name_override = None
|
|
23
|
+
dirs_override = _build_dir_override(args.scan_ids, args.reco_ids)
|
|
24
|
+
template_vars = _parse_kv_pairs(args.set_vars)
|
|
25
|
+
try:
|
|
26
|
+
spec_path = _resolve_pruner_spec(
|
|
27
|
+
args.spec_name if args.spec_name else args.spec
|
|
28
|
+
)
|
|
29
|
+
except ValueError as exc:
|
|
30
|
+
logger.error("%s", exc)
|
|
31
|
+
return 2
|
|
32
|
+
if output is None:
|
|
33
|
+
root_name = _load_root_name(spec_path)
|
|
34
|
+
if not root_name:
|
|
35
|
+
logger.error("Prune spec has no root_name; please provide --output.")
|
|
36
|
+
return 2
|
|
37
|
+
output = _default_output_path(Path(args.path), spec_path=spec_path)
|
|
38
|
+
else:
|
|
39
|
+
root_name_override = Path(output).stem
|
|
40
|
+
try:
|
|
41
|
+
logger.info("Pruning dataset: %s", args.path)
|
|
42
|
+
logger.info("Prune spec: %s", spec_path)
|
|
43
|
+
logger.info("Output zip: %s", output)
|
|
44
|
+
with spinner("Pruning"):
|
|
45
|
+
out_path = prune_dataset_to_zip_from_spec(
|
|
46
|
+
spec_path,
|
|
47
|
+
source=args.path,
|
|
48
|
+
dest=output,
|
|
49
|
+
validate=not args.no_validate,
|
|
50
|
+
strip_jcamp_comments=args.strip_jcamp_comments,
|
|
51
|
+
root_name=root_name_override,
|
|
52
|
+
dirs=dirs_override,
|
|
53
|
+
mode=args.mode,
|
|
54
|
+
template_vars=template_vars,
|
|
55
|
+
)
|
|
56
|
+
logger.info("Wrote pruned zip: %s", out_path)
|
|
57
|
+
_write_prune_sidecar(
|
|
58
|
+
out_path=Path(out_path),
|
|
59
|
+
input_path=Path(args.path),
|
|
60
|
+
spec_path=spec_path,
|
|
61
|
+
args=args,
|
|
62
|
+
root_name_override=root_name_override,
|
|
63
|
+
dirs_override=dirs_override,
|
|
64
|
+
template_vars=template_vars,
|
|
65
|
+
)
|
|
66
|
+
except Exception as exc:
|
|
67
|
+
logger.error("%s", exc)
|
|
68
|
+
return 2
|
|
69
|
+
return 0
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[name-defined]
|
|
73
|
+
prune_parser = subparsers.add_parser(
|
|
74
|
+
"prune",
|
|
75
|
+
help="Create a pruned dataset zip from a prune spec.",
|
|
76
|
+
)
|
|
77
|
+
prune_parser.add_argument(
|
|
78
|
+
"path",
|
|
79
|
+
type=str,
|
|
80
|
+
help="Source dataset path.",
|
|
81
|
+
)
|
|
82
|
+
spec_group = prune_parser.add_mutually_exclusive_group(required=True)
|
|
83
|
+
spec_group.add_argument(
|
|
84
|
+
"--spec",
|
|
85
|
+
dest="spec",
|
|
86
|
+
type=str,
|
|
87
|
+
help="Path to prune spec YAML (or basename of installed spec).",
|
|
88
|
+
)
|
|
89
|
+
spec_group.add_argument(
|
|
90
|
+
"--spec-name",
|
|
91
|
+
dest="spec_name",
|
|
92
|
+
type=str,
|
|
93
|
+
help="Use an installed pruner spec by name (basename, no path).",
|
|
94
|
+
)
|
|
95
|
+
prune_parser.add_argument(
|
|
96
|
+
"-o",
|
|
97
|
+
"--output",
|
|
98
|
+
dest="output",
|
|
99
|
+
type=str,
|
|
100
|
+
help="Output zip path (default: <input>_pruned.*).",
|
|
101
|
+
)
|
|
102
|
+
prune_parser.add_argument(
|
|
103
|
+
"--no-validate",
|
|
104
|
+
action="store_true",
|
|
105
|
+
help="Skip prune spec validation.",
|
|
106
|
+
)
|
|
107
|
+
prune_parser.add_argument(
|
|
108
|
+
"--strip-jcamp-comments",
|
|
109
|
+
action="store_true",
|
|
110
|
+
help="Remove $$ comment lines from kept JCAMP files.",
|
|
111
|
+
)
|
|
112
|
+
prune_parser.add_argument(
|
|
113
|
+
"--mode",
|
|
114
|
+
choices=["keep", "drop"],
|
|
115
|
+
help="Override spec mode for file selection.",
|
|
116
|
+
)
|
|
117
|
+
prune_parser.add_argument(
|
|
118
|
+
"--set-var",
|
|
119
|
+
dest="set_vars",
|
|
120
|
+
action="append",
|
|
121
|
+
metavar="KEY=VALUE",
|
|
122
|
+
help="Template variable for use in prune spec (can repeat).",
|
|
123
|
+
)
|
|
124
|
+
prune_parser.add_argument(
|
|
125
|
+
"--scan-ids",
|
|
126
|
+
nargs="+",
|
|
127
|
+
metavar="SCAN_ID",
|
|
128
|
+
help="Override scan IDs to keep (space or comma separated).",
|
|
129
|
+
)
|
|
130
|
+
prune_parser.add_argument(
|
|
131
|
+
"--reco-ids",
|
|
132
|
+
nargs="+",
|
|
133
|
+
metavar="RECO_ID",
|
|
134
|
+
help="Override reco IDs to keep (space or comma separated).",
|
|
135
|
+
)
|
|
136
|
+
prune_parser.set_defaults(func=cmd_prune)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _default_output_path(path: Path, *, spec_path: Path) -> str:
|
|
140
|
+
root_name = _load_root_name(spec_path)
|
|
141
|
+
suffix = path.suffix
|
|
142
|
+
base_dir = Path.cwd()
|
|
143
|
+
base_name = root_name or _stem_or_name(path)
|
|
144
|
+
if path.is_dir():
|
|
145
|
+
return str(base_dir / f"{base_name}.zip")
|
|
146
|
+
if suffix and path.name.endswith(suffix):
|
|
147
|
+
return str(base_dir / f"{base_name}{suffix}")
|
|
148
|
+
return str(base_dir / f"{base_name}.zip")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _stem_or_name(path: Path) -> str:
|
|
152
|
+
if path.is_dir():
|
|
153
|
+
return f"{path.name}_pruned"
|
|
154
|
+
suffix = path.suffix
|
|
155
|
+
return path.name[:-len(suffix)] if suffix else path.name
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _load_root_name(spec_path: Path) -> Optional[str]:
|
|
159
|
+
try:
|
|
160
|
+
data = yaml.safe_load(spec_path.read_text(encoding="utf-8"))
|
|
161
|
+
except Exception:
|
|
162
|
+
return None
|
|
163
|
+
if not isinstance(data, dict):
|
|
164
|
+
return None
|
|
165
|
+
root_name = data.get("root_name")
|
|
166
|
+
if isinstance(root_name, str) and root_name.strip():
|
|
167
|
+
return root_name.strip()
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _write_prune_sidecar(
|
|
172
|
+
*,
|
|
173
|
+
out_path: Path,
|
|
174
|
+
input_path: Path,
|
|
175
|
+
spec_path: Path,
|
|
176
|
+
args: argparse.Namespace,
|
|
177
|
+
root_name_override: Optional[str],
|
|
178
|
+
dirs_override: Optional[list],
|
|
179
|
+
template_vars: dict,
|
|
180
|
+
) -> None:
|
|
181
|
+
sidecar = out_path.with_suffix(".prune.yaml")
|
|
182
|
+
spec_summary = _load_prune_spec_summary(spec_path)
|
|
183
|
+
payload = {
|
|
184
|
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
|
185
|
+
"command": "brkraw prune",
|
|
186
|
+
"input_path": str(input_path),
|
|
187
|
+
"output_path": str(out_path),
|
|
188
|
+
"spec_path": str(spec_path),
|
|
189
|
+
"spec": spec_summary,
|
|
190
|
+
"mode": args.mode,
|
|
191
|
+
"strip_jcamp_comments": bool(args.strip_jcamp_comments),
|
|
192
|
+
"scan_ids": args.scan_ids,
|
|
193
|
+
"reco_ids": args.reco_ids,
|
|
194
|
+
"set_vars": args.set_vars,
|
|
195
|
+
"template_vars": template_vars,
|
|
196
|
+
"root_name_override": root_name_override,
|
|
197
|
+
"dirs_override": dirs_override,
|
|
198
|
+
}
|
|
199
|
+
sidecar.write_text(yaml.safe_dump(payload, sort_keys=False), encoding="utf-8")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _resolve_pruner_spec(value: Optional[str]) -> Path:
|
|
203
|
+
if value is None:
|
|
204
|
+
raise ValueError("A prune spec is required (use --spec or --spec-name).")
|
|
205
|
+
raw = Path(value).expanduser()
|
|
206
|
+
candidates = []
|
|
207
|
+
if raw.suffix:
|
|
208
|
+
candidates.append(raw)
|
|
209
|
+
else:
|
|
210
|
+
candidates.append(raw)
|
|
211
|
+
candidates.append(raw.with_suffix(".yaml"))
|
|
212
|
+
candidates.append(raw.with_suffix(".yml"))
|
|
213
|
+
|
|
214
|
+
if raw.is_absolute():
|
|
215
|
+
for cand in candidates:
|
|
216
|
+
if cand.exists():
|
|
217
|
+
return cand
|
|
218
|
+
raise ValueError(f"Prune spec not found: {value}")
|
|
219
|
+
|
|
220
|
+
search_roots = [Path.cwd(), config_core.paths().pruner_specs_dir]
|
|
221
|
+
for base in search_roots:
|
|
222
|
+
for cand in candidates:
|
|
223
|
+
path = (base / cand).resolve()
|
|
224
|
+
if path.exists():
|
|
225
|
+
return path
|
|
226
|
+
raise ValueError(f"Prune spec not found: {value}")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _load_prune_spec_summary(spec_path: Path) -> dict:
|
|
232
|
+
try:
|
|
233
|
+
data = yaml.safe_load(spec_path.read_text(encoding="utf-8"))
|
|
234
|
+
except Exception:
|
|
235
|
+
return {}
|
|
236
|
+
if not isinstance(data, dict):
|
|
237
|
+
return {}
|
|
238
|
+
return {
|
|
239
|
+
"__meta__": data.get("__meta__", {}),
|
|
240
|
+
"mode": data.get("mode"),
|
|
241
|
+
"files": data.get("files"),
|
|
242
|
+
"dirs": data.get("dirs"),
|
|
243
|
+
"update_params_keys": list((data.get("update_params") or {}).keys()),
|
|
244
|
+
"add_root": data.get("add_root"),
|
|
245
|
+
"root_name": data.get("root_name"),
|
|
246
|
+
"strip_jcamp_comments": data.get("strip_jcamp_comments"),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _build_dir_override(
|
|
251
|
+
scan_ids: Optional[list[str]],
|
|
252
|
+
reco_ids: Optional[list[str]],
|
|
253
|
+
) -> Optional[list[dict[str, object]]]:
|
|
254
|
+
scan_list = _parse_id_list(scan_ids)
|
|
255
|
+
reco_list = _parse_id_list(reco_ids)
|
|
256
|
+
rules: list[dict[str, object]] = []
|
|
257
|
+
if scan_list:
|
|
258
|
+
rules.append({"level": 1, "dirs": scan_list})
|
|
259
|
+
if reco_list:
|
|
260
|
+
rules.append({"level": 3, "dirs": reco_list})
|
|
261
|
+
return rules or None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _parse_id_list(values: Optional[list[str]]) -> list[str]:
|
|
265
|
+
if not values:
|
|
266
|
+
return []
|
|
267
|
+
result: list[str] = []
|
|
268
|
+
for value in values:
|
|
269
|
+
for part in str(value).split(","):
|
|
270
|
+
part = part.strip()
|
|
271
|
+
if part:
|
|
272
|
+
result.append(part)
|
|
273
|
+
return result
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _parse_kv_pairs(items: Optional[list[str]]) -> dict[str, str]:
|
|
277
|
+
if not items:
|
|
278
|
+
return {}
|
|
279
|
+
pairs: dict[str, str] = {}
|
|
280
|
+
for item in items:
|
|
281
|
+
if "=" not in item:
|
|
282
|
+
continue
|
|
283
|
+
key, value = item.split("=", 1)
|
|
284
|
+
key = key.strip()
|
|
285
|
+
if not key:
|
|
286
|
+
continue
|
|
287
|
+
pairs[key] = value
|
|
288
|
+
return pairs
|