Coverage for src/activeconfigprogramoptions/ActiveConfigProgramOptions.py: 100%
185 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-04 16:10 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-04 16:10 +0000
1#!/usr/bin/env python3
2# -*- mode: python; py-indent-offset: 4; py-continuation-offset: 4 -*-
3#===============================================================================
4# Copyright Notice
5# ----------------
6# Copyright 2021 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
7# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains
8# certain rights in this software.
9# Copyright (c) 2022 William McLendon
10# All rights reserved.
11#
12# License (3-Clause BSD)
13# ----------------------
14# Copyright 2021 National Technology & Engineering Solutions of Sandia,
15# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS,
16# the U.S. Government retains certain rights in this software.
17#
18# Redistribution and use in source and binary forms, with or without
19# modification, are permitted provided that the following conditions
20# are met:
21#
22# 1. Redistributions of source code must retain the above copyright
23# notice, this list of conditions and the following disclaimer.
24#
25# 2. Redistributions in binary form must reproduce the above
26# copyright notice, this list of conditions and the following
27# disclaimer in the documentation and/or other materials provided
28# with the distribution.
29#
30# 3. Neither the name of the copyright holder nor the names of its
31# contributors may be used to endorse or promote products derived
32# from this software without specific prior written permission.
33#
34# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
35# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
36# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
37# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
38# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
39# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
41# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
42# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
43# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44#===============================================================================
45"""
46ActiveConfigProgramOptions
47==========================
49``ActiveConfigProgramOptions`` is a subclass of ``ActiveConfigParser``
50that adds program-option handling capabilities.
52Operation: ``opt-set``
53++++++++++++++++++++++++++++++++
54The ``opt-set`` operation can have the following format:
56.. code-block:: bash
57 :linenos:
59 opt-set Param1 [Param2] ... [ParamN] : <value>
61Options are processed using :py:meth:`gen_option_list` to produce a
62``list`` or ``strings`` representing a command or set of command line
63options that can be processed by the caller.
65These options are added to the :py:attr:`options` property.
68Operation: ``opt-remove``
69++++++++++++++++++++++++++++++++
70The ``opt-remove`` operation can have the following format:
72.. code-block:: bash
73 :linenos:
75 opt-remove Param1 [SUBSTR]
77This command is used to remove options that have *already* been processed
78and added to the :py:attr:`options` property.
80Option entries are removed according to the rules:
821. If ``SUBSTR`` is not provided, entries are removed if *any* of
83 its *Param* options are exactly matched.
842. If the ``SUBSTR`` parameter is included, the matching criteria
85 is relaxed so that an option is removed from the list if any of
86 its parameters *contains* ``Param1`` (i.e., ``Param1`` is a
87 *sub string* of any paramter in the option).
90:Authors:
91 - William McLendon <Semantik-Codeworks@outlook.com>
92"""
94# For type-hinting
95from typing import Dict, Iterable, List, Optional, Set, Tuple, Union
97try: # pragma: no cover
98 # @final decorator, requires Python 3.8.x
99 from typing import final
100except ImportError: # pragma: no cover
101 pass
103import copy
104#from pathlib import Path
105#from pprint import pprint
106import re
107import sys
110MIN_PYTHON = (3, 6)
111if sys.version_info < MIN_PYTHON: # pragma: no cover
112 sys.exit("Python %s.%s or later is required.\n" % (MIN_PYTHON))
114from activeconfigparser import *
115from exceptioncontrol import *
116from stronglytypedproperty import strongly_typed_property
118from .common import *
120# ==============================
121# F R E E F U N C T I O N S
122# ==============================
124# ===============================
125# H E L P E R C L A S S E S
126# ===============================
130class _VARTYPE_UNKNOWN(object):
131 """
132 This class serves as a 'default' sentinel type to guard
133 against unset variable types in ``ExpandVarsInText``.
134 """
135 pass
139class ExpandVarsInText(ExceptionControl):
140 """
141 Utility to identify and format variables that are found in a text string.
143 This looks for variables embedded in text strings that are formatted like:
144 ``${VARNAME|TYPE}`` where ``TYPE`` denotes what kind of variable we are defining.
146 The base type that is known is "ENV" for environment variables.
148 The ``TYPE`` field MUST BE PRESENT. We do not provide a default to enforce
149 users of ``ActiveConfigProgramOptions`` to be *explicit* in defining the type of variable
150 they're declaring. These are a variables declared in a *pseudo-language* not bash.
151 """
153 def __init__(self):
154 self.exception_control_level = 4 1nghmlat
156 class VariableFieldData(object):
157 """
158 This is essentially a dataclass that is used to pass field data around within
159 the generators, etc. This captures the relevant fields from a given action
160 entry.
161 """
162 varfield = strongly_typed_property('varfield', expected_type=str, req_assign_before_use=True)
163 varname = strongly_typed_property('varname', expected_type=str, req_assign_before_use=True)
164 vartype = strongly_typed_property('vartype', expected_type=str, req_assign_before_use=True)
165 start = strongly_typed_property('start', expected_type=int, req_assign_before_use=True)
166 end = strongly_typed_property('end', expected_type=int, req_assign_before_use=True)
168 def __init__(self, varfield: str, varname: str, vartype: str, start: int, end: int):
169 self.varfield = varfield 1abcdef
170 self.varname = varname 1abcdef
171 self.vartype = vartype 1abcdef
172 self.start = start 1abcdef
173 self.end = end 1abcdef
174 return 1abcdef
176 def __str__(self): # pragma: no cover
177 return f"{self.varname}"
179 # ---------------------
180 # P R O P E R T I E S
181 # ---------------------
183 # Default variable type. Default is _VARTYPE_UNKNOWN. If not overridden
184 # or changed then a variable string like "${VARNAME}" that does not contain
185 # a `|TYPE` component will cause a `ValueError` to be raised.
186 default_vartype = strongly_typed_property(
187 "default_vartype", expected_type=str, default_factory=_VARTYPE_UNKNOWN, transform=str_toupper
188 )
190 # Generator type. This is the _kind of output_ that we wish to generate.
191 generator = strongly_typed_property("generator", expected_type=str, default="BASH", transform=str_toupper)
193 # Owner is a link back to the class that instantiated this object.
194 # We use this to look back into the owning `ActiveConfigProgramOptions` class
195 # to check for cached information (if needed).
196 owner = strongly_typed_property("owner", expected_type=object, default=None)
198 # ---------------
199 # M E T H O D S
200 # ---------------
202 def process(self, text: str) -> str:
203 """
204 Process a text string and expand any detected ``variables`` in them.
206 This function *detects* each of the fields in a text string that are
207 formatted like ``${VARNAME|VARTYPE}``. There can be multiple fields
208 within a single text entry.
210 Each field is converted based on the specified GENERATOR and the VARTYPE of the
211 field. These fields are converted using class methods that are named according
212 to this naming scheme:
213 ``_fieldhandler_GENERATOR_VARTYPE`` where:
215 - ``GENERATOR``: The *output* type we're generating, such as "BASH" or "CMAKE".
216 - ``VARTYPE``: The kind of variable that is being processed, such as "ENV" for an
217 environment variable or "CMAKE" for cmake file variables, etc.
219 For example, the field handler to convert an ``ENV`` field using a ``BASH`` generator
220 would be named ``_fieldhandler_BASH_ENV(self, field)`` and it accepts an instance
221 of the iner class ``VariableFieldData``.
222 """
223 output = copy.copy(text) 1nghmlatosuibcdekfjqrp
225 tokenized_text = self._tokenize_text_string(text) 1nghmlatosuibcdekfjqrp
227 for i in range(len(tokenized_text)): 1nghmlaosuibcdekfjqrp
228 field = tokenized_text[i] 1nghmlaosuibcdekfjqrp
229 if isinstance(field, self.VariableFieldData): 1nghmlaosuibcdekfjqrp
230 conversion_method_name = "_fieldhandler_{}_{}".format(self.generator, field.vartype) 1abcdef
231 conversion_method_ref = get_function_ref(self, conversion_method_name) 1abcdef
232 tokenized_text[i] = conversion_method_ref(field) 1abcdef
234 output = "".join(tokenized_text) 1nghmlaosuibcdekfjqrp
236 return output 1nghmlaosuibcdekfjqrp
238 # ---------------------------------------
239 # C O N V E R S I O N H A N D L E R S
240 # ---------------------------------------
242 def _fieldhandler_BASH_ENV(self, field) -> str:
243 """
244 Format a field containing an ENVVAR as a BASH entry.
245 """
246 return "${" + field.varname + "}" 1abcd
248 # ---------------
249 # H E L P E R S
250 # ---------------
252 def _tokenize_text_string(self, text: str):
253 """
254 Takes a text string and returns a list of text and VariableFieldData entries
256 Called By:
257 - ``process()``
259 """
260 output = [] 1nghmlatosuibcdekfjqrp
262 varfield_list = self._extract_fields_from_text(text) 1nghmlatosuibcdekfjqrp
264 curidx = 0 1nghmlaosuibcdekfjqrp
265 for varfield in varfield_list: 1nghmlaosuibcdekfjqrp
266 output.append(text[curidx : varfield.start]) 1abcdef
267 output.append(varfield) 1abcdef
268 curidx = varfield.end 1abcdef
270 output.append(text[curidx :]) 1nghmlaosuibcdekfjqrp
272 return output 1nghmlaosuibcdekfjqrp
274 def _extract_fields_from_text(self, text: str, sep: str = "|"):
275 """Extracts the variablefields from a text string.
277 Extracts fields from a text string that are formatted like:
278 ``${<VARNAME><SEP><VARTYPE>}`` where:
280 - VARNAME is the variable name (REQUIRED)
281 - SEP is the separator. Default is `|`
282 - VARTYPE is the variable type. (REQUIRED)
284 Returns:
285 list: A list containing text strings and VariableFieldData entries in place
286 of variable fields that were detected (these are converted later).
287 i.e., ``["foo", VariableFieldData(...), " -a"]``
289 Raises:
290 ValueError: If the TYPE field is missing.
292 Called By:
293 - ``_tokenize_text_string()``
294 """
295 output = [] 1nghmlatosuibcdekfjqrp
296 pattern = r"\$\{([a-zA-Z0-9_" + sep + r"\*\@\[\]]+)\}" 1nghmlatosuibcdekfjqrp
297 matches = re.finditer(pattern, text) 1nghmlatosuibcdekfjqrp
299 for m in matches: 1nghmlatosuibcdekfjqrp
300 varfield = m.groups()[0] 1atbcdef
302 vartype = self.default_vartype 1atbcdef
304 idxsep = varfield.index(sep) if sep in varfield else None 1atbcdef
305 varname = varfield[: idxsep] 1atbcdef
307 if idxsep is not None: 1atbcdef
308 vartype = varfield[idxsep + len(sep):] 1abcdef
309 vartype = vartype.upper().strip() 1abcdef
311 varfield = "${" + varfield + "}" 1atbcdef
313 if isinstance(vartype, _VARTYPE_UNKNOWN): 1atbcdef
314 raise ValueError("Variable missing TYPE field in expansion of `{}`".format(varfield)) 1t
316 output.append(self.VariableFieldData(varfield, varname, vartype, m.start(), m.end())) 1abcdef
318 return output 1nghmlaosuibcdekfjqrp
322# ===============================
323# M A I N C L A S S
324# ===============================
328class ActiveConfigProgramOptions(ActiveConfigParser):
329 """
330 Todo:
331 Add docstrings to functions and handlers.
333 .. configparser reference:
334 https://docs.python.org/3/library/configparser.html
335 .. docstrings style reference:
336 https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html
337 """
339 def __init__(self, filename=None):
340 if filename is not None: 1nghABxymzDClat
341 self.inifilepath = filename 1nghABxymzClat
343 # -----------------------
344 # P R O P E R T I E S
345 # -----------------------
347 _var_formatter_cache = strongly_typed_property("_varcache", expected_type=dict, default_factory=dict)
348 _var_formatter = strongly_typed_property(
349 "_var_formatter", expected_type=ExpandVarsInText, default_factory=ExpandVarsInText
350 )
352 @property
353 def _data_shared_key(self) -> str:
354 """Key used by ``handler_parameters`` for ``shared_data``
356 This key is used inside the ``handler_parameters.shared_data[_data_shared_key]``
357 as the place we're storing the action list and other data associated with the
358 ``ActiveConfigParser`` parsing of a given *root* section.
360 This information is generally copied out during the :py:meth:`handler_finalize` into
361 some class property for accessing beyond the search itself.
363 Currently this just uses the current class name.
365 Returns:
366 str: Default dictionary key internal parser use.
368 This property returns a string that is used as the default
369 *key* in ``handler_parameters.data_shared`` to bin the data
370 being generated during a search.
372 This can be used in the :py:meth:`handler_finalize`
373 method to extract the shared data captured by the parser before it
374 is discarded at the end of a search.
376 """
377 return self.__class__.__name__ 1nghABmlatowsuibcdekfjqrp
379 @property
380 def options(self) -> dict:
381 """
382 The :py:attr:`options` property contains the set of parsed options that has
383 been processed so far stored as a dictionary. For example, the following
384 .ini snippet:
386 .. code-block:: ini
387 :linenos:
389 [SECTION_A]
390 opt-set cmake
391 opt-set -G : Ninja
393 might generate the folllowing result in :py:attr:`options`:
395 >>> parser.options
396 {'SECTION_A':
397 [
398 {'type': ['opt_set'], 'params': ['cmake'], 'value': None },
399 {'type': ['opt_set'], 'params': ['-G'], 'value': 'Ninja' }
400 ]
401 }
403 would encode the reults of a ``.ini`` file *section* "SECTION_A" which
404 encoded the command: ``cmake -G Ninja``.
406 This data is used by the ``gen_option_list`` method to generate snippets
407 according to the requested generator, such as "bash" or "cmake_fragment".
409 Raises:
410 TypeError: A TypeError can be raised if a non-dictionary is assigned
411 to this property.
413 """
414 if not hasattr(self, '_property_options'): 1nghmClatowsuibcdekfjqrp
415 self._property_options = {} 1nghmlatowsuibcdekfjqrp
416 return self._property_options 1nghmClatowsuibcdekfjqrp
418 @options.setter
419 def options(self, value) -> dict:
420 self._validate_parameter(value, (dict)) 1C
421 self._property_options = value 1C
422 return self._property_options 1C
424 # -------------------------------
425 # P U B L I C M E T H O D S
426 # -------------------------------
428 def gen_option_list(self, section, generator='bash') -> list:
429 """Generate a list of options for a section.
431 Generates a list of strings that captures the requested
432 operation based on the entries in the .ini file that is
433 stored in :py:attr:`options` during parsing.
435 The ``bash`` generator will generate a set of 'bash' like
436 entries that could be concactenated to generate a bash command.
438 For example, the .ini section:
440 .. code-block:: ini
441 :linenos:
443 [SECTION_A]
444 opt-set cmake
445 opt-set -G : Ninja
446 opt-set /path/to/source/dir
448 will generate this output:
450 >>> option_list = parser.gen_option_list("SECTION_A", "bash")
451 >>> option_list
452 [
453 'cmake',
454 '-G=Ninja',
455 '/path/to/source/dir'
456 ]
458 Which can be joined easily to create a bash instruction, such as:
460 >>> " ".join(option_list)
461 cmake -G=Ninja
463 or we could generate a multi-line bash command with continuation
464 lines like this:
466 >>> " \\\\\\n ".join(option_list)
467 cmake \\
468 -G=Ninja \\
469 /path/to/source/dir
471 This can then be executed in a bash shell or saved to a script file
472 that could be executed separately.
474 Args:
475 section (str): The section name that contains the options
476 we wish to process.
477 generator (str): What kind of generator are we to use to
478 build up our options list? Currently we allow ``bash``
479 but subclasses can define their own functions using the
480 format ``_gen_option_entry_<generator>(option_entry:dict)``
482 Returns:
483 list: A ``list`` containing the processed options text.
484 """
485 self._validate_parameter(section, (str)) 1nghmlatosuibcdekfjqrp
486 self._validate_parameter(generator, (str)) 1nghmlatosuibcdekfjqrp
488 output = [] 1nghmlatosuibcdekfjqrp
490 if section not in self.options.keys(): 1nghmlatosuibcdekfjqrp
491 self.parse_section(section) 1mlatosejp
493 section_data = self.options[section] 1nghmlatosuibcdekfjqrp
495 # Reset the cached vars in the formatter utility
496 del self._var_formatter_cache 1nghmlatosuibcdekfjqrp
498 for option_entry in section_data: 1nghmlatosuibcdekfjqrp
499 line = self._gen_option_entry(option_entry, generator=generator) 1nghmlatosuibcdekfjqrp
500 if line is not None: 1nghmlaosibcdekfjqrp
501 output.append(line) 1nghmlaosibcdekfjqrp
503 return output 1nghmlaosibcekfjqrp
505 # ---------------------------------------------------------------
506 # H A N D L E R S - P R O G R A M O P T I O N S
507 # ---------------------------------------------------------------
509 def _gen_option_entry(self, option_entry: dict, generator='bash') -> Union[str, None]:
510 """
511 Takes an ``option_entry`` and looks for a specialized handler
512 for that option **type**.
514 This looks for a method named ``_program_option_handler_<typename>_<generator>``
515 where ``typename`` comes from the ``option_entry['type']`` field. For example,
516 if ``option_entry['type']`` is ``opt_set`` and ``generator`` is ``bash`` then
517 we look for a method called :py:meth:`_program_option_handler_opt_set_bash`, which
518 is executed and returns the line-item entry for the given ``option_entry``.
520 Args:
521 option_entry (dict): A dictionary storing a single *option* entry.
522 generator (string): The generator to use. Currently only supports ``bash``.
524 Returns:
525 Union[str,None]: A ``string`` containing the single entry for this option or ``None``
527 ``None`` is returned IF the ``type`` field in ``option_entry``
528 does not resolve to a proper method and the *exception control level*
529 is not set sufficiently high to trigger an exception.
531 Raises:
532 ValueError: If we aren't able to locate the options formatter.
533 """
534 self._validate_parameter(option_entry, (dict)) 1nghxymzlatosuibcdekfjqrp
535 self._validate_parameter(generator, (str)) 1nghxymzlatosuibcdekfjqrp
537 output = None 1nghxymzlatosuibcdekfjqrp
539 method_name = None 1nghxymzlatosuibcdekfjqrp
540 method_ref = None 1nghxymzlatosuibcdekfjqrp
542 method_name_list = [] 1nghxymzlatosuibcdekfjqrp
544 # Look for a matching method in the list of 'types' that
545 # this operation can map to.
546 for typename in option_entry['type']: 1nghxymzlatosuibcdekfjqrp
547 method_name = "_".join(["_program_option_handler", str(typename), generator]) 1nghxymzlatosuibcdekfjqrp
548 (method_name, method_ref) = self._locate_class_method(method_name) 1nghxymzlatosuibcdekfjqrp
549 if method_ref is not None: 1nghxymzlatosuibcdekfjqrp
550 break 1nghmlatosuibcdekfjqrp
551 method_name_list.append(method_name) 1xyz
552 else:
553 # The for did _not_ exit via the break...
554 message = ["ERROR: Unable to locate an option formatter named:"] 1xyz
555 for method_name in method_name_list: 1xyz
556 message.append("- `{}()`".format(method_name)) 1xyz
557 self.exception_control_event("SILENT", ValueError, "\n".join(message)) 1xyz
559 # Found a match.
560 if method_ref is not None: 1nghxymlatosuibcdekfjqrp
561 params = copy.deepcopy(option_entry['params']) 1nghmlatosuibcdekfjqrp
562 value = copy.deepcopy(option_entry['value']) 1nghmlatosuibcdekfjqrp
564 if value is not None: 1nghmlatosuibcdekfjqrp
565 if " " in value: 1nghmlatosuibcdekfjqrp
566 value = '"' + value + '"' 1latsbcdef
568 # Update the var formatter's ECL to match the current value.
569 self._var_formatter.exception_control_level = self.exception_control_level 1nghmlatosuibcdekfjqrp
570 self._var_formatter.exception_control_compact_warnings = self.exception_control_compact_warnings 1nghmlatosuibcdekfjqrp
572 # format the value
573 formatter = self._var_formatter 1nghmlatosuibcdekfjqrp
574 formatter.generator = generator 1nghmlatosuibcdekfjqrp
575 formatter.owner = self 1nghmlatosuibcdekfjqrp
576 value = formatter.process(value) 1nghmlatosuibcdekfjqrp
578 output = method_ref(params, value) 1nghmlaosuibcdekfjqrp
580 return output 1nghxymlaosibcdekfjqrp
582 def _generic_program_option_handler_bash(self, params: list, value: str) -> str:
583 """Generic processer for generic bash options.
585 This is the simplest kind of option handler for bash-like commands
586 where we just concatenate all the ``params`` together and set them
587 equal to the ``value``.
589 For example:
591 >>> params = ['-D','SOMEFLAG',':BOOL']
592 >>> value = "ON"
593 >>> _generic_program_option_handler_bash(params,value)
594 "-DSOMEFLAG:BOOL=ON"
597 Called by: :py:meth:`_program_option_handler_opt_set_bash`
599 Args:
600 params (list): A ``list`` of strings containing the parameters
601 that would be to the LHS or the ``=`` when constructing an
602 option entry.
603 value (str): A ``str`` that is placed to the RHS of the option
604 assignment expression.
606 Returns:
607 str: A ``str`` object representing an option.
608 """
609 output = "".join(params) 1nghmlaoibcdejqrp
610 if value is not None: 1nghmlaoibcdejqrp
611 # Make sure STRING flag values are surrounded by double quotes
612 if "STRING" in output and not value.startswith('"') and not value.endswith('"'): 1nghmlaoibcdejqrp
613 value = f'"{value}"' 1oejqrp
614 output += "=" + value 1nghmlaoibcdejqrp
615 return output 1nghmlaoibcdejqrp
617 def _program_option_handler_opt_set_bash(self, params: list, value: str) -> str:
618 """Bash generator for ``opt-set`` operations.
620 This method handles the generation of an entry for an
621 ``opt-set`` operation when the **generator** is set to be ``bash``.
623 Called by: :py:meth:`_gen_option_entry`.
625 Returns:
626 str: A ``string`` containing a generated program option
627 with the parameters concactenated together using the format
628 ``<param1><param2>...<paramN>=<value>`` will be returned.
629 """
630 return self._generic_program_option_handler_bash(params, value) 1nghmlaibcd
632 # ---------------------------------------------------------------
633 # H A N D L E R S - C O N F I G P A R S E R E N H A N C E D
634 # ---------------------------------------------------------------
636 @ActiveConfigParser.operation_handler
637 def handler_initialize(self, section_name: str, handler_parameters) -> int:
638 """Initialize a recursive parse search.
640 Args:
641 section_name (str): The section name string.
642 handler_parameters (:obj:`HandlerParameters`): A HandlerParameters object containing
643 the state data we need for this handler.
645 Returns:
646 int: Status value indicating success or failure.
648 - 0 : SUCCESS
649 - [1-10]: Reserved for future use (WARNING)
650 - > 10 : An unknown failure occurred (SERIOUS)
651 """
652 self._initialize_handler_parameters(section_name, handler_parameters) 1nghAmlatowsuibcdekfjqrp
653 return 0 1nghAmlatowsuibcdekfjqrp
655 @ActiveConfigParser.operation_handler
656 def handler_finalize(self, section_name: str, handler_parameters) -> int:
657 """Finalize a recursive parse search.
659 Returns:
660 int: Status value indicating success or failure.
662 - 0 : SUCCESS
663 - [1-10]: Reserved for future use (WARNING)
664 - > 10 : An unknown failure occurred (SERIOUS)
665 """
666 # save the results into the right `options_cache` entry
667 self.options[section_name] = handler_parameters.data_shared[self._data_shared_key] 1nghmlatowsuibcdekfjqrp
668 return 0 1nghmlatowsuibcdekfjqrp
670 @ActiveConfigParser.operation_handler
671 def _handler_opt_set(self, section_name: str, handler_parameters) -> int:
672 """Handler for ``opt-set`` operations
674 This handler is used for options whose ``key:value`` pair does not
675 get resolved to a proper ``<operation>`` and therefore do not get
676 routed to a ``handler_<operation>()`` method.
678 This method provides a great *template* for subclasses to use when
679 creating new custom handlers according to the naming scheme
680 ``handler_<operation>()`` or ``_handler_<operation>()``.
682 Args:
683 section_name (str): The name of the section being processed.
684 handler_parameters (:obj:`HandlerParameters`): The parameters passed to
685 the handler.
687 Returns:
688 int: Status value indicating success or failure.
690 - 0 : SUCCESS
691 - [1-10]: Reserved for future use (WARNING)
692 - > 10 : An unknown failure occurred (CRITICAL)
693 """
694 return self._option_handler_helper_add(section_name, handler_parameters) 1nghmlatwibcdkf
696 @ActiveConfigParser.operation_handler
697 def _handler_opt_remove(self, section_name: str, handler_parameters) -> int:
698 """Handler for ``opt-remove`` operations.
700 Note:
701 This method should not be overridden by subclasses.
703 Args:
704 section_name (str): The name of the section being processed.
705 handler_parameters (:obj:`HandlerParameters`): The parameters passed to
706 the handler.
708 Returns:
709 int: Status value indicating success or failure.
711 - 0 : SUCCESS
712 - [1-10]: Reserved for future use (WARNING)
713 - > 10 : An unknown failure occurred (CRITICAL)
714 """
715 return self._option_handler_helper_remove(section_name, handler_parameters) 1ghAikj
717 # ---------------------------------
718 # H A N D L E R H E L P E R S
719 # ---------------------------------
721 def _option_handler_helper_remove(self, section_name: str, handler_parameters) -> int:
722 """Removes option(s) from the shared data options list.
724 Removes an option or options from the ``handler_parameters.data_shared["{_data_shared_key}"]``
725 list, where ``{_data_shared_key}`` is generated from the property :py:attr:`_data_shared_key`.
726 Currently, :py:attr:`_data_shared_key` returns the class name of the class object.
728 In this case the format is based on the following .ini snippet:
730 .. code-block:: ini
731 :linenos:
733 [SECTION NAME]
734 <operation> KEYWORD [SUBSTR]
736 where we remove entries from the *shared data options* list according to one of the
737 following methods:
739 1. Removes entries if one of the parameters matches the provided ``KEYWORD``.
740 2. If the optional ``SUBSTR`` parameter is provided, then we remove entries
741 if any paramter *contains* the ``KEYWORD`` as either an exact match
742 or a substring.
744 Args:
745 section_name (str): The name of the section being processed.
746 handler_parameters (:obj:`HandlerParameters`): The parameters passed to
747 the handler.
749 Returns:
750 int:
751 * 0 : SUCCESS
752 * [1-10]: Reserved for future use (WARNING)
753 * > 10 : An unknown failure occurred (CRITICAL)
754 """
755 data_shared_ref = handler_parameters.data_shared[self._data_shared_key] 1ghAikj
757 params = handler_parameters.params 1ghAikj
759 if params is None or len(params) == 0: 1ghAikj
760 self.exception_control_event("CATASTROPHIC", IndexError) 1A
762 removal_key = params[0] 1ghikj
764 if len(params) == 1: 1ghikj
765 self.debug_message(2, " -> Remove all options containing:`{}`".format(removal_key)) 1gikj
766 data_shared_ref = list(filter(lambda x: removal_key not in x['params'], data_shared_ref)) 1gikj
768 if len(params) >= 2 and params[1] == "SUBSTR": 1ghikj
769 self.debug_message(2, " -> Remove all options containing SUBSTRING:`{}`".format(removal_key)) 1hik
770 data_shared_ref = list( 1hik
771 filter(
772 lambda entry,
773 rkey=removal_key: all(rkey not in item for item in entry['params']),
774 data_shared_ref
775 )
776 )
778 handler_parameters.data_shared[self._data_shared_key] = data_shared_ref 1ghikj
779 return 0 1ghikj
781 def _option_handler_helper_add(self, section_name: str, handler_parameters) -> int:
782 """Add an option to the shared data options list
784 Inserts an option into the ``handler_parameters.data_shared["{_data_shared_key}"]``
785 list, where ``{_data_shared_key}`` is generated from the property :py:attr:`_data_shared_key`.
786 Currently, :py:attr:`_data_shared_key` returns the class name of the class object.
788 Operations with the format such as this:
790 .. code-block:: ini
791 :linenos:
793 [SECTION NAME]
794 operation Param1 Param2 ... ParamN : Value
796 which result in a ``dict`` entry:
798 .. code-block:: python
799 :linenos:
801 {
802 'type' : [ operation ],
803 'params': [ param1, param2, ... , paramN ],
804 'value' : Value
805 }
807 this entry is then appended to the
808 ``handler_parameters.data_shared[{_data_shared_key}]`` list, where
809 :py:attr:`_data_shared_key` is generated from the property :py:attr:`_data_shared_key`.
811 Currently :py:attr:`_data_shared_key` returns the current class name, but
812 this can be changed if needed.
814 Args:
815 section_name (str): The name of the section being processed.
816 handler_parameters (:obj:`HandlerParameters`): The parameters passed to
817 the handler.
819 Returns:
820 int:
821 * 0 : SUCCESS
822 * [1-10]: Reserved for future use (WARNING)
823 * > 10 : An unknown failure occurred (CRITICAL)
824 """
825 data_shared_ref = handler_parameters.data_shared[self._data_shared_key] 1nghmlatowsuibcdekfjqrp
826 op = handler_parameters.op 1nghmlatowsuibcdekfjqrp
827 value = handler_parameters.value 1nghmlatowsuibcdekfjqrp
828 params = handler_parameters.params 1nghmlatowsuibcdekfjqrp
830 entry = {'type': [op], 'value': value, 'params': params} 1nghmlatowsuibcdekfjqrp
832 data_shared_ref.append(entry) 1nghmlatowsuibcdekfjqrp
833 return 0 1nghmlatowsuibcdekfjqrp
835 # -----------------------
836 # H E L P E R S
837 # -----------------------
839 def _initialize_handler_parameters(self, section_name, handler_parameters) -> int:
840 """Initialize ``handler_parameters``
842 Initializes any default settings needed for ``handler_parameters``.
843 Takes the same parameters as handlers.
845 Args:
846 section_name (str): The section name string.
847 handler_parameters (:obj:`HandlerParameters`): A HandlerParameters
848 object containing the state data we need for this handler.
850 Called By:
851 - ``handler_initialize()``
852 """
853 self._validate_parameter(section_name, (str)) 1nghABmlatowsuibcdekfjqrp
854 self._validate_handlerparameters(handler_parameters) 1nghABmlatowsuibcdekfjqrp
856 data_shared_ref = handler_parameters.data_shared 1nghABmlatowsuibcdekfjqrp
857 if self._data_shared_key not in data_shared_ref.keys(): 1nghABmlatowsuibcdekfjqrp
858 data_shared_ref[self._data_shared_key] = [] 1nghAmlatowsuibcdekfjqrp
860 return 0 1nghABmlatowsuibcdekfjqrp