From b2ed42ebca06f2dee203fb8d2fe411652cbd3a6e Mon Sep 17 00:00:00 2001 From: Rob van der Most Date: Fri, 4 Mar 2022 15:37:56 +0100 Subject: [PATCH] feat(kotlin): initial support for Kotlin through Dokka Support for simple classes with constructors, methods, and properties. Implements: #41 --- .../generator/templates/kotlin/helpers.py | 10 + asciidoxy/parser/dokka/__init__.py | 18 ++ asciidoxy/parser/dokka/parser.py | 233 ++++++++++++++++++ .../parser/doxygen/description_parser.py | 16 +- asciidoxy/parser/factory.py | 3 + .../dokka/kotlin/default/asciidoxy.json | 22 ++ .../dokka/kotlin/default/contents.toml | 21 ++ tests/unit/api_reference_loader.py | 10 +- tests/unit/builders.py | 4 +- .../templates/helpers/test_kotlin.py | 26 ++ tests/unit/parser/dokka/__init__.py | 0 tests/unit/parser/dokka/test_parser.py | 162 ++++++++++++ tests/visual/kotlin.adoc | 19 ++ tests/visual/kotlin.toml | 19 ++ 14 files changed, 551 insertions(+), 12 deletions(-) create mode 100644 asciidoxy/parser/dokka/__init__.py create mode 100644 asciidoxy/parser/dokka/parser.py create mode 100644 tests/data/generated/dokka/kotlin/default/contents.toml create mode 100644 tests/unit/parser/dokka/__init__.py create mode 100644 tests/unit/parser/dokka/test_parser.py create mode 100644 tests/visual/kotlin.adoc create mode 100644 tests/visual/kotlin.toml diff --git a/asciidoxy/generator/templates/kotlin/helpers.py b/asciidoxy/generator/templates/kotlin/helpers.py index 0dfd7ec1..3da5f40a 100644 --- a/asciidoxy/generator/templates/kotlin/helpers.py +++ b/asciidoxy/generator/templates/kotlin/helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """Helper functions for Kotlin templates.""" +from itertools import chain from typing import Iterator from asciidoxy.generator.templates.helpers import TemplateHelper @@ -38,3 +39,12 @@ def _method_suffix(self, method: Compound, *, link: bool = True) -> str: if method.returns: return f": {self.print_ref(method.returns.type, link=link)}" return "" + + def constructors(self, prot: str) -> Iterator[Compound]: + assert self.element is not None + assert self.insert_filter is not None + + # TODO: Fix transcoder to detect constructors + return chain((m for m in self.insert_filter.members(self.element) + if m.kind == "constructor" and m.prot == prot), + super().constructors(prot)) diff --git a/asciidoxy/parser/dokka/__init__.py b/asciidoxy/parser/dokka/__init__.py new file mode 100644 index 00000000..9eacbe3c --- /dev/null +++ b/asciidoxy/parser/dokka/__init__.py @@ -0,0 +1,18 @@ +# Copyright (C) 2019, TomTom (http://tomtom.com). +# +# 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. +"""Parser for Dokka generated API reference, depending on AsciiDoxy-Dokka.""" + +from .parser import Parser + +__all__ = "Parser", diff --git a/asciidoxy/parser/dokka/parser.py b/asciidoxy/parser/dokka/parser.py new file mode 100644 index 00000000..069f0ebb --- /dev/null +++ b/asciidoxy/parser/dokka/parser.py @@ -0,0 +1,233 @@ +# Copyright (C) 2019, TomTom (http://tomtom.com). +# +# 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. +"""Read API reference information from AsciiDoxy-Dokka JSON output.""" +from __future__ import annotations + +import json +import logging +import xml.etree.ElementTree as ET +from pathlib import Path +from typing import Any, Dict, NamedTuple, Optional, TypeVar, Union + +from ...model import Compound, Parameter, ReferableElement, ReturnValue, TypeRef +from ..doxygen.description_parser import ParaContainer, parse_description, select_descriptions +from ..factory import ReferenceParserBase + +logger = logging.getLogger(__name__) + + +def _dri_clean(value: str) -> str: + return value.replace("#", "2_").replace(".", "1_1_") + + +def _join(sep: str, *values: str) -> str: + return sep.join((v for v in values if v)) + + +class Description(NamedTuple): + brief: str = "" + detailed: str = "" + + @staticmethod + def parse(description: str) -> Description: + if not description: + return Description() + + contents = parse_description(ET.fromstring(description), "kotlin") + return Description(*select_descriptions(ParaContainer("kotlin"), contents)) + + +def parse_single_description(description: str) -> str: + return parse_description(ET.fromstring(description), "kotlin").to_asciidoc() + + +class DRI(NamedTuple): + package_name: str = "" + class_names: str = "" + callable_name: str = "" + callable_signature: str = "" + target: str = "" + extra: str = "" + + @staticmethod + def parse(dri_string: str) -> DRI: + return DRI(*dri_string.split("/")) + + @property + def id(self) -> str: + return _join("1_1_", _dri_clean(self.package_name), self.class_names, self.callable_name, + _dri_clean(self.callable_signature)) + + @property + def name(self) -> str: + return self.callable_name or self.class_names or self.package_name + + @property + def full_name(self) -> str: + return _join(".", self.package_name, self.class_names, self.callable_name) + + @property + def namespace(self) -> str: + if self.callable_name: + return _join(".", self.package_name, self.class_names) + else: + return self.package_name + + +_ReferableElement = TypeVar("_ReferableElement", bound=ReferableElement) + + +class Parser(ReferenceParserBase): + """Parse AsciiDoxy-Dokka JSON output.""" + + TAG: str = "kotlin" + + def parse(self, reference_path: Union[Path, str]) -> bool: + """Parse reference documentation from the given path. + + Args: + reference_path File or directory containing the reference documentation. + + Returns: + True if the reference has been parsed. False if the reference path does not contain + valid content for this parser. + """ + reference_path = Path(reference_path) + if reference_path.is_file(): + return self._parse_file(reference_path) + else: + for json_file in reference_path.glob("**/*.json"): + if not self._parse_file(json_file): + return False + return True + + def _parse_file(self, file: Path) -> bool: + with file.open("r") as file_handle: + json.load(file_handle, object_hook=self._parse_object) + return True + + def _parse_object(self, data: Dict[str, Any]) -> Any: + obj_type = data.get("type") + if not obj_type: + return data + + if obj_type == "org.asciidoxy.dokka.JsonDClasslike": + return self._parse_compound(data) + elif obj_type == "org.asciidoxy.dokka.JsonDFunction": + return self._parse_compound(data) + elif obj_type == "org.asciidoxy.dokka.JsonDProperty": + return self._parse_compound(data) + elif obj_type == "org.asciidoxy.dokka.JsonDParameter": + return self._parse_parameter(data) + elif obj_type == "org.asciidoxy.dokka.JsonGenericTypeConstructor": + return self._parse_typeref(data) + elif obj_type == "org.asciidoxy.dokka.JsonDPackage": + # Ignore + return None + else: + logger.error(f"Unexpected type: {obj_type}") + return None + + def _determine_kind(self, dokka_type: Optional[str]) -> str: + if not dokka_type: + return "" + namespace, _, name = dokka_type.rpartition(".") + if namespace == "org.asciidoxy.dokka": + return name[5:].lower() + else: + return name.lower() + + def _make_id(self, dri: DRI) -> str: + return f"{self.TAG}-{dri.id}" + + def _register(self, element: _ReferableElement) -> _ReferableElement: + self.api_reference.append(element) + return element + + def _parse_compound(self, data: Dict[str, Any]) -> Compound: + dri = DRI.parse(data["dri"]) + name = data.get("name") or dri.name + + if "returnType" in data: + return_value = ReturnValue(type=data["returnType"]) + else: + return_value = None + + if data.get("isConstructor", False): + kind = "constructor" + elif "kind" in data: + kind = data["kind"] + else: + kind = self._determine_kind(data.get("type")) + + description = Description() + docs = data.get("docs") + if docs: + if "Description" in docs: + description = Description.parse(docs["Description"]) + else: + obj_doc_name = f"{kind.title()}: {name}" + if obj_doc_name in docs: + description = Description.parse(docs[obj_doc_name]) + + if return_value is not None and "Return" in docs: + return_value.description = parse_single_description(docs["Return"]) + + return self._register( + Compound( + language=self.TAG, + id=self._make_id(dri), + name=name, + full_name=dri.full_name, + namespace=dri.namespace, + kind=kind, + prot=data.get("visibility", ""), + returns=return_value, + members=data.get("children", []), + params=data.get("parameters", []), + brief=description.brief, + description=description.detailed, + )) + + def _parse_typeref(self, data: Dict[str, Any]) -> TypeRef: + dri = DRI.parse(data["dri"]) + name = data.get("presentableName") or dri.name + + # TODO: Handle links to types in other packages and stdlib + if dri.package_name != "kotlin": + id = self._make_id(dri) + else: + id = None + + return TypeRef( + language=self.TAG, + id=id, + name=name, + ) + + def _parse_parameter(self, data: Dict[str, Any]) -> Parameter: + name = data["name"] + description = "" + + docs = data.get("docs") + if docs: + obj_doc_name = f"Property: {name}" + if obj_doc_name in docs: + description = parse_single_description(docs[obj_doc_name]) + + return Parameter( + type=data["parameterType"], + name=name, + description=description, + ) diff --git a/asciidoxy/parser/doxygen/description_parser.py b/asciidoxy/parser/doxygen/description_parser.py index 54372e9a..338451e1 100644 --- a/asciidoxy/parser/doxygen/description_parser.py +++ b/asciidoxy/parser/doxygen/description_parser.py @@ -1349,6 +1349,7 @@ def add_tail(self, parent: "NestedDescriptionElement", text: str) -> None: "itemizedlist": ListContainer, "listitem": ListItem, "orderedlist": ListContainer, + "p": Para, "para": Para, "parameterdescription": ParameterDescription, "parameteritem": ParameterItem, @@ -1454,27 +1455,28 @@ def add_tail(self, parent: "NestedDescriptionElement", text: str) -> None: def _parse_description(xml_element: ET.Element, parent: NestedDescriptionElement, language_tag: str): element = None + tag = xml_element.tag.lower() - if xml_element.tag in NEW_ELEMENT: - element = NEW_ELEMENT[xml_element.tag].from_xml(xml_element, language_tag) + if tag in NEW_ELEMENT: + element = NEW_ELEMENT[tag].from_xml(xml_element, language_tag) - elif xml_element.tag in UPDATE_PARENT: + elif tag in UPDATE_PARENT: assert isinstance(parent, UPDATE_PARENT[xml_element.tag]) parent.update_from_xml(xml_element) element = parent - elif xml_element.tag in USE_PARENT: + elif tag in USE_PARENT: assert isinstance(parent, USE_PARENT[xml_element.tag]) element = parent - elif xml_element.tag in SpecialCharacter.SPECIAL_CHARACTERS: + elif tag in SpecialCharacter.SPECIAL_CHARACTERS: element = SpecialCharacter.from_xml(xml_element, language_tag) - elif xml_element.tag in IGNORE: + elif tag in IGNORE: element = Skipped.from_xml(xml_element, language_tag) else: - warning = UNSUPPORTED.get(xml_element.tag) + warning = UNSUPPORTED.get(tag) if warning is None: logger.warning(f"Unknown XML tag <{xml_element.tag}>. Please report an issue on GitHub" " with example code.") diff --git a/asciidoxy/parser/factory.py b/asciidoxy/parser/factory.py index c37b6589..13a14cbe 100644 --- a/asciidoxy/parser/factory.py +++ b/asciidoxy/parser/factory.py @@ -15,6 +15,7 @@ from ..api_reference import ApiReference from .base import ReferenceParserBase +from .dokka import Parser as DokkaParser from .doxygen import Parser as DoxygenParser @@ -37,5 +38,7 @@ def parser_factory(reference_type: str, api_reference: ApiReference) -> Referenc """Create a parser for the given type of API reference documentation.""" if reference_type == "doxygen": return DoxygenParser(api_reference) + elif reference_type == "dokka": + return DokkaParser(api_reference) else: raise UnsupportedReferenceTypeError(reference_type) diff --git a/tests/data/generated/dokka/kotlin/default/asciidoxy.json b/tests/data/generated/dokka/kotlin/default/asciidoxy.json index d454fa19..69af09e2 100644 --- a/tests/data/generated/dokka/kotlin/default/asciidoxy.json +++ b/tests/data/generated/dokka/kotlin/default/asciidoxy.json @@ -37,6 +37,13 @@ "dri": "asciidoxy/Coordinate/latitude/#/PointingToDeclaration/", "name": "latitude", "visibility": "public", + "returnType": { + "type": "org.asciidoxy.dokka.JsonGenericTypeConstructor", + "dri": "kotlin/Double///PointingToDeclaration/", + "projections": [ + ], + "presentableName": null + }, "docs": { "Property: latitude": "

The latitude in degrees.

" } @@ -46,6 +53,13 @@ "dri": "asciidoxy/Coordinate/longitude/#/PointingToDeclaration/", "name": "longitude", "visibility": "public", + "returnType": { + "type": "org.asciidoxy.dokka.JsonGenericTypeConstructor", + "dri": "kotlin/Double///PointingToDeclaration/", + "projections": [ + ], + "presentableName": null + }, "docs": { "Property: longitude": "

The longitude in degrees.

" } @@ -55,6 +69,13 @@ "dri": "asciidoxy/Coordinate/altitude/#/PointingToDeclaration/", "name": "altitude", "visibility": "public", + "returnType": { + "type": "org.asciidoxy.dokka.JsonGenericTypeConstructor", + "dri": "kotlin/Double///PointingToDeclaration/", + "projections": [ + ], + "presentableName": null + }, "docs": { "Property: altitude": "

The altitude in meters.

" } @@ -124,6 +145,7 @@ } ], "visibility": "public", + "kind": "class", "docs": { "Description": "

Class to hold information about a coordinate.

A coordinate has a latitude, longitude, and an altitude.

", "Property: latitude": "

The latitude in degrees.

", diff --git a/tests/data/generated/dokka/kotlin/default/contents.toml b/tests/data/generated/dokka/kotlin/default/contents.toml new file mode 100644 index 00000000..12c8e61b --- /dev/null +++ b/tests/data/generated/dokka/kotlin/default/contents.toml @@ -0,0 +1,21 @@ +# Copyright (C) 2019, TomTom (http://tomtom.com). +# +# 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. + +[package] +name = "kotlin-default" + +[reference] +type = "dokka" +dir = "" + diff --git a/tests/unit/api_reference_loader.py b/tests/unit/api_reference_loader.py index 10c5de92..409ea502 100644 --- a/tests/unit/api_reference_loader.py +++ b/tests/unit/api_reference_loader.py @@ -29,9 +29,13 @@ def doxygen_versions(): def generated_test_data_factory(reference_type: str, name: str, version: str = None): - assert reference_type == "doxygen" - assert version is not None - return generated_test_data_dir / reference_type / version / name + if reference_type == "doxygen": + assert version is not None + return generated_test_data_dir / reference_type / version / name + elif reference_type == "dokka": + return generated_test_data_dir / reference_type / name + else: + assert False class ApiReferenceLoader: diff --git a/tests/unit/builders.py b/tests/unit/builders.py index fb934c39..7ff01900 100644 --- a/tests/unit/builders.py +++ b/tests/unit/builders.py @@ -120,10 +120,10 @@ def member_property(self, prot: str, name: str = None): name=name, returns=make_return_value(type=make_type_ref(language=self.lang, name="Type"))) - def member_function(self, has_return_value: bool = True, **kwargs): + def member_function(self, has_return_value: bool = True, kind: str = "function", **kwargs): if has_return_value: returns = make_return_value() else: returns = None self.compound.members.append( - make_compound(language=self.lang, kind="function", returns=returns, **kwargs)) + make_compound(language=self.lang, kind=kind, returns=returns, **kwargs)) diff --git a/tests/unit/generator/templates/helpers/test_kotlin.py b/tests/unit/generator/templates/helpers/test_kotlin.py index 84f70c67..88bb81bd 100644 --- a/tests/unit/generator/templates/helpers/test_kotlin.py +++ b/tests/unit/generator/templates/helpers/test_kotlin.py @@ -34,6 +34,10 @@ def kotlin_class(): # add property builder.member_property(prot=visibility) + # add constructor + builder.member_function(prot=visibility, + name=visibility.capitalize() + "Constructor", + kind="constructor") # add some method builder.member_function(prot=visibility, name=visibility.capitalize() + "Method") # add some method without return type @@ -86,6 +90,28 @@ def test_private_constants__no_filter(helper): assert result == ["PrivateConstant"] +def test_public_constructors__no_filter(helper): + result = [m.name for m in helper.constructors(prot="public")] + assert result == ["PublicConstructor"] + + +def test_public_constructors__filter_match(helper): + helper.insert_filter = InsertionFilter(members="Public") + result = [m.name for m in helper.constructors(prot="public")] + assert result == ["PublicConstructor"] + + +def test_public_constructors__filter_no_match(helper): + helper.insert_filter = InsertionFilter(members="NONE") + result = [m.name for m in helper.constructors(prot="public")] + assert len(result) == 0 + + +def test_private_constructors__no_filter(helper): + result = [m.name for m in helper.constructors(prot="private")] + assert result == ["PrivateConstructor"] + + def test_parameter(helper): ref = TypeRef(language="kotlin") ref.name = "MyType" diff --git a/tests/unit/parser/dokka/__init__.py b/tests/unit/parser/dokka/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/parser/dokka/test_parser.py b/tests/unit/parser/dokka/test_parser.py new file mode 100644 index 00000000..17795293 --- /dev/null +++ b/tests/unit/parser/dokka/test_parser.py @@ -0,0 +1,162 @@ +# Copyright (C) 2019, TomTom (http://tomtom.com). +# +# 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 pytest +from pytest import param + +from asciidoxy.api_reference import ApiReference +from asciidoxy.parser import parser_factory +from tests.unit.api_reference_loader import ApiReferenceLoader +from tests.unit.matchers import ( + IsEmpty, + IsNone, + Unordered, + m_compound, + m_parameter, + m_returnvalue, + m_typeref, +) + + +@pytest.fixture(scope="module") +def api_reference(): + return ApiReferenceLoader().add("dokka", "kotlin/default").load() + + +def test_create_dokka_parser(generated_test_data): + api_reference = ApiReference() + parser = parser_factory("dokka", api_reference) + assert parser is not None + + parser.parse(generated_test_data("dokka", "kotlin/default/asciidoxy.json")) + + element = api_reference.find(name="asciidoxy.Coordinate", kind="class") + assert element is not None + + +@pytest.mark.parametrize( + "search_params,matcher", + [ + param(dict(name="asciidoxy.Coordinate", kind="class", lang="kotlin"), + m_compound( + language="kotlin", + id="kotlin-asciidoxy1_1_Coordinate", + name="Coordinate", + full_name="asciidoxy.Coordinate", + namespace="asciidoxy", + kind="class", + prot="public", + brief="Class to hold information about a coordinate.", + description="A coordinate has a latitude, longitude, and an altitude.", + members=Unordered( + m_compound(name="isValid"), + m_compound(name="latitude"), + m_compound(name="longitude"), + m_compound(name="altitude"), + m_compound(name="Coordinate"), + ), + ), + id="Basic class"), + param( + dict(name="asciidoxy.Coordinate.isValid", kind="function", lang="kotlin"), + m_compound( + language="kotlin", + id="kotlin-asciidoxy1_1_Coordinate1_1_isValid1_1_2_", + name="isValid", + full_name="asciidoxy.Coordinate.isValid", + namespace="asciidoxy.Coordinate", + kind="function", + prot="public", + brief="Check if the coordinate is valid.", + description="A coordinate is valid if its values are within WGS84 bounds.", + params=[], + returns=m_returnvalue( + description="True if valid, false if not.", + type=m_typeref( + name="Boolean", + id=IsNone(), # TODO: id="kotlin-kotlin1_1_Boolean", + ), + ), + ), + id="Basic method"), + param( + dict(name="asciidoxy.Coordinate.latitude", kind="property", lang="kotlin"), + m_compound( + language="kotlin", + id="kotlin-asciidoxy1_1_Coordinate1_1_latitude1_1_2_", + name="latitude", + full_name="asciidoxy.Coordinate.latitude", + namespace="asciidoxy.Coordinate", + kind="property", + prot="public", + brief="The latitude in degrees.", + description=IsEmpty(), + returns=m_returnvalue( + description=IsEmpty(), + type=m_typeref( + name="Double", + id=IsNone(), # TODO: id="kotlin-kotlin1_1_Double", + ), + ), + ), + id="Basic property"), + param( + dict(name="asciidoxy.Coordinate.Coordinate", kind="constructor", lang="kotlin"), + m_compound( + language="kotlin", + id=("kotlin-asciidoxy1_1_Coordinate1_1_Coordinate1_1_2_kotlin1_1_Double2_kotlin" + "1_1_Double2_kotlin1_1_Double"), + name="Coordinate", + full_name="asciidoxy.Coordinate.Coordinate", + namespace="asciidoxy.Coordinate", + kind="constructor", + prot="public", + brief=IsEmpty(), + params=[ + m_parameter( + name="latitude", + description="The latitude in degrees.", + type=m_typeref( + name="Double", + id=IsNone(), # TODO: id="kotlin-kotlin1_1_Double", + ), + ), + m_parameter( + name="longitude", + description="The longitude in degrees.", + type=m_typeref( + name="Double", + id=IsNone(), # TODO: id="kotlin-kotlin1_1_Double", + ), + ), + m_parameter( + name="altitude", + description="The altitude in meters.", + type=m_typeref( + name="Double", + id=IsNone(), # TODO: id="kotlin-kotlin1_1_Double", + ), + ), + ], + returns=m_returnvalue( + description=IsEmpty(), + type=m_typeref( + name="Coordinate", + id="kotlin-asciidoxy1_1_Coordinate", + ), + ), + ), + id="Basic constructor"), + ]) +def test_parse_kotlin(api_reference, search_params, matcher): + matcher.assert_matches(api_reference.find(**search_params)) diff --git a/tests/visual/kotlin.adoc b/tests/visual/kotlin.adoc new file mode 100644 index 00000000..9305aec4 --- /dev/null +++ b/tests/visual/kotlin.adoc @@ -0,0 +1,19 @@ +// Copyright (C) 2019, TomTom (http://tomtom.com). +// +// 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. += Kotlin test +:toc: left + +== Default + +${insert("asciidoxy.Coordinate", leveloffset="+2")} diff --git a/tests/visual/kotlin.toml b/tests/visual/kotlin.toml new file mode 100644 index 00000000..1d33e622 --- /dev/null +++ b/tests/visual/kotlin.toml @@ -0,0 +1,19 @@ +# Copyright (C) 2019, TomTom (http://tomtom.com). +# +# 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. + +[packages] + +[packages.kotlin-default] +type = "local" +package_dir = "tests/data/generated/dokka/kotlin/default"