From 1ac3dcb9034082a936e08e0a5e1de23b8850aabf Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Wed, 20 Mar 2024 10:56:40 +0100 Subject: [PATCH 1/5] Create LeftRightLogicalSubstitution This reduces the duplicate code Signed-off-by: Matthijs van der Burgh --- .../substitutions/boolean_substitution.py | 75 +++++++------------ 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/launch/launch/substitutions/boolean_substitution.py b/launch/launch/substitutions/boolean_substitution.py index 14b57ae60..df73494c5 100644 --- a/launch/launch/substitutions/boolean_substitution.py +++ b/launch/launch/substitutions/boolean_substitution.py @@ -14,6 +14,7 @@ """Module for boolean substitutions.""" +from typing import Callable from typing import Iterable from typing import List from typing import Sequence @@ -25,6 +26,7 @@ from ..some_substitutions_type import SomeSubstitutionsType from ..substitution import Substitution from ..utilities import normalize_to_list_of_substitutions +from ..utilities.type_utils import StrSomeValueType from ..utilities.type_utils import perform_typed_substitution @@ -64,14 +66,14 @@ def perform(self, context: LaunchContext) -> Text: return str(not condition).lower() -@expose_substitution('and') -class AndSubstitution(Substitution): - """Substitution that returns 'and' of the input boolean values.""" +class LeftRightLogicalSubstitution(Substitution): + """Substitution that returns the result of logical evaluation of the input boolean values.""" - def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: - """Create an AndSubstitution substitution.""" + def __init__(self, func: Callable[[bool, bool], bool], left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: + """Create an LeftRightLogicalSubstitution substitution.""" super().__init__() + self.__func = func self.__left = normalize_to_list_of_substitutions(left) self.__right = normalize_to_list_of_substitutions(right) @@ -79,9 +81,14 @@ def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> def parse(cls, data: Sequence[SomeSubstitutionsType]): """Parse `AndSubstitution` substitution.""" if len(data) != 2: - raise TypeError('and substitution expects 2 arguments') + raise TypeError(f'{self.__class__.__name__} expects 2 arguments') return cls, {'left': data[0], 'right': data[1]} + @property + def func(self) -> Callable[[bool, bool], bool]: + """Getter for the logical evaluation function.""" + return self.__func + @property def left(self) -> List[Substitution]: """Getter for left.""" @@ -94,20 +101,29 @@ def right(self) -> List[Substitution]: def describe(self) -> Text: """Return a description of this substitution as a string.""" - return f'AndSubstitution({self.left} {self.right})' + return f'{self.__class__.__name__}({self.left} {self.right})' def perform(self, context: LaunchContext) -> Text: """Perform the substitution.""" try: - left_condition = perform_typed_substitution(context, self.left, bool) + left_condition: bool = perform_typed_substitution(context, self.left, bool) except (TypeError, ValueError) as e: raise SubstitutionFailure(e) try: - right_condition = perform_typed_substitution(context, self.right, bool) + right_condition: bool = perform_typed_substitution(context, self.right, bool) except (TypeError, ValueError) as e: raise SubstitutionFailure(e) - return str(left_condition and right_condition).lower() + return str(self.func(left_condition, right_condition)).lower() + + +@expose_substitution('and') +class AndSubstitution(LeftRightLogicalSubstitution): + """Substitution that returns 'and' of the input boolean values.""" + + def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: + """Create an AndSubstitution substitution.""" + super().__init__(lambda l, r: l and r, left, right) @expose_substitution('or') @@ -116,44 +132,7 @@ class OrSubstitution(Substitution): def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: """Create an OrSubstitution substitution.""" - super().__init__() - - self.__left = normalize_to_list_of_substitutions(left) - self.__right = normalize_to_list_of_substitutions(right) - - @classmethod - def parse(cls, data: Sequence[SomeSubstitutionsType]): - """Parse `OrSubstitution` substitution.""" - if len(data) != 2: - raise TypeError('and substitution expects 2 arguments') - return cls, {'left': data[0], 'right': data[1]} - - @property - def left(self) -> List[Substitution]: - """Getter for left.""" - return self.__left - - @property - def right(self) -> List[Substitution]: - """Getter for right.""" - return self.__right - - def describe(self) -> Text: - """Return a description of this substitution as a string.""" - return f'AndSubstitution({self.left} {self.right})' - - def perform(self, context: LaunchContext) -> Text: - """Perform the substitution.""" - try: - left_condition = perform_typed_substitution(context, self.left, bool) - except (TypeError, ValueError) as e: - raise SubstitutionFailure(e) - try: - right_condition = perform_typed_substitution(context, self.right, bool) - except (TypeError, ValueError) as e: - raise SubstitutionFailure(e) - - return str(left_condition or right_condition).lower() + super().__init__(lambda l, r: l or r, left, right) @expose_substitution('any') From 7f942aed255098b3694a8549f47426346384b965 Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Wed, 20 Mar 2024 11:06:27 +0100 Subject: [PATCH 2/5] Fix parent type of OrSubstitution Signed-off-by: Matthijs van der Burgh --- launch/launch/substitutions/boolean_substitution.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launch/launch/substitutions/boolean_substitution.py b/launch/launch/substitutions/boolean_substitution.py index df73494c5..88c4860fe 100644 --- a/launch/launch/substitutions/boolean_substitution.py +++ b/launch/launch/substitutions/boolean_substitution.py @@ -127,7 +127,7 @@ def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> @expose_substitution('or') -class OrSubstitution(Substitution): +class OrSubstitution(LeftRightLogicalSubstitution): """Substitution that returns 'or' of the input boolean values.""" def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: From 0bdb8b8e9d09e0dd7658867899097d606a0bc211 Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Wed, 20 Mar 2024 11:20:10 +0100 Subject: [PATCH 3/5] Small fixes Signed-off-by: Matthijs van der Burgh --- launch/launch/substitutions/boolean_substitution.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launch/launch/substitutions/boolean_substitution.py b/launch/launch/substitutions/boolean_substitution.py index 88c4860fe..e61713ed3 100644 --- a/launch/launch/substitutions/boolean_substitution.py +++ b/launch/launch/substitutions/boolean_substitution.py @@ -81,7 +81,7 @@ def __init__(self, func: Callable[[bool, bool], bool], left: SomeSubstitutionsTy def parse(cls, data: Sequence[SomeSubstitutionsType]): """Parse `AndSubstitution` substitution.""" if len(data) != 2: - raise TypeError(f'{self.__class__.__name__} expects 2 arguments') + raise TypeError(f'{cls.__name__} expects 2 arguments') return cls, {'left': data[0], 'right': data[1]} @property @@ -106,11 +106,11 @@ def describe(self) -> Text: def perform(self, context: LaunchContext) -> Text: """Perform the substitution.""" try: - left_condition: bool = perform_typed_substitution(context, self.left, bool) + left_condition = perform_typed_substitution(context, self.left, bool) except (TypeError, ValueError) as e: raise SubstitutionFailure(e) try: - right_condition: bool = perform_typed_substitution(context, self.right, bool) + right_condition = perform_typed_substitution(context, self.right, bool) except (TypeError, ValueError) as e: raise SubstitutionFailure(e) From 0e945fe39da691b18fdb067519bd06b67cf0c913 Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Wed, 20 Mar 2024 11:27:09 +0100 Subject: [PATCH 4/5] Fix typing Signed-off-by: Matthijs van der Burgh --- launch/launch/substitutions/boolean_substitution.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launch/launch/substitutions/boolean_substitution.py b/launch/launch/substitutions/boolean_substitution.py index e61713ed3..83384386c 100644 --- a/launch/launch/substitutions/boolean_substitution.py +++ b/launch/launch/substitutions/boolean_substitution.py @@ -69,7 +69,7 @@ def perform(self, context: LaunchContext) -> Text: class LeftRightLogicalSubstitution(Substitution): """Substitution that returns the result of logical evaluation of the input boolean values.""" - def __init__(self, func: Callable[[bool, bool], bool], left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: + def __init__(self, func: Callable[[StrSomeValueType, StrSomeValueType], bool], left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None: """Create an LeftRightLogicalSubstitution substitution.""" super().__init__() @@ -85,7 +85,7 @@ def parse(cls, data: Sequence[SomeSubstitutionsType]): return cls, {'left': data[0], 'right': data[1]} @property - def func(self) -> Callable[[bool, bool], bool]: + def func(self) -> Callable[[StrSomeValueType, StrSomeValueType], bool]: """Getter for the logical evaluation function.""" return self.__func From 94f6f0791e873d61c81df89d8f1da10fec562ab2 Mon Sep 17 00:00:00 2001 From: Matthijs van der Burgh Date: Mon, 8 Apr 2024 14:42:22 +0200 Subject: [PATCH 5/5] (launch) create ContainerSubstitution Signed-off-by: Matthijs van der Burgh --- .../substitutions/boolean_substitution.py | 76 ++++++++----------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/launch/launch/substitutions/boolean_substitution.py b/launch/launch/substitutions/boolean_substitution.py index 83384386c..a376f5228 100644 --- a/launch/launch/substitutions/boolean_substitution.py +++ b/launch/launch/substitutions/boolean_substitution.py @@ -135,22 +135,19 @@ def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> super().__init__(lambda l, r: l or r, left, right) -@expose_substitution('any') -class AnySubstitution(Substitution): - """ - Substitutes to the string 'true' if at least one of the input arguments evaluates to true. +class ContainerSubstitution(Substitution): + """Substitution that returns the result of a logical evaluation on the boolean values of the container.""" - If none of the arguments evaluate to true, then this substitution returns the string 'false'. - """ - - def __init__(self, *args: SomeSubstitutionsType) -> None: + def __init__(self, func: Callable[[StrSomeValueType], bool], *args: SomeSubstitutionsType) -> None: """ - Create an AnySubstitution substitution. + Create a ContainerSubstitution substitution. The following string arguments evaluate to true: '1', 'true', 'True', 'on' + The following string arguments evaluate to false: '0', 'false', 'False', 'off' """ super().__init__() + self.__func = func self.__args = [normalize_to_list_of_substitutions(arg) for arg in args] @classmethod @@ -158,6 +155,11 @@ def parse(cls, data: Iterable[SomeSubstitutionsType]): """Parse `AnySubstitution` substitution.""" return cls, {'args': data} + @property + def func(self) -> Callable[[StrSomeValueType], bool]: + """Getter for the logical evaluation function.""" + return self.__func + @property def args(self) -> List[List[Substitution]]: """Getter for args.""" @@ -165,11 +167,11 @@ def args(self) -> List[List[Substitution]]: def describe(self) -> Text: """Return a description of this substitution as a string.""" - return f'AnySubstitution({" ".join(str(arg) for arg in self.args)})' + return f'{self.__class__.__name__}({" ".join(str(arg) for arg in self.args)})' def perform(self, context: LaunchContext) -> Text: """Perform the substitution.""" - substituted_conditions = [] + substituted_conditions: List[StrSomeValueType] = [] for arg in self.args: try: arg_condition = perform_typed_substitution(context, arg, bool) @@ -177,50 +179,34 @@ def perform(self, context: LaunchContext) -> Text: except (TypeError, ValueError) as e: raise SubstitutionFailure(e) - return str(any(substituted_conditions)).lower() + return str(self.func(substituted_conditions)).lower() -@expose_substitution('all') -class AllSubstitution(Substitution): +@expose_substitution('any') +class AnySubstitution(ContainerSubstitution): """ - Substitutes to the string 'true' if all of the input arguments evaluate to true. + Substitutes to the string 'true' if at least one of the input arguments evaluates to true. - If any of the arguments evaluates to false, then this substitution returns the string 'false'. + If none of the arguments evaluate to true, then this substitution returns the string 'false'. """ def __init__(self, *args: SomeSubstitutionsType) -> None: """ - Create an AllSubstitution substitution. - - The following string arguments evaluate to true: '1', 'true', 'True', 'on' - The following string arguments evaluate to false: '0', 'false', 'False', 'off' + Create an AnySubstitution substitution. """ - super().__init__() + super().__init__(func=any, *args) - self.__args = [normalize_to_list_of_substitutions(arg) for arg in args] - @classmethod - def parse(cls, data: Iterable[SomeSubstitutionsType]): - """Parse `AllSubstitution` substitution.""" - return cls, {'args': data} - - @property - def args(self) -> List[List[Substitution]]: - """Getter for args.""" - return self.__args - - def describe(self) -> Text: - """Return a description of this substitution as a string.""" - return f'AllSubstitution({" ".join(str(arg) for arg in self.args)})' +@expose_substitution('all') +class AllSubstitution(ContainerSubstitution): + """ + Substitutes to the string 'true' if all the input arguments evaluate to true. - def perform(self, context: LaunchContext) -> Text: - """Perform the substitution.""" - substituted_conditions = [] - for arg in self.args: - try: - arg_condition = perform_typed_substitution(context, arg, bool) - substituted_conditions.append(arg_condition) - except (TypeError, ValueError) as e: - raise SubstitutionFailure(e) + If any of the arguments evaluates to false, then this substitution returns the string 'false'. + """ - return str(all(substituted_conditions)).lower() + def __init__(self, *args: SomeSubstitutionsType) -> None: + """ + Create an AllSubstitution substitution. + """ + super().__init__(func=all, *args)