# 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 ast
import re
from robot.utils import normalize_whitespace, split_from_equals
from robot.variables import is_scalar_assign, is_dict_variable, search_variable
from ..lexer import Token
FOUR_SPACES = ' '
EOL = '\n'
[docs]class Statement(ast.AST):
type = None
handles_types = ()
_fields = ('type', 'tokens')
_attributes = ('lineno', 'col_offset', 'end_lineno', 'end_col_offset', 'errors')
_statement_handlers = {}
def __init__(self, tokens, errors=()):
self.tokens = tuple(tokens)
self.errors = errors
@property
def lineno(self):
return self.tokens[0].lineno if self.tokens else -1
@property
def col_offset(self):
return self.tokens[0].col_offset if self.tokens else -1
@property
def end_lineno(self):
return self.tokens[-1].lineno if self.tokens else -1
@property
def end_col_offset(self):
return self.tokens[-1].end_col_offset if self.tokens else -1
[docs] @classmethod
def register(cls, subcls):
types = subcls.handles_types or (subcls.type,)
for typ in types:
cls._statement_handlers[typ] = subcls
return subcls
[docs] @classmethod
def from_tokens(cls, tokens):
handlers = cls._statement_handlers
for token in tokens:
if token.type in handlers:
return handlers[token.type](tokens)
return EmptyLine(tokens)
[docs] @classmethod
def from_params(cls, *args, **kwargs):
"""Create statement from passed parameters.
Required and optional arguments should match class properties. Values are
used to create matching tokens.
There is one notable difference for `Documentation` statement where
``settings_header`` flag is used to determine if statement belongs to
settings header or test/keyword.
Most implementations support following general properties:
- `separator` whitespace inserted between each token. Default is four spaces.
- ``indent`` whitespace inserted before first token. Default is four spaces.
- ``eol`` end of line sign. Default is ``'\\n'``.
"""
raise NotImplementedError
@property
def data_tokens(self):
return [t for t in self.tokens if t.type not in Token.NON_DATA_TOKENS]
[docs] def get_token(self, *types):
"""Return a token with the given ``type``.
If there are no matches, return ``None``. If there are multiple
matches, return the first match.
"""
for token in self.tokens:
if token.type in types:
return token
return None
[docs] def get_tokens(self, *types):
"""Return tokens having any of the given ``types``."""
return [t for t in self.tokens if t.type in types]
[docs] def get_value(self, type, default=None):
"""Return value of a token with the given ``type``.
If there are no matches, return ``default``. If there are multiple
matches, return the value of the first match.
"""
token = self.get_token(type)
return token.value if token else default
[docs] def get_values(self, *types):
"""Return values of tokens having any of the given ``types``."""
return tuple(t.value for t in self.tokens if t.type in types)
@property
def lines(self):
line = []
for token in self.tokens:
line.append(token)
if token.type == Token.EOL:
yield line
line = []
if line:
yield line
[docs] def validate(self):
pass
def __iter__(self):
return iter(self.tokens)
def __len__(self):
return len(self.tokens)
def __getitem__(self, item):
return self.tokens[item]
def __repr__(self):
errors = '' if not self.errors else ', errors=%s' % list(self.errors)
return '%s(tokens=%s%s)' % (type(self).__name__, list(self.tokens), errors)
[docs]class SingleValue(Statement):
@property
def value(self):
values = self.get_values(Token.NAME, Token.ARGUMENT)
if values and values[0].upper() != 'NONE':
return values[0]
return None
[docs]class MultiValue(Statement):
@property
def values(self):
return self.get_values(Token.ARGUMENT)
[docs]class Fixture(Statement):
@property
def name(self):
return self.get_value(Token.NAME)
@property
def args(self):
return self.get_values(Token.ARGUMENT)
[docs]@Statement.register
class LibraryImport(Statement):
type = Token.LIBRARY
[docs] @classmethod
def from_params(cls, name, args=(), alias=None, separator=FOUR_SPACES, eol=EOL):
sep = Token(Token.SEPARATOR, separator)
tokens = [Token(Token.LIBRARY, 'Library'), sep, Token(Token.NAME, name)]
for arg in args:
tokens.append(sep)
tokens.append(Token(Token.ARGUMENT, arg))
if alias is not None:
tokens.append(sep)
tokens.append(Token(Token.WITH_NAME))
tokens.append(sep)
tokens.append(Token(Token.NAME, alias))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def name(self):
return self.get_value(Token.NAME)
@property
def args(self):
return self.get_values(Token.ARGUMENT)
@property
def alias(self):
with_name = self.get_token(Token.WITH_NAME)
return self.get_tokens(Token.NAME)[-1].value if with_name else None
[docs]@Statement.register
class ResourceImport(Statement):
type = Token.RESOURCE
[docs] @classmethod
def from_params(cls, name, separator=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.RESOURCE, 'Resource'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, name),
Token(Token.EOL, eol)
])
@property
def name(self):
return self.get_value(Token.NAME)
[docs]@Statement.register
class VariablesImport(Statement):
type = Token.VARIABLES
[docs] @classmethod
def from_params(cls, name, args=(), separator=FOUR_SPACES, eol=EOL):
sep = Token(Token.SEPARATOR, separator)
tokens = [
Token(Token.VARIABLES, 'Variables'),
sep,
Token(Token.NAME, name)
]
for arg in args:
tokens.append(sep)
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def name(self):
return self.get_value(Token.NAME)
@property
def args(self):
return self.get_values(Token.ARGUMENT)
[docs]@Statement.register
class Documentation(DocumentationOrMetadata):
type = Token.DOCUMENTATION
[docs] @classmethod
def from_params(cls, value, indent=FOUR_SPACES, separator=FOUR_SPACES,
eol=EOL, settings_section=True):
if settings_section:
tokens = [
Token(Token.DOCUMENTATION, 'Documentation'),
Token(Token.SEPARATOR, separator)
]
else:
tokens = [
Token(Token.SEPARATOR, indent),
Token(Token.DOCUMENTATION, '[Documentation]'),
Token(Token.SEPARATOR, separator)
]
multiline_separator = ' ' * (len(tokens[-2].value) + len(separator) - 3)
doc_lines = value.splitlines()
if doc_lines:
tokens.append(Token(Token.ARGUMENT, doc_lines[0]))
tokens.append(Token(Token.EOL, eol))
for line in doc_lines[1:]:
if not settings_section:
tokens.append(Token(Token.SEPARATOR, indent))
tokens.append(Token(Token.CONTINUATION))
if line:
tokens.append(Token(Token.SEPARATOR, multiline_separator))
tokens.append(Token(Token.ARGUMENT, line))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def value(self):
tokens = self.get_tokens(Token.ARGUMENT)
return self._join_value(tokens)
[docs]@Statement.register
class SuiteSetup(Fixture):
type = Token.SUITE_SETUP
[docs] @classmethod
def from_params(cls, name, args=(), separator=FOUR_SPACES, eol=EOL):
tokens = [
Token(Token.SUITE_SETUP, 'Suite Setup'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, name)
]
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class SuiteTeardown(Fixture):
type = Token.SUITE_TEARDOWN
[docs] @classmethod
def from_params(cls, name, args=(), separator=FOUR_SPACES, eol=EOL):
tokens = [
Token(Token.SUITE_TEARDOWN, 'Suite Teardown'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, name)
]
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class TestSetup(Fixture):
type = Token.TEST_SETUP
[docs] @classmethod
def from_params(cls, name, args=(), separator=FOUR_SPACES, eol=EOL):
tokens = [
Token(Token.TEST_SETUP, 'Test Setup'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, name)
]
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class TestTeardown(Fixture):
type = Token.TEST_TEARDOWN
[docs] @classmethod
def from_params(cls, name, args=(), separator=FOUR_SPACES, eol=EOL):
tokens = [
Token(Token.TEST_TEARDOWN, 'Test Teardown'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, name)
]
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class TestTemplate(SingleValue):
type = Token.TEST_TEMPLATE
[docs] @classmethod
def from_params(cls, value, separator=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.TEST_TEMPLATE, 'Test Template'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, value),
Token(Token.EOL, eol)
])
[docs]@Statement.register
class TestTimeout(SingleValue):
type = Token.TEST_TIMEOUT
[docs] @classmethod
def from_params(cls, value, separator=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.TEST_TIMEOUT, 'Test Timeout'),
Token(Token.SEPARATOR, separator),
Token(Token.ARGUMENT, value),
Token(Token.EOL, eol)
])
[docs]@Statement.register
class Variable(Statement):
type = Token.VARIABLE
[docs] @classmethod
def from_params(cls, name, value, separator=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.VARIABLE, name),
Token(Token.SEPARATOR, separator),
Token(Token.ARGUMENT, value),
Token(Token.EOL, eol)
])
@property
def name(self):
name = self.get_value(Token.VARIABLE)
if name.endswith('='):
return name[:-1].rstrip()
return name
@property
def value(self):
return self.get_values(Token.ARGUMENT)
[docs] def validate(self):
name = self.get_value(Token.VARIABLE)
match = search_variable(name, ignore_errors=True)
if not match.is_assign(allow_assign_mark=True):
self.errors += ("Invalid variable name '%s'." % name,)
if match.is_dict_assign(allow_assign_mark=True):
self._validate_dict_items()
def _validate_dict_items(self):
for item in self.get_values(Token.ARGUMENT):
if not self._is_valid_dict_item(item):
self.errors += (
"Invalid dictionary variable item '%s'. "
"Items must use 'name=value' syntax or be dictionary "
"variables themselves." % item,
)
def _is_valid_dict_item(self, item):
name, value = split_from_equals(item)
return value is not None or is_dict_variable(item)
[docs]@Statement.register
class TestCaseName(Statement):
type = Token.TESTCASE_NAME
[docs] @classmethod
def from_params(cls, name, eol=EOL):
tokens = [Token(Token.TESTCASE_NAME, name)]
if eol:
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def name(self):
return self.get_value(Token.TESTCASE_NAME)
[docs]@Statement.register
class KeywordName(Statement):
type = Token.KEYWORD_NAME
[docs] @classmethod
def from_params(cls, name, eol=EOL):
tokens = [Token(Token.KEYWORD_NAME, name)]
if eol:
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def name(self):
return self.get_value(Token.KEYWORD_NAME)
[docs]@Statement.register
class Setup(Fixture):
type = Token.SETUP
[docs] @classmethod
def from_params(cls, name, args=(), indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
sep = Token(Token.SEPARATOR, separator)
tokens = [
Token(Token.SEPARATOR, indent),
Token(Token.SETUP, '[Setup]'),
sep,
Token(Token.NAME, name)
]
for arg in args:
tokens.append(sep)
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class Teardown(Fixture):
type = Token.TEARDOWN
[docs] @classmethod
def from_params(cls, name, args=(), indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
sep = Token(Token.SEPARATOR, separator)
tokens = [
Token(Token.SEPARATOR, indent),
Token(Token.TEARDOWN, '[Teardown]'),
sep,
Token(Token.NAME, name)
]
for arg in args:
tokens.append(sep)
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class Template(SingleValue):
type = Token.TEMPLATE
[docs] @classmethod
def from_params(cls, value, indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.SEPARATOR, indent),
Token(Token.TEMPLATE, '[Template]'),
Token(Token.SEPARATOR, separator),
Token(Token.NAME, value),
Token(Token.EOL, eol)
])
[docs]@Statement.register
class Timeout(SingleValue):
type = Token.TIMEOUT
[docs] @classmethod
def from_params(cls, value, indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.SEPARATOR, indent),
Token(Token.TIMEOUT, '[Timeout]'),
Token(Token.SEPARATOR, separator),
Token(Token.ARGUMENT, value),
Token(Token.EOL, eol)
])
[docs]@Statement.register
class Arguments(MultiValue):
type = Token.ARGUMENTS
[docs] @classmethod
def from_params(cls, args, indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
tokens = [
Token(Token.SEPARATOR, indent),
Token(Token.ARGUMENTS, '[Arguments]'),
]
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class Return(MultiValue):
type = Token.RETURN
[docs] @classmethod
def from_params(cls, args, indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
tokens = [
Token(Token.SEPARATOR, indent),
Token(Token.RETURN, '[Return]'),
]
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
[docs]@Statement.register
class KeywordCall(Statement):
type = Token.KEYWORD
handles_types = (Token.KEYWORD, Token.ASSIGN)
[docs] @classmethod
def from_params(cls, name, assign=(), args=(), indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
tokens = [Token(Token.SEPARATOR, indent)]
for assignment in assign:
tokens.append(Token(Token.ASSIGN, assignment))
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.KEYWORD, name))
for arg in args:
tokens.append(Token(Token.SEPARATOR, separator))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def keyword(self):
return self.get_value(Token.KEYWORD)
@property
def args(self):
return self.get_values(Token.ARGUMENT)
@property
def assign(self):
return self.get_values(Token.ASSIGN)
[docs]@Statement.register
class TemplateArguments(Statement):
type = Token.ARGUMENT
[docs] @classmethod
def from_params(cls, args, indent=FOUR_SPACES, separator=FOUR_SPACES, eol=EOL):
tokens = []
for index, arg in enumerate(args):
tokens.append(Token(Token.SEPARATOR, separator if index else indent))
tokens.append(Token(Token.ARGUMENT, arg))
tokens.append(Token(Token.EOL, eol))
return cls(tokens)
@property
def args(self):
return self.get_values(self.type)
[docs]@Statement.register
class End(Statement):
type = Token.END
[docs] @classmethod
def from_params(cls, indent=FOUR_SPACES, eol=EOL):
return cls([
Token(Token.SEPARATOR, indent),
Token(Token.END),
Token(Token.EOL, eol)
])
[docs] def validate(self):
if self.get_tokens(Token.ARGUMENT):
self.errors += ('END does not accept arguments.',)
[docs]@Statement.register
class Error(Statement):
type = Token.ERROR
handles_types = (Token.ERROR, Token.FATAL_ERROR)
_errors = ()
@property
def errors(self):
"""Errors got from the underlying ``ERROR`` and ``FATAL_ERROR`` tokens.
Errors can be set also explicitly. When accessing errors, they are returned
along with errors got from tokens.
"""
tokens = self.get_tokens(Token.ERROR, Token.FATAL_ERROR)
return tuple(t.error for t in tokens) + self._errors
@errors.setter
def errors(self, errors):
self._errors = tuple(errors)
[docs]class EmptyLine(Statement):
type = Token.EOL
[docs] @classmethod
def from_params(cls, eol=EOL):
return cls([Token(Token.EOL, eol)])