Source code for launch.frontend.parse_substitution

# Copyright 2019 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for parsing substitutions."""

import os
import re
from typing import Text

from ament_index_python.packages import get_package_share_directory

from lark import Lark
from lark import Token
from lark import Transformer

from .expose import instantiate_substitution
from ..substitutions import TextSubstitution
from ..utilities.type_utils import NormalizedValueType
from ..utilities.type_utils import StrSomeValueType


[docs] def replace_escaped_characters(data: Text) -> Text: """Search escaped characters and replace them.""" return re.sub(r'\\(.)', r'\1', data)
[docs] class ExtractSubstitution(Transformer): """Extract a substitution."""
[docs] def part(self, content): assert len(content) == 1 content = content[0] if isinstance(content, Token): assert content.type.endswith('_RSTRING') return TextSubstitution(text=replace_escaped_characters(content.value)) return content
single_quoted_part = part double_quoted_part = part
[docs] def value(self, parts): if len(parts) == 1 and isinstance(parts[0], list): # Deal with single and double quoted templates return parts[0] return parts
single_quoted_value = value double_quoted_value = value
[docs] def arguments(self, values): if len(values) > 1: # Deal with tail recursive argument parsing return [*values[0], values[1]] return values
single_quoted_arguments = arguments double_quoted_arguments = arguments
[docs] def substitution(self, args): assert len(args) >= 1 name = args[0] assert isinstance(name, Token) assert name.type == 'IDENTIFIER' return instantiate_substitution(name.value, *args[1:])
single_quoted_substitution = substitution double_quoted_substitution = substitution
[docs] def fragment(self, content): assert len(content) == 1 content = content[0] if isinstance(content, Token): assert content.type.endswith('_STRING') return TextSubstitution(text=replace_escaped_characters(content.value)) return content
single_quoted_fragment = fragment double_quoted_fragment = fragment
[docs] def template(self, fragments): return fragments
single_quoted_template = template double_quoted_template = template
[docs] def get_grammar_path() -> str: return os.path.join( get_package_share_directory('launch'), 'frontend', 'grammar.lark')
_parser = None
[docs] def parse_substitution(string_value): global _parser if not string_value: # Grammar cannot deal with zero-width expressions. return [TextSubstitution(text=string_value)] if _parser is None: with open(get_grammar_path(), 'r') as h: _parser = Lark(h, start='template') tree = _parser.parse(string_value) transformer = ExtractSubstitution() return transformer.transform(tree)
[docs] def parse_if_substitutions( value: StrSomeValueType ) -> NormalizedValueType: """ Parse substitutions in `value`, if there are any, and return a normalized value type. If `value` is a `str`, substitutions will be interpolated in it. If `value` is any other scalar type, it will be returned as-is. If `value` is a list, the two rules above will be applied to each item. When interpolating substitutions in a string, `TextSubstitution` instances are resolved and the original `str` is left. :raise: `ValueError` if the result cannot be parsed into a valid type. """ data_types = set() def _parse_if(value): if isinstance(value, str): output = parse_substitution(value) if len(output) == 1 and isinstance(output[0], TextSubstitution): data_types.add(str) return output[0].text return output data_types.add(type(value)) return value if isinstance(value, list): output = [_parse_if(x) for x in value] else: output = _parse_if(value) if len(data_types) > 1: raise ValueError('The result is a non-uniform list') return output