Coverage for pyTooling/CLIAbstraction/Argument.py: 89%
210 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-22 21:29 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-22 21:29 +0000
1# ==================================================================================================================== #
2# _____ _ _ ____ _ ___ _ _ _ _ _ #
3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / ___| | |_ _| / \ | |__ ___| |_ _ __ __ _ ___| |_(_) ___ _ __ #
4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` || | | | | | / _ \ | '_ \/ __| __| '__/ _` |/ __| __| |/ _ \| '_ \ #
5# | |_) | |_| || | (_) | (_) | | | | | | (_| || |___| |___ | | / ___ \| |_) \__ \ |_| | | (_| | (__| |_| | (_) | | | | #
6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____|_____|___/_/ \_\_.__/|___/\__|_| \__,_|\___|\__|_|\___/|_| |_| #
7# |_| |___/ |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2026 Patrick Lehmann - Bötzingen, Germany #
15# Copyright 2014-2016 Technische Universität Dresden - Germany, Chair of VLSI-Design, Diagnostics and Architecture #
16# #
17# Licensed under the Apache License, Version 2.0 (the "License"); #
18# you may not use this file except in compliance with the License. #
19# You may obtain a copy of the License at #
20# #
21# http://www.apache.org/licenses/LICENSE-2.0 #
22# #
23# Unless required by applicable law or agreed to in writing, software #
24# distributed under the License is distributed on an "AS IS" BASIS, #
25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
26# See the License for the specific language governing permissions and #
27# limitations under the License. #
28# #
29# SPDX-License-Identifier: Apache-2.0 #
30# ==================================================================================================================== #
31#
32"""
33This module implements command line arguments without prefix character(s).
36"""
37from abc import abstractmethod
38from pathlib import Path
39from typing import ClassVar, Union, Iterable, TypeVar, Generic, Any, Optional as Nullable
40from pyTooling.Decorators import export, readonly
41from pyTooling.MetaClasses import ExtendedType, abstractclass
42from pyTooling.Common import getFullyQualifiedName
45__all__ = ["ValueT"]
48ValueT = TypeVar("ValueT") #: The type of value in a valued argument.
51@export
52@abstractclass
53class CommandLineArgument(metaclass=ExtendedType):
54 """
55 Base-class for all *Argument* classes.
57 An argument instance can be converted via ``AsArgument`` to a single string value or a sequence of string values
58 (tuple) usable e.g. with :class:`subprocess.Popen`. Each argument class implements at least one ``pattern`` parameter
59 to specify how argument are formatted.
61 There are multiple derived formats supporting:
63 * commands |br|
64 |rarr| :mod:`~pyTooling.CLIAbstraction.Command`
65 * simple names (flags) |br|
66 |rarr| :mod:`~pyTooling.CLIAbstraction.Flag`, :mod:`~pyTooling.CLIAbstraction.BooleanFlag`
67 * simple values (vlaued flags) |br|
68 |rarr| :class:`~pyTooling.CLIAbstraction.Argument.StringArgument`, :class:`~pyTooling.CLIAbstraction.Argument.PathArgument`
69 * names and values |br|
70 |rarr| :mod:`~pyTooling.CLIAbstraction.ValuedFlag`, :mod:`~pyTooling.CLIAbstraction.OptionalValuedFlag`
71 * key-value pairs |br|
72 |rarr| :class:`~pyTooling.CLIAbstraction.KeyValueFlag.NamedKeyValuePairsArgument`
73 """
75 _pattern: ClassVar[str] #: Format string to render the argument on the command line.
77 def __init_subclass__(cls, *args: Any, pattern: Nullable[str] = None, **kwargs: Any) -> None:
78 """
79 This method is called when a class is derived.
81 :param args: Any positional arguments.
82 :param pattern: Optional, this pattern is used to format an argument. |br|
83 Default: ``None``.
84 :param kwargs: Any keyword argument.
85 """
86 super().__init_subclass__(*args, **kwargs)
87 cls._pattern = pattern
89 # TODO: Add property to read pattern
91 @abstractmethod
92 def AsArgument(self) -> Union[str, Iterable[str]]: # type: ignore[empty-body]
93 """
94 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
95 the internal name and value.
97 :returns: Formatted argument.
98 :raises NotImplementedError: This is an abstract method and must be overwritten by a subclass.
99 """
100 raise NotImplementedError(f"Method 'AsArgument' is an abstract method and must be implemented by a subclass.")
102 @abstractmethod
103 def __str__(self) -> str: # type: ignore[empty-body]
104 """
105 Return a string representation of this argument instance.
107 :returns: Argument formatted and enclosed in double quotes.
108 :raises NotImplementedError: This is an abstract method and must be overwritten by a subclass.
109 """
110 raise NotImplementedError(f"Method '__str__' is an abstract method and must be implemented by a subclass.")
112 @abstractmethod
113 def __repr__(self) -> str: # type: ignore[empty-body]
114 """
115 Return a string representation of this argument instance.
117 .. note:: By default, this method is identical to :meth:`__str__`.
119 :returns: Argument formatted and enclosed in double quotes.
120 :raises NotImplementedError: This is an abstract method and must be overwritten by a subclass.
121 """
122 raise NotImplementedError(f"Method '__repr__' is an abstract method and must be implemented by a subclass.")
125@export
126class ExecutableArgument(CommandLineArgument):
127 """
128 Represents the executable.
129 """
131 _executable: Path #: Path to the executable this argument represents.
133 def __init__(self, executable: Path) -> None:
134 """
135 Initializes a ExecutableArgument instance.
137 :param executable: Path to the executable.
138 :raises TypeError: If parameter 'executable' is not of type :class:`~pathlib.Path`.
139 """
140 if not isinstance(executable, Path):
141 ex = TypeError("Parameter 'executable' is not of type 'Path'.")
142 ex.add_note(f"Got type '{getFullyQualifiedName(executable)}'.")
143 raise ex
145 self._executable = executable
147 @property
148 def Executable(self) -> Path:
149 """
150 Property to access the path to the wrapped executable (:attr:`_executable`).
152 :returns: Internal path to the executable.
153 :raises TypeError: If an assigned value is not of type :class:`~pathlib.Path`.
154 """
155 return self._executable
157 @Executable.setter
158 def Executable(self, value: Path) -> None:
159 if not isinstance(value, Path):
160 ex = TypeError("Parameter 'value' is not of type 'Path'.")
161 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
162 raise ex
164 self._executable = value
166 def AsArgument(self) -> Union[str, Iterable[str]]:
167 """
168 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
169 the internal path to the wrapped executable.
171 :returns: Formatted argument.
172 """
173 return f"{self._executable}"
175 def __str__(self) -> str:
176 """
177 Return a string representation of this argument instance.
179 :returns: Argument formatted and enclosed in double quotes.
180 """
181 return f"\"{self._executable}\""
183 __repr__ = __str__
186@export
187class DelimiterArgument(CommandLineArgument, pattern="--"):
188 """
189 Represents a delimiter symbol like ``--``.
190 """
192 def __init_subclass__(cls, *args: Any, pattern: str = "--", **kwargs: Any) -> None:
193 """
194 This method is called when a class is derived.
196 :param args: Any positional arguments.
197 :param pattern: Optional, this pattern is used to format an argument. |br|
198 Default: ``"--"``.
199 :param kwargs: Any keyword argument.
200 """
201 kwargs["pattern"] = pattern
202 super().__init_subclass__(*args, **kwargs)
204 def AsArgument(self) -> Union[str, Iterable[str]]:
205 """
206 Convert this argument instance to a string representation with proper escaping using the matching pattern.
208 :returns: Formatted argument.
209 """
210 return self._pattern
212 def __str__(self) -> str:
213 """
214 Return a string representation of this argument instance.
216 :returns: Argument formatted and enclosed in double quotes.
217 """
218 return f"\"{self._pattern}\""
220 __repr__ = __str__
223@export
224@abstractclass
225class NamedArgument(CommandLineArgument, pattern="{0}"):
226 """
227 Base-class for all command line arguments with a name.
228 """
230 _name: ClassVar[str] #: Name of the argument, inserted into :attr:`_pattern`.
232 def __init_subclass__(cls, *args: Any, name: Nullable[str] = None, pattern: str = "{0}", **kwargs: Any) -> None:
233 """
234 This method is called when a class is derived.
236 :param args: Any positional arguments.
237 :param name: Optional, name of the CLI argument.
238 :param pattern: Optional, this pattern is used to format an argument. |br|
239 Default: ``"{0}"``.
240 :param kwargs: Any keyword argument.
241 """
242 kwargs["pattern"] = pattern
243 super().__init_subclass__(*args, **kwargs)
244 cls._name = name
246 @readonly
247 def Name(self) -> str:
248 """
249 Get the internal name.
251 :returns: Internal name.
252 """
253 return self._name
255 def AsArgument(self) -> Union[str, Iterable[str]]:
256 """
257 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
258 the internal name.
260 :returns: Formatted argument.
261 :raises ValueError: If internal name is None.
262 """
263 if self._name is None: 263 ↛ 264line 263 didn't jump to line 264 because the condition on line 263 was never true
264 raise ValueError(f"Internal value '_name' is None.")
266 return self._pattern.format(self._name)
268 def __str__(self) -> str:
269 """
270 Return a string representation of this argument instance.
272 :returns: Argument formatted and enclosed in double quotes.
273 """
274 return f"\"{self.AsArgument()}\""
276 __repr__ = __str__
279@export
280class ValuedArgument(CommandLineArgument, Generic[ValueT], pattern="{0}"):
281 """
282 Base-class for all command line arguments with a value.
283 """
285 _value: ValueT #: Value of the argument, inserted into :attr:`_pattern`.
287 def __init_subclass__(cls, *args: Any, pattern: str = "{0}", **kwargs: Any) -> None:
288 """
289 This method is called when a class is derived.
291 :param args: Any positional arguments.
292 :param pattern: Optional, this pattern is used to format an argument. |br|
293 Default: ``"{0}"``.
294 :param kwargs: Any keyword argument.
295 """
296 kwargs["pattern"] = pattern
297 super().__init_subclass__(*args, **kwargs)
299 def __init__(self, value: ValueT) -> None:
300 """
301 Initializes a ValuedArgument instance.
303 :param value: Value to be stored internally.
304 :raises ValueError: If parameter 'value' is None.
305 :raises ValueError: If parameter 'value' is None.
306 """
307 if value is None: 307 ↛ 308line 307 didn't jump to line 308 because the condition on line 307 was never true
308 raise ValueError("Parameter 'value' is None.")
310 self._value = value
312 @property
313 def Value(self) -> ValueT:
314 """
315 Property to access the internal value (:attr:`_value`).
317 :returns: Internal value.
318 :raises ValueError: If ``None`` is assigned.
319 """
320 return self._value
322 @Value.setter
323 def Value(self, value: ValueT) -> None:
324 if value is None: 324 ↛ 325line 324 didn't jump to line 325 because the condition on line 324 was never true
325 raise ValueError(f"Value to set is None.")
327 self._value = value
329 def AsArgument(self) -> Union[str, Iterable[str]]:
330 """
331 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
332 the internal value.
334 :returns: Formatted argument.
335 """
336 return self._pattern.format(self._value)
338 def __str__(self) -> str:
339 """
340 Return a string representation of this argument instance.
342 :returns: Argument formatted and enclosed in double quotes.
343 """
344 return f"\"{self.AsArgument()}\""
346 __repr__ = __str__
349class NamedAndValuedArgument(NamedArgument, ValuedArgument[ValueT], Generic[ValueT], pattern="{0}={1}"):
350 """
351 Base-class for all command line arguments with a name and a value.
352 """
354 def __init_subclass__(cls, *args: Any, name: Nullable[str] = None, pattern: str = "{0}={1}", **kwargs: Any) -> None:
355 """
356 This method is called when a class is derived.
358 :param args: Any positional arguments.
359 :param name: Optional, name of the CLI argument.
360 :param pattern: Optional, this pattern is used to format an argument. |br|
361 Default: ``"{0}={1}"``.
362 :param kwargs: Any keyword argument.
363 """
364 kwargs["name"] = name
365 kwargs["pattern"] = pattern
366 super().__init_subclass__(*args, **kwargs)
367 del kwargs["name"]
368 del kwargs["pattern"]
369 ValuedArgument.__init_subclass__(*args, **kwargs)
371 def __init__(self, value: ValueT) -> None:
372 """
373 Initialize the argument with the value rendered into its pattern.
375 :param value: Value of the argument.
376 """
377 ValuedArgument.__init__(self, value)
379 def AsArgument(self) -> Union[str, Iterable[str]]:
380 """
381 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
382 the internal name and value.
384 :returns: Formatted argument.
385 :raises ValueError: If internal name is None.
386 """
387 if self._name is None: 387 ↛ 388line 387 didn't jump to line 388 because the condition on line 387 was never true
388 raise ValueError(f"Internal value '_name' is None.")
390 return self._pattern.format(self._name, self._value)
392 def __str__(self) -> str:
393 """
394 Return a string representation of this argument instance.
396 :returns: Argument formatted and enclosed in double quotes.
397 """
398 return f"\"{self.AsArgument()}\""
400 __repr__ = __str__
403@abstractclass
404class NamedTupledArgument(NamedArgument, ValuedArgument[ValueT], Generic[ValueT], pattern="{0}"):
405 """
406 Class and base-class for all TupleFlag classes, which represents an argument with separate value.
408 A tuple argument is a command line argument followed by a separate value. Name and value are passed as two arguments
409 to the executable.
411 **Example: **
413 * `width 100``
414 """
416 _valuePattern: ClassVar[str] #: Format string to render the argument's value as a second command line element.
418 def __init_subclass__(cls, *args: Any, name: Nullable[str] = None, pattern: str = "{0}", valuePattern: str = "{0}", **kwargs: Any) -> None:
419 """
420 This method is called when a class is derived.
422 :param args: Any positional arguments.
423 :param name: Optional, name of the CLI argument.
424 :param pattern: Optional, this pattern is used to format the CLI argument name. |br|
425 Default: ``"{0}"``.
426 :param valuePattern: Optional, this pattern is used to format the value. |br|
427 Default: ``"{0}"``.
428 :param kwargs: Any keyword argument.
429 """
430 kwargs["name"] = name
431 kwargs["pattern"] = pattern
432 super().__init_subclass__(*args, **kwargs)
433 cls._valuePattern = valuePattern
435 def __init__(self, value: ValueT) -> None:
436 """
437 Initialize the argument with the value rendered into its pattern.
439 :param value: Value of the argument.
440 """
441 ValuedArgument.__init__(self, value)
443 # TODO: Add property to read value pattern
445 # @property
446 # def ValuePattern(self) -> str:
447 # if self._valuePattern is None:
448 # raise ValueError("Internal value '_valuePattern' is None.")
449 #
450 # return self._valuePattern
452 def AsArgument(self) -> Union[str, Iterable[str]]:
453 """
454 Convert this argument instance to a sequence of string representations with proper escaping using the matching
455 pattern based on the internal name and value.
457 :returns: Formatted argument as tuple of strings.
458 :raises ValueError: If internal name is None.
459 """
460 if self._name is None: 460 ↛ 461line 460 didn't jump to line 461 because the condition on line 460 was never true
461 raise ValueError(f"Internal value '_name' is None.")
463 return (
464 self._pattern.format(self._name),
465 self._valuePattern.format(self._value)
466 )
468 def __str__(self) -> str:
469 """
470 Return a string representation of this argument instance.
472 :returns: Space separated sequence of arguments formatted and each enclosed in double quotes.
473 """
474 return " ".join([f"\"{item}\"" for item in self.AsArgument()])
476 def __repr__(self) -> str:
477 """
478 Return a string representation of this argument instance.
480 :returns: Comma separated sequence of arguments formatted and each enclosed in double quotes.
481 """
482 return ", ".join([f"\"{item}\"" for item in self.AsArgument()])
485@export
486class StringArgument(ValuedArgument[str], pattern="{0}"):
487 """
488 Represents a simple string argument.
490 A list of strings is available as :class:`~pyTooling.CLIAbstraction.Argument.StringListArgument`.
491 """
493 def __init_subclass__(cls, *args: Any, pattern: str = "{0}", **kwargs: Any) -> None:
494 """
495 This method is called when a class is derived.
497 :param args: Any positional arguments.
498 :param pattern: Optional, this pattern is used to format an argument. |br|
499 Default: ``"{0}"``.
500 :param kwargs: Any keyword argument.
501 """
502 kwargs["pattern"] = pattern
503 super().__init_subclass__(*args, **kwargs)
506@export
507class StringListArgument(ValuedArgument[str]):
508 """
509 Represents a list of string argument (:class:`~pyTooling.CLIAbstraction.Argument.StringArgument`)."""
511 def __init__(self, values: Iterable[str]) -> None:
512 """
513 Initializes a StringListArgument instance.
515 :param values: An iterable of str instances.
516 :raises TypeError: If iterable parameter 'values' contains elements not of type string.
517 """
518 self._values = []
519 for value in values:
520 if not isinstance(value, str): 520 ↛ 521line 520 didn't jump to line 521 because the condition on line 520 was never true
521 ex = TypeError(f"Parameter 'values' contains elements which are not of type 'str'.")
522 ex.add_note(f"Got type '{getFullyQualifiedName(values)}'.")
523 raise ex
525 self._values.append(value)
527 @property
528 def Value(self) -> list[str]:
529 """
530 Property to access the internal list of str objects (:attr:`_values`).
532 .. note:: On assignment, the list object is not replaced, but cleared and then reused by adding the given elements
533 of the iterable.
535 :returns: Reference to the internal list of str objects.
536 :raises TypeError: If an assigned iterable contains elements which are not of type string.
537 """
538 return self._values
540 @Value.setter
541 def Value(self, value: Iterable[str]) -> None:
542 self._values.clear()
543 for value in value:
544 if not isinstance(value, str):
545 ex = TypeError(f"Value contains elements which are not of type 'str'.")
546 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
547 raise ex
548 self._values.append(value)
550 def AsArgument(self) -> Union[str, Iterable[str]]:
551 """
552 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
553 the internal value.
555 :returns: Sequence of formatted arguments.
556 """
557 return [f"{value}" for value in self._values]
559 def __str__(self) -> str:
560 """
561 Return a string representation of this argument instance.
563 :returns: Space separated sequence of arguments formatted and each enclosed in double quotes.
564 """
565 return " ".join([f"\"{value}\"" for value in self.AsArgument()])
567 def __repr__(self) -> str:
568 """
569 Return a string representation of this argument instance.
571 :returns: Comma separated sequence of arguments formatted and each enclosed in double quotes.
572 """
573 return ", ".join([f"\"{value}\"" for value in self.AsArgument()])
576# TODO: Add option to class if path should be checked for existence
577@export
578class PathArgument(CommandLineArgument):
579 """
580 Represents a single path argument.
582 A list of paths is available as :class:`~pyTooling.CLIAbstraction.Argument.PathListArgument`.
583 """
584 # The output format can be forced to the POSIX format with :py:data:`_PosixFormat`.
585 _path: Path #: Path this argument represents.
587 def __init__(self, path: Path) -> None:
588 """
589 Initializes a PathArgument instance.
591 :param path: Path to a filesystem object.
592 :raises TypeError: If parameter 'path' is not of type :class:`~pathlib.Path`.
593 """
594 if not isinstance(path, Path): 594 ↛ 595line 594 didn't jump to line 595 because the condition on line 594 was never true
595 ex = TypeError("Parameter 'path' is not of type 'Path'.")
596 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
597 raise ex
598 self._path = path
600 @property
601 def Value(self) -> Path:
602 """
603 Property to access the internal path object (:attr:`_path`).
605 :returns: Internal path object.
606 :raises TypeError: If an assigned value is not of type :class:`~pathlib.Path`.
607 """
608 return self._path
610 @Value.setter
611 def Value(self, value: Path) -> None:
612 if not isinstance(value, Path): 612 ↛ 613line 612 didn't jump to line 613 because the condition on line 612 was never true
613 ex = TypeError("Value is not of type 'Path'.")
614 ex.add_note(f"Got type '{getFullyQualifiedName(value)}'.")
615 raise ex
617 self._path = value
619 def AsArgument(self) -> Union[str, Iterable[str]]:
620 """
621 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
622 the internal value.
624 :returns: Formatted argument.
625 """
626 return f"{self._path}"
628 def __str__(self) -> str:
629 """
630 Return a string representation of this argument instance.
632 :returns: Argument formatted and enclosed in double quotes.
633 """
634 return f"\"{self._path}\""
636 __repr__ = __str__
639@export
640class PathListArgument(CommandLineArgument):
641 """
642 Represents a list of path arguments (:class:`~pyTooling.CLIAbstraction.Argument.PathArgument`).
643 """
644 # The output format can be forced to the POSIX format with :py:data:`_PosixFormat`.
645 _paths: list[Path] #: Paths this argument represents.
647 def __init__(self, paths: Iterable[Path]) -> None:
648 """
649 Initializes a PathListArgument instance.
651 :param paths: An iterable os Path instances.
652 :raises TypeError: If iterable parameter 'paths' contains elements not of type :class:`~pathlib.Path`.
653 """
654 self._paths = []
655 for path in paths:
656 if not isinstance(path, Path): 656 ↛ 657line 656 didn't jump to line 657 because the condition on line 656 was never true
657 ex = TypeError(f"Parameter 'paths' contains elements which are not of type 'Path'.")
658 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
659 raise ex
661 self._paths.append(path)
663 @property
664 def Value(self) -> list[Path]:
665 """
666 Property to access the internal list of path objects (:attr:`_paths`).
668 .. note:: On assignment, the list object is not replaced, but cleared and then reused by adding the given elements
669 of the iterable.
671 :returns: Reference to the internal list of path objects.
672 :raises TypeError: If an assigned iterable contains elements which are not of type :class:`~pathlib.Path`.
673 """
674 return self._paths
676 @Value.setter
677 def Value(self, value: Iterable[Path]) -> None:
678 self._paths.clear()
679 for path in value:
680 if not isinstance(path, Path):
681 ex = TypeError(f"Value contains elements which are not of type 'Path'.")
682 ex.add_note(f"Got type '{getFullyQualifiedName(path)}'.")
683 raise ex
684 self._paths.append(path)
686 def AsArgument(self) -> Union[str, Iterable[str]]:
687 """
688 Convert this argument instance to a string representation with proper escaping using the matching pattern based on
689 the internal value.
691 :returns: Sequence of formatted arguments.
692 """
693 return [f"{path}" for path in self._paths]
695 def __str__(self) -> str:
696 """
697 Return a string representation of this argument instance.
699 :returns: Space separated sequence of arguments formatted and each enclosed in double quotes.
700 """
701 return " ".join([f"\"{value}\"" for value in self.AsArgument()])
703 def __repr__(self) -> str:
704 """
705 Return a string representation of this argument instance.
707 :returns: Comma separated sequence of arguments formatted and each enclosed in double quotes.
708 """
709 return ", ".join([f"\"{value}\"" for value in self.AsArgument()])