# 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 inspect import isclass
import os
import sys
try:
from enum import Enum
except ImportError: # Standard in Py 3.4+ but can be separately installed
[docs] class Enum(object):
pass
from robot.errors import DataError
from robot.running import (TestLibrary, UserLibrary, UserErrorHandler,
ResourceFileBuilder)
from robot.utils import split_tags_from_doc, unescape, unic
from .model import LibraryDoc, KeywordDoc
[docs]class LibraryDocBuilder(object):
_argument_separator = '::'
[docs] def build(self, library):
name, args = self._split_library_name_and_args(library)
lib = TestLibrary(name, args)
libdoc = LibraryDoc(name=lib.name,
doc=self._get_doc(lib),
version=lib.version,
scope=str(lib.scope),
doc_format=lib.doc_format,
source=lib.source,
lineno=lib.lineno)
libdoc.inits = self._get_initializers(lib)
libdoc.keywords = KeywordDocBuilder().build_keywords(lib)
return libdoc
def _split_library_name_and_args(self, library):
args = library.split(self._argument_separator)
name = args.pop(0)
return self._normalize_library_path(name), args
def _normalize_library_path(self, library):
path = library.replace('/', os.sep)
if os.path.exists(path):
return os.path.abspath(path)
return library
def _get_doc(self, lib):
return lib.doc or "Documentation for library ``%s``." % lib.name
def _get_initializers(self, lib):
if lib.init.arguments.maxargs:
return [KeywordDocBuilder().build_keyword(lib.init)]
return []
[docs]class ResourceDocBuilder(object):
[docs] def build(self, path):
res = self._import_resource(path)
libdoc = LibraryDoc(name=res.name,
doc=self._get_doc(res),
type='RESOURCE',
scope='GLOBAL',
source=res.source,
lineno=1)
libdoc.keywords = KeywordDocBuilder(resource=True).build_keywords(res)
return libdoc
def _import_resource(self, path):
ast = ResourceFileBuilder(process_curdir=False).build(
self._find_resource_file(path))
return UserLibrary(ast)
def _find_resource_file(self, path):
if os.path.isfile(path):
return os.path.normpath(path)
for dire in [item for item in sys.path if os.path.isdir(item)]:
candidate = os.path.normpath(os.path.join(dire, path))
if os.path.isfile(candidate):
return candidate
raise DataError("Resource file '%s' does not exist." % path)
def _get_doc(self, res):
if res.doc:
return unescape(res.doc)
return "Documentation for resource file ``%s``." % res.name
[docs]class KeywordDocBuilder(object):
def __init__(self, resource=False):
self._resource = resource
[docs] def build_keywords(self, lib):
return [self.build_keyword(kw) for kw in lib.handlers]
[docs] def build_keyword(self, kw):
doc, tags = self._get_doc_and_tags(kw)
return KeywordDoc(name=kw.name,
args=self._get_args(kw.arguments),
doc=doc,
tags=tags,
source=kw.source,
lineno=kw.lineno)
def _get_doc_and_tags(self, kw):
doc = self._get_doc(kw)
doc, tags = split_tags_from_doc(doc)
return doc, kw.tags + tags
def _get_doc(self, kw):
if self._resource and not isinstance(kw, UserErrorHandler):
return unescape(kw.doc)
return kw.doc
def _get_args(self, argspec):
""":type argspec: :py:class:`robot.running.arguments.ArgumentSpec`"""
args = [self._format_arg(arg, argspec) for arg in argspec.positional]
if argspec.varargs:
args.append('*%s' % self._format_arg(argspec.varargs, argspec))
if argspec.kwonlyargs:
if not argspec.varargs:
args.append('*')
args.extend(self._format_arg(arg, argspec)
for arg in argspec.kwonlyargs)
if argspec.kwargs:
args.append('**%s' % self._format_arg(argspec.kwargs, argspec))
return args
def _format_arg(self, arg, argspec):
result = arg
if argspec.types is not None and arg in argspec.types:
type_info = argspec.types[arg]
result = '%s: %s' % (result, self._format_type(type_info))
if isclass(type_info) and issubclass(type_info, Enum):
result = '%s { %s }' % (result, self._format_enum(type_info))
default_format = '%s = %s'
else:
default_format = '%s=%s'
if arg in argspec.defaults:
result = default_format % (result, unic(argspec.defaults[arg]))
return result
def _format_type(self, type_info):
return type_info.__name__ if isclass(type_info) else type_info
def _format_enum(self, enum):
try:
members = list(enum.__members__)
except AttributeError: # old enum module
members = [attr for attr in dir(enum) if not attr.startswith('_')]
while len(members) > 3 and len(' | '.join(members)) > 42:
members[-2:] = ['...']
return ' | '.join(members)