Skip to content

Commit 4568f5a

Browse files
committed
WIP Markdown support
1 parent 1715dfe commit 4568f5a

File tree

21 files changed

+819
-13
lines changed

21 files changed

+819
-13
lines changed

autoapi/extension.py

+47-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
This extension allows you to automagically generate API documentation from your project.
44
"""
5+
import collections
56
import io
67
import os
78
import shutil
@@ -38,6 +39,11 @@
3839
"special-members",
3940
"imported-members",
4041
]
42+
_TEMPLATE_SUFFIXES = {
43+
"markdown": ".md",
44+
"restructuredtext": ".rst",
45+
}
46+
"""Map an output format to the file extension of the templates to use."""
4147
_VIEWCODE_CACHE: Dict[str, Tuple[str, Dict]] = {}
4248
"""Caches a module's parse results for use in viewcode."""
4349

@@ -64,6 +70,35 @@ def _normalise_autoapi_dirs(autoapi_dirs, srcdir):
6470
return normalised_dirs
6571

6672

73+
def _normalise_source_suffix(source_suffix):
74+
result = collections.OrderedDict()
75+
76+
if isinstance(source_suffix, str):
77+
result[source_suffix] = "restructuredtext"
78+
elif isinstance(source_suffix, list):
79+
for suffix in source_suffix:
80+
result[suffix] = "restructuredtext"
81+
else:
82+
result = source_suffix
83+
84+
return result
85+
86+
87+
def _normalise_output_suffix(output_suffix, source_suffix):
88+
result = output_suffix
89+
90+
if not result:
91+
if ".rst" in source_suffix:
92+
result = ".rst"
93+
elif ".txt" in source_suffix:
94+
result = ".txt"
95+
else:
96+
# Fallback to first suffix listed
97+
result = next(iter(source_suffix))
98+
99+
return result
100+
101+
67102
def run_autoapi(app): # pylint: disable=too-many-branches
68103
"""Load AutoAPI data from the filesystem."""
69104
if app.config.autoapi_type not in LANGUAGE_MAPPERS:
@@ -142,20 +177,25 @@ def run_autoapi(app): # pylint: disable=too-many-branches
142177
else:
143178
ignore_patterns = DEFAULT_IGNORE_PATTERNS.get(app.config.autoapi_type, [])
144179

145-
if ".rst" in app.config.source_suffix:
146-
out_suffix = ".rst"
147-
elif ".txt" in app.config.source_suffix:
148-
out_suffix = ".txt"
180+
source_suffix = _normalise_source_suffix(app.config.source_suffix)
181+
out_suffix = _normalise_output_suffix(app.config.autoapi_output_suffix, source_suffix)
182+
output_format = source_suffix.get(out_suffix)
183+
if output_format:
184+
if app.config.autoapi_type == "python":
185+
if output_format not in _TEMPLATE_SUFFIXES:
186+
raise ExtensionError(f"Unknown output format '{output_format}'")
187+
elif output_format != "restructuredtext":
188+
raise ExtensionError(f"Unknown output format '{output_format}'")
149189
else:
150-
# Fallback to first suffix listed
151-
out_suffix = next(iter(app.config.source_suffix))
190+
raise ExtensionError(f"autoapi_output_suffix '{out_suffix}' must be registered with source_suffix")
152191

153192
if sphinx_mapper_obj.load(
154193
patterns=file_patterns, dirs=normalised_dirs, ignore=ignore_patterns
155194
):
156195
sphinx_mapper_obj.map(options=app.config.autoapi_options)
157196

158197
if app.config.autoapi_generate_api_docs:
198+
app.env.autoapi_template_suffix = _TEMPLATE_SUFFIXES[output_format]
159199
sphinx_mapper_obj.output_rst(root=normalized_root, source_suffix=out_suffix)
160200

161201

@@ -298,6 +338,7 @@ def setup(app):
298338
app.add_config_value("autoapi_python_class_content", "class", "html")
299339
app.add_config_value("autoapi_generate_api_docs", True, "html")
300340
app.add_config_value("autoapi_prepare_jinja_env", None, "html")
341+
app.add_config_value("autoapi_output_suffix", None, "html")
301342
app.add_autodocumenter(documenters.AutoapiFunctionDocumenter)
302343
app.add_autodocumenter(documenters.AutoapiPropertyDocumenter)
303344
app.add_autodocumenter(documenters.AutoapiDecoratorDocumenter)

autoapi/mappers/base.py

+20-7
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
Path = namedtuple("Path", ["absolute", "relative"])
1919

2020

21+
def _md_fence(config):
22+
colon_fence = "colon_fence" in getattr(config, "myst_enable_extensions", [])
23+
return ":::" if colon_fence else "```"
24+
25+
2126
class PythonMapperBase:
2227

2328
"""Base object for JSON -> Python object mapping.
@@ -75,9 +80,11 @@ def render(self, **kwargs):
7580

7681
ctx = {}
7782
try:
78-
template = self.jinja_env.get_template(f"{self.language}/{self.type}.rst")
83+
template_suffix = self.app.env.autoapi_template_suffix
84+
template_path = f"{self.language}/{self.type}{template_suffix}"
85+
template = self.jinja_env.get_template(template_path)
7986
except TemplateNotFound:
80-
template = self.jinja_env.get_template(f"base/{self.type}.rst")
87+
template = self.jinja_env.get_template(f"base/{self.type}{template_suffix}")
8188

8289
ctx.update(**self.get_context_data())
8390
ctx.update(**kwargs)
@@ -92,6 +99,7 @@ def get_context_data(self):
9299
return {
93100
"autoapi_options": self.app.config.autoapi_options,
94101
"include_summaries": self.app.config.autoapi_include_summaries,
102+
"md_fence": _md_fence(self.app.config),
95103
"obj": self,
96104
"sphinx_version": sphinx.version_info,
97105
}
@@ -327,12 +335,17 @@ def output_rst(self, root, source_suffix):
327335
detail_file.write(rst.encode("utf-8"))
328336

329337
if self.app.config.autoapi_add_toctree_entry:
330-
self._output_top_rst(root)
338+
self._output_top_rst(root, source_suffix)
331339

332-
def _output_top_rst(self, root):
340+
def _output_top_rst(self, root, source_suffix):
333341
# Render Top Index
334-
top_level_index = os.path.join(root, "index.rst")
342+
top_level_index = os.path.join(root, f"index{source_suffix}")
335343
pages = self.objects.values()
344+
md_fence = _md_fence(self.app.config)
345+
346+
template_suffix = self.app.env.autoapi_template_suffix
347+
template = self.jinja_env.get_template(f"index{template_suffix}")
348+
content = template.render(pages=pages, md_fence=md_fence)
349+
336350
with open(top_level_index, "wb") as top_level_file:
337-
content = self.jinja_env.get_template("index.rst")
338-
top_level_file.write(content.render(pages=pages).encode("utf-8"))
351+
top_level_file.write(content.encode("utf-8"))

autoapi/templates/base/base.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{ md_fence + "{" + obj.type + "}" }} {{ obj.name }}
2+
{% if summary %}
3+
4+
{{ obj.summary }}
5+
6+
{% endif %}
7+
{{ md_fence }}

autoapi/templates/index.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# API Reference
2+
3+
This page contains auto-generated API reference documentation [^f1].
4+
5+
{{ md_fence }}{toctree}
6+
:titlesonly:
7+
8+
{% for page in pages %}
9+
{% if page.top_level_object and page.display %}
10+
{{ page.include_path }}
11+
{% endif %}
12+
{% endfor %}
13+
{{ md_fence }}
14+
15+
[^f1]: Created with [sphinx-autoapi](https://github.com/readthedocs/sphinx-autoapi)

autoapi/templates/python/attribute.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% extends "python/data.md" %}

autoapi/templates/python/class.md

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{% if obj.display %}
2+
{{ md_fence + "{py:" + obj.type + "} " + obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %}
3+
{% for (args, return_annotation) in obj.overloads %}
4+
{{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %}
5+
{% endfor %}
6+
7+
8+
{% if obj.bases %}
9+
{% if "show-inheritance" in autoapi_options %}
10+
Bases: {% for base in obj.bases %}{{ base|link_objs }}{% if not loop.last %}, {% endif %}{% endfor %}
11+
{% endif %}
12+
13+
14+
{% if "show-inheritance-diagram" in autoapi_options and obj.bases != ["object"] %}
15+
{{ md_fence + "{autoapi-inheritance-diagram} " + obj.obj["full_name"] }}
16+
:parts: 1
17+
{% if "private-members" in autoapi_options %}
18+
:private-bases:
19+
{% endif %}
20+
{{ md_fence }}
21+
22+
{% endif %}
23+
{% endif %}
24+
{% if obj.docstring %}
25+
{{ obj.docstring|indent(3) }}
26+
{% endif %}
27+
{% if "inherited-members" in autoapi_options %}
28+
{% set visible_classes = obj.classes|selectattr("display")|list %}
29+
{% else %}
30+
{% set visible_classes = obj.classes|rejectattr("inherited")|selectattr("display")|list %}
31+
{% endif %}
32+
{% for klass in visible_classes %}
33+
{{ klass.render()|indent(3) }}
34+
{% endfor %}
35+
{% if "inherited-members" in autoapi_options %}
36+
{% set visible_properties = obj.properties|selectattr("display")|list %}
37+
{% else %}
38+
{% set visible_properties = obj.properties|rejectattr("inherited")|selectattr("display")|list %}
39+
{% endif %}
40+
{% for property in visible_properties %}
41+
{{ property.render()|indent(3) }}
42+
{% endfor %}
43+
{% if "inherited-members" in autoapi_options %}
44+
{% set visible_attributes = obj.attributes|selectattr("display")|list %}
45+
{% else %}
46+
{% set visible_attributes = obj.attributes|rejectattr("inherited")|selectattr("display")|list %}
47+
{% endif %}
48+
{% for attribute in visible_attributes %}
49+
{{ attribute.render()|indent(3) }}
50+
{% endfor %}
51+
{% if "inherited-members" in autoapi_options %}
52+
{% set visible_methods = obj.methods|selectattr("display")|list %}
53+
{% else %}
54+
{% set visible_methods = obj.methods|rejectattr("inherited")|selectattr("display")|list %}
55+
{% endif %}
56+
{% for method in visible_methods %}
57+
{{ method.render()|indent(3) }}
58+
{% endfor %}
59+
{{ md_fence }}
60+
{% endif %}

autoapi/templates/python/data.md

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{% if obj.display %}
2+
{{ md_fence + "{py:" + obj.type + "} " + obj.name }}
3+
{%- if obj.annotation is not none %}
4+
5+
:type: {%- if obj.annotation %} {{ obj.annotation }}{%- endif %}
6+
7+
{%- endif %}
8+
9+
{%- if obj.value is not none %}
10+
11+
:value: {% if obj.value is string and obj.value.splitlines()|count > 1 -%}
12+
Multiline-String
13+
14+
{{ md_fence }}{raw} html
15+
16+
<details><summary>Show Value</summary>
17+
{{ md_fence }}
18+
19+
```python
20+
"""{{ obj.value|indent(width=8,blank=true) }}"""
21+
```
22+
23+
{{ md_fence }}{raw} html
24+
25+
</details>
26+
{{ md_fence }}
27+
{%- else -%}
28+
{{ "%r" % obj.value|string|truncate(100) }}
29+
{%- endif %}
30+
{%- endif %}
31+
32+
33+
{{ obj.docstring|indent(3) }}
34+
{{ md_fence }}
35+
{% endif %}

autoapi/templates/python/exception.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% extends "python/class.md" %}

autoapi/templates/python/function.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{% if obj.display %}
2+
{{ md_fence + "{py:function} " + obj.short_name + "(" + obj.args + ")" }}{% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
3+
4+
{% for (args, return_annotation) in obj.overloads %}
5+
{{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
6+
7+
{% endfor %}
8+
{% for property in obj.properties %}
9+
:{{ property }}:
10+
{% endfor %}
11+
12+
{% if obj.docstring %}
13+
{{ obj.docstring|indent(3) }}
14+
{% endif %}
15+
{% endif %}
16+
{{ md_fence }}

autoapi/templates/python/method.md

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{%- if obj.display %}
2+
{{ md_fence + "{py:method} " + obj.short_name + "(" + obj.args + ")" }}{% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
3+
4+
{% for (args, return_annotation) in obj.overloads %}
5+
{{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
6+
7+
{% endfor %}
8+
{% if obj.properties %}
9+
{% for property in obj.properties %}
10+
:{{ property }}:
11+
{% endfor %}
12+
13+
{% else %}
14+
15+
{% endif %}
16+
{% if obj.docstring %}
17+
{{ obj.docstring|indent(3) }}
18+
{% endif %}
19+
{% endif %}
20+
{{ md_fence }}

0 commit comments

Comments
 (0)