Skip to content

Commit

Permalink
* Updated entity abstraction to allow get typed values.
Browse files Browse the repository at this point in the history
* Updated xml entity with above change.
* Updated action parsing methods.

Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
  • Loading branch information
ivanpauno committed May 10, 2019
1 parent f2376db commit 58d958d
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 122 deletions.
12 changes: 9 additions & 3 deletions launch_frontend/launch_frontend/__init__.py
Expand Up @@ -15,19 +15,25 @@
"""Main entry point for the `launch_frontend` package."""

# All files containing parsing methods should be imported here.
# If not, the action or substitution isn't going to be exposed.
# If not, the action or substitution are not going to be exposed.
from . import action_parse_methods # noqa: F401
from . import substitution_parse_methods # noqa: F401
from . import type_utils
from .entity import Entity
from .expose import __expose_impl, expose_action, expose_substitution
from .parser import Parser


__all__ = [
# Classes
'Entity',
# Implementation detail, should only be imported in test_expose_decorators.
'__expose_impl',
# Decorators
'expose_action',
'expose_substitution',
'Parser',
# Modules
'type_utils',

# Implementation detail, should only be imported in test_expose_decorators.
'__expose_impl',
]
49 changes: 24 additions & 25 deletions launch_frontend/launch_frontend/action_parse_methods.py
Expand Up @@ -21,7 +21,6 @@
from launch.conditions import UnlessCondition
from launch.substitutions import TextSubstitution

from .convert_text_to import str_to_bool
from .entity import Entity
from .expose import expose_action
from .parser import Parser
Expand All @@ -30,37 +29,44 @@
@expose_action('executable')
def parse_executable(entity: Entity, parser: Parser):
"""Parse executable tag."""
cmd = parser.parse_substitution(entity.cmd)
cmd = parser.parse_substitution(entity.get_attr('cmd'))
kwargs = {}
cwd = getattr(entity, 'cwd', None)
cwd = entity.get_attr('cwd', optional=True)
if cwd is not None:
kwargs['cwd'] = parser.parse_substitution(cwd)
name = getattr(entity, 'name', None)
name = entity.get_attr('name', optional=True)
if name is not None:
kwargs['name'] = parser.parse_substitution(name)
prefix = getattr(entity, 'launch-prefix', None)
prefix = entity.get_attr('launch-prefix', optional=True)
if prefix is not None:
kwargs['prefix'] = parser.parse_substitution(prefix)
output = getattr(entity, 'output', None)
output = entity.get_attr('output', optional=True)
if output is not None:
kwargs['output'] = output
shell = getattr(entity, 'shell', None)
shell = entity.get_attr('shell', types='bool', optional=True)
if shell is not None:
kwargs['shell'] = str_to_bool(shell)
kwargs['shell'] = shell
# Conditions won't be allowed in the `env` tag.
# If that feature is needed, `set_enviroment_variable` and
# `unset_enviroment_variable` actions should be used.
env = getattr(entity, 'env', None)
env = entity.get_attr('env', types='list[Entity]', optional=True)
if env is not None:
env = {e.name: parser.parse_substitution(e.value) for e in env}
# TODO(ivanpauno): Change `ExecuteProcess` api. `additional_env`
# argument is supposed to be a dictionary with `SomeSubstitutionType`
# keys, but `SomeSubstitutionType` is not always hashable.
# Proposed `additional_env` type:
# Iterable[Tuple[SomeSubstitutionType, SomeSubstitutionsType]]
env = {e.get_attr('name'): parser.parse_substitution(e.get_attr('value')) for e in env}
kwargs['additional_env'] = env
args = getattr(entity, 'args', None)
args = entity.get_attr('args', optional=True)
# `args` is supposed to be a list separated with ' '.
# All the found `TextSubstitution` items are split and
# added to the list again as a `TextSubstitution`.
# Another option: Enforce to explicetly write a list in
# the launch file (if that's wanted)
# In xml 'args' and 'args-sep' tags should be used.
# TODO(ivanpauno): Change `ExecuteProcess` api from accepting
# `Iterable[SomeSubstitutionType]` `cmd` to `SomeSubstitutionType`.
# After performing the substitution in `cmd`, shlex.split should be done.
# This will also allow having a substitution which ends in more than one
# argument.
if args is not None:
args = parser.parse_substitution(args)
new_args = []
Expand All @@ -75,17 +81,10 @@ def parse_executable(entity: Entity, parser: Parser):
args = new_args
else:
args = []
# Option 2:
# if args is not None:
# if isinstance(args, Text):
# args = [args]
# args = [parser.parse_substitution(arg) for arg in args]
# else:
# args = []
cmd_list = [cmd]
cmd_list.extend(args)
if_cond = getattr(entity, 'if', None)
unless_cond = getattr(entity, 'unless', None)
if_cond = entity.get_attr('if', optional=True)
unless_cond = entity.get_attr('unless', optional=True)
if if_cond is not None and unless_cond is not None:
raise RuntimeError("if and unless conditions can't be usede simultaneously")
if if_cond is not None:
Expand All @@ -102,8 +101,8 @@ def parse_executable(entity: Entity, parser: Parser):
@expose_action('let')
def parse_let(entity: Entity, parser: Parser):
"""Parse let tag."""
name = parser.parse_substitution(entity.var)
value = parser.parse_substitution(entity.value)
name = parser.parse_substitution(entity.get_attr('name'))
value = parser.parse_substitution(entity.get_attr('value'))
return launch.actions.SetLaunchConfiguration(
name,
value
Expand Down
68 changes: 0 additions & 68 deletions launch_frontend/launch_frontend/convert_text_to.py

This file was deleted.

64 changes: 59 additions & 5 deletions launch_frontend/launch_frontend/entity.py
Expand Up @@ -14,10 +14,11 @@

"""Module for Entity class."""

from typing import Any
from typing import List
from typing import Optional
from typing import Text
from typing import Tuple
from typing import Union


class Entity:
Expand All @@ -34,10 +35,63 @@ def parent(self) -> Optional['Entity']:
raise NotImplementedError()

@property
def children(self) -> Optional[List['Entity']]:
"""Get Entity children."""
def children(self) -> List['Entity']:
"""Get the Entity's children."""
raise NotImplementedError()

def __getattr__(self, name: Text) -> Optional[Any]:
"""Get attribute."""
def get_attr(
self,
name: Text,
*,
types: Union[Text, Tuple[Text]] = 'str',
optional: bool = False
) -> Optional[Union[
Text,
int,
float,
List[Text],
List[int],
List[float],
List['Entity']
]]:
"""
Access an element in the tree.
By default, it will try to return it as an string.
`types` is used in the following way:
- For frontends that natively recoginize data types (like yaml),
it will check if the attribute read match with one in `types`.
If it is one of them, the value is returned.
If not, an `TypeError` is raised.
- For frontends that don't natively recognize data types (like xml),
it will try to convert the value read to one of the specified `types`.
The first convertion that success is returned.
If no conversion success, a `TypeError` is raised.
The allowed types are:
- 'str'
- 'int'
- 'float'
- 'bool'
- 'list[str]'
- 'list[int]'
- 'list[float]'
- 'list[bool]'
Types that can not be combined with the others:
- 'guess'
- 'list[Entity]'
'guess' work in the same way as:
('float', 'int', 'list[float]', 'list[int]', 'list[str]', 'str').
'list[Entity]' will return a list of more enties.
See the frontend documentation to see how 'list' and 'list[Entity]' look like.
If `optional` argument is `True`, will return `None` instead of raising `AttributeError`.
Possible errors:
- `AttributeError`: Attribute not found. Only possible if `optional` is `False`.
- `TypeError`: Attribute found but it is not of the correct type.
"""
raise NotImplementedError()

0 comments on commit 58d958d

Please sign in to comment.