# 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, VariableError
from robot.utils import (
get_env_var, get_env_vars, get_error_message, normalize, NormalizedDict
)
from .evaluation import evaluate_expression
from .notfound import variable_not_found
from .search import search_variable, VariableMatch
NOT_FOUND = object()
[docs]
class VariableFinder:
def __init__(self, variables):
self._finders = (
StoredFinder(variables.store),
NumberFinder(),
EmptyFinder(),
InlinePythonFinder(variables),
EnvironmentFinder(),
ExtendedFinder(self),
)
self._store = variables.store
[docs]
def find(self, variable):
match = self._get_match(variable)
name = match.name
for finder in self._finders:
if match.identifier in finder.identifiers:
result = finder.find(name)
if result is not NOT_FOUND:
return result
variable_not_found(name, self._store.data)
def _get_match(self, variable):
if isinstance(variable, VariableMatch):
return variable
match = search_variable(variable)
if not match.is_variable() or match.items:
raise DataError(f"Invalid variable name '{variable}'.")
return match
[docs]
class StoredFinder:
identifiers = "$@&"
def __init__(self, store):
self._store = store
[docs]
def find(self, name):
return self._store.get(name, NOT_FOUND)
[docs]
class NumberFinder:
identifiers = "$"
[docs]
def find(self, name):
number = normalize(name)[2:-1]
for converter in self._get_int, float:
try:
return converter(number)
except ValueError:
pass
return NOT_FOUND
def _get_int(self, number):
bases = {"0b": 2, "0o": 8, "0x": 16}
if number.startswith(tuple(bases)):
return int(number[2:], bases[number[:2]])
return int(number)
[docs]
class EmptyFinder:
identifiers = "$@&"
empty = NormalizedDict({"${EMPTY}": "", "@{EMPTY}": (), "&{EMPTY}": {}}, ignore="_")
[docs]
def find(self, name):
return self.empty.get(name, NOT_FOUND)
[docs]
class InlinePythonFinder:
identifiers = "$@&"
def __init__(self, variables):
self._variables = variables
[docs]
def find(self, name):
base = name[2:-1]
if not base or base[0] != "{" or base[-1] != "}":
return NOT_FOUND
try:
return evaluate_expression(base[1:-1].strip(), self._variables)
except DataError as err:
raise VariableError(f"Resolving variable '{name}' failed: {err}")
[docs]
class ExtendedFinder:
identifiers = "$@&"
_match_extended = re.compile(
r"""
(.+?) # base name (group 1)
([^\s\w].+) # extended part (group 2)
""",
re.UNICODE | re.VERBOSE,
).match
def __init__(self, finder):
self._find_variable = finder.find
[docs]
def find(self, name):
match = self._match_extended(name[2:-1])
if match is None:
return NOT_FOUND
base_name, extended = match.groups()
try:
variable = self._find_variable(f"${{{base_name}}}")
except DataError as err:
raise VariableError(f"Resolving variable '{name}' failed: {err}")
try:
return eval("_BASE_VAR_" + extended, {"_BASE_VAR_": variable})
except Exception:
msg = get_error_message()
raise VariableError(f"Resolving variable '{name}' failed: {msg}")
[docs]
class EnvironmentFinder:
identifiers = "%"
[docs]
def find(self, name):
var_name, has_default, default_value = name[2:-1].partition("=")
value = get_env_var(var_name)
if value is not None:
return value
if has_default:
return default_value
error = f"Environment variable '{name}' not found."
variable_not_found(name, get_env_vars(), error)