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

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========================== 

48 

49``ActiveConfigProgramOptions`` is a subclass of ``ActiveConfigParser`` 

50that adds program-option handling capabilities. 

51 

52Operation: ``opt-set`` 

53++++++++++++++++++++++++++++++++ 

54The ``opt-set`` operation can have the following format: 

55 

56.. code-block:: bash 

57 :linenos: 

58 

59 opt-set Param1 [Param2] ... [ParamN] : <value> 

60 

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. 

64 

65These options are added to the :py:attr:`options` property. 

66 

67 

68Operation: ``opt-remove`` 

69++++++++++++++++++++++++++++++++ 

70The ``opt-remove`` operation can have the following format: 

71 

72.. code-block:: bash 

73 :linenos: 

74 

75 opt-remove Param1 [SUBSTR] 

76 

77This command is used to remove options that have *already* been processed 

78and added to the :py:attr:`options` property. 

79 

80Option entries are removed according to the rules: 

81 

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). 

88 

89 

90:Authors: 

91 - William McLendon <Semantik-Codeworks@outlook.com> 

92""" 

93 

94# For type-hinting 

95from typing import Dict, Iterable, List, Optional, Set, Tuple, Union 

96 

97try: # pragma: no cover 

98 # @final decorator, requires Python 3.8.x 

99 from typing import final 

100except ImportError: # pragma: no cover 

101 pass 

102 

103import copy 

104#from pathlib import Path 

105#from pprint import pprint 

106import re 

107import sys 

108 

109 

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)) 

113 

114from activeconfigparser import * 

115from exceptioncontrol import * 

116from stronglytypedproperty import strongly_typed_property 

117 

118from .common import * 

119 

120# ============================== 

121# F R E E F U N C T I O N S 

122# ============================== 

123 

124# =============================== 

125# H E L P E R C L A S S E S 

126# =============================== 

127 

128 

129 

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 

136 

137 

138 

139class ExpandVarsInText(ExceptionControl): 

140 """ 

141 Utility to identify and format variables that are found in a text string. 

142 

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. 

145 

146 The base type that is known is "ENV" for environment variables. 

147 

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 """ 

152 

153 def __init__(self): 

154 self.exception_control_level = 4 1nghmlat

155 

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) 

167 

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

175 

176 def __str__(self): # pragma: no cover 

177 return f"{self.varname}" 

178 

179 # --------------------- 

180 # P R O P E R T I E S 

181 # --------------------- 

182 

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 ) 

189 

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) 

192 

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) 

197 

198 # --------------- 

199 # M E T H O D S 

200 # --------------- 

201 

202 def process(self, text: str) -> str: 

203 """ 

204 Process a text string and expand any detected ``variables`` in them. 

205 

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. 

209 

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: 

214 

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. 

218 

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

224 

225 tokenized_text = self._tokenize_text_string(text) 1nghmlatosuibcdekfjqrp

226 

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

233 

234 output = "".join(tokenized_text) 1nghmlaosuibcdekfjqrp

235 

236 return output 1nghmlaosuibcdekfjqrp

237 

238 # --------------------------------------- 

239 # C O N V E R S I O N H A N D L E R S 

240 # --------------------------------------- 

241 

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

247 

248 # --------------- 

249 # H E L P E R S 

250 # --------------- 

251 

252 def _tokenize_text_string(self, text: str): 

253 """ 

254 Takes a text string and returns a list of text and VariableFieldData entries 

255 

256 Called By: 

257 - ``process()`` 

258 

259 """ 

260 output = [] 1nghmlatosuibcdekfjqrp

261 

262 varfield_list = self._extract_fields_from_text(text) 1nghmlatosuibcdekfjqrp

263 

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

269 

270 output.append(text[curidx :]) 1nghmlaosuibcdekfjqrp

271 

272 return output 1nghmlaosuibcdekfjqrp

273 

274 def _extract_fields_from_text(self, text: str, sep: str = "|"): 

275 """Extracts the variablefields from a text string. 

276 

277 Extracts fields from a text string that are formatted like: 

278 ``${<VARNAME><SEP><VARTYPE>}`` where: 

279 

280 - VARNAME is the variable name (REQUIRED) 

281 - SEP is the separator. Default is `|` 

282 - VARTYPE is the variable type. (REQUIRED) 

283 

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"]`` 

288 

289 Raises: 

290 ValueError: If the TYPE field is missing. 

291 

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

298 

299 for m in matches: 1nghmlatosuibcdekfjqrp

300 varfield = m.groups()[0] 1atbcdef

301 

302 vartype = self.default_vartype 1atbcdef

303 

304 idxsep = varfield.index(sep) if sep in varfield else None 1atbcdef

305 varname = varfield[: idxsep] 1atbcdef

306 

307 if idxsep is not None: 1atbcdef

308 vartype = varfield[idxsep + len(sep):] 1abcdef

309 vartype = vartype.upper().strip() 1abcdef

310 

311 varfield = "${" + varfield + "}" 1atbcdef

312 

313 if isinstance(vartype, _VARTYPE_UNKNOWN): 1atbcdef

314 raise ValueError("Variable missing TYPE field in expansion of `{}`".format(varfield)) 1t

315 

316 output.append(self.VariableFieldData(varfield, varname, vartype, m.start(), m.end())) 1abcdef

317 

318 return output 1nghmlaosuibcdekfjqrp

319 

320 

321 

322# =============================== 

323# M A I N C L A S S 

324# =============================== 

325 

326 

327 

328class ActiveConfigProgramOptions(ActiveConfigParser): 

329 """ 

330 Todo: 

331 Add docstrings to functions and handlers. 

332 

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 """ 

338 

339 def __init__(self, filename=None): 

340 if filename is not None: 1nghABxymzDClat

341 self.inifilepath = filename 1nghABxymzClat

342 

343 # ----------------------- 

344 # P R O P E R T I E S 

345 # ----------------------- 

346 

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 ) 

351 

352 @property 

353 def _data_shared_key(self) -> str: 

354 """Key used by ``handler_parameters`` for ``shared_data`` 

355 

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. 

359 

360 This information is generally copied out during the :py:meth:`handler_finalize` into 

361 some class property for accessing beyond the search itself. 

362 

363 Currently this just uses the current class name. 

364 

365 Returns: 

366 str: Default dictionary key internal parser use. 

367 

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. 

371 

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. 

375 

376 """ 

377 return self.__class__.__name__ 1nghABmlatowsuibcdekfjqrp

378 

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: 

385 

386 .. code-block:: ini 

387 :linenos: 

388 

389 [SECTION_A] 

390 opt-set cmake 

391 opt-set -G : Ninja 

392 

393 might generate the folllowing result in :py:attr:`options`: 

394 

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 } 

402 

403 would encode the reults of a ``.ini`` file *section* "SECTION_A" which 

404 encoded the command: ``cmake -G Ninja``. 

405 

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". 

408 

409 Raises: 

410 TypeError: A TypeError can be raised if a non-dictionary is assigned 

411 to this property. 

412 

413 """ 

414 if not hasattr(self, '_property_options'): 1nghmClatowsuibcdekfjqrp

415 self._property_options = {} 1nghmlatowsuibcdekfjqrp

416 return self._property_options 1nghmClatowsuibcdekfjqrp

417 

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

423 

424 # ------------------------------- 

425 # P U B L I C M E T H O D S 

426 # ------------------------------- 

427 

428 def gen_option_list(self, section, generator='bash') -> list: 

429 """Generate a list of options for a section. 

430 

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. 

434 

435 The ``bash`` generator will generate a set of 'bash' like 

436 entries that could be concactenated to generate a bash command. 

437 

438 For example, the .ini section: 

439 

440 .. code-block:: ini 

441 :linenos: 

442 

443 [SECTION_A] 

444 opt-set cmake 

445 opt-set -G : Ninja 

446 opt-set /path/to/source/dir 

447 

448 will generate this output: 

449 

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 ] 

457 

458 Which can be joined easily to create a bash instruction, such as: 

459 

460 >>> " ".join(option_list) 

461 cmake -G=Ninja 

462 

463 or we could generate a multi-line bash command with continuation 

464 lines like this: 

465 

466 >>> " \\\\\\n ".join(option_list) 

467 cmake \\ 

468 -G=Ninja \\ 

469 /path/to/source/dir 

470 

471 This can then be executed in a bash shell or saved to a script file 

472 that could be executed separately. 

473 

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)`` 

481 

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

487 

488 output = [] 1nghmlatosuibcdekfjqrp

489 

490 if section not in self.options.keys(): 1nghmlatosuibcdekfjqrp

491 self.parse_section(section) 1mlatosejp

492 

493 section_data = self.options[section] 1nghmlatosuibcdekfjqrp

494 

495 # Reset the cached vars in the formatter utility 

496 del self._var_formatter_cache 1nghmlatosuibcdekfjqrp

497 

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

502 

503 return output 1nghmlaosibcekfjqrp

504 

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 # --------------------------------------------------------------- 

508 

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**. 

513 

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``. 

519 

520 Args: 

521 option_entry (dict): A dictionary storing a single *option* entry. 

522 generator (string): The generator to use. Currently only supports ``bash``. 

523 

524 Returns: 

525 Union[str,None]: A ``string`` containing the single entry for this option or ``None`` 

526 

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. 

530 

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

536 

537 output = None 1nghxymzlatosuibcdekfjqrp

538 

539 method_name = None 1nghxymzlatosuibcdekfjqrp

540 method_ref = None 1nghxymzlatosuibcdekfjqrp

541 

542 method_name_list = [] 1nghxymzlatosuibcdekfjqrp

543 

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

558 

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

563 

564 if value is not None: 1nghmlatosuibcdekfjqrp

565 if " " in value: 1nghmlatosuibcdekfjqrp

566 value = '"' + value + '"' 1latsbcdef

567 

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

571 

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

577 

578 output = method_ref(params, value) 1nghmlaosuibcdekfjqrp

579 

580 return output 1nghxymlaosibcdekfjqrp

581 

582 def _generic_program_option_handler_bash(self, params: list, value: str) -> str: 

583 """Generic processer for generic bash options. 

584 

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``. 

588 

589 For example: 

590 

591 >>> params = ['-D','SOMEFLAG',':BOOL'] 

592 >>> value = "ON" 

593 >>> _generic_program_option_handler_bash(params,value) 

594 "-DSOMEFLAG:BOOL=ON" 

595 

596 

597 Called by: :py:meth:`_program_option_handler_opt_set_bash` 

598 

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. 

605 

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

616 

617 def _program_option_handler_opt_set_bash(self, params: list, value: str) -> str: 

618 """Bash generator for ``opt-set`` operations. 

619 

620 This method handles the generation of an entry for an 

621 ``opt-set`` operation when the **generator** is set to be ``bash``. 

622 

623 Called by: :py:meth:`_gen_option_entry`. 

624 

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

631 

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 # --------------------------------------------------------------- 

635 

636 @ActiveConfigParser.operation_handler 

637 def handler_initialize(self, section_name: str, handler_parameters) -> int: 

638 """Initialize a recursive parse search. 

639 

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. 

644 

645 Returns: 

646 int: Status value indicating success or failure. 

647 

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

654 

655 @ActiveConfigParser.operation_handler 

656 def handler_finalize(self, section_name: str, handler_parameters) -> int: 

657 """Finalize a recursive parse search. 

658 

659 Returns: 

660 int: Status value indicating success or failure. 

661 

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

669 

670 @ActiveConfigParser.operation_handler 

671 def _handler_opt_set(self, section_name: str, handler_parameters) -> int: 

672 """Handler for ``opt-set`` operations 

673 

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. 

677 

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>()``. 

681 

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. 

686 

687 Returns: 

688 int: Status value indicating success or failure. 

689 

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

695 

696 @ActiveConfigParser.operation_handler 

697 def _handler_opt_remove(self, section_name: str, handler_parameters) -> int: 

698 """Handler for ``opt-remove`` operations. 

699 

700 Note: 

701 This method should not be overridden by subclasses. 

702 

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. 

707 

708 Returns: 

709 int: Status value indicating success or failure. 

710 

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

716 

717 # --------------------------------- 

718 # H A N D L E R H E L P E R S 

719 # --------------------------------- 

720 

721 def _option_handler_helper_remove(self, section_name: str, handler_parameters) -> int: 

722 """Removes option(s) from the shared data options list. 

723 

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. 

727 

728 In this case the format is based on the following .ini snippet: 

729 

730 .. code-block:: ini 

731 :linenos: 

732 

733 [SECTION NAME] 

734 <operation> KEYWORD [SUBSTR] 

735 

736 where we remove entries from the *shared data options* list according to one of the 

737 following methods: 

738 

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. 

743 

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. 

748 

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

756 

757 params = handler_parameters.params 1ghAikj

758 

759 if params is None or len(params) == 0: 1ghAikj

760 self.exception_control_event("CATASTROPHIC", IndexError) 1A

761 

762 removal_key = params[0] 1ghikj

763 

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

767 

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 ) 

777 

778 handler_parameters.data_shared[self._data_shared_key] = data_shared_ref 1ghikj

779 return 0 1ghikj

780 

781 def _option_handler_helper_add(self, section_name: str, handler_parameters) -> int: 

782 """Add an option to the shared data options list 

783 

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. 

787 

788 Operations with the format such as this: 

789 

790 .. code-block:: ini 

791 :linenos: 

792 

793 [SECTION NAME] 

794 operation Param1 Param2 ... ParamN : Value 

795 

796 which result in a ``dict`` entry: 

797 

798 .. code-block:: python 

799 :linenos: 

800 

801 { 

802 'type' : [ operation ], 

803 'params': [ param1, param2, ... , paramN ], 

804 'value' : Value 

805 } 

806 

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`. 

810 

811 Currently :py:attr:`_data_shared_key` returns the current class name, but 

812 this can be changed if needed. 

813 

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. 

818 

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

829 

830 entry = {'type': [op], 'value': value, 'params': params} 1nghmlatowsuibcdekfjqrp

831 

832 data_shared_ref.append(entry) 1nghmlatowsuibcdekfjqrp

833 return 0 1nghmlatowsuibcdekfjqrp

834 

835 # ----------------------- 

836 # H E L P E R S 

837 # ----------------------- 

838 

839 def _initialize_handler_parameters(self, section_name, handler_parameters) -> int: 

840 """Initialize ``handler_parameters`` 

841 

842 Initializes any default settings needed for ``handler_parameters``. 

843 Takes the same parameters as handlers. 

844 

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. 

849 

850 Called By: 

851 - ``handler_initialize()`` 

852 """ 

853 self._validate_parameter(section_name, (str)) 1nghABmlatowsuibcdekfjqrp

854 self._validate_handlerparameters(handler_parameters) 1nghABmlatowsuibcdekfjqrp

855 

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

859 

860 return 0 1nghABmlatowsuibcdekfjqrp