Source code for robot.running.arguments.embedded

#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  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.

import re

from robot.errors import DataError
from robot.utils import get_error_message, is_string
from robot.variables import VariableIterator


ENABLE_STRICT_ARGUMENT_VALIDATION = False


[docs]class EmbeddedArguments: def __init__(self, name=None, args=(), custom_patterns=None): self.name = name self.args = args self.custom_patterns = custom_patterns
[docs] @classmethod def from_name(cls, name): return EmbeddedArgumentParser().parse(name) if '${' in name else cls()
[docs] def match(self, name): return self.name.match(name)
[docs] def map(self, values): self.validate(values) return list(zip(self.args, values))
[docs] def validate(self, values): # Validating that embedded args match custom regexps also if args are # given as variables was initially implemented in RF 6.0. It needed # to be reverted due to backwards incompatibility reasons but the plan # is to enable it again in RF 7.0: # https://github.com/robotframework/robotframework/issues/4069 # # TODO: Emit deprecation warnings if patterns don't match in RF 6.1: # https://github.com/robotframework/robotframework/issues/4524 # # Because the plan is to add validation back, the code was not removed # but the `ENABLE_STRICT_ARGUMENT_VALIDATION` guard was added instead. # Enabling validation requires only removing the following two lines # (along with this comment). If someone wants to enable strict validation # already now, they set `ENABLE_STRICT_ARGUMENT_VALIDATION` to True # before running tests. if not ENABLE_STRICT_ARGUMENT_VALIDATION: return if not self.custom_patterns: return for arg, value in zip(self.args, values): if arg in self.custom_patterns and is_string(value): pattern = self.custom_patterns[arg] if not re.fullmatch(pattern, value): raise ValueError(f"Embedded argument '{arg}' got value '{value}' " f"that does not match custom pattern '{pattern}'.")
def __bool__(self): return self.name is not None
[docs]class EmbeddedArgumentParser: _regexp_extension = re.compile(r'(?<!\\)\(\?.+\)') _regexp_group_start = re.compile(r'(?<!\\)\((.*?)\)') _escaped_curly = re.compile(r'(\\+)([{}])') _regexp_group_escape = r'(?:\1)' _default_pattern = '.*?' _variable_pattern = r'\$\{[^\}]+\}'
[docs] def parse(self, string): args = [] custom_patterns = {} name_regexp = ['^'] for before, variable, string in VariableIterator(string, identifiers='$'): name, pattern, custom = self._get_name_and_pattern(variable[2:-1]) args.append(name) if custom: custom_patterns[name] = pattern pattern = self._format_custom_regexp(pattern) name_regexp.extend([re.escape(before), f'({pattern})']) name_regexp.extend([re.escape(string), '$']) name = self._compile_regexp(name_regexp) if args else None return EmbeddedArguments(name, args, custom_patterns or None)
def _get_name_and_pattern(self, name): if ':' in name: name, pattern = name.split(':', 1) custom = True else: pattern = self._default_pattern custom = False return name, pattern, custom def _format_custom_regexp(self, pattern): for formatter in (self._regexp_extensions_are_not_allowed, self._make_groups_non_capturing, self._unescape_curly_braces, self._escape_escapes, self._add_automatic_variable_pattern): pattern = formatter(pattern) return pattern def _regexp_extensions_are_not_allowed(self, pattern): if self._regexp_extension.search(pattern): raise DataError('Regexp extensions are not allowed in embedded arguments.') return pattern def _make_groups_non_capturing(self, pattern): return self._regexp_group_start.sub(self._regexp_group_escape, pattern) def _unescape_curly_braces(self, pattern): # Users must escape possible lone curly braces in patters (e.g. `${x:\{}`) # or otherwise the variable syntax is invalid. def unescape(match): backslashes = len(match.group(1)) return '\\' * (backslashes // 2 * 2) + match.group(2) return self._escaped_curly.sub(unescape, pattern) def _escape_escapes(self, pattern): # When keywords are matched, embedded arguments have not yet been # resolved which means possible escapes are still doubled. We thus # need to double them in the pattern as well. return pattern.replace(r'\\', r'\\\\') def _add_automatic_variable_pattern(self, pattern): return f'{pattern}|{self._variable_pattern}' def _compile_regexp(self, pattern): try: return re.compile(''.join(pattern), re.IGNORECASE) except Exception: raise DataError("Compiling embedded arguments regexp failed: %s" % get_error_message())