fprime-gds 3.6.2a1__py3-none-any.whl → 4.0.0a2__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.
- fprime_gds/common/communication/adapters/uart.py +34 -25
- fprime_gds/common/decoders/ch_decoder.py +1 -1
- fprime_gds/common/decoders/event_decoder.py +2 -1
- fprime_gds/common/decoders/pkt_decoder.py +1 -1
- fprime_gds/common/distributor/distributor.py +2 -2
- fprime_gds/common/encoders/ch_encoder.py +2 -2
- fprime_gds/common/encoders/cmd_encoder.py +2 -2
- fprime_gds/common/encoders/event_encoder.py +2 -2
- fprime_gds/common/encoders/pkt_encoder.py +2 -2
- fprime_gds/common/encoders/seq_writer.py +2 -2
- fprime_gds/common/fpy/__init__.py +0 -0
- fprime_gds/common/fpy/serialize_bytecode.py +229 -0
- fprime_gds/common/fpy/types.py +203 -0
- fprime_gds/common/gds_cli/base_commands.py +1 -1
- fprime_gds/common/handlers.py +39 -0
- fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
- fprime_gds/common/loaders/pkt_json_loader.py +121 -0
- fprime_gds/common/loaders/prm_json_loader.py +85 -0
- fprime_gds/common/pipeline/dictionaries.py +21 -4
- fprime_gds/common/pipeline/encoding.py +19 -0
- fprime_gds/common/pipeline/histories.py +4 -0
- fprime_gds/common/pipeline/standard.py +16 -2
- fprime_gds/common/templates/prm_template.py +81 -0
- fprime_gds/common/testing_fw/api.py +42 -0
- fprime_gds/common/testing_fw/pytest_integration.py +25 -2
- fprime_gds/common/tools/README.md +34 -0
- fprime_gds/common/tools/params.py +246 -0
- fprime_gds/common/utils/config_manager.py +6 -6
- fprime_gds/executables/apps.py +184 -11
- fprime_gds/executables/cli.py +443 -125
- fprime_gds/executables/comm.py +5 -2
- fprime_gds/executables/fprime_cli.py +3 -3
- fprime_gds/executables/run_deployment.py +12 -4
- fprime_gds/flask/static/js/vue-support/channel.js +1 -1
- fprime_gds/flask/static/js/vue-support/event.js +1 -1
- fprime_gds/plugin/definitions.py +86 -8
- fprime_gds/plugin/system.py +171 -58
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/METADATA +18 -19
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/RECORD +44 -35
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/entry_points.txt +2 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a2.dist-info}/top_level.txt +0 -0
fprime_gds/executables/cli.py
CHANGED
@@ -11,12 +11,17 @@ code that they are importing.
|
|
11
11
|
import argparse
|
12
12
|
import datetime
|
13
13
|
import errno
|
14
|
+
import functools
|
14
15
|
import getpass
|
16
|
+
import inspect
|
15
17
|
import itertools
|
16
18
|
import os
|
17
19
|
import platform
|
18
20
|
import re
|
19
21
|
import sys
|
22
|
+
|
23
|
+
import yaml
|
24
|
+
|
20
25
|
from abc import ABC, abstractmethod
|
21
26
|
from pathlib import Path
|
22
27
|
from typing import Any, Dict, List, Tuple
|
@@ -30,7 +35,7 @@ from fprime_gds.common.transport import ThreadedTCPSocketClient
|
|
30
35
|
from fprime_gds.common.utils.config_manager import ConfigManager
|
31
36
|
from fprime_gds.executables.utils import find_app, find_dict, get_artifacts_root
|
32
37
|
from fprime_gds.plugin.definitions import PluginType
|
33
|
-
from fprime_gds.plugin.system import Plugins
|
38
|
+
from fprime_gds.plugin.system import Plugins, PluginsNotLoadedException
|
34
39
|
from fprime_gds.common.zmq_transport import ZmqClient
|
35
40
|
|
36
41
|
|
@@ -81,22 +86,51 @@ class ParserBase(ABC):
|
|
81
86
|
self.fill_parser(parser)
|
82
87
|
return parser
|
83
88
|
|
89
|
+
@staticmethod
|
90
|
+
def safe_add_argument(parser, *flags, **keywords):
|
91
|
+
"""Add an argument allowing duplicates
|
92
|
+
|
93
|
+
Add arguments to the parser (passes through *flags and **keywords) to the supplied parser. This method traps
|
94
|
+
errors to prevent duplicates from crashing the system when two plugins use the same flags.
|
95
|
+
|
96
|
+
Args:
|
97
|
+
parser: parser or argument group to add arguments to
|
98
|
+
*flags: positional arguments passed to `add_argument`
|
99
|
+
**keywords: key word arguments passed to `add_argument`
|
100
|
+
"""
|
101
|
+
try:
|
102
|
+
parser.add_argument(*flags, **keywords)
|
103
|
+
except argparse.ArgumentError:
|
104
|
+
# flag has already been added, pass
|
105
|
+
pass
|
106
|
+
|
107
|
+
@classmethod
|
108
|
+
def add_arguments_from_specification(cls, parser, arguments):
|
109
|
+
"""Safely add arguments to parser
|
110
|
+
|
111
|
+
In parsers and plugins, arguments are represented as a map of flag tuples to argparse keyword arguments. This
|
112
|
+
function will add arguments of that representation supplied as `arguments` to the supplied parser in a safe
|
113
|
+
collision-avoidant manner.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
parser: argparse Parser or ArgumentGroup, or anything with an `add_argument` function
|
117
|
+
arguments: arguments specification
|
118
|
+
|
119
|
+
"""
|
120
|
+
for flags, keywords in arguments.items():
|
121
|
+
cls.safe_add_argument(parser, *flags, **keywords)
|
122
|
+
|
84
123
|
def fill_parser(self, parser):
|
85
|
-
"""
|
124
|
+
"""Fill supplied parser with arguments
|
86
125
|
|
87
126
|
Fills the supplied parser with the arguments returned via the `get_arguments` method invocation. This
|
88
|
-
implementation
|
127
|
+
implementation adds the arguments directly to the parser.
|
89
128
|
|
90
129
|
Args:
|
91
130
|
parser: parser to fill with arguments
|
92
131
|
|
93
132
|
"""
|
94
|
-
|
95
|
-
try:
|
96
|
-
parser.add_argument(*flags, **keywords)
|
97
|
-
except argparse.ArgumentError:
|
98
|
-
# flag has already been added, pass
|
99
|
-
pass
|
133
|
+
self.add_arguments_from_specification(parser, self.get_arguments())
|
100
134
|
|
101
135
|
def reproduce_cli_args(self, args_ns):
|
102
136
|
"""Reproduce the list of arguments needed on the command line"""
|
@@ -154,8 +188,32 @@ class ParserBase(ABC):
|
|
154
188
|
Returns: namespace with processed results of arguments.
|
155
189
|
"""
|
156
190
|
|
157
|
-
@
|
191
|
+
@classmethod
|
192
|
+
def parse_known_args(
|
193
|
+
cls,
|
194
|
+
parser_classes,
|
195
|
+
description="No tool description provided",
|
196
|
+
arguments=None,
|
197
|
+
**kwargs,
|
198
|
+
):
|
199
|
+
"""Parse and post-process arguments
|
200
|
+
|
201
|
+
Create a parser for the given application using the description provided. This will then add all specified
|
202
|
+
ParserBase subclasses' get_parser output as parent parses for the created parser. Then all of the handle
|
203
|
+
arguments methods will be called, and the final namespace will be returned. This will allow unknown arguments
|
204
|
+
which are returned as the last tuple result.
|
205
|
+
|
206
|
+
Args:
|
207
|
+
parser_classes: a list of ParserBase subclasses that will be used to
|
208
|
+
description: description passed ot the argument parser
|
209
|
+
arguments: arguments to process, None to use command line input
|
210
|
+
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
211
|
+
"""
|
212
|
+
return cls._parse_args(parser_classes, description, arguments, use_parse_known=True, **kwargs)
|
213
|
+
|
214
|
+
@classmethod
|
158
215
|
def parse_args(
|
216
|
+
cls,
|
159
217
|
parser_classes,
|
160
218
|
description="No tool description provided",
|
161
219
|
arguments=None,
|
@@ -163,20 +221,53 @@ class ParserBase(ABC):
|
|
163
221
|
):
|
164
222
|
"""Parse and post-process arguments
|
165
223
|
|
224
|
+
Create a parser for the given application using the description provided. This will then add all specified
|
225
|
+
ParserBase subclasses' get_parser output as parent parses for the created parser. Then all of the handle
|
226
|
+
arguments methods will be called, and the final namespace will be returned. This does not allow unknown
|
227
|
+
arguments.
|
228
|
+
|
229
|
+
Args:
|
230
|
+
parser_classes: a list of ParserBase subclasses that will be used to
|
231
|
+
description: description passed ot the argument parser
|
232
|
+
arguments: arguments to process, None to use command line input
|
233
|
+
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
234
|
+
"""
|
235
|
+
return cls._parse_args(parser_classes, description, arguments, **kwargs)
|
236
|
+
|
237
|
+
|
238
|
+
@staticmethod
|
239
|
+
def _parse_args(
|
240
|
+
parser_classes,
|
241
|
+
description="No tool description provided",
|
242
|
+
arguments=None,
|
243
|
+
use_parse_known=False,
|
244
|
+
**kwargs,
|
245
|
+
):
|
246
|
+
"""Parse and post-process arguments helper
|
247
|
+
|
166
248
|
Create a parser for the given application using the description provided. This will then add all specified
|
167
249
|
ParserBase subclasses' get_parser output as parent parses for the created parser. Then all of the handle
|
168
250
|
arguments methods will be called, and the final namespace will be returned.
|
169
251
|
|
252
|
+
This takes a function that will take in a parser and return the parsing function to call on arguments.
|
253
|
+
|
170
254
|
Args:
|
255
|
+
parse_function_processor: takes a parser, returns the parse function to call
|
171
256
|
parser_classes: a list of ParserBase subclasses that will be used to
|
172
257
|
description: description passed ot the argument parser
|
173
258
|
arguments: arguments to process, None to use command line input
|
259
|
+
use_parse_known: use parse_known_arguments from argparse
|
260
|
+
|
174
261
|
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
175
262
|
"""
|
176
263
|
composition = CompositeParser(parser_classes, description)
|
177
264
|
parser = composition.get_parser()
|
178
265
|
try:
|
179
|
-
|
266
|
+
if use_parse_known:
|
267
|
+
args_ns, *unknowns = parser.parse_known_args(arguments)
|
268
|
+
else:
|
269
|
+
args_ns = parser.parse_args(arguments)
|
270
|
+
unknowns = []
|
180
271
|
args_ns = composition.handle_arguments(args_ns, **kwargs)
|
181
272
|
except ValueError as ver:
|
182
273
|
print(f"[ERROR] Failed to parse arguments: {ver}", file=sys.stderr)
|
@@ -185,7 +276,7 @@ class ParserBase(ABC):
|
|
185
276
|
except Exception as exc:
|
186
277
|
print(f"[ERROR] {exc}", file=sys.stderr)
|
187
278
|
sys.exit(-1)
|
188
|
-
return args_ns, parser
|
279
|
+
return args_ns, parser, *unknowns
|
189
280
|
|
190
281
|
@staticmethod
|
191
282
|
def find_in(token, deploy, is_file=True):
|
@@ -205,6 +296,106 @@ class ParserBase(ABC):
|
|
205
296
|
return None
|
206
297
|
|
207
298
|
|
299
|
+
class ConfigDrivenParser(ParserBase):
|
300
|
+
""" Parser that allows options from configuration and command line
|
301
|
+
|
302
|
+
This parser reads a configuration file (if supplied) and uses the values to drive the inputs to arguments. Command
|
303
|
+
line arguments will still take precedence over the configured values.
|
304
|
+
"""
|
305
|
+
DEFAULT_CONFIGURATION_PATH = Path("fprime-gds.yml")
|
306
|
+
|
307
|
+
@classmethod
|
308
|
+
def set_default_configuration(cls, path: Path):
|
309
|
+
""" Set path for (global) default configuration file
|
310
|
+
|
311
|
+
Set the path for default configuration file. If unset, will use 'fprime-gds.yml'. Set to None to disable default
|
312
|
+
configuration.
|
313
|
+
"""
|
314
|
+
cls.DEFAULT_CONFIGURATION_PATH = path
|
315
|
+
|
316
|
+
@classmethod
|
317
|
+
def parse_args(
|
318
|
+
cls,
|
319
|
+
parser_classes,
|
320
|
+
description="No tool description provided",
|
321
|
+
arguments=None,
|
322
|
+
**kwargs,
|
323
|
+
):
|
324
|
+
""" Parse and post-process arguments using inputs and config
|
325
|
+
|
326
|
+
Parse the arguments in two stages: first parse the configuration data, ignoring unknown inputs, then parse the
|
327
|
+
full argument set with the supplied configuration to fill in additional options.
|
328
|
+
|
329
|
+
Args:
|
330
|
+
parser_classes: a list of ParserBase subclasses that will be used to
|
331
|
+
description: description passed ot the argument parser
|
332
|
+
arguments: arguments to process, None to use command line input
|
333
|
+
Returns: namespace with all parsed arguments from all provided ParserBase subclasses
|
334
|
+
"""
|
335
|
+
arguments = sys.argv[1:] if arguments is None else arguments
|
336
|
+
|
337
|
+
# Help should spill all the arguments, so delegate to the normal parsing flow including
|
338
|
+
# this and supplied parsers
|
339
|
+
if "-h" in arguments or "--help" in arguments:
|
340
|
+
parsers = [ConfigDrivenParser] + parser_classes
|
341
|
+
ParserBase.parse_args(parsers, description, arguments, **kwargs)
|
342
|
+
sys.exit(0)
|
343
|
+
|
344
|
+
# Custom flow involving parsing the arguments of this parser first, then passing the configured values
|
345
|
+
# as part of the argument source
|
346
|
+
ns_config, _, remaining = ParserBase.parse_known_args([ConfigDrivenParser], description, arguments, **kwargs)
|
347
|
+
config_options = ns_config.config_values.get("command-line-options", {})
|
348
|
+
config_args = cls.flatten_options(config_options)
|
349
|
+
# Argparse allows repeated (overridden) arguments, thus the CLI override is accomplished by providing
|
350
|
+
# remaining arguments after the configured ones
|
351
|
+
ns_full, parser = ParserBase.parse_args(parser_classes, description, config_args + remaining, **kwargs)
|
352
|
+
ns_final = argparse.Namespace(**vars(ns_config), **vars(ns_full))
|
353
|
+
return ns_final, parser
|
354
|
+
|
355
|
+
@staticmethod
|
356
|
+
def flatten_options(configured_options):
|
357
|
+
""" Flatten options down to arguments """
|
358
|
+
flattened = []
|
359
|
+
for option, value in configured_options.items():
|
360
|
+
flattened.append(f"--{option}")
|
361
|
+
if value is not None:
|
362
|
+
flattened.extend(value if isinstance(value, (list, tuple)) else [f"{value}"])
|
363
|
+
return flattened
|
364
|
+
|
365
|
+
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
366
|
+
"""Arguments needed for config processing"""
|
367
|
+
return {
|
368
|
+
("-c", "--config"): {
|
369
|
+
"dest": "config",
|
370
|
+
"required": False,
|
371
|
+
"default": self.DEFAULT_CONFIGURATION_PATH,
|
372
|
+
"type": Path,
|
373
|
+
"help": f"Argument configuration file path.",
|
374
|
+
}
|
375
|
+
}
|
376
|
+
|
377
|
+
def handle_arguments(self, args, **kwargs):
|
378
|
+
""" Handle the arguments
|
379
|
+
|
380
|
+
Loads the configuration file specified and fills in the `config_values` attribute of the namespace with the
|
381
|
+
loaded configuration dictionary.
|
382
|
+
"""
|
383
|
+
args.config_values = {}
|
384
|
+
# Specified but non-existent config file is a hard error
|
385
|
+
if ("-c" in sys.argv[1:] or "--config" in sys.argv[1:]) and not args.config.exists():
|
386
|
+
raise ValueError(f"Specified configuration file '{args.config}' does not exist")
|
387
|
+
# Read configuration if the file was set and exists
|
388
|
+
if args.config is not None and args.config.exists():
|
389
|
+
print(f"[INFO] Reading command-line configuration from: {args.config}")
|
390
|
+
with open(args.config, "r") as file_handle:
|
391
|
+
try:
|
392
|
+
loaded = yaml.safe_load(file_handle)
|
393
|
+
args.config_values = loaded if loaded is not None else {}
|
394
|
+
except Exception as exc:
|
395
|
+
raise ValueError(f"Malformed configuration {args.config}: {exc}", exc)
|
396
|
+
return args
|
397
|
+
|
398
|
+
|
208
399
|
class DetectionParser(ParserBase):
|
209
400
|
"""Parser that detects items from a root/directory or deployment"""
|
210
401
|
|
@@ -253,6 +444,133 @@ class DetectionParser(ParserBase):
|
|
253
444
|
return args
|
254
445
|
|
255
446
|
|
447
|
+
class BareArgumentParser(ParserBase):
|
448
|
+
"""Takes in the argument specification (used in plugins and get_arguments) to parse args
|
449
|
+
|
450
|
+
This parser takes in and uses a raw specification of arguments as seen in plugins and arguments to perform argument
|
451
|
+
parsing. The spec is a map of flag tuples to argparse kwargs.
|
452
|
+
|
453
|
+
Argument handling only validates using the checking_function which is a function taking in keyword arguments for
|
454
|
+
each cli argument specified. This function will be called as such: `checking_function(**args)`. Use None to skip
|
455
|
+
argument checking. checking_function should raise ValueError to indicate an error with an argument.
|
456
|
+
"""
|
457
|
+
|
458
|
+
def __init__(self, specification, checking_function=None):
|
459
|
+
"""Initialize this parser with the provided specification"""
|
460
|
+
self.specification = specification
|
461
|
+
self.checking_function = checking_function
|
462
|
+
|
463
|
+
def get_arguments(self):
|
464
|
+
"""Raw specification is returned immediately"""
|
465
|
+
return self.specification
|
466
|
+
|
467
|
+
def handle_arguments(self, args, **kwargs):
|
468
|
+
"""Handle argument calls checking function to validate"""
|
469
|
+
if self.checking_function is not None:
|
470
|
+
self.checking_function(**self.extract_arguments(args))
|
471
|
+
return args
|
472
|
+
|
473
|
+
def extract_arguments(self, args) -> Dict[str, Any]:
|
474
|
+
"""Extract argument values from the args namespace into a map matching the original specification
|
475
|
+
|
476
|
+
This function extracts arguments matching the original specification and returns them as a dictionary of key-
|
477
|
+
value pairs.
|
478
|
+
|
479
|
+
Return:
|
480
|
+
filled arguments dictionary
|
481
|
+
"""
|
482
|
+
expected_args = self.specification
|
483
|
+
argument_destinations = [
|
484
|
+
(
|
485
|
+
value["dest"]
|
486
|
+
if "dest" in value
|
487
|
+
else key[0].replace("--", "").replace("-", "_")
|
488
|
+
)
|
489
|
+
for key, value in expected_args.items()
|
490
|
+
]
|
491
|
+
filled_arguments = {
|
492
|
+
destination: getattr(args, destination)
|
493
|
+
for destination in argument_destinations
|
494
|
+
}
|
495
|
+
return filled_arguments
|
496
|
+
|
497
|
+
|
498
|
+
class IndividualPluginParser(BareArgumentParser):
|
499
|
+
"""Parser for an individual plugin's command line
|
500
|
+
|
501
|
+
A CLI parser for an individual plugin. This handles all the functions and arguments that apply to the parsing of a
|
502
|
+
single plugin's arguments. It also handles FEATURE plugin disable flags.
|
503
|
+
"""
|
504
|
+
|
505
|
+
def __init__(self, plugin_system: Plugins, plugin_class: type):
|
506
|
+
"""Initialize the plugin parser
|
507
|
+
|
508
|
+
Args:
|
509
|
+
plugin_system: Plugins object used to work with the plugin system
|
510
|
+
plugin_class: plugin class used for this specific parser
|
511
|
+
"""
|
512
|
+
# Add disable flags for feature type plugins
|
513
|
+
super().__init__(plugin_class.get_arguments(), plugin_class.check_arguments)
|
514
|
+
self.disable_flag_destination = (
|
515
|
+
f"disable-{plugin_class.get_name()}".lower().replace("-", "_")
|
516
|
+
)
|
517
|
+
self.plugin_class = plugin_class
|
518
|
+
self.plugin_system = plugin_system
|
519
|
+
|
520
|
+
def get_arguments(self):
|
521
|
+
"""Get the arguments for this plugin
|
522
|
+
|
523
|
+
The individual plugin parser will read the arguments from the supplied plugin class. Additionally, if the
|
524
|
+
plugin_class's plugin_type is FEATURE then this parser will add an disable flag to allow users to turn disable
|
525
|
+
the plugin feature.
|
526
|
+
"""
|
527
|
+
arguments = {}
|
528
|
+
if self.plugin_class.type == PluginType.FEATURE:
|
529
|
+
arguments.update(
|
530
|
+
{
|
531
|
+
(f"--disable-{self.plugin_class.get_name()}",): {
|
532
|
+
"action": "store_true",
|
533
|
+
"default": False,
|
534
|
+
"dest": self.disable_flag_destination,
|
535
|
+
"help": f"Disable the {self.plugin_class.category} plugin '{self.plugin_class.get_name()}'",
|
536
|
+
}
|
537
|
+
}
|
538
|
+
)
|
539
|
+
arguments.update(super().get_arguments())
|
540
|
+
return arguments
|
541
|
+
|
542
|
+
def handle_arguments(self, args, **kwargs):
|
543
|
+
"""Handle the given arguments for a plugin
|
544
|
+
|
545
|
+
This will process the arguments for a given plugin. Additionally, it will construct the plugin object and
|
546
|
+
supply the constructed object to the plugin system if the plugin is a selection or is enabled.
|
547
|
+
|
548
|
+
Args:
|
549
|
+
args: argparse namespace
|
550
|
+
"""
|
551
|
+
arguments = super().handle_arguments(
|
552
|
+
args, **kwargs
|
553
|
+
) # Perform argument checking first
|
554
|
+
if not getattr(args, self.disable_flag_destination, False):
|
555
|
+
# Remove the disable flag from the arguments
|
556
|
+
plugin_arguments = {
|
557
|
+
key: value
|
558
|
+
for key, value in self.extract_arguments(arguments).items()
|
559
|
+
if key != self.disable_flag_destination
|
560
|
+
}
|
561
|
+
plugin_zero_argument_class = functools.partial(
|
562
|
+
self.plugin_class.get_implementor(), **plugin_arguments
|
563
|
+
)
|
564
|
+
self.plugin_system.add_bound_class(
|
565
|
+
self.plugin_class.category, plugin_zero_argument_class
|
566
|
+
)
|
567
|
+
return arguments
|
568
|
+
|
569
|
+
def get_plugin_class(self):
|
570
|
+
"""Plugin class accessor"""
|
571
|
+
return self.plugin_class
|
572
|
+
|
573
|
+
|
256
574
|
class PluginArgumentParser(ParserBase):
|
257
575
|
"""Parser for arguments coming from plugins"""
|
258
576
|
|
@@ -262,62 +580,69 @@ class PluginArgumentParser(ParserBase):
|
|
262
580
|
"communication": "ip",
|
263
581
|
}
|
264
582
|
|
265
|
-
def __init__(self):
|
266
|
-
"""Initialize the plugin information for this parser
|
583
|
+
def __init__(self, plugin_system: Plugins = None):
|
584
|
+
"""Initialize the plugin information for this parser
|
585
|
+
|
586
|
+
This will initialize this plugin argument parser with the supplied plugin system. If not supplied this will use
|
587
|
+
the system plugin singleton, which is configured elsewhere.
|
588
|
+
"""
|
589
|
+
# Accept the supplied plugin system defaulting to the global singleton
|
590
|
+
self.plugin_system = plugin_system if plugin_system else Plugins.system()
|
267
591
|
self._plugin_map = {
|
268
|
-
category:
|
269
|
-
|
592
|
+
category: [
|
593
|
+
IndividualPluginParser(self.plugin_system, plugin)
|
594
|
+
for plugin in self.plugin_system.get_plugins(category)
|
595
|
+
]
|
596
|
+
for category in self.plugin_system.get_categories()
|
270
597
|
}
|
271
598
|
|
272
|
-
|
273
|
-
|
274
|
-
""" Add an argument allowing duplicates
|
599
|
+
def fill_parser(self, parser):
|
600
|
+
"""Fill supplied parser with grouped arguments
|
275
601
|
|
276
|
-
|
277
|
-
|
602
|
+
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
603
|
+
arguments based on the constituent parser that the argument comes from. Category specific arguments are also
|
604
|
+
added (i.e. SELECTION type selection arguments).
|
278
605
|
|
279
606
|
Args:
|
280
|
-
parser: parser
|
281
|
-
*flags: positional arguments passed to `add_argument`
|
282
|
-
**keywords: key word arguments passed to `add_argument`
|
607
|
+
parser: parser to fill
|
283
608
|
"""
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
609
|
+
for category, plugin_parsers in self._plugin_map.items():
|
610
|
+
# Add category specific flags (selection flags, etc)
|
611
|
+
argument_group = parser.add_argument_group(
|
612
|
+
title=f"{category.title()} Plugin Options"
|
613
|
+
)
|
614
|
+
self.add_arguments_from_specification(
|
615
|
+
argument_group, self.get_category_arguments(category)
|
616
|
+
)
|
289
617
|
|
290
|
-
|
291
|
-
|
618
|
+
# Handle the individual plugin parsers
|
619
|
+
for plugin_parser in plugin_parsers:
|
620
|
+
plugin = plugin_parser.get_plugin_class()
|
621
|
+
argument_group = parser.add_argument_group(
|
622
|
+
title=f"{category.title()} Plugin '{plugin.get_name()}' Options"
|
623
|
+
)
|
624
|
+
plugin_parser.fill_parser(argument_group)
|
292
625
|
|
293
|
-
|
294
|
-
|
626
|
+
def get_category_arguments(self, category):
|
627
|
+
"""Get category arguments for a given plugin category
|
628
|
+
|
629
|
+
This function will generate category arguments for the supplied category. These arguments will follow the
|
630
|
+
standard argument specification of a dictionary of flag tuples to argparse keyword arguments.
|
631
|
+
|
632
|
+
Currently category specific arguments are just selection flags for SELECTION type plugins.
|
295
633
|
|
296
634
|
Args:
|
297
|
-
|
635
|
+
category: category arguments
|
298
636
|
"""
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
for plugin in plugins:
|
305
|
-
argument_group = parser.add_argument_group(title=f"{category.title()} Plugin '{plugin.get_name()}' Options")
|
306
|
-
if plugin.type == PluginType.FEATURE:
|
307
|
-
self.safe_add_argument(argument_group,
|
308
|
-
f"--disable-{plugin.get_name()}",
|
309
|
-
action="store_true",
|
310
|
-
default=False,
|
311
|
-
help=f"Disable the {category} plugin '{plugin.get_name()}'")
|
312
|
-
for flags, keywords in plugin.get_arguments().items():
|
313
|
-
self.safe_add_argument(argument_group, *flags, **keywords)
|
637
|
+
plugin_type = self.plugin_system.get_category_plugin_type(category)
|
638
|
+
plugins = [
|
639
|
+
plugin_parser.get_plugin_class()
|
640
|
+
for plugin_parser in self._plugin_map[category]
|
641
|
+
]
|
314
642
|
|
315
|
-
def get_category_arguments(self, category):
|
316
|
-
""" Get arguments for a plugin category """
|
317
643
|
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
318
|
-
|
644
|
+
|
319
645
|
# Add category options: SELECTION plugins add a selection flag
|
320
|
-
plugin_type = Plugins.get_category_plugin_type(category)
|
321
646
|
if plugin_type == PluginType.SELECTION:
|
322
647
|
arguments.update(
|
323
648
|
{
|
@@ -333,81 +658,54 @@ class PluginArgumentParser(ParserBase):
|
|
333
658
|
return arguments
|
334
659
|
|
335
660
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
336
|
-
"""Return arguments to used in
|
661
|
+
"""Return arguments to used in plugin system
|
662
|
+
|
663
|
+
This will return the command line arguments all the plugins contained within the supplied plugin system. This
|
664
|
+
will recursively return plugins from all of the IndividualPluginParser objects composing this plugin argument
|
665
|
+
parser. Arguments are returned in the standard specification form of tuple of flags mapped to a dictionary of
|
666
|
+
argparse kwarg inputs.
|
667
|
+
"""
|
337
668
|
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
338
|
-
for category,
|
669
|
+
for category, plugin_parsers in self._plugin_map.items():
|
339
670
|
arguments.update(self.get_category_arguments(category))
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
(f"--disable-{plugin.get_name()}", ): {
|
345
|
-
"action": "store_true",
|
346
|
-
"default": False,
|
347
|
-
"help": f"Disable the {category} plugin '{plugin.get_name()}'"
|
348
|
-
}
|
349
|
-
})
|
350
|
-
arguments.update(plugin.get_arguments())
|
671
|
+
[
|
672
|
+
arguments.update(plugin_parser.get_arguments())
|
673
|
+
for plugin_parser in plugin_parsers
|
674
|
+
]
|
351
675
|
return arguments
|
352
676
|
|
353
677
|
def handle_arguments(self, args, **kwargs):
|
354
|
-
"""
|
355
|
-
for category, plugins in self._plugin_map.items():
|
356
|
-
plugin_type = Plugins.get_category_plugin_type(category)
|
678
|
+
"""Handle the plugin arguments
|
357
679
|
|
680
|
+
This will handle the plugin arguments delegating each to the IndividualPluginParser. For SELECTION plugins this
|
681
|
+
will bind a single instance of the selected plugin to its arguments. For FEATURE plugins it will bind arguments
|
682
|
+
to every enabled plugin. Bound plugins are registered with the plugin system.
|
683
|
+
"""
|
684
|
+
for category, plugin_parsers in self._plugin_map.items():
|
685
|
+
plugin_type = self.plugin_system.get_category_plugin_type(category)
|
686
|
+
self.plugin_system.start_loading(category)
|
358
687
|
# Selection plugins choose one plugin and instantiate it
|
359
688
|
if plugin_type == PluginType.SELECTION:
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
689
|
+
try:
|
690
|
+
self.plugin_system.get_selected_class(category)
|
691
|
+
except PluginsNotLoadedException:
|
692
|
+
selection_string = getattr(args, f"{category}_selection")
|
693
|
+
matching_plugin_parsers = [
|
694
|
+
plugin_parser
|
695
|
+
for plugin_parser in plugin_parsers
|
696
|
+
if plugin_parser.get_plugin_class().get_name()
|
697
|
+
== selection_string
|
698
|
+
]
|
699
|
+
assert (
|
700
|
+
len(matching_plugin_parsers) == 1
|
701
|
+
), "Plugin selection system failed"
|
702
|
+
args = matching_plugin_parsers[0].handle_arguments(args, **kwargs)
|
367
703
|
# Feature plugins instantiate all enabled plugins
|
368
704
|
elif plugin_type == PluginType.FEATURE:
|
369
|
-
|
370
|
-
|
371
|
-
if not getattr(args, f"disable_{plugin.get_name().replace('-', '_')}", False)
|
372
|
-
]
|
373
|
-
plugin_instantiations = [
|
374
|
-
plugin.plugin_class(**self.extract_plugin_arguments(args, plugin))
|
375
|
-
for plugin in enabled_plugins
|
376
|
-
]
|
377
|
-
setattr(args, f"{category}_enabled_instances", plugin_instantiations)
|
705
|
+
for plugin_parser in plugin_parsers:
|
706
|
+
args = plugin_parser.handle_arguments(args, **kwargs)
|
378
707
|
return args
|
379
708
|
|
380
|
-
@staticmethod
|
381
|
-
def extract_plugin_arguments(args, plugin) -> Dict[str, Any]:
|
382
|
-
"""Extract plugin argument values from the args namespace into a map
|
383
|
-
|
384
|
-
Plugin arguments will be supplied to the `__init__` function of the plugin via a keyword argument dictionary.
|
385
|
-
This function maps from the argument namespace from parsing back into that dictionary.
|
386
|
-
|
387
|
-
Args:
|
388
|
-
args: argument namespace from argparse
|
389
|
-
plugin: plugin to extract arguments for
|
390
|
-
Return:
|
391
|
-
filled arguments dictionary
|
392
|
-
"""
|
393
|
-
expected_args = plugin.get_arguments()
|
394
|
-
argument_destinations = [
|
395
|
-
(
|
396
|
-
value["dest"]
|
397
|
-
if "dest" in value
|
398
|
-
else key[0].replace("--", "").replace("-", "_")
|
399
|
-
)
|
400
|
-
for key, value in expected_args.items()
|
401
|
-
]
|
402
|
-
filled_arguments = {
|
403
|
-
destination: getattr(args, destination)
|
404
|
-
for destination in argument_destinations
|
405
|
-
}
|
406
|
-
# Check arguments or yield a Value error
|
407
|
-
if hasattr(plugin, "check_arguments"):
|
408
|
-
plugin.check_arguments(**filled_arguments)
|
409
|
-
return filled_arguments
|
410
|
-
|
411
709
|
|
412
710
|
class CompositeParser(ParserBase):
|
413
711
|
"""Composite parser handles parsing as a composition of multiple other parsers"""
|
@@ -415,7 +713,15 @@ class CompositeParser(ParserBase):
|
|
415
713
|
def __init__(self, constituents, description=None):
|
416
714
|
"""Construct this parser by instantiating the sub-parsers"""
|
417
715
|
self.given = description
|
418
|
-
constructed = [
|
716
|
+
constructed = [
|
717
|
+
constituent() if callable(constituent) else constituent
|
718
|
+
for constituent in constituents
|
719
|
+
]
|
720
|
+
# Check to ensure everything passed in became a ParserBase after construction
|
721
|
+
for i, construct in enumerate(constructed):
|
722
|
+
assert isinstance(
|
723
|
+
construct, ParserBase
|
724
|
+
), f"{construct.__class__.__name__} ({i}) not a ParserBase child"
|
419
725
|
flattened = [
|
420
726
|
item.constituents if isinstance(item, CompositeParser) else [item]
|
421
727
|
for item in constructed
|
@@ -423,7 +729,7 @@ class CompositeParser(ParserBase):
|
|
423
729
|
self.constituent_parsers = {*itertools.chain.from_iterable(flattened)}
|
424
730
|
|
425
731
|
def fill_parser(self, parser):
|
426
|
-
"""
|
732
|
+
"""File supplied parser with grouped arguments
|
427
733
|
|
428
734
|
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
429
735
|
arguments based on the constituent that sources the argument.
|
@@ -435,7 +741,9 @@ class CompositeParser(ParserBase):
|
|
435
741
|
if isinstance(constituent, (PluginArgumentParser, CompositeParser)):
|
436
742
|
constituent.fill_parser(parser)
|
437
743
|
else:
|
438
|
-
argument_group = parser.add_argument_group(
|
744
|
+
argument_group = parser.add_argument_group(
|
745
|
+
title=constituent.description
|
746
|
+
)
|
439
747
|
constituent.fill_parser(argument_group)
|
440
748
|
|
441
749
|
@property
|
@@ -649,7 +957,15 @@ class DictionaryParser(DetectionParser):
|
|
649
957
|
"default": None,
|
650
958
|
"required": False,
|
651
959
|
"type": str,
|
652
|
-
"help": "Path to packet specification.",
|
960
|
+
"help": "Path to packet XML specification (should not be used if JSON packet definitions are used).",
|
961
|
+
},
|
962
|
+
("--packet-set-name",): {
|
963
|
+
"dest": "packet_set_name",
|
964
|
+
"action": "store",
|
965
|
+
"default": None,
|
966
|
+
"required": False,
|
967
|
+
"type": str,
|
968
|
+
"help": "Name of packet set defined in the JSON dictionary.",
|
653
969
|
},
|
654
970
|
},
|
655
971
|
}
|
@@ -730,6 +1046,7 @@ class StandardPipelineParser(CompositeParser):
|
|
730
1046
|
"dictionary": args_ns.dictionary,
|
731
1047
|
"file_store": args_ns.files_storage_directory,
|
732
1048
|
"packet_spec": args_ns.packet_spec,
|
1049
|
+
"packet_set_name": args_ns.packet_set_name,
|
733
1050
|
"logging_prefix": args_ns.logs,
|
734
1051
|
}
|
735
1052
|
pipeline = pipeline if pipeline else StandardPipeline()
|
@@ -753,13 +1070,16 @@ class CommParser(CompositeParser):
|
|
753
1070
|
CommExtraParser,
|
754
1071
|
MiddleWareParser,
|
755
1072
|
LogDeployParser,
|
756
|
-
PluginArgumentParser,
|
757
1073
|
]
|
758
1074
|
|
759
1075
|
def __init__(self):
|
760
1076
|
"""Initialization"""
|
1077
|
+
# Added here to ensure the call to Plugins does not interfere with the full plugin system
|
1078
|
+
comm_plugin_parser_instance = PluginArgumentParser(
|
1079
|
+
Plugins(["communication", "framing"])
|
1080
|
+
)
|
761
1081
|
super().__init__(
|
762
|
-
constituents=self.CONSTITUENTS,
|
1082
|
+
constituents=self.CONSTITUENTS + [comm_plugin_parser_instance],
|
763
1083
|
description="Communications bridge application",
|
764
1084
|
)
|
765
1085
|
|
@@ -867,9 +1187,7 @@ class BinaryDeployment(DetectionParser):
|
|
867
1187
|
class SearchArgumentsParser(ParserBase):
|
868
1188
|
"""Parser for search arguments"""
|
869
1189
|
|
870
|
-
DESCRIPTION =
|
871
|
-
"Searching and filtering options"
|
872
|
-
)
|
1190
|
+
DESCRIPTION = "Searching and filtering options"
|
873
1191
|
|
874
1192
|
def __init__(self, command_name: str) -> None:
|
875
1193
|
self.command_name = command_name
|