mkdocs-apidescribed-plugin 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mkdocs-apidescribed-plugin might be problematic. Click here for more details.
- mkdocs_apidescribed/__init__.py +2 -0
- mkdocs_apidescribed/config.py +84 -0
- mkdocs_apidescribed/extension.py +104 -0
- mkdocs_apidescribed/formatter.py +304 -0
- mkdocs_apidescribed/inspector.py +205 -0
- mkdocs_apidescribed/parsers.py +229 -0
- mkdocs_apidescribed/plugin.py +8 -0
- mkdocs_apidescribed/probe.py +174 -0
- mkdocs_apidescribed_plugin-0.1.0.dist-info/METADATA +45 -0
- mkdocs_apidescribed_plugin-0.1.0.dist-info/RECORD +13 -0
- mkdocs_apidescribed_plugin-0.1.0.dist-info/WHEEL +4 -0
- mkdocs_apidescribed_plugin-0.1.0.dist-info/entry_points.txt +2 -0
- mkdocs_apidescribed_plugin-0.1.0.dist-info/licenses/LICENSE +28 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
from mkdocs.plugins import get_plugin_logger
|
|
3
|
+
|
|
4
|
+
log = get_plugin_logger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
DEFAULT_CONFIG = {
|
|
8
|
+
'debug': False, # show traceback
|
|
9
|
+
'skip': False, # do not describe (allows instructions to be ignored)
|
|
10
|
+
'format': 'auto', # docstrings format (sphinx, google, numpy)
|
|
11
|
+
|
|
12
|
+
'heading_base': 2, # headings base level
|
|
13
|
+
'autolinks': True, # automatically create hyperlinks
|
|
14
|
+
'categorize': True, # show symbol types (categories) titles
|
|
15
|
+
|
|
16
|
+
'location': {
|
|
17
|
+
# signature block title options
|
|
18
|
+
# hidden - do not show the block
|
|
19
|
+
# module - show module "path"
|
|
20
|
+
# file - show filepath
|
|
21
|
+
'mode': 'hidden',
|
|
22
|
+
'line': False, # show line number of source file
|
|
23
|
+
},
|
|
24
|
+
'icons': {
|
|
25
|
+
'attr': ':material-alpha:', # for attributes
|
|
26
|
+
'func': ':material-function:', # for functions
|
|
27
|
+
'cls': ':material-delta:', # for classes
|
|
28
|
+
'property': ':material-alpha-p-box-outline:', # for class properties
|
|
29
|
+
'classmethod': ':material-alpha-c-box-outline:', # for class properties
|
|
30
|
+
'staticmethod': ':material-alpha-s-box-outline:', # for class properties
|
|
31
|
+
'inherited': ':material-subdirectory-arrow-left:', # for class inherited items
|
|
32
|
+
'raises': ':material-lightning-bolt:', # for descriptions of raised exceptions
|
|
33
|
+
'returns': ':material-arrow-right-thin:', # for descriptions of returned value
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
'undocumented': False, # describe items without docstrings
|
|
37
|
+
'types': [
|
|
38
|
+
# symbol types to describe (order respected)
|
|
39
|
+
'attributes',
|
|
40
|
+
'functions',
|
|
41
|
+
'classes',
|
|
42
|
+
],
|
|
43
|
+
'ignore': [
|
|
44
|
+
# patterns of symbol names to ignore
|
|
45
|
+
'_*',
|
|
46
|
+
],
|
|
47
|
+
'only': [
|
|
48
|
+
# symbol names to describe (others will be ignored)
|
|
49
|
+
],
|
|
50
|
+
}
|
|
51
|
+
"""Default configuration for mkdocs-apidescribed."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def config_parse(src: str) -> dict:
|
|
55
|
+
try:
|
|
56
|
+
cfg = yaml.load(src, yaml.SafeLoader)
|
|
57
|
+
except yaml.MarkedYAMLError:
|
|
58
|
+
log.exception('Unable to parse options.')
|
|
59
|
+
cfg = {}
|
|
60
|
+
|
|
61
|
+
return cfg or {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def config_merge(base: dict, *, upd: dict) -> dict:
|
|
65
|
+
out = {}
|
|
66
|
+
|
|
67
|
+
for key, val in base.items():
|
|
68
|
+
|
|
69
|
+
if (upd_val := upd.get(key)) is not None:
|
|
70
|
+
|
|
71
|
+
if isinstance(val, dict) and isinstance(upd_val, dict):
|
|
72
|
+
upd_val = config_merge(val, upd=upd_val)
|
|
73
|
+
|
|
74
|
+
val = upd_val
|
|
75
|
+
|
|
76
|
+
out[key] = val
|
|
77
|
+
|
|
78
|
+
return out
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_config(src: str) -> dict:
|
|
82
|
+
parsed = config_parse(src)
|
|
83
|
+
merged = config_merge(DEFAULT_CONFIG, upd=parsed)
|
|
84
|
+
return merged
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from markdown import Extension
|
|
4
|
+
from markdown.preprocessors import Preprocessor
|
|
5
|
+
from mkdocs.plugins import get_plugin_logger
|
|
6
|
+
|
|
7
|
+
from .config import get_config
|
|
8
|
+
from .formatter import Formatter
|
|
9
|
+
from .inspector import inspect
|
|
10
|
+
|
|
11
|
+
log = get_plugin_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ApidescribedProcessor(Preprocessor):
|
|
15
|
+
|
|
16
|
+
RE_START = re.compile(r'^::: apidescribed:(.*)')
|
|
17
|
+
|
|
18
|
+
cls_fmt = Formatter
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def process_instruction(cls, *, target: str, config: dict) -> list[str]:
|
|
22
|
+
cls_fmt = cls.cls_fmt
|
|
23
|
+
out = []
|
|
24
|
+
last_type = ''
|
|
25
|
+
|
|
26
|
+
root, symbols = inspect(target, types=config['types'], ignore=config['ignore'], only=config['only'])
|
|
27
|
+
formatter = cls_fmt(symbol=root, config=config)
|
|
28
|
+
|
|
29
|
+
if prolog := formatter.run():
|
|
30
|
+
out.append(prolog)
|
|
31
|
+
|
|
32
|
+
for symbol in symbols:
|
|
33
|
+
formatter = cls_fmt(symbol=symbol, config=config)
|
|
34
|
+
|
|
35
|
+
if symbol.depth == 0:
|
|
36
|
+
if last_type != symbol.type.name and (categorized := formatter.format_category()):
|
|
37
|
+
out.append(categorized)
|
|
38
|
+
|
|
39
|
+
last_type = symbol.type.name
|
|
40
|
+
|
|
41
|
+
formatted = formatter.run()
|
|
42
|
+
out.append(formatted)
|
|
43
|
+
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
def run(self, lines: list[str]) -> list[str]:
|
|
47
|
+
out = []
|
|
48
|
+
|
|
49
|
+
re_start = self.RE_START
|
|
50
|
+
process_instruction = self.process_instruction
|
|
51
|
+
instruction = []
|
|
52
|
+
|
|
53
|
+
for line in lines:
|
|
54
|
+
|
|
55
|
+
to_add = [line]
|
|
56
|
+
|
|
57
|
+
if matched := re_start.search(line):
|
|
58
|
+
instruction.append(matched[1].strip())
|
|
59
|
+
to_add = []
|
|
60
|
+
|
|
61
|
+
elif instruction:
|
|
62
|
+
if line.startswith(' '):
|
|
63
|
+
# new option for instruction
|
|
64
|
+
instruction.append(line)
|
|
65
|
+
to_add = []
|
|
66
|
+
else:
|
|
67
|
+
# end of an instruction options block
|
|
68
|
+
debug = False
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
target = instruction[0]
|
|
72
|
+
config = get_config('\n'.join(instruction[1:]))
|
|
73
|
+
|
|
74
|
+
skip = config['skip']
|
|
75
|
+
debug = config['debug']
|
|
76
|
+
|
|
77
|
+
if skip:
|
|
78
|
+
log.debug(f'Skipped instruction for: {target}')
|
|
79
|
+
|
|
80
|
+
else:
|
|
81
|
+
to_add = process_instruction(target=target, config=config)
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
|
|
85
|
+
to_add = [Formatter.format_error(e, instruction=instruction, debug=debug)]
|
|
86
|
+
log.exception('Automated API documentation error')
|
|
87
|
+
|
|
88
|
+
finally:
|
|
89
|
+
instruction.clear()
|
|
90
|
+
|
|
91
|
+
out.extend(to_add)
|
|
92
|
+
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class ApiDescribedExtension(Extension):
|
|
97
|
+
|
|
98
|
+
def extendMarkdown(self, md):
|
|
99
|
+
md.registerExtension(self)
|
|
100
|
+
md.preprocessors.register(ApidescribedProcessor(md.parser), "apidescribed", 110)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def makeExtension(**kwargs):
|
|
104
|
+
return ApiDescribedExtension(**kwargs)
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from textwrap import indent, dedent
|
|
3
|
+
from traceback import format_exc
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import griffe
|
|
7
|
+
from mkdocs.plugins import get_plugin_logger
|
|
8
|
+
|
|
9
|
+
from .inspector import Symbol, SymbolType
|
|
10
|
+
from .parsers import DocstrParser, Block
|
|
11
|
+
|
|
12
|
+
log = get_plugin_logger(__name__)
|
|
13
|
+
|
|
14
|
+
RE_LINK = re.compile(
|
|
15
|
+
r'https?://(www\.)?[A-z0-9@:%.-_+~#=]{1,256}\.[A-z0-9()]{1,6}\b([A-z0-9()@:%-_+.~#?&/=]*)'
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Formatter:
|
|
20
|
+
|
|
21
|
+
category_titles = {
|
|
22
|
+
SymbolType.attr: 'Attributes',
|
|
23
|
+
SymbolType.func: 'Functions',
|
|
24
|
+
SymbolType.cls: 'Classes',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@classmethod
|
|
28
|
+
def format_error(cls, exc: Exception, *, instruction: list[str], debug: bool = False) -> str:
|
|
29
|
+
|
|
30
|
+
def indent_raw(val: str) -> str:
|
|
31
|
+
return f' ```\n{indent(val, prefix=' ')}\n ```\n'
|
|
32
|
+
|
|
33
|
+
lines = [
|
|
34
|
+
f'??? failure "Automated documentation error: {exc.__class__.__name__}"',
|
|
35
|
+
' **Configuration**',
|
|
36
|
+
indent_raw("\n".join(instruction)),
|
|
37
|
+
f' **Error**',
|
|
38
|
+
indent_raw(f'{exc}\n\n{format_exc() if debug else ""}'),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
return '\n'.join(lines)
|
|
42
|
+
|
|
43
|
+
@classmethod
|
|
44
|
+
def format_block_admonition(cls, block: Block) -> str:
|
|
45
|
+
return f'!!! {block.marker}\n{cls.reindent(block)}'
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def format_block_code(cls, block: Block) -> str:
|
|
49
|
+
return f'```{block.value}\n' + dedent('\n'.join(block.addition)) + '\n```'
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def format_block_version(cls, block: Block) -> str:
|
|
53
|
+
return ''
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def reindent(cls, src: Any) -> str:
|
|
57
|
+
return indent(dedent(f'{src}'), ' ')
|
|
58
|
+
|
|
59
|
+
def __init__(self, *, symbol: Symbol, config: dict):
|
|
60
|
+
self.symbol = symbol
|
|
61
|
+
self.config = config
|
|
62
|
+
|
|
63
|
+
categorize = config['categorize']
|
|
64
|
+
self.categorize = categorize
|
|
65
|
+
|
|
66
|
+
heading_base = config['heading_base']
|
|
67
|
+
self.heading_base = heading_base
|
|
68
|
+
self.heading_level = heading_base + 1 if categorize else heading_base
|
|
69
|
+
|
|
70
|
+
self.docstr = DocstrParser.parse(self.symbol.docstr, config=config, formatter=self)
|
|
71
|
+
|
|
72
|
+
def signature_reconstruct_params(self) -> dict[str, tuple[str, str]]:
|
|
73
|
+
docstring = self.docstr
|
|
74
|
+
|
|
75
|
+
params = {}
|
|
76
|
+
|
|
77
|
+
for param in self.symbol.params:
|
|
78
|
+
param_name = param.name
|
|
79
|
+
param_str = param_name
|
|
80
|
+
param_note = docstring.params.get(param_str, '')
|
|
81
|
+
|
|
82
|
+
default = param.default
|
|
83
|
+
annotation = param.annotation
|
|
84
|
+
|
|
85
|
+
match param.kind:
|
|
86
|
+
case griffe.ParameterKind.var_positional:
|
|
87
|
+
param_str = f'*{param_str}'
|
|
88
|
+
default = False
|
|
89
|
+
|
|
90
|
+
case griffe.ParameterKind.var_keyword:
|
|
91
|
+
param_str = f'**{param_str}'
|
|
92
|
+
default = False
|
|
93
|
+
|
|
94
|
+
if annotation:
|
|
95
|
+
if default:
|
|
96
|
+
annotation = f'{annotation} '
|
|
97
|
+
param_str = f'{param_str}: {annotation}'
|
|
98
|
+
|
|
99
|
+
if default:
|
|
100
|
+
if annotation:
|
|
101
|
+
default = f' {default}'
|
|
102
|
+
param_str = f'{param_str}={default}'
|
|
103
|
+
|
|
104
|
+
params[param_name] = (f'{param_str},', param_note)
|
|
105
|
+
|
|
106
|
+
return params
|
|
107
|
+
|
|
108
|
+
def signature_build(self, params: dict) -> tuple[str, list[str]]:
|
|
109
|
+
symbol = self.symbol
|
|
110
|
+
reindent = self.reindent
|
|
111
|
+
|
|
112
|
+
notes_counter = 0
|
|
113
|
+
param_strings = []
|
|
114
|
+
param_notes = []
|
|
115
|
+
|
|
116
|
+
for param_str, param_note in params.values():
|
|
117
|
+
postfix = ''
|
|
118
|
+
|
|
119
|
+
if param_note:
|
|
120
|
+
notes_counter += 1
|
|
121
|
+
postfix = f' # ({notes_counter})!' # add annotation index
|
|
122
|
+
param_notes.append(f'{notes_counter}. {reindent(param_note)}\n')
|
|
123
|
+
|
|
124
|
+
param_strings.append(f'{param_str}{postfix}')
|
|
125
|
+
|
|
126
|
+
def get_signature() -> str:
|
|
127
|
+
chunks = [
|
|
128
|
+
'.' if symbol.is_class_member else '',
|
|
129
|
+
f'{symbol.name}(',
|
|
130
|
+
f'{joiner}{indented}',
|
|
131
|
+
f'{joiner}{indented}'.join(param_strings).replace(',', ', ').rstrip(', '),
|
|
132
|
+
f'{joiner})',
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
if symbol.type == SymbolType.func and (returns := symbol.returns):
|
|
136
|
+
chunks.extend(f' -> {returns}')
|
|
137
|
+
|
|
138
|
+
return ''.join(chunks)
|
|
139
|
+
|
|
140
|
+
indented = ' '
|
|
141
|
+
joiner = '\n'
|
|
142
|
+
|
|
143
|
+
oneline = not len(param_notes)
|
|
144
|
+
|
|
145
|
+
if len(get_signature()) > 80:
|
|
146
|
+
oneline = False
|
|
147
|
+
|
|
148
|
+
if oneline:
|
|
149
|
+
indented = ''
|
|
150
|
+
joiner = ''
|
|
151
|
+
|
|
152
|
+
return get_signature(), param_notes
|
|
153
|
+
|
|
154
|
+
def handle_opt_location(self) -> str:
|
|
155
|
+
symbol = self.symbol
|
|
156
|
+
caption = ''
|
|
157
|
+
|
|
158
|
+
if location := self.config['location']:
|
|
159
|
+
|
|
160
|
+
match location['mode']:
|
|
161
|
+
case 'module':
|
|
162
|
+
caption = f'{symbol.path}'.rpartition('.')[0]
|
|
163
|
+
|
|
164
|
+
case 'file':
|
|
165
|
+
caption = f'{symbol.fpath}'
|
|
166
|
+
|
|
167
|
+
if caption and location['line']:
|
|
168
|
+
caption = f'{caption}:{symbol.line_start}'
|
|
169
|
+
|
|
170
|
+
return caption
|
|
171
|
+
|
|
172
|
+
def format_category(self) -> str:
|
|
173
|
+
|
|
174
|
+
if self.categorize and (category := self.category_titles.get(self.symbol.type)):
|
|
175
|
+
return f'{"#" * self.heading_base} {category}\n'
|
|
176
|
+
|
|
177
|
+
return ''
|
|
178
|
+
|
|
179
|
+
def format_signature(self) -> str:
|
|
180
|
+
symbol = self.symbol
|
|
181
|
+
param_notes = []
|
|
182
|
+
|
|
183
|
+
def get_signature():
|
|
184
|
+
return self.signature_build(self.signature_reconstruct_params())
|
|
185
|
+
|
|
186
|
+
match symbol.type:
|
|
187
|
+
case SymbolType.func | SymbolType.cls:
|
|
188
|
+
signature, param_notes = get_signature()
|
|
189
|
+
|
|
190
|
+
case SymbolType.attr:
|
|
191
|
+
if symbol.is_property:
|
|
192
|
+
signature, param_notes = get_signature()
|
|
193
|
+
|
|
194
|
+
else:
|
|
195
|
+
signature = (
|
|
196
|
+
('.' if symbol.is_class_member else '') +
|
|
197
|
+
symbol.source
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
case _:
|
|
201
|
+
signature = ''
|
|
202
|
+
|
|
203
|
+
if not signature:
|
|
204
|
+
return ''
|
|
205
|
+
|
|
206
|
+
caption = self.handle_opt_location()
|
|
207
|
+
if caption:
|
|
208
|
+
caption = f'title="{caption}"'
|
|
209
|
+
|
|
210
|
+
lines = [
|
|
211
|
+
f'\n``` py {caption}\n{signature}\n```\n',
|
|
212
|
+
# param descriptions block
|
|
213
|
+
*param_notes,
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
return '\n'.join(lines)
|
|
217
|
+
|
|
218
|
+
def format_description(self) -> str:
|
|
219
|
+
docstring = self.docstr
|
|
220
|
+
|
|
221
|
+
text = docstring.text
|
|
222
|
+
|
|
223
|
+
text = f'{text}\n'
|
|
224
|
+
|
|
225
|
+
icons = self.config['icons']
|
|
226
|
+
|
|
227
|
+
if raised := docstring.raises:
|
|
228
|
+
raises = '\n\n'.join(
|
|
229
|
+
f'{icons["raises"]} `{raised_key}` {raised_val}'
|
|
230
|
+
for raised_key, raised_val in raised.items()
|
|
231
|
+
)
|
|
232
|
+
text = f'{text}\n{raises}\n'
|
|
233
|
+
|
|
234
|
+
if returns := docstring.returns:
|
|
235
|
+
returns = '\n\n'.join(
|
|
236
|
+
f'{icons["returns"]} {returned}'
|
|
237
|
+
for returned in returns
|
|
238
|
+
)
|
|
239
|
+
text = f'{text}\n{returns}\n'
|
|
240
|
+
|
|
241
|
+
return text
|
|
242
|
+
|
|
243
|
+
def format_title(self) -> str:
|
|
244
|
+
symbol = self.symbol
|
|
245
|
+
icons = self.config['icons']
|
|
246
|
+
|
|
247
|
+
icon_before = icons.get(symbol.type.name, '')
|
|
248
|
+
icons_after = []
|
|
249
|
+
|
|
250
|
+
if symbol.is_inherited:
|
|
251
|
+
icons_after.append(icons['inherited'])
|
|
252
|
+
|
|
253
|
+
if symbol.is_property:
|
|
254
|
+
icons_after.append(icons['property'])
|
|
255
|
+
|
|
256
|
+
if symbol.is_classmethod:
|
|
257
|
+
icons_after.append(icons['classmethod'])
|
|
258
|
+
|
|
259
|
+
if symbol.is_staticmethod:
|
|
260
|
+
icons_after.append(icons['staticmethod'])
|
|
261
|
+
|
|
262
|
+
title = (
|
|
263
|
+
f'{"#" * (self.heading_level + symbol.depth)} '
|
|
264
|
+
f'{icon_before} {symbol.name} {" ".join(icons_after)}\n'
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return title
|
|
268
|
+
|
|
269
|
+
def indent(self, *, title: str, body: str) -> str:
|
|
270
|
+
|
|
271
|
+
left = '<div style="margin-left:1.8em" markdown>'
|
|
272
|
+
right = '</div>'
|
|
273
|
+
|
|
274
|
+
depth = self.symbol.depth
|
|
275
|
+
|
|
276
|
+
indented = (
|
|
277
|
+
(left * depth) +
|
|
278
|
+
title + (left + body + right) +
|
|
279
|
+
(right * depth)
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return indented
|
|
283
|
+
|
|
284
|
+
def run(self) -> str:
|
|
285
|
+
|
|
286
|
+
signature = self.format_signature()
|
|
287
|
+
|
|
288
|
+
out = [
|
|
289
|
+
signature,
|
|
290
|
+
self.format_description(),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
out = '\n'.join(out)
|
|
294
|
+
|
|
295
|
+
if signature:
|
|
296
|
+
out = self.indent(title=self.format_title(), body=out)
|
|
297
|
+
|
|
298
|
+
if out.strip():
|
|
299
|
+
out = f'{out}\n----'
|
|
300
|
+
|
|
301
|
+
if self.config['autolinks']:
|
|
302
|
+
out = RE_LINK.sub(r'<\g<0>>', out)
|
|
303
|
+
|
|
304
|
+
return out
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
from copy import deepcopy
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from fnmatch import fnmatch
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import griffe
|
|
8
|
+
from mkdocs.plugins import get_plugin_logger
|
|
9
|
+
|
|
10
|
+
log = get_plugin_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SymbolType(Enum):
|
|
14
|
+
attr = 1
|
|
15
|
+
func = 2
|
|
16
|
+
cls = 3
|
|
17
|
+
module = 4
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Symbol:
|
|
22
|
+
|
|
23
|
+
depth: int
|
|
24
|
+
raw: Any
|
|
25
|
+
type: SymbolType
|
|
26
|
+
name: str
|
|
27
|
+
source: str
|
|
28
|
+
docstr: griffe.Docstring
|
|
29
|
+
path: str
|
|
30
|
+
fpath: str
|
|
31
|
+
params: griffe.Parameters | None
|
|
32
|
+
returns: str
|
|
33
|
+
line_start: int = None
|
|
34
|
+
line_end: int = None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_property(self) -> bool:
|
|
38
|
+
return 'property' in self.raw.labels
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_inherited(self) -> bool:
|
|
42
|
+
return self.raw.inherited
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def is_class_member(self) -> bool:
|
|
46
|
+
return self.raw.parent.is_class
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def is_classmethod(self) -> bool:
|
|
50
|
+
return 'classmethod' in self.raw.labels
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def is_staticmethod(self) -> bool:
|
|
54
|
+
return 'staticmethod' in self.raw.labels
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def simplify_args(symb: griffe.Object, parameters: griffe.Parameters):
|
|
58
|
+
# drop self, cls
|
|
59
|
+
if symb.is_class or symb.parent.is_class:
|
|
60
|
+
if parameters._params:
|
|
61
|
+
parameters = deepcopy(parameters)
|
|
62
|
+
# Exceptions can have no params
|
|
63
|
+
parameters._params.pop(0)
|
|
64
|
+
return parameters
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def get_symbol(
|
|
68
|
+
obj: griffe.Object,
|
|
69
|
+
*,
|
|
70
|
+
types: list[str] = None,
|
|
71
|
+
undocumented: bool = False,
|
|
72
|
+
ignore: list[str] = None,
|
|
73
|
+
only: set[str] = None,
|
|
74
|
+
depth: int = 0,
|
|
75
|
+
) -> tuple[Symbol, list[Symbol]] | None:
|
|
76
|
+
|
|
77
|
+
symbol_type = 0
|
|
78
|
+
symbols_sub = []
|
|
79
|
+
types = types or []
|
|
80
|
+
|
|
81
|
+
params = getattr(obj, 'parameters', [])
|
|
82
|
+
returns = getattr(obj, 'returns', None)
|
|
83
|
+
|
|
84
|
+
if obj.is_attribute:
|
|
85
|
+
symbol_type = SymbolType.attr
|
|
86
|
+
|
|
87
|
+
elif obj.is_module:
|
|
88
|
+
symbol_type = SymbolType.module
|
|
89
|
+
|
|
90
|
+
elif obj.is_function:
|
|
91
|
+
symbol_type = SymbolType.func
|
|
92
|
+
params = simplify_args(obj, params)
|
|
93
|
+
|
|
94
|
+
elif obj.is_class:
|
|
95
|
+
symbol_type = SymbolType.cls
|
|
96
|
+
params = simplify_args(obj, params)
|
|
97
|
+
|
|
98
|
+
symbols_sub = get_symbols(
|
|
99
|
+
obj,
|
|
100
|
+
types=types,
|
|
101
|
+
undocumented=undocumented,
|
|
102
|
+
ignore=ignore,
|
|
103
|
+
only=only,
|
|
104
|
+
depth=depth + 1
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not symbol_type:
|
|
108
|
+
log.error(f'Unsupported symbol type: {obj}. Skipped.')
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
symbol = Symbol(
|
|
112
|
+
depth=depth,
|
|
113
|
+
raw=obj,
|
|
114
|
+
type=symbol_type,
|
|
115
|
+
name=obj.name,
|
|
116
|
+
source=obj.source,
|
|
117
|
+
docstr=obj.docstring,
|
|
118
|
+
path=obj.path,
|
|
119
|
+
fpath=f'{obj.relative_package_filepath}',
|
|
120
|
+
params=params,
|
|
121
|
+
returns=returns,
|
|
122
|
+
line_start=obj.lineno,
|
|
123
|
+
line_end=obj.endlineno,
|
|
124
|
+
)
|
|
125
|
+
return symbol, symbols_sub
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_symbols(
|
|
129
|
+
container: griffe.Object,
|
|
130
|
+
*,
|
|
131
|
+
types: list[str],
|
|
132
|
+
undocumented: bool = False,
|
|
133
|
+
ignore: list[str] = None,
|
|
134
|
+
only: set[str] = None,
|
|
135
|
+
depth: int = 0
|
|
136
|
+
) -> list[Symbol]:
|
|
137
|
+
|
|
138
|
+
symbols = []
|
|
139
|
+
ignore = ignore or []
|
|
140
|
+
only = only or set()
|
|
141
|
+
|
|
142
|
+
for key in types:
|
|
143
|
+
|
|
144
|
+
for _, symbol_raw in sorted(getattr(container, key).items(), key=lambda item: item[0]):
|
|
145
|
+
|
|
146
|
+
if not depth and symbol_raw.is_alias:
|
|
147
|
+
# hide imported from other modules
|
|
148
|
+
# but preserve class members inherited from base
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
name = symbol_raw.name
|
|
152
|
+
|
|
153
|
+
docstring = symbol_raw.docstring
|
|
154
|
+
skip = False
|
|
155
|
+
|
|
156
|
+
if docstring is None and not undocumented:
|
|
157
|
+
continue
|
|
158
|
+
|
|
159
|
+
if only:
|
|
160
|
+
skip = name not in only
|
|
161
|
+
|
|
162
|
+
else:
|
|
163
|
+
for rule in ignore:
|
|
164
|
+
if fnmatch(name, rule):
|
|
165
|
+
skip = True
|
|
166
|
+
break
|
|
167
|
+
|
|
168
|
+
if skip:
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
symbol, symbols_sub = get_symbol(
|
|
172
|
+
symbol_raw,
|
|
173
|
+
types=types,
|
|
174
|
+
undocumented=undocumented,
|
|
175
|
+
ignore=ignore,
|
|
176
|
+
only=only,
|
|
177
|
+
depth=depth,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if symbol:
|
|
181
|
+
symbols.append(symbol)
|
|
182
|
+
|
|
183
|
+
if symbols_sub:
|
|
184
|
+
symbols.extend(symbols_sub)
|
|
185
|
+
# symbols_sub.clear()
|
|
186
|
+
|
|
187
|
+
return symbols
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def inspect(
|
|
191
|
+
target: str,
|
|
192
|
+
*,
|
|
193
|
+
types: list[str],
|
|
194
|
+
undocumented: bool = False,
|
|
195
|
+
ignore: list[str],
|
|
196
|
+
only: list[str]
|
|
197
|
+
) -> tuple[Symbol, list[Symbol]]:
|
|
198
|
+
|
|
199
|
+
log.info(f'Getting API information for: {target} ...')
|
|
200
|
+
obj = griffe.load(target)
|
|
201
|
+
|
|
202
|
+
root, _ = get_symbol(obj)
|
|
203
|
+
symbols = get_symbols(obj, types=types, undocumented=undocumented, ignore=ignore, only=set(only))
|
|
204
|
+
|
|
205
|
+
return root, symbols
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from textwrap import dedent
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import griffe
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .formatter import Formatter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Docstring:
|
|
15
|
+
|
|
16
|
+
text: str = ''
|
|
17
|
+
params: dict[str, str] = field(default_factory=dict)
|
|
18
|
+
returns: list[str] = field(default_factory=list)
|
|
19
|
+
raises: dict[str, str] = field(default_factory=dict)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Block:
|
|
23
|
+
|
|
24
|
+
unmarked: str = '_unmarked'
|
|
25
|
+
|
|
26
|
+
def __init__(self, marker: str = '', arg: str = '', value: str = ''):
|
|
27
|
+
self.marker = marker or self.unmarked
|
|
28
|
+
self.arg = arg.strip()
|
|
29
|
+
self.value = value
|
|
30
|
+
self.addition = []
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
prefix = f'{value}\n' if (value := self.value) else ''
|
|
34
|
+
return prefix + '\n'.join(self.addition)
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def is_marked(self) -> bool:
|
|
38
|
+
return self.marker != self.unmarked
|
|
39
|
+
|
|
40
|
+
def contribute(self, val: str):
|
|
41
|
+
self.addition.append(val)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DocstrParser:
|
|
45
|
+
alias: str = ''
|
|
46
|
+
|
|
47
|
+
registry: dict[str, type['DocstrParser']] = {}
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def parse(cls, docstr: griffe.Docstring | None, *, config: dict, formatter: 'Formatter') -> Docstring:
|
|
51
|
+
|
|
52
|
+
value = getattr(docstr, 'value', None) or ''
|
|
53
|
+
docstring = Docstring(text=f'{value}')
|
|
54
|
+
|
|
55
|
+
if value:
|
|
56
|
+
preferred_parser = config['format']
|
|
57
|
+
|
|
58
|
+
parser = DocstrParser.registry.get(preferred_parser)
|
|
59
|
+
parsers = [parser] if parser else DocstrParser.registry.values()
|
|
60
|
+
|
|
61
|
+
for parser_cls in parsers:
|
|
62
|
+
parser = parser_cls(value, config=config, formatter=formatter)
|
|
63
|
+
|
|
64
|
+
if (result := parser.run()) and parser.markers_detected:
|
|
65
|
+
docstring = result
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
return docstring
|
|
69
|
+
|
|
70
|
+
def __init_subclass__(cls):
|
|
71
|
+
alias = cls.alias
|
|
72
|
+
registry = cls.registry
|
|
73
|
+
|
|
74
|
+
assert alias
|
|
75
|
+
assert alias not in registry
|
|
76
|
+
|
|
77
|
+
super().__init_subclass__()
|
|
78
|
+
|
|
79
|
+
registry[alias] = cls
|
|
80
|
+
|
|
81
|
+
def __init__(self, src: str, *, config: dict, formatter: 'Formatter'):
|
|
82
|
+
self.src = src
|
|
83
|
+
self.config = config
|
|
84
|
+
self.markers_detected = False
|
|
85
|
+
self.formatter = formatter
|
|
86
|
+
|
|
87
|
+
def run(self) -> Docstring | None:
|
|
88
|
+
return self._parse(self.src)
|
|
89
|
+
|
|
90
|
+
def postprocess(self, text: str) -> str:
|
|
91
|
+
return text
|
|
92
|
+
|
|
93
|
+
def _parse(self, value: str) -> Docstring | None: # pragma: nocover
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
def to_dict(self, blocks: list[Block]) -> dict[str, str]:
|
|
97
|
+
result = {
|
|
98
|
+
block.arg: self.postprocess(f'{block}')
|
|
99
|
+
for block in blocks
|
|
100
|
+
}
|
|
101
|
+
return result
|
|
102
|
+
|
|
103
|
+
def to_list(self, blocks: list[Block]) -> list[str]:
|
|
104
|
+
return [self.postprocess(f'{block}'.strip()) for block in blocks]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class SphinxParser(DocstrParser):
|
|
108
|
+
alias: str = 'sphinx'
|
|
109
|
+
|
|
110
|
+
re_markers = re.compile(
|
|
111
|
+
r':(?P<marker>param|type|raises|return|rtype)'
|
|
112
|
+
r'(?P<arg>[^:]*):\s*(?P<value>.*)'
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
re_directives = re.compile(
|
|
116
|
+
r'\.\. (?P<marker>note|warning|code-block|versionadded|versionchanged|deprecated)'
|
|
117
|
+
r'(?P<arg>[^:]*)::\s*(?P<value>.*)'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def postprocess(self, text: str) -> str:
|
|
121
|
+
text = super().postprocess(text)
|
|
122
|
+
formatter = self.formatter
|
|
123
|
+
|
|
124
|
+
if (split := text.splitlines()) and len(split) > 1:
|
|
125
|
+
# nested indented block (e.g. in param descriptions)
|
|
126
|
+
# reindent with prefix from 2nd line
|
|
127
|
+
line_two = split[1]
|
|
128
|
+
prefix = ' ' * (len(line_two) - len(line_two.lstrip(' ')))
|
|
129
|
+
text = dedent('\n'.join([
|
|
130
|
+
f'{prefix}{split[0].lstrip()}',
|
|
131
|
+
*split[1:]
|
|
132
|
+
]))
|
|
133
|
+
|
|
134
|
+
_, processed = self.parse_blocks(
|
|
135
|
+
pattern=self.re_directives,
|
|
136
|
+
value=text,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
lines = []
|
|
140
|
+
|
|
141
|
+
for block in processed:
|
|
142
|
+
if block.is_marked:
|
|
143
|
+
match block.marker:
|
|
144
|
+
case 'versionadded' | 'versionchanged' | 'deprecated':
|
|
145
|
+
block = formatter.format_block_version(block)
|
|
146
|
+
|
|
147
|
+
case 'note' | 'warning':
|
|
148
|
+
block = formatter.format_block_admonition(block)
|
|
149
|
+
|
|
150
|
+
case 'code-block':
|
|
151
|
+
block = formatter.format_block_code(block)
|
|
152
|
+
|
|
153
|
+
lines.append(f'{block}')
|
|
154
|
+
|
|
155
|
+
return '\n'.join(lines)
|
|
156
|
+
|
|
157
|
+
def parse_blocks(
|
|
158
|
+
self,
|
|
159
|
+
*,
|
|
160
|
+
pattern: re.Pattern,
|
|
161
|
+
value: str,
|
|
162
|
+
) -> tuple[dict[str, list[Block]], list[Block]]:
|
|
163
|
+
|
|
164
|
+
blocks: dict[str, list[Block]] = defaultdict(list)
|
|
165
|
+
block_last: Block | None = None
|
|
166
|
+
marker_detected = False
|
|
167
|
+
|
|
168
|
+
processed = []
|
|
169
|
+
|
|
170
|
+
for line in value.splitlines():
|
|
171
|
+
|
|
172
|
+
is_empty = len(line.strip()) == 0
|
|
173
|
+
|
|
174
|
+
if block_last:
|
|
175
|
+
if line.startswith(' ') or is_empty:
|
|
176
|
+
# must be a continuation of a block
|
|
177
|
+
block_last.contribute(line)
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
# previous block ended
|
|
181
|
+
processed.append(block_last)
|
|
182
|
+
block_last = None
|
|
183
|
+
|
|
184
|
+
if matched := pattern.match(line):
|
|
185
|
+
marker_detected = True
|
|
186
|
+
marker = matched.group('marker')
|
|
187
|
+
|
|
188
|
+
arg = matched.group('arg')
|
|
189
|
+
val = matched.group('value')
|
|
190
|
+
|
|
191
|
+
block = Block(marker, arg, val.strip())
|
|
192
|
+
blocks[marker].append(block)
|
|
193
|
+
block_last = block
|
|
194
|
+
|
|
195
|
+
else:
|
|
196
|
+
block = Block()
|
|
197
|
+
block.contribute(line)
|
|
198
|
+
blocks[Block.unmarked].append(block)
|
|
199
|
+
processed.append(block)
|
|
200
|
+
|
|
201
|
+
if block_last:
|
|
202
|
+
# block in the last line
|
|
203
|
+
processed.append(block_last)
|
|
204
|
+
|
|
205
|
+
self.markers_detected = self.markers_detected or marker_detected
|
|
206
|
+
|
|
207
|
+
return blocks, processed
|
|
208
|
+
|
|
209
|
+
def _parse(self, value: str) -> Docstring | None:
|
|
210
|
+
|
|
211
|
+
blocks, processed = self.parse_blocks(
|
|
212
|
+
pattern=self.re_markers,
|
|
213
|
+
value=value,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return Docstring(
|
|
217
|
+
text=self.postprocess('\n'.join(map(str, blocks[Block.unmarked]))),
|
|
218
|
+
params=self.to_dict(blocks['param']),
|
|
219
|
+
returns=self.to_list(blocks['return']),
|
|
220
|
+
raises=self.to_dict(blocks['raises']),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class GoogleParser(DocstrParser):
|
|
225
|
+
alias: str = 'google'
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class NumpyParser(DocstrParser):
|
|
229
|
+
alias: str = 'numpy'
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""This is a module description.
|
|
2
|
+
|
|
3
|
+
This takes several lines and contains formatted text.
|
|
4
|
+
|
|
5
|
+
https://github.com/idlesign/mkdocs-apidescribed-plugin
|
|
6
|
+
|
|
7
|
+
!!! note
|
|
8
|
+
This text is an admonition.
|
|
9
|
+
|
|
10
|
+
.. warning:: This is a reStructuredText mixed warning.
|
|
11
|
+
"""
|
|
12
|
+
from os import environ
|
|
13
|
+
|
|
14
|
+
from markdown.util import deprecated
|
|
15
|
+
|
|
16
|
+
CONSTANT: int = 1
|
|
17
|
+
"""Constant description."""
|
|
18
|
+
|
|
19
|
+
ENVIRON = environ
|
|
20
|
+
"""Alias description."""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def undocumented():
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _hidden():
|
|
28
|
+
"""Hide it."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def just_ignore_that():
|
|
32
|
+
"""Ignore it."""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def myfunc_rst(
|
|
36
|
+
a: list[str],
|
|
37
|
+
b: int = 0,
|
|
38
|
+
*args,
|
|
39
|
+
another: str,
|
|
40
|
+
**kwargs
|
|
41
|
+
) -> bool:
|
|
42
|
+
"""Function description.
|
|
43
|
+
This one is split into several lines
|
|
44
|
+
yet supposes a single sentence.
|
|
45
|
+
|
|
46
|
+
Uses reStructuredText markup for signature description.
|
|
47
|
+
|
|
48
|
+
See: https://github.com/idlesign/mkdocs-apidescribed-plugin
|
|
49
|
+
|
|
50
|
+
.. note::
|
|
51
|
+
Some note here.
|
|
52
|
+
|
|
53
|
+
.. code-block:: bash
|
|
54
|
+
cd ~
|
|
55
|
+
touch this/
|
|
56
|
+
|
|
57
|
+
:param a: comment for a
|
|
58
|
+
.. versionadded:: 2.5
|
|
59
|
+
Add this.
|
|
60
|
+
|
|
61
|
+
.. versionchanged:: 2.5
|
|
62
|
+
Something has changed.
|
|
63
|
+
|
|
64
|
+
:type a: List[str], optional
|
|
65
|
+
:param b: hint for b
|
|
66
|
+
Several lines.
|
|
67
|
+
|
|
68
|
+
.. warning::
|
|
69
|
+
Some warning
|
|
70
|
+
|
|
71
|
+
Another line.
|
|
72
|
+
https://github.com/idlesign/mkdocs-apidescribed-plugin
|
|
73
|
+
:param args: info for args
|
|
74
|
+
:param another: text for another
|
|
75
|
+
.. deprecated:: 3.1
|
|
76
|
+
Use something instead.
|
|
77
|
+
|
|
78
|
+
:param kwargs: description for kwargs
|
|
79
|
+
|
|
80
|
+
:raises ValueError: If something happens
|
|
81
|
+
:raises KeyError: If something other happens
|
|
82
|
+
|
|
83
|
+
:return: This is returned
|
|
84
|
+
:return: This is also returned
|
|
85
|
+
:rtype: bool
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def myfunc_google(q: str, w: int, e: list, **kwargs) -> bool:
|
|
91
|
+
"""Function description.
|
|
92
|
+
|
|
93
|
+
Uses Google markup for signature description.
|
|
94
|
+
|
|
95
|
+
Note:
|
|
96
|
+
Some note for you.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
q (str): Param q
|
|
100
|
+
w: Param w
|
|
101
|
+
e: Param e
|
|
102
|
+
**kwargs: Keyword parameters
|
|
103
|
+
|
|
104
|
+
Yields:
|
|
105
|
+
int: This one
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
bool: True if successful, False otherwise.
|
|
109
|
+
|
|
110
|
+
Even more text for return description.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
AttributeError: Can raise
|
|
114
|
+
this in some cases.
|
|
115
|
+
ValueError: If something.
|
|
116
|
+
|
|
117
|
+
Examples:
|
|
118
|
+
This is an example.
|
|
119
|
+
Multiple Lines.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class MyBaseClass:
|
|
125
|
+
"""Base class description."""
|
|
126
|
+
|
|
127
|
+
base_attr: str = "base_attr value"
|
|
128
|
+
"""Base attr description."""
|
|
129
|
+
|
|
130
|
+
@classmethod
|
|
131
|
+
def method_cl(cls, arg1: str):
|
|
132
|
+
"""Class method."""
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def method_st(cls, val):
|
|
136
|
+
"""Static method."""
|
|
137
|
+
|
|
138
|
+
def __init__(self, in_a: int):
|
|
139
|
+
"""Base __init__ description."""
|
|
140
|
+
|
|
141
|
+
def __call__(self, in_b: int):
|
|
142
|
+
"""Base __call__ description."""
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def prop_a(self) -> str:
|
|
146
|
+
"""Property A."""
|
|
147
|
+
|
|
148
|
+
@prop_a.setter
|
|
149
|
+
def _prop_a_set(self, val: int) -> str:
|
|
150
|
+
"""Property A setter."""
|
|
151
|
+
|
|
152
|
+
def __str__(self):
|
|
153
|
+
"""Specialized. To string."""
|
|
154
|
+
|
|
155
|
+
def method_a(self, a=1):
|
|
156
|
+
"""Base method_a description."""
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class MySubClass(MyBaseClass):
|
|
161
|
+
"""Sub class description."""
|
|
162
|
+
|
|
163
|
+
sub_attr = "sub_attr value"
|
|
164
|
+
"""Sub attr description."""
|
|
165
|
+
|
|
166
|
+
@deprecated('This one is deprecated.')
|
|
167
|
+
def method_sub_b(
|
|
168
|
+
self,
|
|
169
|
+
a: str,
|
|
170
|
+
b: int,
|
|
171
|
+
d: float,
|
|
172
|
+
) -> str:
|
|
173
|
+
"""Sub method_sub_b description."""
|
|
174
|
+
return ''
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mkdocs-apidescribed-plugin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: mkdocs plugin to generate API documentation for Python programs
|
|
5
|
+
Project-URL: Homepage, https://github.com/idlesign/mkdocs-apidescribed-plugin
|
|
6
|
+
Project-URL: Documentation, https://mkdocs-apidescribed-plugin.readthedocs.org/
|
|
7
|
+
Author-email: Igor Starikov <idlesign@yandex.ru>
|
|
8
|
+
License-Expression: BSD-3-Clause
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: api,mkdocs,plugin
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: griffe
|
|
13
|
+
Requires-Dist: mkdocs
|
|
14
|
+
Requires-Dist: pyyaml
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# mkdocs-apidescribed-plugin
|
|
18
|
+
|
|
19
|
+
https://github.com/idlesign/mkdocs-apidescribed-plugin
|
|
20
|
+
|
|
21
|
+
[](https://pypi.python.org/pypi/mkdocs-apidescribed-plugin)
|
|
22
|
+
[](https://pypi.python.org/pypi/mkdocs-apidescribed-plugin)
|
|
23
|
+
[](https://coveralls.io/r/idlesign/mkdocs-apidescribed-plugin)
|
|
24
|
+
|
|
25
|
+
## Description
|
|
26
|
+
|
|
27
|
+
*mkdocs plugin to generate API documentation for Python programs*
|
|
28
|
+
|
|
29
|
+
* Made to autodocument Python code
|
|
30
|
+
* Plays well with [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/)
|
|
31
|
+
* Configurable
|
|
32
|
+
* Easy to use
|
|
33
|
+
|
|
34
|
+
Just add ``::: apidescribed:`` directive into your documentation followed by a module path:
|
|
35
|
+
|
|
36
|
+
```md title="index.md"
|
|
37
|
+
This is my documentation
|
|
38
|
+
|
|
39
|
+
::: apidescribed: somepackage.somemodule
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Documentation
|
|
44
|
+
|
|
45
|
+
https://mkdocs-apidescribed-plugin.readthedocs.org/
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
mkdocs_apidescribed/__init__.py,sha256=2FRI0QQVpqa9D3Ny5p2lmon3dWjttDU7YivT4wXMJlM,19
|
|
2
|
+
mkdocs_apidescribed/config.py,sha256=3P5OpESEg5FQMG0FDScVCoM6HJE1yJwu3FxEetH8L2Y,2529
|
|
3
|
+
mkdocs_apidescribed/extension.py,sha256=3iBv7dQr9dOBwqssimo1J3C_R5O8kmq7HUiuzk5o0uY,2980
|
|
4
|
+
mkdocs_apidescribed/formatter.py,sha256=ONVHitfZ0U8MyeRygy1-v9QIdIsXqvc5fv_T8qbfgEw,8387
|
|
5
|
+
mkdocs_apidescribed/inspector.py,sha256=uovf-2BQkoD8O2DXV0RLSvPT_PMIg6qWUAVXHE2nPDM,4731
|
|
6
|
+
mkdocs_apidescribed/parsers.py,sha256=nDHJ7mGECyeLFIOm8dcv6iPDId2AIgbeMbsTgLM_ag4,6429
|
|
7
|
+
mkdocs_apidescribed/plugin.py,sha256=cxlukYTLJnAikD_TiucINExyR6sbHJTwzUYPFGLTjFo,157
|
|
8
|
+
mkdocs_apidescribed/probe.py,sha256=zyXG4HPVeu5mLqcAJerfXCKeU9c8kbB_GOytUJ8A6Xo,3399
|
|
9
|
+
mkdocs_apidescribed_plugin-0.1.0.dist-info/METADATA,sha256=y4qH55fOn8SBCRpq28Uj8wUqLqwGz_F-WHsj7QaQVzM,1566
|
|
10
|
+
mkdocs_apidescribed_plugin-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
mkdocs_apidescribed_plugin-0.1.0.dist-info/entry_points.txt,sha256=xVopkdzEjXeUcUbhsbz9KB1uP2cy2A34H4w437cwXWs,78
|
|
12
|
+
mkdocs_apidescribed_plugin-0.1.0.dist-info/licenses/LICENSE,sha256=Mjp1Mmud42bwXWclwd8Rg-RZokGySkTc_Kt9yEeetr4,1514
|
|
13
|
+
mkdocs_apidescribed_plugin-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Copyright (c) 2025, Igor `idle sign` Starikov
|
|
2
|
+
All rights reserved.
|
|
3
|
+
|
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
|
6
|
+
|
|
7
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
|
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
11
|
+
this list of conditions and the following disclaimer in the documentation
|
|
12
|
+
and/or other materials provided with the distribution.
|
|
13
|
+
|
|
14
|
+
* Neither the name of the mkdocs-apidescribed-plugin nor the names of its
|
|
15
|
+
contributors may be used to endorse or promote products derived from
|
|
16
|
+
this software without specific prior written permission.
|
|
17
|
+
|
|
18
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
21
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
22
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
23
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
24
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
25
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
26
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
27
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
28
|
+
|