# 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.
from abc import ABC
from inspect import signature
from pathlib import Path
from robot.conf import LanguagesLike
from robot.errors import DataError
from robot.parsing import File, get_init_model, get_model, get_resource_model
from robot.utils import FileReader, get_error_message, read_rest_data, type_name
from ..model import TestSuite
from ..resourcemodel import ResourceFile
from .settings import FileSettings, InitFileSettings, TestDefaults
from .transformers import ResourceBuilder, SuiteBuilder
[docs]
class Parser(ABC):
@property
def name(self) -> str:
return type(self).__name__
[docs]
def parse_suite_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
raise DataError(f"'{self.name}' does not support parsing suite files.")
[docs]
def parse_init_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
raise DataError(f"'{self.name}' does not support parsing initialization files.")
[docs]
def parse_resource_file(self, source: Path) -> ResourceFile:
raise DataError(f"'{self.name}' does not support parsing resource files.")
[docs]
class RobotParser(Parser):
extensions = ()
def __init__(self, lang: LanguagesLike = None, process_curdir: bool = True):
self.lang = lang
self.process_curdir = process_curdir
[docs]
def parse_suite_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
model = get_model(self._get_source(source), data_only=True,
curdir=self._get_curdir(source), lang=self.lang)
model.source = source
return self.parse_model(model, defaults)
[docs]
def parse_init_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
model = get_init_model(self._get_source(source), data_only=True,
curdir=self._get_curdir(source), lang=self.lang)
model.source = source
suite = TestSuite(name=TestSuite.name_from_source(source.parent),
source=source.parent, rpa=None)
SuiteBuilder(suite, InitFileSettings(defaults)).build(model)
return suite
[docs]
def parse_model(self, model: File, defaults: 'TestDefaults|None' = None) -> TestSuite:
name = TestSuite.name_from_source(model.source, self.extensions)
suite = TestSuite(name=name, source=model.source)
SuiteBuilder(suite, FileSettings(defaults)).build(model)
return suite
def _get_curdir(self, source: Path) -> 'str|None':
return str(source.parent).replace('\\', '\\\\') if self.process_curdir else None
def _get_source(self, source: Path) -> 'Path|str':
return source
[docs]
def parse_resource_file(self, source: Path) -> ResourceFile:
model = get_resource_model(self._get_source(source), data_only=True,
curdir=self._get_curdir(source), lang=self.lang)
model.source = source
resource = self.parse_resource_model(model)
return resource
[docs]
def parse_resource_model(self, model: File) -> ResourceFile:
resource = ResourceFile(source=model.source)
ResourceBuilder(resource).build(model)
return resource
[docs]
class RestParser(RobotParser):
extensions = ('.robot.rst', '.rst', '.rest')
def _get_source(self, source: Path) -> str:
with FileReader(source) as reader:
return read_rest_data(reader)
[docs]
class JsonParser(Parser):
[docs]
def parse_suite_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
return TestSuite.from_json(source)
[docs]
def parse_init_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
return TestSuite.from_json(source)
[docs]
def parse_resource_file(self, source: Path) -> ResourceFile:
try:
return ResourceFile.from_json(source)
except DataError as err:
raise DataError(f"Parsing JSON resource file '{source}' failed: {err}")
[docs]
class NoInitFileDirectoryParser(Parser):
[docs]
def parse_init_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
return TestSuite(name=TestSuite.name_from_source(source),
source=source, rpa=None)
[docs]
class CustomParser(Parser):
def __init__(self, parser):
self.parser = parser
if not getattr(parser, 'parse', None):
raise TypeError(f"'{self.name}' does not have mandatory 'parse' method.")
if not self.extensions:
raise TypeError(f"'{self.name}' does not have mandatory 'EXTENSION' "
f"or 'extension' attribute.")
@property
def name(self) -> str:
return type_name(self.parser)
@property
def extensions(self) -> 'tuple[str, ...]':
ext = (getattr(self.parser, 'EXTENSION', None)
or getattr(self.parser, 'extension', None))
extensions = [ext] if isinstance(ext, str) else list(ext or ())
return tuple(ext.lower().lstrip('.') for ext in extensions)
[docs]
def parse_suite_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
return self._parse(self.parser.parse, source, defaults)
[docs]
def parse_init_file(self, source: Path, defaults: TestDefaults) -> TestSuite:
parse_init = getattr(self.parser, 'parse_init', None)
try:
return self._parse(parse_init, source, defaults, init=True)
except NotImplementedError:
return super().parse_init_file(source, defaults) # Raises DataError
def _parse(self, method, source, defaults, init=False) -> TestSuite:
if not method:
raise NotImplementedError
accepts_defaults = len(signature(method).parameters) == 2
try:
suite = method(source, defaults) if accepts_defaults else method(source)
if not isinstance(suite, TestSuite):
raise TypeError(f"Return value should be 'robot.running.TestSuite', "
f"got '{type_name(suite)}'.")
except Exception:
method_name = 'parse' if not init else 'parse_init'
raise DataError(f"Calling '{self.name}.{method_name}()' failed: "
f"{get_error_message()}")
return suite