# 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
return self.parse_resource_model(model)
[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' or 'extension' "
f"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)
) # fmt: skip
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', got "
f"'{type_name(suite)}'."
)
except Exception:
method_name = "parse" if not init else "parse_init"
raise DataError(
f"Calling '{self.name}.{method_name}()' failed: {get_error_message()}"
)
return suite