sierra-research 1.3.6__py3-none-any.whl → 1.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.
- sierra/__init__.py +3 -3
- sierra/core/__init__.py +3 -3
- sierra/core/batchroot.py +223 -0
- sierra/core/cmdline.py +681 -1057
- sierra/core/compare.py +11 -0
- sierra/core/config.py +96 -88
- sierra/core/engine.py +306 -0
- sierra/core/execenv.py +380 -0
- sierra/core/expdef.py +11 -0
- sierra/core/experiment/__init__.py +1 -0
- sierra/core/experiment/bindings.py +150 -101
- sierra/core/experiment/definition.py +414 -245
- sierra/core/experiment/spec.py +83 -85
- sierra/core/exproot.py +44 -0
- sierra/core/generators/__init__.py +10 -0
- sierra/core/generators/experiment.py +528 -0
- sierra/core/generators/generator_factory.py +138 -137
- sierra/core/graphs/__init__.py +23 -0
- sierra/core/graphs/bcbridge.py +94 -0
- sierra/core/graphs/heatmap.py +245 -324
- sierra/core/graphs/pathset.py +27 -0
- sierra/core/graphs/schema.py +77 -0
- sierra/core/graphs/stacked_line.py +341 -0
- sierra/core/graphs/summary_line.py +506 -0
- sierra/core/logging.py +3 -2
- sierra/core/models/__init__.py +3 -1
- sierra/core/models/info.py +19 -0
- sierra/core/models/interface.py +52 -122
- sierra/core/pipeline/__init__.py +2 -5
- sierra/core/pipeline/pipeline.py +228 -126
- sierra/core/pipeline/stage1/__init__.py +10 -0
- sierra/core/pipeline/stage1/pipeline_stage1.py +45 -31
- sierra/core/pipeline/stage2/__init__.py +10 -0
- sierra/core/pipeline/stage2/pipeline_stage2.py +8 -11
- sierra/core/pipeline/stage2/runner.py +401 -0
- sierra/core/pipeline/stage3/__init__.py +12 -0
- sierra/core/pipeline/stage3/gather.py +321 -0
- sierra/core/pipeline/stage3/pipeline_stage3.py +37 -84
- sierra/core/pipeline/stage4/__init__.py +12 -2
- sierra/core/pipeline/stage4/pipeline_stage4.py +36 -354
- sierra/core/pipeline/stage5/__init__.py +12 -0
- sierra/core/pipeline/stage5/pipeline_stage5.py +33 -208
- sierra/core/pipeline/yaml.py +48 -0
- sierra/core/plugin.py +529 -62
- sierra/core/proc.py +11 -0
- sierra/core/prod.py +11 -0
- sierra/core/ros1/__init__.py +5 -1
- sierra/core/ros1/callbacks.py +22 -21
- sierra/core/ros1/cmdline.py +59 -88
- sierra/core/ros1/generators.py +159 -175
- sierra/core/ros1/variables/__init__.py +3 -0
- sierra/core/ros1/variables/exp_setup.py +122 -116
- sierra/core/startup.py +106 -76
- sierra/core/stat_kernels.py +4 -5
- sierra/core/storage.py +13 -32
- sierra/core/trampoline.py +30 -0
- sierra/core/types.py +116 -71
- sierra/core/utils.py +103 -106
- sierra/core/variables/__init__.py +1 -1
- sierra/core/variables/base_variable.py +12 -17
- sierra/core/variables/batch_criteria.py +387 -481
- sierra/core/variables/builtin.py +135 -0
- sierra/core/variables/exp_setup.py +19 -39
- sierra/core/variables/population_size.py +72 -76
- sierra/core/variables/variable_density.py +44 -68
- sierra/core/vector.py +1 -1
- sierra/main.py +256 -88
- sierra/plugins/__init__.py +119 -0
- sierra/plugins/compare/__init__.py +14 -0
- sierra/plugins/compare/graphs/__init__.py +19 -0
- sierra/plugins/compare/graphs/cmdline.py +120 -0
- sierra/plugins/compare/graphs/comparator.py +291 -0
- sierra/plugins/compare/graphs/inter_controller.py +531 -0
- sierra/plugins/compare/graphs/inter_scenario.py +297 -0
- sierra/plugins/compare/graphs/namecalc.py +53 -0
- sierra/plugins/compare/graphs/outputroot.py +73 -0
- sierra/plugins/compare/graphs/plugin.py +147 -0
- sierra/plugins/compare/graphs/preprocess.py +172 -0
- sierra/plugins/compare/graphs/schema.py +37 -0
- sierra/plugins/engine/__init__.py +14 -0
- sierra/plugins/engine/argos/__init__.py +18 -0
- sierra/plugins/{platform → engine}/argos/cmdline.py +144 -151
- sierra/plugins/{platform/argos/variables → engine/argos/generators}/__init__.py +5 -0
- sierra/plugins/engine/argos/generators/engine.py +394 -0
- sierra/plugins/engine/argos/plugin.py +393 -0
- sierra/plugins/{platform/argos/generators → engine/argos/variables}/__init__.py +5 -0
- sierra/plugins/engine/argos/variables/arena_shape.py +183 -0
- sierra/plugins/engine/argos/variables/cameras.py +240 -0
- sierra/plugins/engine/argos/variables/constant_density.py +112 -0
- sierra/plugins/engine/argos/variables/exp_setup.py +82 -0
- sierra/plugins/{platform → engine}/argos/variables/physics_engines.py +83 -87
- sierra/plugins/engine/argos/variables/population_constant_density.py +178 -0
- sierra/plugins/engine/argos/variables/population_size.py +115 -0
- sierra/plugins/engine/argos/variables/population_variable_density.py +123 -0
- sierra/plugins/engine/argos/variables/rendering.py +108 -0
- sierra/plugins/engine/ros1gazebo/__init__.py +18 -0
- sierra/plugins/engine/ros1gazebo/cmdline.py +175 -0
- sierra/plugins/{platform/ros1robot → engine/ros1gazebo}/generators/__init__.py +5 -0
- sierra/plugins/engine/ros1gazebo/generators/engine.py +125 -0
- sierra/plugins/engine/ros1gazebo/plugin.py +404 -0
- sierra/plugins/engine/ros1gazebo/variables/__init__.py +15 -0
- sierra/plugins/engine/ros1gazebo/variables/population_size.py +214 -0
- sierra/plugins/engine/ros1robot/__init__.py +18 -0
- sierra/plugins/engine/ros1robot/cmdline.py +159 -0
- sierra/plugins/{platform/ros1gazebo → engine/ros1robot}/generators/__init__.py +4 -0
- sierra/plugins/engine/ros1robot/generators/engine.py +95 -0
- sierra/plugins/engine/ros1robot/plugin.py +410 -0
- sierra/plugins/{hpc/local → engine/ros1robot/variables}/__init__.py +5 -0
- sierra/plugins/engine/ros1robot/variables/population_size.py +146 -0
- sierra/plugins/execenv/__init__.py +11 -0
- sierra/plugins/execenv/hpc/__init__.py +18 -0
- sierra/plugins/execenv/hpc/adhoc/__init__.py +18 -0
- sierra/plugins/execenv/hpc/adhoc/cmdline.py +30 -0
- sierra/plugins/execenv/hpc/adhoc/plugin.py +131 -0
- sierra/plugins/execenv/hpc/cmdline.py +137 -0
- sierra/plugins/execenv/hpc/local/__init__.py +18 -0
- sierra/plugins/execenv/hpc/local/cmdline.py +31 -0
- sierra/plugins/execenv/hpc/local/plugin.py +145 -0
- sierra/plugins/execenv/hpc/pbs/__init__.py +18 -0
- sierra/plugins/execenv/hpc/pbs/cmdline.py +30 -0
- sierra/plugins/execenv/hpc/pbs/plugin.py +121 -0
- sierra/plugins/execenv/hpc/slurm/__init__.py +18 -0
- sierra/plugins/execenv/hpc/slurm/cmdline.py +30 -0
- sierra/plugins/execenv/hpc/slurm/plugin.py +133 -0
- sierra/plugins/execenv/prefectserver/__init__.py +18 -0
- sierra/plugins/execenv/prefectserver/cmdline.py +66 -0
- sierra/plugins/execenv/prefectserver/dockerremote/__init__.py +18 -0
- sierra/plugins/execenv/prefectserver/dockerremote/cmdline.py +66 -0
- sierra/plugins/execenv/prefectserver/dockerremote/plugin.py +132 -0
- sierra/plugins/execenv/prefectserver/flow.py +66 -0
- sierra/plugins/execenv/prefectserver/local/__init__.py +18 -0
- sierra/plugins/execenv/prefectserver/local/cmdline.py +29 -0
- sierra/plugins/execenv/prefectserver/local/plugin.py +133 -0
- sierra/plugins/{hpc/adhoc → execenv/robot}/__init__.py +1 -0
- sierra/plugins/execenv/robot/turtlebot3/__init__.py +18 -0
- sierra/plugins/execenv/robot/turtlebot3/plugin.py +204 -0
- sierra/plugins/expdef/__init__.py +14 -0
- sierra/plugins/expdef/json/__init__.py +14 -0
- sierra/plugins/expdef/json/plugin.py +504 -0
- sierra/plugins/expdef/xml/__init__.py +14 -0
- sierra/plugins/expdef/xml/plugin.py +386 -0
- sierra/{core/hpc → plugins/proc}/__init__.py +1 -1
- sierra/plugins/proc/collate/__init__.py +15 -0
- sierra/plugins/proc/collate/cmdline.py +47 -0
- sierra/plugins/proc/collate/plugin.py +271 -0
- sierra/plugins/proc/compress/__init__.py +18 -0
- sierra/plugins/proc/compress/cmdline.py +47 -0
- sierra/plugins/proc/compress/plugin.py +123 -0
- sierra/plugins/proc/decompress/__init__.py +18 -0
- sierra/plugins/proc/decompress/plugin.py +96 -0
- sierra/plugins/proc/imagize/__init__.py +15 -0
- sierra/plugins/proc/imagize/cmdline.py +49 -0
- sierra/plugins/proc/imagize/plugin.py +270 -0
- sierra/plugins/proc/modelrunner/__init__.py +16 -0
- sierra/plugins/proc/modelrunner/plugin.py +250 -0
- sierra/plugins/proc/statistics/__init__.py +15 -0
- sierra/plugins/proc/statistics/cmdline.py +64 -0
- sierra/plugins/proc/statistics/plugin.py +390 -0
- sierra/plugins/{hpc → prod}/__init__.py +1 -0
- sierra/plugins/prod/graphs/__init__.py +18 -0
- sierra/plugins/prod/graphs/cmdline.py +269 -0
- sierra/plugins/prod/graphs/collate.py +279 -0
- sierra/plugins/prod/graphs/inter/__init__.py +13 -0
- sierra/plugins/prod/graphs/inter/generate.py +83 -0
- sierra/plugins/prod/graphs/inter/heatmap.py +86 -0
- sierra/plugins/prod/graphs/inter/line.py +134 -0
- sierra/plugins/prod/graphs/intra/__init__.py +15 -0
- sierra/plugins/prod/graphs/intra/generate.py +202 -0
- sierra/plugins/prod/graphs/intra/heatmap.py +74 -0
- sierra/plugins/prod/graphs/intra/line.py +114 -0
- sierra/plugins/prod/graphs/plugin.py +103 -0
- sierra/plugins/prod/graphs/targets.py +63 -0
- sierra/plugins/prod/render/__init__.py +18 -0
- sierra/plugins/prod/render/cmdline.py +72 -0
- sierra/plugins/prod/render/plugin.py +282 -0
- sierra/plugins/storage/__init__.py +5 -0
- sierra/plugins/storage/arrow/__init__.py +18 -0
- sierra/plugins/storage/arrow/plugin.py +38 -0
- sierra/plugins/storage/csv/__init__.py +9 -0
- sierra/plugins/storage/csv/plugin.py +12 -5
- sierra/version.py +3 -2
- sierra_research-1.5.0.dist-info/METADATA +238 -0
- sierra_research-1.5.0.dist-info/RECORD +186 -0
- {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info}/WHEEL +1 -2
- sierra/core/experiment/xml.py +0 -454
- sierra/core/generators/controller_generator_parser.py +0 -34
- sierra/core/generators/exp_creator.py +0 -351
- sierra/core/generators/exp_generators.py +0 -142
- sierra/core/graphs/scatterplot2D.py +0 -109
- sierra/core/graphs/stacked_line_graph.py +0 -249
- sierra/core/graphs/stacked_surface_graph.py +0 -220
- sierra/core/graphs/summary_line_graph.py +0 -369
- sierra/core/hpc/cmdline.py +0 -142
- sierra/core/models/graphs.py +0 -87
- sierra/core/pipeline/stage2/exp_runner.py +0 -286
- sierra/core/pipeline/stage3/imagizer.py +0 -149
- sierra/core/pipeline/stage3/run_collator.py +0 -317
- sierra/core/pipeline/stage3/statistics_calculator.py +0 -478
- sierra/core/pipeline/stage4/graph_collator.py +0 -319
- sierra/core/pipeline/stage4/inter_exp_graph_generator.py +0 -240
- sierra/core/pipeline/stage4/intra_exp_graph_generator.py +0 -317
- sierra/core/pipeline/stage4/model_runner.py +0 -168
- sierra/core/pipeline/stage4/rendering.py +0 -283
- sierra/core/pipeline/stage4/yaml_config_loader.py +0 -103
- sierra/core/pipeline/stage5/inter_scenario_comparator.py +0 -328
- sierra/core/pipeline/stage5/intra_scenario_comparator.py +0 -989
- sierra/core/platform.py +0 -493
- sierra/core/plugin_manager.py +0 -369
- sierra/core/root_dirpath_generator.py +0 -241
- sierra/plugins/hpc/adhoc/plugin.py +0 -125
- sierra/plugins/hpc/local/plugin.py +0 -81
- sierra/plugins/hpc/pbs/__init__.py +0 -9
- sierra/plugins/hpc/pbs/plugin.py +0 -126
- sierra/plugins/hpc/slurm/__init__.py +0 -9
- sierra/plugins/hpc/slurm/plugin.py +0 -130
- sierra/plugins/platform/__init__.py +0 -9
- sierra/plugins/platform/argos/__init__.py +0 -9
- sierra/plugins/platform/argos/generators/platform_generators.py +0 -383
- sierra/plugins/platform/argos/plugin.py +0 -337
- sierra/plugins/platform/argos/variables/arena_shape.py +0 -145
- sierra/plugins/platform/argos/variables/cameras.py +0 -243
- sierra/plugins/platform/argos/variables/constant_density.py +0 -136
- sierra/plugins/platform/argos/variables/exp_setup.py +0 -113
- sierra/plugins/platform/argos/variables/population_constant_density.py +0 -175
- sierra/plugins/platform/argos/variables/population_size.py +0 -102
- sierra/plugins/platform/argos/variables/population_variable_density.py +0 -132
- sierra/plugins/platform/argos/variables/rendering.py +0 -104
- sierra/plugins/platform/ros1gazebo/__init__.py +0 -9
- sierra/plugins/platform/ros1gazebo/cmdline.py +0 -213
- sierra/plugins/platform/ros1gazebo/generators/platform_generators.py +0 -137
- sierra/plugins/platform/ros1gazebo/plugin.py +0 -335
- sierra/plugins/platform/ros1gazebo/variables/__init__.py +0 -10
- sierra/plugins/platform/ros1gazebo/variables/population_size.py +0 -204
- sierra/plugins/platform/ros1robot/__init__.py +0 -9
- sierra/plugins/platform/ros1robot/cmdline.py +0 -175
- sierra/plugins/platform/ros1robot/generators/platform_generators.py +0 -112
- sierra/plugins/platform/ros1robot/plugin.py +0 -373
- sierra/plugins/platform/ros1robot/variables/__init__.py +0 -10
- sierra/plugins/platform/ros1robot/variables/population_size.py +0 -146
- sierra/plugins/robot/__init__.py +0 -9
- sierra/plugins/robot/turtlebot3/__init__.py +0 -9
- sierra/plugins/robot/turtlebot3/plugin.py +0 -194
- sierra_research-1.3.6.data/data/share/man/man1/sierra-cli.1 +0 -2349
- sierra_research-1.3.6.data/data/share/man/man7/sierra-examples.7 +0 -488
- sierra_research-1.3.6.data/data/share/man/man7/sierra-exec-envs.7 +0 -331
- sierra_research-1.3.6.data/data/share/man/man7/sierra-glossary.7 +0 -285
- sierra_research-1.3.6.data/data/share/man/man7/sierra-platforms.7 +0 -358
- sierra_research-1.3.6.data/data/share/man/man7/sierra-usage.7 +0 -725
- sierra_research-1.3.6.data/data/share/man/man7/sierra.7 +0 -78
- sierra_research-1.3.6.dist-info/METADATA +0 -500
- sierra_research-1.3.6.dist-info/RECORD +0 -133
- sierra_research-1.3.6.dist-info/top_level.txt +0 -1
- {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info}/entry_points.txt +0 -0
- {sierra_research-1.3.6.dist-info → sierra_research-1.5.0.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,504 @@
|
|
1
|
+
# Copyright 2024 John Harwell, All rights reserved.
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
"""Plugin for parsing and manipulating template input files in XML format."""
|
5
|
+
|
6
|
+
# Core packages
|
7
|
+
import pathlib
|
8
|
+
import logging
|
9
|
+
import typing as tp
|
10
|
+
import json
|
11
|
+
|
12
|
+
# 3rd party packages
|
13
|
+
import implements
|
14
|
+
from jsonpath_ng.ext import parse as jpparse
|
15
|
+
|
16
|
+
# Project packages
|
17
|
+
from sierra.core.experiment import definition
|
18
|
+
from sierra.core import types, utils
|
19
|
+
|
20
|
+
|
21
|
+
class Writer:
|
22
|
+
"""Write the XML experiment to the filesystem according to configuration.
|
23
|
+
|
24
|
+
More than one file may be written, as specified.
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(self, tree: types.JSON) -> None:
|
28
|
+
self.tree = tree
|
29
|
+
self.logger = logging.getLogger(__name__)
|
30
|
+
|
31
|
+
def __call__(
|
32
|
+
self, write_config: definition.WriterConfig, base_opath: pathlib.Path
|
33
|
+
) -> None:
|
34
|
+
for config in write_config.values:
|
35
|
+
self._write_with_config(base_opath, config)
|
36
|
+
|
37
|
+
def _write_with_config(self, base_opath: pathlib.Path, config: dict) -> None:
|
38
|
+
tree, src_root, opath = self._write_prepare_tree(base_opath, config)
|
39
|
+
|
40
|
+
self.logger.trace("Write tree@%s to %s", src_root, opath) # type: ignore
|
41
|
+
|
42
|
+
to_write = tree
|
43
|
+
|
44
|
+
with utils.utf8open(opath, "w") as f:
|
45
|
+
json.dump(to_write, f, indent=2)
|
46
|
+
|
47
|
+
def _write_prepare_tree(
|
48
|
+
self, base_opath: pathlib.Path, config: dict
|
49
|
+
) -> tp.Tuple[tp.Optional[types.JSON], str, pathlib.Path]:
|
50
|
+
if config["src_parent"] is None:
|
51
|
+
src_root = config["src_tag"]
|
52
|
+
else:
|
53
|
+
src_root = "{0}.{1}".format(config["src_parent"], config["src_tag"])
|
54
|
+
|
55
|
+
expr = jpparse(src_root)
|
56
|
+
matches = expr.find(self.tree)
|
57
|
+
assert len(matches) == 1, "src_root was not unique!"
|
58
|
+
tree_out = matches[0].value
|
59
|
+
|
60
|
+
# Customizing the output write path is not required
|
61
|
+
if "opath_leaf" in config and config["opath_leaf"] is not None:
|
62
|
+
opath = base_opath.with_name(base_opath.name + config["opath_leaf"])
|
63
|
+
else:
|
64
|
+
opath = base_opath
|
65
|
+
|
66
|
+
return (tree_out, src_root, opath)
|
67
|
+
|
68
|
+
|
69
|
+
def root_querypath() -> str:
|
70
|
+
return "$"
|
71
|
+
|
72
|
+
|
73
|
+
@implements.implements(definition.BaseExpDef)
|
74
|
+
class ExpDef:
|
75
|
+
"""Read, write, and modify parsed XML files into experiment definitions."""
|
76
|
+
|
77
|
+
def __init__(
|
78
|
+
self,
|
79
|
+
input_fpath: pathlib.Path,
|
80
|
+
write_config: tp.Optional[definition.WriterConfig] = None,
|
81
|
+
) -> None:
|
82
|
+
|
83
|
+
self.write_config = write_config
|
84
|
+
self.input_fpath = input_fpath
|
85
|
+
with utils.utf8open(self.input_fpath, "r") as f:
|
86
|
+
self.tree = json.load(f)
|
87
|
+
self.element_adds = definition.ElementAddList()
|
88
|
+
self.attr_chgs = definition.AttrChangeSet()
|
89
|
+
|
90
|
+
self.logger = logging.getLogger(__name__)
|
91
|
+
|
92
|
+
def n_mods(self) -> tp.Tuple[int, int]:
|
93
|
+
return len(self.element_adds), len(self.attr_chgs)
|
94
|
+
|
95
|
+
def write_config_set(self, config: definition.WriterConfig) -> None:
|
96
|
+
"""Set the write config for the object.
|
97
|
+
|
98
|
+
Provided for cases in which the configuration is dependent on whether or
|
99
|
+
not certain tags are present in the input file.
|
100
|
+
|
101
|
+
"""
|
102
|
+
self.write_config = config
|
103
|
+
|
104
|
+
def write(self, base_opath: pathlib.Path) -> None:
|
105
|
+
assert self.write_config is not None, "Can't write without write config"
|
106
|
+
|
107
|
+
writer = Writer(self.tree)
|
108
|
+
writer(self.write_config, base_opath)
|
109
|
+
|
110
|
+
def flatten(self, keys: tp.List[str]) -> None:
|
111
|
+
"""
|
112
|
+
Flatten a nested JSON structure.
|
113
|
+
|
114
|
+
Recursively searches for each of the supplies keys, and replaces the
|
115
|
+
values of all matching keys with the corresponding config files. The
|
116
|
+
paths to the nested config files are assumed to be specified relative to
|
117
|
+
the root/main config file, and to reside in subdirs/adjacent dirs to it.
|
118
|
+
"""
|
119
|
+
for k in keys:
|
120
|
+
self.logger.debug("Flattening with key=%s", k)
|
121
|
+
self._flatten_recurse(self.tree, self.input_fpath, k)
|
122
|
+
|
123
|
+
def _flatten_recurse(
|
124
|
+
self, blob: types.JSON, prefix: pathlib.Path, path_key: str
|
125
|
+
) -> None:
|
126
|
+
"""
|
127
|
+
Recursive flattening implementation.
|
128
|
+
|
129
|
+
The use of recursion enables searching for simple key matches instead of
|
130
|
+
having to deal with complicated jsonpath expressions, which is a huge
|
131
|
+
win. Plus, it's generally faster than an iterating implementation when
|
132
|
+
it comes to large files.
|
133
|
+
|
134
|
+
Arguments:
|
135
|
+
blob: The tree of JSON containing filepath references to flatten.
|
136
|
+
|
137
|
+
prefix: The prefix which should be prepended to all values which
|
138
|
+
match ``prefix``. This allows nested JSON structures where
|
139
|
+
filepaths are specified relative to the root-level
|
140
|
+
configuration (really it's parent directory), which is very
|
141
|
+
convenient. Note that all paths must be relative to
|
142
|
+
root-level configuration--relativity to a sub-path will NOT
|
143
|
+
work.
|
144
|
+
|
145
|
+
path_key: The key to recursively search for. Not a substring--will
|
146
|
+
be checked for exact match.
|
147
|
+
"""
|
148
|
+
|
149
|
+
def _flatten_update_path(parent, key: str, value) -> None:
|
150
|
+
# Base case
|
151
|
+
if path_key != key:
|
152
|
+
return
|
153
|
+
|
154
|
+
# Make relative to input prefix. This SHOULD work recursively for
|
155
|
+
# nested dirs, though I'm not 100% sure.
|
156
|
+
path = pathlib.Path(value)
|
157
|
+
|
158
|
+
if not path.is_absolute():
|
159
|
+
path = prefix.parent / path
|
160
|
+
value = str(path.resolve())
|
161
|
+
|
162
|
+
# If the file doesn't exist, that's an error, so don't catch the
|
163
|
+
# exception if that happens.
|
164
|
+
with open(path, "r") as f:
|
165
|
+
subblob = json.load(f)
|
166
|
+
|
167
|
+
self._flatten_recurse(subblob, path, path_key)
|
168
|
+
|
169
|
+
if isinstance(parent, dict):
|
170
|
+
parent.update(subblob)
|
171
|
+
|
172
|
+
# This ensures that the original <key,value> pair is removed
|
173
|
+
# from the parent.
|
174
|
+
parent.pop(path_key)
|
175
|
+
|
176
|
+
def _flatten_erase_key(_, __, value):
|
177
|
+
if isinstance(value, dict):
|
178
|
+
keys_to_erase = [key for key in value if path_key == key]
|
179
|
+
for key in reversed(keys_to_erase):
|
180
|
+
value.pop(key, None)
|
181
|
+
|
182
|
+
self._flatten_apply1(blob, _flatten_update_path)
|
183
|
+
self._flatten_apply2(blob, _flatten_erase_key)
|
184
|
+
|
185
|
+
def _flatten_apply1(self, blob: types.JSON, f: tp.Callable) -> None:
|
186
|
+
"""Apply the given callable to every unstructured key-value pair.
|
187
|
+
|
188
|
+
"Unstructured" here means pairs where the value is a literal instead of
|
189
|
+
a list or dict.
|
190
|
+
"""
|
191
|
+
if isinstance(blob, dict):
|
192
|
+
c = blob.copy()
|
193
|
+
for key, val in c.items():
|
194
|
+
if isinstance(val, (dict, list)):
|
195
|
+
# recurse on each value in dict. Key is ignored.
|
196
|
+
self._flatten_apply1(val, f)
|
197
|
+
else:
|
198
|
+
# Base case: literal
|
199
|
+
f(blob, key, val)
|
200
|
+
|
201
|
+
elif isinstance(blob, list):
|
202
|
+
for item in blob:
|
203
|
+
# Recurse on each item in list
|
204
|
+
self._flatten_apply1(item, f)
|
205
|
+
|
206
|
+
def _flatten_apply2(self, blob: types.JSON, f: tp.Callable) -> None:
|
207
|
+
"""Apply the given callable to every structured key-value pair.
|
208
|
+
|
209
|
+
"Structured" here means pairs where the value is a list or dict instead
|
210
|
+
of a literal.
|
211
|
+
|
212
|
+
This function does not have a base case per-se, because we iterate
|
213
|
+
through each item in the dict/list passed in and call this function on
|
214
|
+
each one; recursion will terminate after we have exhaustively applied
|
215
|
+
the callback to all sub-blobs.
|
216
|
+
"""
|
217
|
+
if isinstance(blob, dict):
|
218
|
+
for key, val in blob.items():
|
219
|
+
if isinstance(val, (dict, list)):
|
220
|
+
self._flatten_apply2(val, f)
|
221
|
+
|
222
|
+
f(blob, key, val)
|
223
|
+
elif isinstance(blob, list):
|
224
|
+
for item in blob:
|
225
|
+
self._flatten_apply2(item, f)
|
226
|
+
|
227
|
+
def attr_get(self, path: str, attr: str) -> tp.Optional[tp.Union[str, int, float]]:
|
228
|
+
expr = jpparse(path)
|
229
|
+
matches = expr.find(self.tree)
|
230
|
+
|
231
|
+
assert len(matches) <= 1, f"Path '{path}' to element was not unique!"
|
232
|
+
|
233
|
+
if len(matches) == 0:
|
234
|
+
return None
|
235
|
+
|
236
|
+
match = matches[0].value
|
237
|
+
|
238
|
+
if not isinstance(match, list):
|
239
|
+
match = [match]
|
240
|
+
|
241
|
+
for m in match:
|
242
|
+
if attr in m.keys() and not isinstance(m[attr], (list, dict)):
|
243
|
+
return m[attr]
|
244
|
+
|
245
|
+
return None
|
246
|
+
|
247
|
+
def attr_change(
|
248
|
+
self,
|
249
|
+
path: str,
|
250
|
+
attr: str,
|
251
|
+
value: tp.Union[str, int, float],
|
252
|
+
noprint: bool = False,
|
253
|
+
) -> bool:
|
254
|
+
|
255
|
+
expr = jpparse(path)
|
256
|
+
matches = expr.find(self.tree)
|
257
|
+
|
258
|
+
if len(matches) == 0:
|
259
|
+
if not noprint:
|
260
|
+
self.logger.warning("Parent element '%s' not found", path)
|
261
|
+
return False
|
262
|
+
|
263
|
+
for m in matches:
|
264
|
+
match = m.value
|
265
|
+
if attr not in match.keys() or isinstance(match[attr], (list, dict)):
|
266
|
+
if not noprint:
|
267
|
+
self.logger.warning(
|
268
|
+
"Attribute '%s' not found in path '%s'", attr, m.full_path
|
269
|
+
)
|
270
|
+
return False
|
271
|
+
|
272
|
+
match[attr] = value
|
273
|
+
self.logger.trace(
|
274
|
+
"Modify attr: '%s/%s' = '%s'", m.full_path, attr, value # type: ignore
|
275
|
+
)
|
276
|
+
|
277
|
+
self.attr_chgs.add(definition.AttrChange(path, attr, value))
|
278
|
+
return True
|
279
|
+
|
280
|
+
def attr_add(
|
281
|
+
self,
|
282
|
+
path: str,
|
283
|
+
attr: str,
|
284
|
+
value: tp.Union[str, int, float],
|
285
|
+
noprint: bool = False,
|
286
|
+
) -> bool:
|
287
|
+
expr = jpparse(path)
|
288
|
+
matches = expr.find(self.tree)
|
289
|
+
|
290
|
+
assert len(matches) <= 1, f"Path '{path}' to element was not unique!"
|
291
|
+
|
292
|
+
if len(matches) == 0:
|
293
|
+
if not noprint:
|
294
|
+
self.logger.warning("Node '%s' not found", path)
|
295
|
+
return False
|
296
|
+
|
297
|
+
for m in matches:
|
298
|
+
match = m.value
|
299
|
+
if attr in match:
|
300
|
+
if not noprint:
|
301
|
+
self.logger.warning(
|
302
|
+
"Attribute '%s' already in path '%s'", attr, m.full_path
|
303
|
+
)
|
304
|
+
return False
|
305
|
+
|
306
|
+
match[attr] = value
|
307
|
+
self.logger.trace(
|
308
|
+
"Add new attribute: '%s/%s' = '%s'", m.full_path, attr, value # type: ignore
|
309
|
+
)
|
310
|
+
self.attr_chgs.add(definition.AttrChange(path, attr, value))
|
311
|
+
return True
|
312
|
+
|
313
|
+
def has_element(self, path: str) -> bool:
|
314
|
+
expr = jpparse(path)
|
315
|
+
el = expr.find(self.tree)
|
316
|
+
|
317
|
+
assert len(el) <= 1, (
|
318
|
+
f"Path '{path}' to element was not unique! Perhaps "
|
319
|
+
"you have malform JSON?"
|
320
|
+
)
|
321
|
+
|
322
|
+
if el:
|
323
|
+
# If path maps to a literal, then we are pointing to an attribute,
|
324
|
+
# which is obviously not an element.
|
325
|
+
return isinstance(el[0].value, (list, dict))
|
326
|
+
|
327
|
+
return False
|
328
|
+
|
329
|
+
def has_attr(self, path: str, attr: str) -> bool:
|
330
|
+
expr = jpparse(path)
|
331
|
+
matches = expr.find(self.tree)
|
332
|
+
|
333
|
+
assert len(matches) <= 1, f"Path '{path}' to parent element was not unique!"
|
334
|
+
|
335
|
+
if len(matches) == 0:
|
336
|
+
return False
|
337
|
+
|
338
|
+
found = False
|
339
|
+
|
340
|
+
match = matches[0].value
|
341
|
+
if not isinstance(match, list):
|
342
|
+
match = [match]
|
343
|
+
|
344
|
+
for m in match:
|
345
|
+
for k in m:
|
346
|
+
# While python/JSON doesn't distinguish between a key which maps
|
347
|
+
# to a literal {bool, int, ...}, and one which maps to a
|
348
|
+
# sub-element, SIERRA does, because it treats one key as
|
349
|
+
# referring to an attribute mapping, and one referring to a
|
350
|
+
# sub-element.
|
351
|
+
if k == attr and not isinstance(m[k], (list, dict)):
|
352
|
+
assert (
|
353
|
+
not found
|
354
|
+
), f"Specified attr '{attr}' is not unique in '{path}'"
|
355
|
+
found = True
|
356
|
+
|
357
|
+
return found
|
358
|
+
|
359
|
+
def element_change(self, path: str, tag: str, value: str) -> bool:
|
360
|
+
expr = jpparse(path)
|
361
|
+
el = expr.find(self.tree).value
|
362
|
+
|
363
|
+
if el is None:
|
364
|
+
self.logger.warning("Parent element '%s' not found", path)
|
365
|
+
return False
|
366
|
+
|
367
|
+
for child in el:
|
368
|
+
if child.tag == tag:
|
369
|
+
child.tag = value
|
370
|
+
self.logger.trace(
|
371
|
+
"Modify tag: '%s.%s' = '%s'", path, tag, value # type: ignore
|
372
|
+
)
|
373
|
+
return True
|
374
|
+
|
375
|
+
self.logger.warning("No such element '%s' found in '%s'", tag, path)
|
376
|
+
return False
|
377
|
+
|
378
|
+
def element_remove(self, path: str, tag: str, noprint: bool = False) -> bool:
|
379
|
+
expr = jpparse(path)
|
380
|
+
parents = expr.find(self.tree)
|
381
|
+
|
382
|
+
assert len(parents) <= 1, (
|
383
|
+
f"Path '{path}' to parent was not unique! If you want to remove "
|
384
|
+
"multiple matching elements, use elements_remove_all()"
|
385
|
+
)
|
386
|
+
|
387
|
+
if len(parents) == 0:
|
388
|
+
if not noprint:
|
389
|
+
self.logger.warning("Parent element '%s' not found", path)
|
390
|
+
return False
|
391
|
+
|
392
|
+
parent = parents[0].value
|
393
|
+
victims = jpparse(tag).find(parent)
|
394
|
+
|
395
|
+
if len(victims) == 0 or not isinstance(victims[0].value, (list, dict)):
|
396
|
+
if not noprint:
|
397
|
+
self.logger.warning("No victim '%s' found in parent '%s'", tag, path)
|
398
|
+
return False
|
399
|
+
|
400
|
+
del parent[tag]
|
401
|
+
return True
|
402
|
+
|
403
|
+
def element_remove_all(self, path: str, tag: str, noprint: bool = False) -> bool:
|
404
|
+
|
405
|
+
expr = jpparse(path)
|
406
|
+
parents = expr.find(self.tree)
|
407
|
+
|
408
|
+
if len(parents) == 0:
|
409
|
+
if not noprint:
|
410
|
+
self.logger.warning("Parent element '%s' not found", path)
|
411
|
+
return False
|
412
|
+
|
413
|
+
parent = parents[0].value
|
414
|
+
|
415
|
+
victims = jpparse(tag).find(parent)
|
416
|
+
|
417
|
+
if len(victims) == 0:
|
418
|
+
if not noprint:
|
419
|
+
self.logger.warning(
|
420
|
+
"No victims matching '%s' found in parent '%s'", tag, path
|
421
|
+
)
|
422
|
+
return False
|
423
|
+
|
424
|
+
del parent[tag]
|
425
|
+
return True
|
426
|
+
|
427
|
+
def element_add(
|
428
|
+
self,
|
429
|
+
path: str,
|
430
|
+
tag: str,
|
431
|
+
attr: tp.Optional[types.StrDict] = None,
|
432
|
+
allow_dup: bool = True,
|
433
|
+
noprint: bool = False,
|
434
|
+
) -> bool:
|
435
|
+
"""
|
436
|
+
Add tag name as a child element of enclosing parent.
|
437
|
+
"""
|
438
|
+
expr = jpparse(path)
|
439
|
+
parents = expr.find(self.tree)
|
440
|
+
|
441
|
+
assert len(parents) <= 1, f"Path '{path}' to parent was not unique!"
|
442
|
+
|
443
|
+
if len(parents) == 0:
|
444
|
+
if not noprint:
|
445
|
+
self.logger.warning("Parent element '%s' not found", path)
|
446
|
+
return False
|
447
|
+
|
448
|
+
parent = parents[0].value
|
449
|
+
|
450
|
+
if not allow_dup:
|
451
|
+
child = jpparse(tag).find(parent)
|
452
|
+
if len(child):
|
453
|
+
if not noprint:
|
454
|
+
self.logger.warning(
|
455
|
+
"Child element '%s' already in parent '%s'", tag, path
|
456
|
+
)
|
457
|
+
return False
|
458
|
+
|
459
|
+
# Child doesn't exist--just assign to single sub-element.
|
460
|
+
parent[tag] = attr
|
461
|
+
self.logger.trace(
|
462
|
+
"Add new unique element: '%s.%s' = '%s'", # type: ignore
|
463
|
+
path,
|
464
|
+
tag,
|
465
|
+
str(attr),
|
466
|
+
)
|
467
|
+
else:
|
468
|
+
child = jpparse(tag).find(parent)
|
469
|
+
|
470
|
+
# Child element exists, so update it to be a list of sub-elements
|
471
|
+
# rather than a single sub-elements.
|
472
|
+
if len(child):
|
473
|
+
d = [parent[tag], attr]
|
474
|
+
jpparse(tag).update(parent, d)
|
475
|
+
else:
|
476
|
+
# Child doesn't exist--just assign to single sub-element.
|
477
|
+
parent[tag] = attr
|
478
|
+
|
479
|
+
self.element_adds.append(definition.ElementAdd(path, tag, attr, allow_dup))
|
480
|
+
return True
|
481
|
+
|
482
|
+
|
483
|
+
def unpickle(
|
484
|
+
fpath: pathlib.Path,
|
485
|
+
) -> tp.Optional[tp.Union[definition.AttrChangeSet, definition.ElementAddList]]:
|
486
|
+
"""Unickle all XML modifications from the pickle file at the path.
|
487
|
+
|
488
|
+
You don't know how many there are, so go until you get an exception.
|
489
|
+
|
490
|
+
"""
|
491
|
+
try:
|
492
|
+
return definition.AttrChangeSet.unpickle(fpath)
|
493
|
+
except EOFError:
|
494
|
+
pass
|
495
|
+
|
496
|
+
try:
|
497
|
+
return definition.ElementAddList.unpickle(fpath)
|
498
|
+
except EOFError:
|
499
|
+
pass
|
500
|
+
|
501
|
+
raise NotImplementedError
|
502
|
+
|
503
|
+
|
504
|
+
__all__ = ["ExpDef", "unpickle"]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Copyright 2024 John Harwell, All rights reserved.
|
2
|
+
#
|
3
|
+
# SPDX-License-Identifier: MIT
|
4
|
+
"""Container module for the XML expdef plugin."""
|
5
|
+
|
6
|
+
# Core packages
|
7
|
+
|
8
|
+
# 3rd party packages
|
9
|
+
|
10
|
+
# Project packages
|
11
|
+
|
12
|
+
|
13
|
+
def sierra_plugin_type() -> str:
|
14
|
+
return "pipeline"
|