# 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 json
import os.path
from robot.errors import DataError
from robot.running import ArgInfo, TypeInfo
from .datatypes import EnumMember, TypedDictItem, TypeDoc
from .model import KeywordDoc, LibraryDoc
[docs]
class JsonDocBuilder:
[docs]
def build(self, path):
spec = self._parse_spec_json(path)
return self.build_from_dict(spec)
[docs]
def build_from_dict(self, spec):
libdoc = LibraryDoc(
name=spec["name"],
doc=spec["doc"],
version=spec["version"],
type=spec["type"],
scope=spec["scope"],
doc_format=spec["docFormat"],
source=spec["source"],
lineno=int(spec.get("lineno", -1)),
)
libdoc.inits = [self._create_keyword(kw) for kw in spec["inits"]]
libdoc.keywords = [self._create_keyword(kw) for kw in spec["keywords"]]
# RF >= 5 have 'typedocs', RF >= 4 have 'dataTypes', older/custom may have neither.
if "typedocs" in spec:
libdoc.type_docs = self._parse_type_docs(spec["typedocs"])
elif "dataTypes" in spec:
libdoc.type_docs = self._parse_data_types(spec["dataTypes"])
return libdoc
def _parse_spec_json(self, path):
if not os.path.isfile(path):
raise DataError(f"Spec file '{path}' does not exist.")
with open(path, encoding="UTF-8") as json_source:
return json.load(json_source)
def _create_keyword(self, data):
kw = KeywordDoc(
name=data.get("name"),
doc=data["doc"],
short_doc=data["shortdoc"],
tags=data["tags"],
private=data.get("private", False),
deprecated=data.get("deprecated", False),
source=data["source"],
lineno=int(data.get("lineno", -1)),
)
self._create_arguments(data["args"], kw)
self._add_return_type(data.get("returnType"), kw)
return kw
def _create_arguments(self, arguments, kw: KeywordDoc):
spec = kw.args
positional_only = []
positional_or_named = []
named_only = []
for arg in arguments:
kind = arg["kind"]
name = arg["name"]
if kind == ArgInfo.POSITIONAL_ONLY:
positional_only.append(name)
elif kind == ArgInfo.POSITIONAL_OR_NAMED:
positional_or_named.append(name)
elif kind == ArgInfo.VAR_POSITIONAL:
spec.var_positional = name
elif kind == ArgInfo.NAMED_ONLY:
named_only.append(name)
elif kind == ArgInfo.VAR_NAMED:
spec.var_named = name
default = arg.get("defaultValue")
if default is not None:
spec.defaults[name] = default
if "type" in arg: # RF >= 6.1
type_docs = {}
type_info = self._parse_type_info(arg["type"], type_docs)
else: # RF < 6.1
type_docs = arg.get("typedocs", {})
type_info = self._parse_legacy_type_info(arg["types"])
if type_info:
if not spec.types:
spec.types = {}
spec.types[name] = type_info
kw.type_docs[name] = type_docs
spec.positional_only = positional_only
spec.positional_or_named = positional_or_named
spec.named_only = named_only
def _parse_type_info(self, data, type_docs):
if not data:
return None
if data.get("typedoc"):
type_docs[data["name"]] = data["typedoc"]
nested = [self._parse_type_info(n, type_docs) for n in data.get("nested", ())]
return TypeInfo(data["name"], None, nested=nested or None)
def _parse_legacy_type_info(self, types):
return TypeInfo.from_sequence(types) if types else None
def _add_return_type(self, data, kw: KeywordDoc):
if data:
type_docs = {}
kw.args.return_type = self._parse_type_info(data, type_docs)
kw.type_docs["return"] = type_docs
def _parse_type_docs(self, type_docs):
for data in type_docs:
doc = TypeDoc(
data["type"],
data["name"],
data["doc"],
data["accepts"],
data["usages"],
)
if doc.type == TypeDoc.ENUM:
doc.members = [
EnumMember(d["name"], d["value"]) for d in data["members"]
]
if doc.type == TypeDoc.TYPED_DICT:
doc.items = [
TypedDictItem(d["key"], d["type"], d["required"])
for d in data["items"]
]
yield doc
# Code below used for parsing legacy 'dataTypes'.
def _parse_data_types(self, data_types):
for obj in data_types["enums"]:
yield self._create_enum_doc(obj)
for obj in data_types["typedDicts"]:
yield self._create_typed_dict_doc(obj)
def _create_enum_doc(self, data):
return TypeDoc(
TypeDoc.ENUM,
data["name"],
data["doc"],
members=[
EnumMember(member["name"], member["value"])
for member in data["members"]
],
)
def _create_typed_dict_doc(self, data):
return TypeDoc(
TypeDoc.TYPED_DICT,
data["name"],
data["doc"],
items=[
TypedDictItem(item["key"], item["type"], item["required"])
for item in data["items"]
],
)