Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shared logic for substitutions #769

Open
wants to merge 5 commits into
base: rolling
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 57 additions & 92 deletions launch/launch/substitutions/boolean_substitution.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Module for boolean substitutions."""

from typing import Callable
from typing import Iterable
from typing import List
from typing import Sequence
Expand All @@ -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


Expand Down Expand Up @@ -64,24 +66,29 @@ 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[[StrSomeValueType, StrSomeValueType], 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)

@classmethod
def parse(cls, data: Sequence[SomeSubstitutionsType]):
"""Parse `AndSubstitution` substitution."""
if len(data) != 2:
raise TypeError('and substitution expects 2 arguments')
raise TypeError(f'{cls.__name__} expects 2 arguments')
return cls, {'left': data[0], 'right': data[1]}

@property
def func(self) -> Callable[[StrSomeValueType, StrSomeValueType], bool]:
"""Getter for the logical evaluation function."""
return self.__func

@property
def left(self) -> List[Substitution]:
"""Getter for left."""
Expand All @@ -94,7 +101,7 @@ 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."""
Expand All @@ -107,141 +114,99 @@ def perform(self, context: LaunchContext) -> Text:
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('or')
class OrSubstitution(Substitution):
"""Substitution that returns 'or' of the input boolean values."""
@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 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})'
"""Create an AndSubstitution substitution."""
super().__init__(lambda l, r: l and r, left, 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()
@expose_substitution('or')
class OrSubstitution(LeftRightLogicalSubstitution):
"""Substitution that returns 'or' of the input boolean values."""

def __init__(self, left: SomeSubstitutionsType, right: SomeSubstitutionsType) -> None:
"""Create an OrSubstitution substitution."""
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.

If none of the arguments evaluate to true, then this substitution returns the string 'false'.
"""
class ContainerSubstitution(Substitution):
"""Substitution that returns the result of a logical evaluation on the boolean values of the container."""

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
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."""
return self.__args

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)
substituted_conditions.append(arg_condition)
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__()

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}
super().__init__(func=any, *args)

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