diff --git a/autoapi/extension.py b/autoapi/extension.py index 24a2b14..ebfffee 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -2,6 +2,7 @@ This extension allows you to automagically generate API documentation from your project. """ +import collections import io import os import shutil @@ -38,6 +39,11 @@ _DEFAULT_OPTIONS = [ "special-members", "imported-members", ] +_TEMPLATE_SUFFIXES = { + "markdown": ".md", + "restructuredtext": ".rst", +} +"""Map an output format to the file extension of the templates to use.""" _VIEWCODE_CACHE: Dict[str, Tuple[str, Dict]] = {} """Caches a module's parse results for use in viewcode.""" @@ -64,6 +70,35 @@ def _normalise_autoapi_dirs(autoapi_dirs, srcdir): return normalised_dirs +def _normalise_source_suffix(source_suffix): + result = collections.OrderedDict() + + if isinstance(source_suffix, str): + result[source_suffix] = "restructuredtext" + elif isinstance(source_suffix, list): + for suffix in source_suffix: + result[suffix] = "restructuredtext" + else: + result = source_suffix + + return result + + +def _normalise_output_suffix(output_suffix, source_suffix): + result = output_suffix + + if not result: + if ".rst" in source_suffix: + result = ".rst" + elif ".txt" in source_suffix: + result = ".txt" + else: + # Fallback to first suffix listed + result = next(iter(source_suffix)) + + return result + + def run_autoapi(app): # pylint: disable=too-many-branches """Load AutoAPI data from the filesystem.""" if app.config.autoapi_type not in LANGUAGE_MAPPERS: @@ -142,13 +177,17 @@ def run_autoapi(app): # pylint: disable=too-many-branches else: ignore_patterns = DEFAULT_IGNORE_PATTERNS.get(app.config.autoapi_type, []) - if ".rst" in app.config.source_suffix: - out_suffix = ".rst" - elif ".txt" in app.config.source_suffix: - out_suffix = ".txt" + source_suffix = _normalise_source_suffix(app.config.source_suffix) + out_suffix = _normalise_output_suffix(app.config.autoapi_output_suffix, source_suffix) + output_format = source_suffix.get(out_suffix) + if output_format: + if app.config.autoapi_type == "python": + if output_format not in _TEMPLATE_SUFFIXES: + raise ExtensionError(f"Unknown output format '{output_format}'") + elif output_format != "restructuredtext": + raise ExtensionError(f"Unknown output format '{output_format}'") else: - # Fallback to first suffix listed - out_suffix = next(iter(app.config.source_suffix)) + raise ExtensionError(f"autoapi_output_suffix '{out_suffix}' must be registered with source_suffix") if sphinx_mapper_obj.load( patterns=file_patterns, dirs=normalised_dirs, ignore=ignore_patterns @@ -156,6 +195,7 @@ def run_autoapi(app): # pylint: disable=too-many-branches sphinx_mapper_obj.map(options=app.config.autoapi_options) if app.config.autoapi_generate_api_docs: + app.env.autoapi_template_suffix = _TEMPLATE_SUFFIXES[output_format] sphinx_mapper_obj.output_rst(root=normalized_root, source_suffix=out_suffix) @@ -298,6 +338,7 @@ def setup(app): app.add_config_value("autoapi_python_class_content", "class", "html") app.add_config_value("autoapi_generate_api_docs", True, "html") app.add_config_value("autoapi_prepare_jinja_env", None, "html") + app.add_config_value("autoapi_output_suffix", None, "html") app.add_autodocumenter(documenters.AutoapiFunctionDocumenter) app.add_autodocumenter(documenters.AutoapiPropertyDocumenter) app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter) diff --git a/autoapi/mappers/base.py b/autoapi/mappers/base.py index 9f14d05..6494800 100644 --- a/autoapi/mappers/base.py +++ b/autoapi/mappers/base.py @@ -18,6 +18,11 @@ LOGGER = sphinx.util.logging.getLogger(__name__) Path = namedtuple("Path", ["absolute", "relative"]) +def _md_fence(config): + colon_fence = "colon_fence" in getattr(config, "myst_enable_extensions", []) + return ":::" if colon_fence else "```" + + class PythonMapperBase: """Base object for JSON -> Python object mapping. @@ -75,9 +80,11 @@ class PythonMapperBase: ctx = {} try: - template = self.jinja_env.get_template(f"{self.language}/{self.type}.rst") + template_suffix = self.app.env.autoapi_template_suffix + template_path = f"{self.language}/{self.type}{template_suffix}" + template = self.jinja_env.get_template(template_path) except TemplateNotFound: - template = self.jinja_env.get_template(f"base/{self.type}.rst") + template = self.jinja_env.get_template(f"base/{self.type}{template_suffix}") ctx.update(**self.get_context_data()) ctx.update(**kwargs) @@ -92,6 +99,7 @@ class PythonMapperBase: return { "autoapi_options": self.app.config.autoapi_options, "include_summaries": self.app.config.autoapi_include_summaries, + "md_fence": _md_fence(self.app.config), "obj": self, "sphinx_version": sphinx.version_info, } @@ -327,12 +335,17 @@ class SphinxMapperBase: detail_file.write(rst.encode("utf-8")) if self.app.config.autoapi_add_toctree_entry: - self._output_top_rst(root) + self._output_top_rst(root, source_suffix) - def _output_top_rst(self, root): + def _output_top_rst(self, root, source_suffix): # Render Top Index - top_level_index = os.path.join(root, "index.rst") + top_level_index = os.path.join(root, f"index{source_suffix}") pages = self.objects.values() + md_fence = _md_fence(self.app.config) + + template_suffix = self.app.env.autoapi_template_suffix + template = self.jinja_env.get_template(f"index{template_suffix}") + content = template.render(pages=pages, md_fence=md_fence) + with open(top_level_index, "wb") as top_level_file: - content = self.jinja_env.get_template("index.rst") - top_level_file.write(content.render(pages=pages).encode("utf-8")) + top_level_file.write(content.encode("utf-8")) diff --git a/autoapi/templates/base/base.md b/autoapi/templates/base/base.md new file mode 100644 index 0000000..73d99e3 --- /dev/null +++ b/autoapi/templates/base/base.md @@ -0,0 +1,7 @@ +{{ md_fence + "{" + obj.type + "}" }} {{ obj.name }} +{% if summary %} + +{{ obj.summary }} + +{% endif %} +{{ md_fence }} diff --git a/autoapi/templates/index.md b/autoapi/templates/index.md new file mode 100644 index 0000000..3ada442 --- /dev/null +++ b/autoapi/templates/index.md @@ -0,0 +1,15 @@ +# API Reference + +This page contains auto-generated API reference documentation [^f1]. + +{{ md_fence }}{toctree} +:titlesonly: + +{% for page in pages %} +{% if page.top_level_object and page.display %} +{{ page.include_path }} +{% endif %} +{% endfor %} +{{ md_fence }} + +[^f1]: Created with [sphinx-autoapi](https://github.com/readthedocs/sphinx-autoapi) diff --git a/autoapi/templates/python/attribute.md b/autoapi/templates/python/attribute.md new file mode 100644 index 0000000..44b7414 --- /dev/null +++ b/autoapi/templates/python/attribute.md @@ -0,0 +1 @@ +{% extends "python/data.md" %} diff --git a/autoapi/templates/python/class.md b/autoapi/templates/python/class.md new file mode 100644 index 0000000..9b9ebb4 --- /dev/null +++ b/autoapi/templates/python/class.md @@ -0,0 +1,60 @@ +{% if obj.display %} +{{ md_fence + "{py:" + obj.type + "} " + obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %} +{% for (args, return_annotation) in obj.overloads %} + {{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %} +{% endfor %} + + + {% if obj.bases %} + {% if "show-inheritance" in autoapi_options %} + Bases: {% for base in obj.bases %}{{ base|link_objs }}{% if not loop.last %}, {% endif %}{% endfor %} + {% endif %} + + + {% if "show-inheritance-diagram" in autoapi_options and obj.bases != ["object"] %} + {{ md_fence + "{autoapi-inheritance-diagram} " + obj.obj["full_name"] }} + :parts: 1 + {% if "private-members" in autoapi_options %} + :private-bases: + {% endif %} + {{ md_fence }} + + {% endif %} + {% endif %} + {% if obj.docstring %} + {{ obj.docstring|indent(3) }} + {% endif %} + {% if "inherited-members" in autoapi_options %} + {% set visible_classes = obj.classes|selectattr("display")|list %} + {% else %} + {% set visible_classes = obj.classes|rejectattr("inherited")|selectattr("display")|list %} + {% endif %} + {% for klass in visible_classes %} + {{ klass.render()|indent(3) }} + {% endfor %} + {% if "inherited-members" in autoapi_options %} + {% set visible_properties = obj.properties|selectattr("display")|list %} + {% else %} + {% set visible_properties = obj.properties|rejectattr("inherited")|selectattr("display")|list %} + {% endif %} + {% for property in visible_properties %} + {{ property.render()|indent(3) }} + {% endfor %} + {% if "inherited-members" in autoapi_options %} + {% set visible_attributes = obj.attributes|selectattr("display")|list %} + {% else %} + {% set visible_attributes = obj.attributes|rejectattr("inherited")|selectattr("display")|list %} + {% endif %} + {% for attribute in visible_attributes %} + {{ attribute.render()|indent(3) }} + {% endfor %} + {% if "inherited-members" in autoapi_options %} + {% set visible_methods = obj.methods|selectattr("display")|list %} + {% else %} + {% set visible_methods = obj.methods|rejectattr("inherited")|selectattr("display")|list %} + {% endif %} + {% for method in visible_methods %} + {{ method.render()|indent(3) }} + {% endfor %} +{{ md_fence }} +{% endif %} diff --git a/autoapi/templates/python/data.md b/autoapi/templates/python/data.md new file mode 100644 index 0000000..c8f1e2d --- /dev/null +++ b/autoapi/templates/python/data.md @@ -0,0 +1,35 @@ +{% if obj.display %} +{{ md_fence + "{py:" + obj.type + "} " + obj.name }} + {%- if obj.annotation is not none %} + + :type: {%- if obj.annotation %} {{ obj.annotation }}{%- endif %} + + {%- endif %} + + {%- if obj.value is not none %} + + :value: {% if obj.value is string and obj.value.splitlines()|count > 1 -%} + Multiline-String + + {{ md_fence }}{raw} html + +
Show Value + {{ md_fence }} + + ```python + """{{ obj.value|indent(width=8,blank=true) }}""" + ``` + + {{ md_fence }}{raw} html + +
+ {{ md_fence }} + {%- else -%} + {{ "%r" % obj.value|string|truncate(100) }} + {%- endif %} + {%- endif %} + + + {{ obj.docstring|indent(3) }} +{{ md_fence }} +{% endif %} diff --git a/autoapi/templates/python/exception.md b/autoapi/templates/python/exception.md new file mode 100644 index 0000000..0a8caaa --- /dev/null +++ b/autoapi/templates/python/exception.md @@ -0,0 +1 @@ +{% extends "python/class.md" %} diff --git a/autoapi/templates/python/function.md b/autoapi/templates/python/function.md new file mode 100644 index 0000000..f85a33f --- /dev/null +++ b/autoapi/templates/python/function.md @@ -0,0 +1,16 @@ +{% if obj.display %} +{{ md_fence + "{py:function} " + obj.short_name + "(" + obj.args + ")" }}{% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %} + +{% for (args, return_annotation) in obj.overloads %} + {{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %} + +{% endfor %} + {% for property in obj.properties %} + :{{ property }}: + {% endfor %} + + {% if obj.docstring %} + {{ obj.docstring|indent(3) }} + {% endif %} +{% endif %} +{{ md_fence }} diff --git a/autoapi/templates/python/method.md b/autoapi/templates/python/method.md new file mode 100644 index 0000000..fdc011c --- /dev/null +++ b/autoapi/templates/python/method.md @@ -0,0 +1,20 @@ +{%- if obj.display %} +{{ md_fence + "{py:method} " + obj.short_name + "(" + obj.args + ")" }}{% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %} + +{% for (args, return_annotation) in obj.overloads %} + {{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %} + +{% endfor %} + {% if obj.properties %} + {% for property in obj.properties %} + :{{ property }}: + {% endfor %} + + {% else %} + + {% endif %} + {% if obj.docstring %} + {{ obj.docstring|indent(3) }} + {% endif %} +{% endif %} +{{ md_fence }} diff --git a/autoapi/templates/python/module.md b/autoapi/templates/python/module.md new file mode 100644 index 0000000..e0d777d --- /dev/null +++ b/autoapi/templates/python/module.md @@ -0,0 +1,113 @@ +{% if not obj.display %} +:orphan: + +{% endif %} +# {py:mod}`{{ obj.name }}` + +{{ md_fence + "{py:module} " + obj.name }} +{{ md_fence }} + +{% if obj.docstring %} +{{ md_fence }}{autoapi-nested-parse} + + {{ obj.docstring|indent(3) }} +{{ md_fence}} + +{% endif %} + +{% block subpackages %} +{% set visible_subpackages = obj.subpackages|selectattr("display")|list %} +{% if visible_subpackages %} +## Subpackages +{{ md_fence }}{toctree} + :titlesonly: + :maxdepth: 3 + +{% for subpackage in visible_subpackages %} + {{ subpackage.short_name }}/index.rst +{% endfor %} +{{ md_fence }} + +{% endif %} +{% endblock %} +{% block submodules %} +{% set visible_submodules = obj.submodules|selectattr("display")|list %} +{% if visible_submodules %} +## Submodules +{{ md_fence }}{toctree} + :titlesonly: + :maxdepth: 1 + +{% for submodule in visible_submodules %} + {{ submodule.short_name }}/index.rst +{% endfor %} +{{ md_fence }} + + +{% endif %} +{% endblock %} +{% block content %} +{% if obj.all is not none %} +{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %} +{% elif obj.type is equalto("package") %} +{% set visible_children = obj.children|selectattr("display")|list %} +{% else %} +{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %} +{% endif %} +{% if visible_children %} +## {{ obj.type|title }} Contents + +{% set visible_classes = visible_children|selectattr("type", "equalto", "class")|list %} +{% set visible_functions = visible_children|selectattr("type", "equalto", "function")|list %} +{% set visible_attributes = visible_children|selectattr("type", "equalto", "data")|list %} +{% if "show-module-summary" in autoapi_options and (visible_classes or visible_functions) %} +{% block classes scoped %} +{% if visible_classes %} +### Classes + +{{ md_fence }}{autoapisummary} + +{% for klass in visible_classes %} + {{ klass.id }} +{% endfor %} +{{ md_fence }} + + +{% endif %} +{% endblock %} + +{% block functions scoped %} +{% if visible_functions %} +### Functions + +{{ md_fence }}{autoapisummary} + +{% for function in visible_functions %} + {{ function.id }} +{% endfor %} +{{ md_fence }} + + +{% endif %} +{% endblock %} + +{% block attributes scoped %} +{% if visible_attributes %} +### Attributes + +{{ md_fence }}{autoapisummary} + +{% for attribute in visible_attributes %} + {{ attribute.id }} +{% endfor %} +{{ md_fence }} + + +{% endif %} +{% endblock %} +{% endif %} +{% for obj_item in visible_children %} +{{ obj_item.render()|indent(0) }} +{% endfor %} +{% endif %} +{% endblock %} diff --git a/autoapi/templates/python/package.md b/autoapi/templates/python/package.md new file mode 100644 index 0000000..ce1870d --- /dev/null +++ b/autoapi/templates/python/package.md @@ -0,0 +1 @@ +{% extends "python/module.md" %} diff --git a/autoapi/templates/python/property.md b/autoapi/templates/python/property.md new file mode 100644 index 0000000..f224f6f --- /dev/null +++ b/autoapi/templates/python/property.md @@ -0,0 +1,16 @@ +{%- if obj.display %} +{{ md_fence }}{py:property} {{ obj.short_name }} + {% if obj.annotation %} + :type: {{ obj.annotation }} + {% endif %} + {% if obj.properties %} + {% for property in obj.properties %} + :{{ property }}: + {% endfor %} + {% endif %} + + {% if obj.docstring %} + {{ obj.docstring|indent(3) }} + {% endif %} +{{ md_fence }} +{% endif %} diff --git a/tests/python/pymarkdownexample/autoapi/example/index.md b/tests/python/pymarkdownexample/autoapi/example/index.md new file mode 100644 index 0000000..1ab4089 --- /dev/null +++ b/tests/python/pymarkdownexample/autoapi/example/index.md @@ -0,0 +1,240 @@ +# {py:mod}`example` + +:::{py:module} example +::: + +:::{autoapi-nested-parse} + + Example module + + This is a description + +::: + + +## Module Contents + +### Classes + +:::{autoapisummary} + + example.Foo + example.Bar + example.ClassWithNoInit + example.One + example.MultilineOne + example.Two +::: + + + +### Functions + +:::{autoapisummary} + + example.decorator_okay + example.fn_with_long_sig +::: + + + +### Attributes + +:::{autoapisummary} + + example.A_TUPLE + example.A_LIST +::: + + +:::{py:data} A_TUPLE + :value: "('a', 'b')" + + A tuple to be rendered as a tuple. + +::: + +:::{py:data} A_LIST + :value: "['c', 'd']" + + A list to be rendered as a list. + +::: + +:::{py:class} Foo(attr) + + Bases: :py:obj:`object` + + Can we parse arguments from the class docstring? + + :param attr: Set an attribute. + :type attr: str + + Constructor docstring + + :::{py:class} Meta + + Bases: :py:obj:`object` + + A nested class just to test things out + + :::{py:method} foo() + :classmethod: + + The foo class method + + ::: + ::: + + :::{py:property} property_simple + :type: int + + This property should parse okay. + + ::: + + :::{py:attribute} class_var + :value: '42' + + + ::: + + :::{py:attribute} another_class_var + :value: '42' + + Another class var docstring + + ::: + + :::{py:attribute} attr2 + + This is the docstring of an instance attribute. + + :type: str + + ::: + + :::{py:method} method_okay(foo=None, bar=None) + + This method should parse okay + + ::: + :::{py:method} method_multiline(foo=None, bar=None, baz=None) + + This is on multiple lines, but should parse okay too + + pydocstyle gives us lines of source. Test if this means that multiline + definitions are covered in the way we're anticipating here + + ::: + :::{py:method} method_tricky(foo=None, bar=dict(foo=1, bar=2)) + + This will likely fail our argument testing + + We parse naively on commas, so the nested dictionary will throw this off + + ::: + :::{py:method} method_sphinx_docs(foo, bar=0) + + This method is documented with sphinx style docstrings. + + :param foo: The first argument. + :type foo: int + + :param int bar: The second argument. + + :returns: The sum of foo and bar. + :rtype: int + + ::: + :::{py:method} method_google_docs(foo, bar=0) + + This method is documented with google style docstrings. + + Args: + foo (int): The first argument. + bar (int): The second argument. + + Returns: + int: The sum of foo and bar. + + ::: + :::{py:method} method_sphinx_unicode() + + This docstring uses unicodé. + + :returns: A string. + :rtype: str + + ::: + :::{py:method} method_google_unicode() + + This docstring uses unicodé. + + Returns: + str: A string. + + ::: +::: + +:::{py:function} decorator_okay(func) + + This decorator should parse okay. + +::: +:::{py:class} Bar(attr) + + Bases: :py:obj:`Foo` + + Can we parse arguments from the class docstring? + + :param attr: Set an attribute. + :type attr: str + + Constructor docstring + + :::{py:method} method_okay(foo=None, bar=None) + + This method should parse okay + + ::: +::: + +:::{py:class} ClassWithNoInit + +::: + +:::{py:class} One + + One. + + One __init__. + +::: + +:::{py:class} MultilineOne + + Bases: :py:obj:`One` + + This is a naughty summary line + that exists on two lines. + + One __init__. + +::: + +:::{py:class} Two + + Bases: :py:obj:`One` + + Two. + + One __init__. + +::: + +:::{py:function} fn_with_long_sig(this, *, function=None, has=True, quite=True, a, long, signature, many, keyword, arguments) + + A function with a long signature. + +::: diff --git a/tests/python/pymarkdownexample/autoapi/index.md b/tests/python/pymarkdownexample/autoapi/index.md new file mode 100644 index 0000000..262e78f --- /dev/null +++ b/tests/python/pymarkdownexample/autoapi/index.md @@ -0,0 +1,11 @@ +# API Reference + +This page contains auto-generated API reference documentation [^f1]. + +:::{toctree} +:titlesonly: + +/autoapi/example/index +::: + +[^f1]: Created with [sphinx-autoapi](https://github.com/readthedocs/sphinx-autoapi) \ No newline at end of file diff --git a/tests/python/pymarkdownexample/conf.py b/tests/python/pymarkdownexample/conf.py new file mode 100644 index 0000000..af865d6 --- /dev/null +++ b/tests/python/pymarkdownexample/conf.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +templates_path = ["_templates"] +source_suffix = {".md": "markdown"} +master_doc = "index" +project = "pymarkdownexample" +copyright = "2015, readthedocs" +author = "readthedocs" +version = "0.1" +release = "0.1" +language = "en" +exclude_patterns = ["_build"] +pygments_style = "sphinx" +todo_include_todos = False +html_theme = "alabaster" +htmlhelp_basename = "pymarkdownexampledoc" +extensions = ["myst_parser", "sphinx.ext.autodoc", "autoapi.extension"] +myst_enable_extensions = ["colon_fence", "fieldlist"] +autoapi_type = "python" +autoapi_dirs = ["example"] +autoapi_file_pattern = "*.py" +autoapi_python_class_content = "both" +autoapi_keep_files = True diff --git a/tests/python/pymarkdownexample/example/example.py b/tests/python/pymarkdownexample/example/example.py new file mode 100644 index 0000000..2572731 --- /dev/null +++ b/tests/python/pymarkdownexample/example/example.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +"""Example module + +This is a description +""" + +A_TUPLE = ("a", "b") +"""A tuple to be rendered as a tuple.""" +A_LIST = ["c", "d"] +"""A list to be rendered as a list.""" + + +class Foo(object): + """Can we parse arguments from the class docstring? + + :param attr: Set an attribute. + :type attr: str + """ + + class_var = 42 #: Class var docstring + + another_class_var = 42 + """Another class var docstring""" + + class Meta(object): + """A nested class just to test things out""" + + @classmethod + def foo(): + """The foo class method""" + return True + + def __init__(self, attr): + """Constructor docstring""" + self.attr = attr + self.attr2 = attr + """This is the docstring of an instance attribute. + + :type: str + """ + + @property + def property_simple(self) -> int: + """This property should parse okay.""" + return 42 + + def method_okay(self, foo=None, bar=None): + """This method should parse okay""" + return True + + def method_multiline(self, foo=None, bar=None, baz=None): + """This is on multiple lines, but should parse okay too + + pydocstyle gives us lines of source. Test if this means that multiline + definitions are covered in the way we're anticipating here + """ + return True + + def method_tricky(self, foo=None, bar=dict(foo=1, bar=2)): + """This will likely fail our argument testing + + We parse naively on commas, so the nested dictionary will throw this off + """ + return True + + def method_sphinx_docs(self, foo, bar=0): + """This method is documented with sphinx style docstrings. + + :param foo: The first argument. + :type foo: int + + :param int bar: The second argument. + + :returns: The sum of foo and bar. + :rtype: int + """ + return foo + bar + + def method_google_docs(self, foo, bar=0): + """This method is documented with google style docstrings. + + Args: + foo (int): The first argument. + bar (int): The second argument. + + Returns: + int: The sum of foo and bar. + """ + return foo + bar + + def method_sphinx_unicode(self): + """This docstring uses unicodé. + + :returns: A string. + :rtype: str + """ + return "sphinx" + + def method_google_unicode(self): + """This docstring uses unicodé. + + Returns: + str: A string. + """ + return "google" + + +Foo.bar = "dinglebop" + + +def decorator_okay(func): + """This decorator should parse okay.""" + + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper + + +class Bar(Foo): + def method_okay(self, foo=None, bar=None): + pass + + +class ClassWithNoInit: + pass + + +class One: + """One.""" + + def __init__(self): + """One __init__.""" + super().__init__() + + +class MultilineOne(One): + """This is a naughty summary line + that exists on two lines.""" + + +class Two(One): + """Two.""" + + +def fn_with_long_sig( + this, + *, + function=None, + has=True, + quite=True, + a, + long, + signature, + many, + keyword, + arguments +): + """A function with a long signature.""" diff --git a/tests/python/pymarkdownexample/index.md b/tests/python/pymarkdownexample/index.md new file mode 100644 index 0000000..b78fc2f --- /dev/null +++ b/tests/python/pymarkdownexample/index.md @@ -0,0 +1,13 @@ +# Welcome to pymarkdownexample's documentation! + +:::{toctree} +manualapi +::: + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/tests/python/pymarkdownexample/manualapi.md b/tests/python/pymarkdownexample/manualapi.md new file mode 100644 index 0000000..f33d368 --- /dev/null +++ b/tests/python/pymarkdownexample/manualapi.md @@ -0,0 +1,11 @@ +# Autodoc Directives + +:::{autoapimodule} example +:members: +:noindex: +::: + + +:::{autoapidecorator} example.decorator_okay +:noindex: +::: diff --git a/tests/python/test_pyintegration.py b/tests/python/test_pyintegration.py index 2eeaece..753c06b 100644 --- a/tests/python/test_pyintegration.py +++ b/tests/python/test_pyintegration.py @@ -187,6 +187,15 @@ class TestMovedConfPy(TestSimpleModule): ) +class TestSimpleMarkdown(TestSimpleModule): + @pytest.fixture(autouse=True, scope="class") + def built(self, builder): + builder( + "pymarkdownexample", + warningiserror=True, + ) + + class TestSimpleModuleDifferentPrimaryDomain: @pytest.fixture(autouse=True, scope="class") def built(self, builder): diff --git a/tox.ini b/tox.ini index d41fdfb..a918c73 100644 --- a/tox.ini +++ b/tox.ini @@ -22,6 +22,7 @@ extras = dotnet go deps = + myst-parser pytest commands = pytest {posargs}