generated from jCloud/repository-template
Add Python module documentation generator
This commit is contained in:
@@ -13,8 +13,8 @@
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import annotations
|
||||
from ...utils import ExistingDirectory, assert_that_is_instance, ExistingFile, non_empty_str
|
||||
from ...exceptions import PythonArgumentStructureError
|
||||
from ...utils import ExistingDirectory, assert_that_is_instance, ExistingFile, non_empty_str, get_relative_path_segments
|
||||
from ...exceptions import PythonArgumentStructureError, InvalidNamespaceError
|
||||
from .namespaces import PythonModuleNamespace, PythonPackageNamespace
|
||||
from .definitions import PythonDefinition, PythonFunctionDefinition, PythonAsyncFunctionDefinition, PythonClassDefinition
|
||||
from .arguments import PythonArgumentKind, PythonFunctionArgument
|
||||
@@ -26,6 +26,7 @@ from enum import Enum
|
||||
import docstring_parser
|
||||
import pathlib
|
||||
import ast
|
||||
import types
|
||||
|
||||
__all__ = [
|
||||
'PythonDefinitionDocumentationIncludeSections',
|
||||
@@ -255,6 +256,7 @@ class _NoDocstringTemplate:
|
||||
many_returns = []
|
||||
deprecation = None
|
||||
examples = []
|
||||
meta = []
|
||||
short_description = None
|
||||
long_description = None
|
||||
blank_after_short_description = None
|
||||
@@ -408,8 +410,8 @@ class PythonDefinitionDocumentationGenerator:
|
||||
self,
|
||||
definition: PythonDefinition,
|
||||
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(),
|
||||
*,
|
||||
level: int = 1,
|
||||
*,
|
||||
allow_html: bool = True,
|
||||
allow_tables: bool = False,
|
||||
skip_empty_sections: bool = True,
|
||||
@@ -741,12 +743,49 @@ class PythonModuleDocumentationGenerator:
|
||||
|
||||
:param module_path: The path of the module file.
|
||||
:type module_path: ExistingFile
|
||||
:param src_directory: The directory of the source code. It is
|
||||
optional and only used for making the module
|
||||
identifiers in the documentation relative.
|
||||
:type src_directory: Union[ExistingDirectory, None]
|
||||
:param include_sections: The sections that will be included.
|
||||
:type include_sections: PythonDefinitionDocumentationIncludeSections
|
||||
:kwargs: The options for ``PythonDefinitionDocumentationGenerator``.
|
||||
'''
|
||||
|
||||
def __init__(self, module_path: ExistingFile) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
module_path: ExistingFile,
|
||||
src_directory: Union[ExistingDirectory, None] = None,
|
||||
include_sections: PythonDefinitionDocumentationIncludeSections = PythonDefinitionDocumentationIncludeSections(),
|
||||
**kwargs
|
||||
) -> None:
|
||||
|
||||
assert_that_is_instance(module_path, ExistingFile)
|
||||
assert_that_is_instance(src_directory, (ExistingDirectory, types.NoneType))
|
||||
|
||||
if module_path.name.count('.') > 1:
|
||||
raise InvalidNamespaceError('namespace identifier cannot contain more than one dot', namespace_identifier = module_path.name)
|
||||
|
||||
self.module_path = pathlib.Path(str(module_path))
|
||||
self.src_directory = pathlib.Path(str(src_directory)) if src_directory is not None else None
|
||||
|
||||
self.include_sections = include_sections
|
||||
self.kwargs = kwargs
|
||||
|
||||
self._source_code_cache = None
|
||||
self._ast_cache = None
|
||||
|
||||
@property
|
||||
def _source_code(self) -> str:
|
||||
if self._source_code_cache is None:
|
||||
self._source_code_cache = self.module_path.read_text()
|
||||
return self._source_code_cache
|
||||
|
||||
@property
|
||||
def _ast(self) -> str:
|
||||
if self._ast_cache is None:
|
||||
self._ast_cache = ast.parse(self._source_code)
|
||||
return self._ast_cache
|
||||
|
||||
def collect_definitions(self) -> Iterator[PythonDefinition]:
|
||||
'''
|
||||
@@ -756,10 +795,47 @@ class PythonModuleDocumentationGenerator:
|
||||
:rtype: Iterator[PythonDefinition]
|
||||
'''
|
||||
|
||||
tree = ast.parse(self.module_path.read_text())
|
||||
tree = self._ast
|
||||
|
||||
return _collect_definitions(tree)
|
||||
|
||||
def generate_documentation(self) -> str:
|
||||
'''
|
||||
Generates the documentation in the markdown format.
|
||||
|
||||
:return: The documentation.
|
||||
:rtype: str
|
||||
'''
|
||||
|
||||
|
||||
md = '# Module `'
|
||||
|
||||
if self.src_directory is not None:
|
||||
module_identifier = '.'.join(get_relative_path_segments(self.module_path, self.src_directory))
|
||||
if module_identifier.endswith('.py'):
|
||||
md += module_identifier[:-3]
|
||||
else:
|
||||
md += module_identifier
|
||||
else:
|
||||
md += self.module_path.stem
|
||||
|
||||
md += '`\n\n'
|
||||
|
||||
docstring = ast.get_docstring(self._ast)
|
||||
if docstring is not None:
|
||||
md += docstring + '\n\n'
|
||||
|
||||
for definition in self.collect_definitions():
|
||||
md += PythonDefinitionDocumentationGenerator(
|
||||
definition,
|
||||
self.include_sections,
|
||||
2,
|
||||
**self.kwargs
|
||||
).generate_documentation() + '\n\n'
|
||||
|
||||
return md.strip()
|
||||
|
||||
|
||||
class PythonDocumentationGenerator:
|
||||
'''
|
||||
The class for the documentation generator.
|
||||
|
||||
Reference in New Issue
Block a user