fprime-gds 3.6.2a1__py3-none-any.whl → 4.0.0a1__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/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 +280 -122
- fprime_gds/executables/comm.py +5 -2
- fprime_gds/executables/fprime_cli.py +3 -3
- fprime_gds/executables/run_deployment.py +10 -3
- 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.0a1.dist-info}/METADATA +17 -19
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/RECORD +43 -34
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/entry_points.txt +2 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0a1.dist-info}/top_level.txt +0 -0
fprime_gds/executables/cli.py
CHANGED
@@ -11,7 +11,9 @@ 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
|
@@ -30,7 +32,7 @@ from fprime_gds.common.transport import ThreadedTCPSocketClient
|
|
30
32
|
from fprime_gds.common.utils.config_manager import ConfigManager
|
31
33
|
from fprime_gds.executables.utils import find_app, find_dict, get_artifacts_root
|
32
34
|
from fprime_gds.plugin.definitions import PluginType
|
33
|
-
from fprime_gds.plugin.system import Plugins
|
35
|
+
from fprime_gds.plugin.system import Plugins, PluginsNotLoadedException
|
34
36
|
from fprime_gds.common.zmq_transport import ZmqClient
|
35
37
|
|
36
38
|
|
@@ -81,22 +83,51 @@ class ParserBase(ABC):
|
|
81
83
|
self.fill_parser(parser)
|
82
84
|
return parser
|
83
85
|
|
86
|
+
@staticmethod
|
87
|
+
def safe_add_argument(parser, *flags, **keywords):
|
88
|
+
"""Add an argument allowing duplicates
|
89
|
+
|
90
|
+
Add arguments to the parser (passes through *flags and **keywords) to the supplied parser. This method traps
|
91
|
+
errors to prevent duplicates from crashing the system when two plugins use the same flags.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
parser: parser or argument group to add arguments to
|
95
|
+
*flags: positional arguments passed to `add_argument`
|
96
|
+
**keywords: key word arguments passed to `add_argument`
|
97
|
+
"""
|
98
|
+
try:
|
99
|
+
parser.add_argument(*flags, **keywords)
|
100
|
+
except argparse.ArgumentError:
|
101
|
+
# flag has already been added, pass
|
102
|
+
pass
|
103
|
+
|
104
|
+
@classmethod
|
105
|
+
def add_arguments_from_specification(cls, parser, arguments):
|
106
|
+
"""Safely add arguments to parser
|
107
|
+
|
108
|
+
In parsers and plugins, arguments are represented as a map of flag tuples to argparse keyword arguments. This
|
109
|
+
function will add arguments of that representation supplied as `arguments` to the supplied parser in a safe
|
110
|
+
collision-avoidant manner.
|
111
|
+
|
112
|
+
Args:
|
113
|
+
parser: argparse Parser or ArgumentGroup, or anything with an `add_argument` function
|
114
|
+
arguments: arguments specification
|
115
|
+
|
116
|
+
"""
|
117
|
+
for flags, keywords in arguments.items():
|
118
|
+
cls.safe_add_argument(parser, *flags, **keywords)
|
119
|
+
|
84
120
|
def fill_parser(self, parser):
|
85
|
-
"""
|
121
|
+
"""Fill supplied parser with arguments
|
86
122
|
|
87
123
|
Fills the supplied parser with the arguments returned via the `get_arguments` method invocation. This
|
88
|
-
implementation
|
124
|
+
implementation adds the arguments directly to the parser.
|
89
125
|
|
90
126
|
Args:
|
91
127
|
parser: parser to fill with arguments
|
92
128
|
|
93
129
|
"""
|
94
|
-
|
95
|
-
try:
|
96
|
-
parser.add_argument(*flags, **keywords)
|
97
|
-
except argparse.ArgumentError:
|
98
|
-
# flag has already been added, pass
|
99
|
-
pass
|
130
|
+
self.add_arguments_from_specification(parser, self.get_arguments())
|
100
131
|
|
101
132
|
def reproduce_cli_args(self, args_ns):
|
102
133
|
"""Reproduce the list of arguments needed on the command line"""
|
@@ -253,6 +284,133 @@ class DetectionParser(ParserBase):
|
|
253
284
|
return args
|
254
285
|
|
255
286
|
|
287
|
+
class BareArgumentParser(ParserBase):
|
288
|
+
"""Takes in the argument specification (used in plugins and get_arguments) to parse args
|
289
|
+
|
290
|
+
This parser takes in and uses a raw specification of arguments as seen in plugins and arguments to perform argument
|
291
|
+
parsing. The spec is a map of flag tuples to argparse kwargs.
|
292
|
+
|
293
|
+
Argument handling only validates using the checking_function which is a function taking in keyword arguments for
|
294
|
+
each cli argument specified. This function will be called as such: `checking_function(**args)`. Use None to skip
|
295
|
+
argument checking. checking_function should raise ValueError to indicate an error with an argument.
|
296
|
+
"""
|
297
|
+
|
298
|
+
def __init__(self, specification, checking_function=None):
|
299
|
+
"""Initialize this parser with the provided specification"""
|
300
|
+
self.specification = specification
|
301
|
+
self.checking_function = checking_function
|
302
|
+
|
303
|
+
def get_arguments(self):
|
304
|
+
"""Raw specification is returned immediately"""
|
305
|
+
return self.specification
|
306
|
+
|
307
|
+
def handle_arguments(self, args, **kwargs):
|
308
|
+
"""Handle argument calls checking function to validate"""
|
309
|
+
if self.checking_function is not None:
|
310
|
+
self.checking_function(**self.extract_arguments(args))
|
311
|
+
return args
|
312
|
+
|
313
|
+
def extract_arguments(self, args) -> Dict[str, Any]:
|
314
|
+
"""Extract argument values from the args namespace into a map matching the original specification
|
315
|
+
|
316
|
+
This function extracts arguments matching the original specification and returns them as a dictionary of key-
|
317
|
+
value pairs.
|
318
|
+
|
319
|
+
Return:
|
320
|
+
filled arguments dictionary
|
321
|
+
"""
|
322
|
+
expected_args = self.specification
|
323
|
+
argument_destinations = [
|
324
|
+
(
|
325
|
+
value["dest"]
|
326
|
+
if "dest" in value
|
327
|
+
else key[0].replace("--", "").replace("-", "_")
|
328
|
+
)
|
329
|
+
for key, value in expected_args.items()
|
330
|
+
]
|
331
|
+
filled_arguments = {
|
332
|
+
destination: getattr(args, destination)
|
333
|
+
for destination in argument_destinations
|
334
|
+
}
|
335
|
+
return filled_arguments
|
336
|
+
|
337
|
+
|
338
|
+
class IndividualPluginParser(BareArgumentParser):
|
339
|
+
"""Parser for an individual plugin's command line
|
340
|
+
|
341
|
+
A CLI parser for an individual plugin. This handles all the functions and arguments that apply to the parsing of a
|
342
|
+
single plugin's arguments. It also handles FEATURE plugin disable flags.
|
343
|
+
"""
|
344
|
+
|
345
|
+
def __init__(self, plugin_system: Plugins, plugin_class: type):
|
346
|
+
"""Initialize the plugin parser
|
347
|
+
|
348
|
+
Args:
|
349
|
+
plugin_system: Plugins object used to work with the plugin system
|
350
|
+
plugin_class: plugin class used for this specific parser
|
351
|
+
"""
|
352
|
+
# Add disable flags for feature type plugins
|
353
|
+
super().__init__(plugin_class.get_arguments(), plugin_class.check_arguments)
|
354
|
+
self.disable_flag_destination = (
|
355
|
+
f"disable-{plugin_class.get_name()}".lower().replace("-", "_")
|
356
|
+
)
|
357
|
+
self.plugin_class = plugin_class
|
358
|
+
self.plugin_system = plugin_system
|
359
|
+
|
360
|
+
def get_arguments(self):
|
361
|
+
"""Get the arguments for this plugin
|
362
|
+
|
363
|
+
The individual plugin parser will read the arguments from the supplied plugin class. Additionally, if the
|
364
|
+
plugin_class's plugin_type is FEATURE then this parser will add an disable flag to allow users to turn disable
|
365
|
+
the plugin feature.
|
366
|
+
"""
|
367
|
+
arguments = {}
|
368
|
+
if self.plugin_class.type == PluginType.FEATURE:
|
369
|
+
arguments.update(
|
370
|
+
{
|
371
|
+
(f"--disable-{self.plugin_class.get_name()}",): {
|
372
|
+
"action": "store_true",
|
373
|
+
"default": False,
|
374
|
+
"dest": self.disable_flag_destination,
|
375
|
+
"help": f"Disable the {self.plugin_class.category} plugin '{self.plugin_class.get_name()}'",
|
376
|
+
}
|
377
|
+
}
|
378
|
+
)
|
379
|
+
arguments.update(super().get_arguments())
|
380
|
+
return arguments
|
381
|
+
|
382
|
+
def handle_arguments(self, args, **kwargs):
|
383
|
+
"""Handle the given arguments for a plugin
|
384
|
+
|
385
|
+
This will process the arguments for a given plugin. Additionally, it will construct the plugin object and
|
386
|
+
supply the constructed object to the plugin system if the plugin is a selection or is enabled.
|
387
|
+
|
388
|
+
Args:
|
389
|
+
args: argparse namespace
|
390
|
+
"""
|
391
|
+
arguments = super().handle_arguments(
|
392
|
+
args, **kwargs
|
393
|
+
) # Perform argument checking first
|
394
|
+
if not getattr(args, self.disable_flag_destination, False):
|
395
|
+
# Remove the disable flag from the arguments
|
396
|
+
plugin_arguments = {
|
397
|
+
key: value
|
398
|
+
for key, value in self.extract_arguments(arguments).items()
|
399
|
+
if key != self.disable_flag_destination
|
400
|
+
}
|
401
|
+
plugin_zero_argument_class = functools.partial(
|
402
|
+
self.plugin_class.get_implementor(), **plugin_arguments
|
403
|
+
)
|
404
|
+
self.plugin_system.add_bound_class(
|
405
|
+
self.plugin_class.category, plugin_zero_argument_class
|
406
|
+
)
|
407
|
+
return arguments
|
408
|
+
|
409
|
+
def get_plugin_class(self):
|
410
|
+
"""Plugin class accessor"""
|
411
|
+
return self.plugin_class
|
412
|
+
|
413
|
+
|
256
414
|
class PluginArgumentParser(ParserBase):
|
257
415
|
"""Parser for arguments coming from plugins"""
|
258
416
|
|
@@ -262,62 +420,69 @@ class PluginArgumentParser(ParserBase):
|
|
262
420
|
"communication": "ip",
|
263
421
|
}
|
264
422
|
|
265
|
-
def __init__(self):
|
266
|
-
"""Initialize the plugin information for this parser
|
423
|
+
def __init__(self, plugin_system: Plugins = None):
|
424
|
+
"""Initialize the plugin information for this parser
|
425
|
+
|
426
|
+
This will initialize this plugin argument parser with the supplied plugin system. If not supplied this will use
|
427
|
+
the system plugin singleton, which is configured elsewhere.
|
428
|
+
"""
|
429
|
+
# Accept the supplied plugin system defaulting to the global singleton
|
430
|
+
self.plugin_system = plugin_system if plugin_system else Plugins.system()
|
267
431
|
self._plugin_map = {
|
268
|
-
category:
|
269
|
-
|
432
|
+
category: [
|
433
|
+
IndividualPluginParser(self.plugin_system, plugin)
|
434
|
+
for plugin in self.plugin_system.get_plugins(category)
|
435
|
+
]
|
436
|
+
for category in self.plugin_system.get_categories()
|
270
437
|
}
|
271
438
|
|
272
|
-
|
273
|
-
|
274
|
-
""" Add an argument allowing duplicates
|
439
|
+
def fill_parser(self, parser):
|
440
|
+
"""Fill supplied parser with grouped arguments
|
275
441
|
|
276
|
-
|
277
|
-
|
442
|
+
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
443
|
+
arguments based on the constituent parser that the argument comes from. Category specific arguments are also
|
444
|
+
added (i.e. SELECTION type selection arguments).
|
278
445
|
|
279
446
|
Args:
|
280
|
-
parser: parser
|
281
|
-
*flags: positional arguments passed to `add_argument`
|
282
|
-
**keywords: key word arguments passed to `add_argument`
|
447
|
+
parser: parser to fill
|
283
448
|
"""
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
449
|
+
for category, plugin_parsers in self._plugin_map.items():
|
450
|
+
# Add category specific flags (selection flags, etc)
|
451
|
+
argument_group = parser.add_argument_group(
|
452
|
+
title=f"{category.title()} Plugin Options"
|
453
|
+
)
|
454
|
+
self.add_arguments_from_specification(
|
455
|
+
argument_group, self.get_category_arguments(category)
|
456
|
+
)
|
289
457
|
|
290
|
-
|
291
|
-
|
458
|
+
# Handle the individual plugin parsers
|
459
|
+
for plugin_parser in plugin_parsers:
|
460
|
+
plugin = plugin_parser.get_plugin_class()
|
461
|
+
argument_group = parser.add_argument_group(
|
462
|
+
title=f"{category.title()} Plugin '{plugin.get_name()}' Options"
|
463
|
+
)
|
464
|
+
plugin_parser.fill_parser(argument_group)
|
292
465
|
|
293
|
-
|
294
|
-
|
466
|
+
def get_category_arguments(self, category):
|
467
|
+
"""Get category arguments for a given plugin category
|
468
|
+
|
469
|
+
This function will generate category arguments for the supplied category. These arguments will follow the
|
470
|
+
standard argument specification of a dictionary of flag tuples to argparse keyword arguments.
|
471
|
+
|
472
|
+
Currently category specific arguments are just selection flags for SELECTION type plugins.
|
295
473
|
|
296
474
|
Args:
|
297
|
-
|
475
|
+
category: category arguments
|
298
476
|
"""
|
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)
|
477
|
+
plugin_type = self.plugin_system.get_category_plugin_type(category)
|
478
|
+
plugins = [
|
479
|
+
plugin_parser.get_plugin_class()
|
480
|
+
for plugin_parser in self._plugin_map[category]
|
481
|
+
]
|
314
482
|
|
315
|
-
def get_category_arguments(self, category):
|
316
|
-
""" Get arguments for a plugin category """
|
317
483
|
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
318
|
-
|
484
|
+
|
319
485
|
# Add category options: SELECTION plugins add a selection flag
|
320
|
-
plugin_type = Plugins.get_category_plugin_type(category)
|
321
486
|
if plugin_type == PluginType.SELECTION:
|
322
487
|
arguments.update(
|
323
488
|
{
|
@@ -333,81 +498,54 @@ class PluginArgumentParser(ParserBase):
|
|
333
498
|
return arguments
|
334
499
|
|
335
500
|
def get_arguments(self) -> Dict[Tuple[str, ...], Dict[str, Any]]:
|
336
|
-
"""Return arguments to used in
|
501
|
+
"""Return arguments to used in plugin system
|
502
|
+
|
503
|
+
This will return the command line arguments all the plugins contained within the supplied plugin system. This
|
504
|
+
will recursively return plugins from all of the IndividualPluginParser objects composing this plugin argument
|
505
|
+
parser. Arguments are returned in the standard specification form of tuple of flags mapped to a dictionary of
|
506
|
+
argparse kwarg inputs.
|
507
|
+
"""
|
337
508
|
arguments: Dict[Tuple[str, ...], Dict[str, Any]] = {}
|
338
|
-
for category,
|
509
|
+
for category, plugin_parsers in self._plugin_map.items():
|
339
510
|
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())
|
511
|
+
[
|
512
|
+
arguments.update(plugin_parser.get_arguments())
|
513
|
+
for plugin_parser in plugin_parsers
|
514
|
+
]
|
351
515
|
return arguments
|
352
516
|
|
353
517
|
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)
|
518
|
+
"""Handle the plugin arguments
|
357
519
|
|
520
|
+
This will handle the plugin arguments delegating each to the IndividualPluginParser. For SELECTION plugins this
|
521
|
+
will bind a single instance of the selected plugin to its arguments. For FEATURE plugins it will bind arguments
|
522
|
+
to every enabled plugin. Bound plugins are registered with the plugin system.
|
523
|
+
"""
|
524
|
+
for category, plugin_parsers in self._plugin_map.items():
|
525
|
+
plugin_type = self.plugin_system.get_category_plugin_type(category)
|
526
|
+
self.plugin_system.start_loading(category)
|
358
527
|
# Selection plugins choose one plugin and instantiate it
|
359
528
|
if plugin_type == PluginType.SELECTION:
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
529
|
+
try:
|
530
|
+
self.plugin_system.get_selected_class(category)
|
531
|
+
except PluginsNotLoadedException:
|
532
|
+
selection_string = getattr(args, f"{category}_selection")
|
533
|
+
matching_plugin_parsers = [
|
534
|
+
plugin_parser
|
535
|
+
for plugin_parser in plugin_parsers
|
536
|
+
if plugin_parser.get_plugin_class().get_name()
|
537
|
+
== selection_string
|
538
|
+
]
|
539
|
+
assert (
|
540
|
+
len(matching_plugin_parsers) == 1
|
541
|
+
), "Plugin selection system failed"
|
542
|
+
args = matching_plugin_parsers[0].handle_arguments(args, **kwargs)
|
367
543
|
# Feature plugins instantiate all enabled plugins
|
368
544
|
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)
|
545
|
+
for plugin_parser in plugin_parsers:
|
546
|
+
args = plugin_parser.handle_arguments(args, **kwargs)
|
378
547
|
return args
|
379
548
|
|
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
549
|
|
412
550
|
class CompositeParser(ParserBase):
|
413
551
|
"""Composite parser handles parsing as a composition of multiple other parsers"""
|
@@ -415,7 +553,15 @@ class CompositeParser(ParserBase):
|
|
415
553
|
def __init__(self, constituents, description=None):
|
416
554
|
"""Construct this parser by instantiating the sub-parsers"""
|
417
555
|
self.given = description
|
418
|
-
constructed = [
|
556
|
+
constructed = [
|
557
|
+
constituent() if callable(constituent) else constituent
|
558
|
+
for constituent in constituents
|
559
|
+
]
|
560
|
+
# Check to ensure everything passed in became a ParserBase after construction
|
561
|
+
for i, construct in enumerate(constructed):
|
562
|
+
assert isinstance(
|
563
|
+
construct, ParserBase
|
564
|
+
), f"{construct.__class__.__name__} ({i}) not a ParserBase child"
|
419
565
|
flattened = [
|
420
566
|
item.constituents if isinstance(item, CompositeParser) else [item]
|
421
567
|
for item in constructed
|
@@ -423,7 +569,7 @@ class CompositeParser(ParserBase):
|
|
423
569
|
self.constituent_parsers = {*itertools.chain.from_iterable(flattened)}
|
424
570
|
|
425
571
|
def fill_parser(self, parser):
|
426
|
-
"""
|
572
|
+
"""File supplied parser with grouped arguments
|
427
573
|
|
428
574
|
Fill the supplied parser with arguments from the `get_arguments` method invocation. This implementation groups
|
429
575
|
arguments based on the constituent that sources the argument.
|
@@ -435,7 +581,9 @@ class CompositeParser(ParserBase):
|
|
435
581
|
if isinstance(constituent, (PluginArgumentParser, CompositeParser)):
|
436
582
|
constituent.fill_parser(parser)
|
437
583
|
else:
|
438
|
-
argument_group = parser.add_argument_group(
|
584
|
+
argument_group = parser.add_argument_group(
|
585
|
+
title=constituent.description
|
586
|
+
)
|
439
587
|
constituent.fill_parser(argument_group)
|
440
588
|
|
441
589
|
@property
|
@@ -649,7 +797,15 @@ class DictionaryParser(DetectionParser):
|
|
649
797
|
"default": None,
|
650
798
|
"required": False,
|
651
799
|
"type": str,
|
652
|
-
"help": "Path to packet specification.",
|
800
|
+
"help": "Path to packet XML specification (should not be used if JSON packet definitions are used).",
|
801
|
+
},
|
802
|
+
("--packet-set-name",): {
|
803
|
+
"dest": "packet_set_name",
|
804
|
+
"action": "store",
|
805
|
+
"default": None,
|
806
|
+
"required": False,
|
807
|
+
"type": str,
|
808
|
+
"help": "Name of packet set defined in the JSON dictionary.",
|
653
809
|
},
|
654
810
|
},
|
655
811
|
}
|
@@ -730,6 +886,7 @@ class StandardPipelineParser(CompositeParser):
|
|
730
886
|
"dictionary": args_ns.dictionary,
|
731
887
|
"file_store": args_ns.files_storage_directory,
|
732
888
|
"packet_spec": args_ns.packet_spec,
|
889
|
+
"packet_set_name": args_ns.packet_set_name,
|
733
890
|
"logging_prefix": args_ns.logs,
|
734
891
|
}
|
735
892
|
pipeline = pipeline if pipeline else StandardPipeline()
|
@@ -753,13 +910,16 @@ class CommParser(CompositeParser):
|
|
753
910
|
CommExtraParser,
|
754
911
|
MiddleWareParser,
|
755
912
|
LogDeployParser,
|
756
|
-
PluginArgumentParser,
|
757
913
|
]
|
758
914
|
|
759
915
|
def __init__(self):
|
760
916
|
"""Initialization"""
|
917
|
+
# Added here to ensure the call to Plugins does not interfere with the full plugin system
|
918
|
+
comm_plugin_parser_instance = PluginArgumentParser(
|
919
|
+
Plugins(["communication", "framing"])
|
920
|
+
)
|
761
921
|
super().__init__(
|
762
|
-
constituents=self.CONSTITUENTS,
|
922
|
+
constituents=self.CONSTITUENTS + [comm_plugin_parser_instance],
|
763
923
|
description="Communications bridge application",
|
764
924
|
)
|
765
925
|
|
@@ -867,9 +1027,7 @@ class BinaryDeployment(DetectionParser):
|
|
867
1027
|
class SearchArgumentsParser(ParserBase):
|
868
1028
|
"""Parser for search arguments"""
|
869
1029
|
|
870
|
-
DESCRIPTION =
|
871
|
-
"Searching and filtering options"
|
872
|
-
)
|
1030
|
+
DESCRIPTION = "Searching and filtering options"
|
873
1031
|
|
874
1032
|
def __init__(self, command_name: str) -> None:
|
875
1033
|
self.command_name = command_name
|
fprime_gds/executables/comm.py
CHANGED
@@ -29,6 +29,7 @@ import fprime_gds.common.logger
|
|
29
29
|
import fprime_gds.executables.cli
|
30
30
|
from fprime_gds.common.communication.updown import Downlinker, Uplinker
|
31
31
|
from fprime_gds.common.zmq_transport import ZmqGround
|
32
|
+
from fprime_gds.plugin.system import Plugins
|
32
33
|
|
33
34
|
# Uses non-standard PIP package pyserial, so test the waters before getting a hard-import crash
|
34
35
|
try:
|
@@ -46,6 +47,8 @@ def main():
|
|
46
47
|
|
47
48
|
:return: return code
|
48
49
|
"""
|
50
|
+
# comm.py supports 2 and only 2 plugin categories
|
51
|
+
Plugins.system(["communication", "framing"])
|
49
52
|
args, _ = fprime_gds.executables.cli.ParserBase.parse_args(
|
50
53
|
[
|
51
54
|
fprime_gds.executables.cli.LogDeployParser,
|
@@ -71,11 +74,11 @@ def main():
|
|
71
74
|
args.tts_addr, args.tts_port
|
72
75
|
)
|
73
76
|
|
74
|
-
adapter =
|
77
|
+
adapter = Plugins.system().get_selected_class("communication")()
|
75
78
|
|
76
79
|
# Set the framing class used and pass it to the uplink and downlink component constructions giving each a separate
|
77
80
|
# instantiation
|
78
|
-
framer_instance =
|
81
|
+
framer_instance = Plugins.system().get_selected_class("framing")()
|
79
82
|
LOGGER.info(
|
80
83
|
"Starting uplinker/downlinker connecting to FSW using %s with %s",
|
81
84
|
args.communication_selection,
|
@@ -14,7 +14,7 @@ from copy import deepcopy
|
|
14
14
|
from typing import Callable, List, Union
|
15
15
|
|
16
16
|
import argcomplete
|
17
|
-
import
|
17
|
+
import importlib.metadata
|
18
18
|
|
19
19
|
# NOTE: These modules are now only lazily loaded below as needed, due to slow
|
20
20
|
# performance when importing them
|
@@ -230,7 +230,7 @@ class CommandSubparserInjector(CliSubparserInjectorBase):
|
|
230
230
|
from fprime_gds.common.pipeline.dictionaries import Dictionaries
|
231
231
|
|
232
232
|
dictionary = Dictionaries()
|
233
|
-
dictionary.load_dictionaries(dict_path, None)
|
233
|
+
dictionary.load_dictionaries(dict_path, None, None)
|
234
234
|
command_names = dictionary.command_name.keys()
|
235
235
|
return [name for name in command_names if name.startswith(prefix)]
|
236
236
|
|
@@ -322,7 +322,7 @@ def create_parser():
|
|
322
322
|
parser = argparse.ArgumentParser(
|
323
323
|
description="provides utilities for interacting with the F' Ground Data System (GDS)"
|
324
324
|
)
|
325
|
-
fprime_gds_version =
|
325
|
+
fprime_gds_version = importlib.metadata.version("fprime-gds")
|
326
326
|
parser.add_argument("-V", "--version", action="version", version=fprime_gds_version)
|
327
327
|
|
328
328
|
# Add subcommands to the parser
|
@@ -16,6 +16,7 @@ from fprime_gds.executables.cli import (
|
|
16
16
|
PluginArgumentParser,
|
17
17
|
)
|
18
18
|
from fprime_gds.executables.utils import AppWrapperException, run_wrapped_application
|
19
|
+
from fprime_gds.plugin.system import Plugins
|
19
20
|
|
20
21
|
BASE_MODULE_ARGUMENTS = [sys.executable, "-u", "-m"]
|
21
22
|
|
@@ -174,7 +175,7 @@ def launch_comm(parsed_args):
|
|
174
175
|
|
175
176
|
|
176
177
|
def launch_plugin(plugin_class_instance):
|
177
|
-
"""
|
178
|
+
"""Launch a plugin instance"""
|
178
179
|
plugin_name = getattr(plugin_class_instance, "get_name", lambda: cls.__name__)()
|
179
180
|
return launch_process(
|
180
181
|
plugin_class_instance.get_process_invocation(),
|
@@ -215,8 +216,14 @@ def main():
|
|
215
216
|
# Launch launchers and wait for the last app to finish
|
216
217
|
try:
|
217
218
|
procs = [launcher(parsed_args) for launcher in launchers]
|
218
|
-
_ = [
|
219
|
-
|
219
|
+
_ = [
|
220
|
+
launch_plugin(cls())
|
221
|
+
for cls in Plugins.system().get_feature_classes("gds_app")
|
222
|
+
]
|
223
|
+
_ = [
|
224
|
+
instance().run()
|
225
|
+
for instance in Plugins.system().get_feature_classes("gds_function")
|
226
|
+
]
|
220
227
|
|
221
228
|
print("[INFO] F prime is now running. CTRL-C to shutdown all components.")
|
222
229
|
procs[-1].wait()
|
@@ -67,7 +67,7 @@ Vue.component("channel-table", {
|
|
67
67
|
if (item.time == null || item.val == null) {
|
68
68
|
return ["", "0x" + item.id.toString(16), template.full_name, ""];
|
69
69
|
}
|
70
|
-
return [timeToString(item.
|
70
|
+
return [timeToString(item.time || item.datetime), "0x" + item.id.toString(16), template.full_name,
|
71
71
|
(typeof(item.display_text) !== "undefined")? item.display_text : item.val]
|
72
72
|
},
|
73
73
|
/**
|
@@ -80,7 +80,7 @@ Vue.component("event-list", {
|
|
80
80
|
const msg = '<span title="' + groups[0] + '">' + command_mnemonic + '</span>'
|
81
81
|
display_text = display_text.replace(OPREG, msg);
|
82
82
|
}
|
83
|
-
return [timeToString(item.
|
83
|
+
return [timeToString(item.time || item.datetime), "0x" + item.id.toString(16), template.full_name,
|
84
84
|
template.severity.value.replace("EventSeverity.", ""), display_text];
|
85
85
|
},
|
86
86
|
/**
|