From 0a79225eabb3e4a5e2fdf743e186a05baf55a474 Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 26 Feb 2025 10:06:05 +1000 Subject: [PATCH 01/13] Revert workaround as sync-label bug was fixed in labeler-v5.0.0 --- .github/workflows/pr-labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 28d6548..1526ca6 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -12,7 +12,7 @@ jobs: repo-token: "${{ secrets.GITHUB_TOKEN }}" configuration-path: .github/pr-labeler-file-path.yml # workaround for problem: https://github.com/wesnoth/wesnoth/commit/958c82d0867568057caaf58356502ec8c87d8366 - sync-labels: "" + sync-labels: false - uses: TimonVS/pr-labeler-action@v3 with: configuration-path: .github/pr-labeler-branch-name.yml From 0c9eb452f2624f373b8749155eaa128f6df4c385 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 23 Feb 2025 18:47:44 +1000 Subject: [PATCH 02/13] Updated missing documentation for mdx support --- README.md | 1 + src/lazydocs/generation.py | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9370673..d201cad 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ lazydocs [OPTIONS] PATHS... * `--ignored-modules TEXT`: A list of modules that should be ignored. [default: ] * `--watermark / --no-watermark`: If `True`, add a watermark with a timestamp to bottom of the markdown files. [default: True] * `--validate / --no-validate`: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. [default: False] +* `--output-format TEXT`: The output format for the creation of the markdown files. This may be 'md' or 'mdx'. Defaults to md. * `--install-completion`: Install completion for the current shell. * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. * `--help`: Show this message and exit. diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 8196665..b4b0aa0 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -211,16 +211,17 @@ def to_md_file( Args: markdown_str (str): Markdown string with line breaks to write to file. filename (str): Filename without the .md + out_path (str): The output directory. watermark (bool): If `True`, add a watermark with a timestamp to bottom of the markdown files. disable_markdownlint (bool): If `True`, an inline tag is added to disable markdownlint for this file. - out_path (str): The output directory + is_mdx (bool, optional): JSX support. Default to False. """ if not markdown_str: # Dont write empty files return md_file = filename - + if is_mdx: if not filename.endswith(".mdx"): md_file = filename + ".mdx" @@ -538,6 +539,7 @@ def func2md(self, func: Callable, clsname: str = "", depth: int = 3, is_mdx: boo func (Callable): Selected function (or method) for markdown generation. clsname (str, optional): Class name to prepend to funcname. Defaults to "". depth (int, optional): Number of # to append to class name. Defaults to 3. + is_mdx (bool, optional): JSX support. Default to False. Returns: str: Markdown documentation for selected function. @@ -614,7 +616,7 @@ def func2md(self, func: Callable, clsname: str = "", depth: int = 3, is_mdx: boo if path: if is_mdx: markdown = _MDX_SOURCE_BADGE_TEMPLATE.format(path=path) + markdown - else: + else: markdown = _SOURCE_BADGE_TEMPLATE.format(path=path) + markdown return markdown @@ -625,6 +627,7 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: Args: cls (class): Selected class for markdown generation. depth (int, optional): Number of # to append to function name. Defaults to 2. + is_mdx (bool, optional): JSX support. Default to False. Returns: str: Markdown documentation for selected class. @@ -739,6 +742,7 @@ def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = Fal Args: module (types.ModuleType): Selected module for markdown generation. depth (int, optional): Number of # to append before module heading. Defaults to 1. + is_mdx (bool, optional): JSX support. Default to False. Returns: str: Markdown documentation for selected module. @@ -837,6 +841,7 @@ def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False) -> str: Args: obj (Any): Selcted object for markdown docs generation. depth (int, optional): Number of # to append before heading. Defaults to 1. + is_mdx (bool, optional): JSX support. Default to False. Returns: str: Markdown documentation of selected object. @@ -852,7 +857,14 @@ def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False) -> str: return "" def overview2md(self, is_mdx: bool = False) -> str: - """Generates a documentation overview file based on the generated docs.""" + """Generates a documentation overview file based on the generated docs. + + Args: + is_mdx (bool, optional): JSX support. Default to False. + + Returns: + str: Markdown documentation of overview file. + """ entries_md = "" for obj in list( @@ -935,6 +947,7 @@ def generate_docs( src_base_url: The base url of the github link. Should include branch name. All source links are generated with this prefix. remove_package_prefix: If `True`, the package prefix will be removed from all functions and methods. ignored_modules: A list of modules that should be ignored. + output_format: Markdown file extension and format. overview_file: Filename of overview file. If not provided, no overview file will be generated. watermark: If `True`, add a watermark with a timestamp to bottom of the markdown files. validate: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. From 87a3d5902bd50e9f43e421715eb055ef9d92321f Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 7 Feb 2025 19:00:17 +1000 Subject: [PATCH 03/13] Fixes functions not rendered during markdown generation and no "find_module" AttributeError Caused by modules not correctly loaded into namespace from commit due to Issue #57. Modules now correctly loaded into namespace. Fixes AttributeError("'FileFinder' object has no attribute 'find_module'") (#69) Prioritized "find_spec" and falls back to "find_module" from loader if find_spec not available. Some whitespace removal. --- src/lazydocs/generation.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index b4b0aa0..1e009d9 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -1011,8 +1011,13 @@ def generate_docs( continue try: - mod_spec = loader.find_spec(module_name) - mod = importlib.util.module_from_spec(mod_spec) + try: + mod_spec = importlib.util.spec_from_loader(module_name, loader) + mod = importlib.util.module_from_spec(mod_spec) + mod_spec.loader.exec_module(mod) + except AttributeError: + # For older python version compatibility + mod = loader.find_module(module_name).load_module(module_name) # type: ignore module_md = generator.module2md(mod, is_mdx=is_mdx) if not module_md: # Module md is empty -> ignore module and all submodules @@ -1090,8 +1095,13 @@ def generate_docs( continue try: - mod_spec = loader.find_spec(module_name) - mod = importlib.util.module_from_spec(mod_spec) + try: + mod_spec = importlib.util.spec_from_loader(module_name, loader) + mod = importlib.util.module_from_spec(mod_spec) + mod_spec.loader.exec_module(mod) + except AttributeError: + # For older python version compatibility + mod = loader.find_module(module_name).load_module(module_name) # type: ignore module_md = generator.module2md(mod, is_mdx=is_mdx) if not module_md: From c743882edad48057434292420d822232c9e55de8 Mon Sep 17 00:00:00 2001 From: Levi Date: Fri, 7 Feb 2025 19:04:42 +1000 Subject: [PATCH 04/13] Overhauled docstring render and added Github flavour admonition support Modify argument regex Fix Colon use in docstring in arguments blocks now formatted correctly. Change argument detection to last colon in line. Added support for "Reference" as a block header. Convert quote block to admonition blocks Added Github admonition quote block support. Added start line anchor to regex Changed "```" code snippet boundary detection from startswith to regex to prevent false positives. Rework docstring markdown render. Solves issue #80 Improved whitespace and newline rendering. Accepts more native markdown syntax without garbling render. Solves Issue #82 Enumerate the docstring to detect end of docstring to appropriately close literal blocks, doctest and code blocks Update literal blocks logic and format. Syntax is same as reStructured text --- src/lazydocs/generation.py | 286 ++++++++++++++++++++++++++++--------- 1 file changed, 215 insertions(+), 71 deletions(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 1e009d9..ca1eefd 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -9,20 +9,31 @@ import re import subprocess import types +from dataclasses import dataclass from pydoc import locate from typing import Any, Callable, Dict, List, Optional _RE_BLOCKSTART_LIST = re.compile( - r"(Args:|Arg:|Arguments:|Parameters:|Kwargs:|Attributes:|Returns:|Yields:|Kwargs:|Raises:).{0,2}$", + r"^(Args:|Arg:|Arguments:|Parameters:|Kwargs:|Attributes:|Returns:|Yields:|Kwargs:|Raises:).{0,2}$", re.IGNORECASE, ) -_RE_BLOCKSTART_TEXT = re.compile(r"(Examples:|Example:|Todo:).{0,2}$", re.IGNORECASE) +_RE_BLOCKSTART_TEXT = re.compile( + r"^(Example[s]?:|Todo:|Reference[s]?:).{0,2}$", + re.IGNORECASE +) + +# https://github.com/orgs/community/discussions/16925 +# https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts +_RE_ADMONITION_TEXT = re.compile( + r"^(?:\[\!?)?(NOTE|TIP|IMPORTANT|WARNING|CAUTION)s?[\]:][^:]?[ ]*(.*)$", + re.IGNORECASE +) -_RE_QUOTE_TEXT = re.compile(r"(Notes:|Note:).{0,2}$", re.IGNORECASE) +_RE_TYPED_ARGSTART = re.compile(r"^([\w\[\]_]{1,}?)[ ]*?\((.*?)\):[ ]+(.{2,})", re.IGNORECASE) +_RE_ARGSTART = re.compile(r"^(.+):[ ]+(.{2,})$", re.IGNORECASE) -_RE_TYPED_ARGSTART = re.compile(r"([\w\[\]_]{1,}?)\s*?\((.*?)\):(.{2,})", re.IGNORECASE) -_RE_ARGSTART = re.compile(r"(.{1,}?):(.{2,})", re.IGNORECASE) +_RE_CODE_TEXT = re.compile(r"^```[\w\-\.]*[ ]*$", re.IGNORECASE) _IGNORE_GENERATION_INSTRUCTION = "lazydocs: ignore" @@ -361,106 +372,239 @@ def _doc2md(obj: Any) -> str: # doc = getdoc(func) or "" doc = _get_docstring(obj) + padding = 0 blockindent = 0 - argindent = 1 + argindent = 0 out = [] arg_list = False - literal_block = False + section_block = False + block_exit = False md_code_snippet = False - quote_block = False + admonition_block = None + literal_block = None + doctest_block = None + prev_blank_line_count = 0 + offset = 0 - for line in doc.split("\n"): - indent = len(line) - len(line.lstrip()) - if not md_code_snippet and not literal_block: - line = line.lstrip() + @dataclass + class SectionBlock(): + line_index: int + indent: int + offset: int - if line.startswith(">>>"): - # support for doctest - line = line.replace(">>>", "```") + "```" + def _get_section_offset(lines: list, start_index: int, blockindent: int): + """Determine base padding offset for section. - if ( - _RE_BLOCKSTART_LIST.match(line) - or _RE_BLOCKSTART_TEXT.match(line) - or _RE_QUOTE_TEXT.match(line) - ): - # start of a new block - blockindent = indent - - if quote_block: - quote_block = False + Args: + lines (list): Line lists. + start_index (int): Index of lines to start parsing. + blockindent (int): Reference block indent of section. - if literal_block: - # break literal block - out.append("```\n") - literal_block = False + Returns: + int: Padding offset. + """ + offset = [] + try: + for line in lines[start_index:]: + indent = len(line) - len(line.lstrip()) + if not line.strip(): + continue + if indent <= blockindent: + return -min(offset) if offset else 0 + if indent > blockindent: + offset.append(indent - blockindent) + except IndexError: + return 0 + return -min(offset) if offset else 0 + + def _lines_isvalid(lines: list, start_index: int, blockindent: int, + allow_same_level: bool = False, + require_next_is_blank: bool = False, + max_blank: int = None): + """Determine following lines fit section rules. - out.append("\n\n**{}**\n".format(line.strip())) + Args: + lines (list): Line lists. + start_index (int): Index of lines to start parsing. + blockindent (int): Reference block indent of section. + allow_same_level (bool, optional): Allow line indent as blockindent. Defaults to False. + require_next_is_blank (bool, optional): Require first parsed line to be blank. Defaults to False. + max_blank (int, optional): Max number of allowable continuous blank lines in section. Defaults to None. - arg_list = bool(_RE_BLOCKSTART_LIST.match(line)) + Returns: + bool: Validity of tested lines. + """ + prev_blank = 0 + try: + for index, line in enumerate(lines[start_index:]): + indent = len(line) - len(line.lstrip()) + line = line.strip() + if require_next_is_blank and index == 0 and line: + return False + if line: + prev_blank = 0 + if indent <= blockindent: + if allow_same_level and indent == blockindent: + return True + return False + return True + if max_blank is not None: + if not line: + prev_blank += 1 + if prev_blank > max_blank: + return False + except IndexError: + pass + return False - if _RE_QUOTE_TEXT.match(line): - quote_block = True - out.append("\n>") - elif line.strip().startswith("```"): - # Code snippet is used - if md_code_snippet: - md_code_snippet = False - else: + docstring = doc.split("\n") + for line_indx, line in enumerate(docstring): + indent = len(line) - len(line.lstrip()) + line = line.lstrip() + offset = 0 + + # Exit condition for args and section blocks + if (any([arg_list, section_block]) + and all([indent <= blockindent, + prev_blank_line_count, + line])): + arg_list = False if arg_list else arg_list + section_block = False if section_block else section_block + blockindent = 0 + + admonition_result = _RE_ADMONITION_TEXT.match(line) + blockstart_result = _RE_BLOCKSTART_LIST.match(line) + blocktext_result = _RE_BLOCKSTART_TEXT.match(line) + + if admonition_result and not (md_code_snippet or admonition_block): + # Admonition block entry condition + admonition_block = SectionBlock( + line_indx, indent, _get_section_offset(docstring, + line_indx + 1, + indent)) + line = "[!{}] {}".format(admonition_result.group(1).upper(), + admonition_result.group(2)) + + # Entry conditions and block offsets + if _RE_CODE_TEXT.match(line): + # Code block, detect "```" + md_code_snippet = not md_code_snippet + elif line.startswith(">>>") and not doctest_block: + # Doctest Entry condition + line = "```python\n" + line + if _lines_isvalid(docstring, line_indx + 1, indent, True, False, 1): + doctest_block = SectionBlock(line_indx, indent, 0) md_code_snippet = True + else: + line = line + "\n```" + elif doctest_block and \ + not _lines_isvalid(docstring, line_indx + 1, doctest_block.indent, + True, False, 1): + # Doctest block Exit Condition + offset = doctest_block.indent - indent + line = " " * (indent - doctest_block.indent + + doctest_block.offset) + line + "\n```" + block_exit = True + elif line.endswith("::") and not (literal_block) and \ + _lines_isvalid(docstring, line_indx + 1, indent, False, True, None): + # Literal Block Entry Conditions + literal_block = SectionBlock( + line_indx, indent, + _get_section_offset(docstring, line_indx + 1, indent)) + line = line.replace("::", "") if line.startswith( + "::") else line.replace("::", ":") + md_code_snippet = True + elif literal_block: + if line_indx == literal_block.line_index + 1 and not line: + # Literal block post entry + line = "```" + line + indent = literal_block.indent + elif not _lines_isvalid(docstring, line_indx + 1, literal_block.indent, + False, False, None): + # Literal block exit condition + offset += literal_block.indent - indent + line = " " * (indent - literal_block.indent + + literal_block.offset) + line + "\n```" + block_exit = True + elif line: + offset += literal_block.offset + + # Admonition block processing and exit condition + if admonition_block: + if md_code_snippet: + if literal_block: + padding = max(indent - literal_block.indent, 0) + elif doctest_block: + padding = max(indent - doctest_block.indent, 0) + else: + padding = max(indent - admonition_block.indent + + admonition_block.offset, 0) + line = " " * (padding + offset) + line + offset = admonition_block.indent - indent + line = "> {}".format(line.replace("\n", "\n> ")) + if not _lines_isvalid(docstring, line_indx + 1, admonition_block.indent, + False, False, None): + admonition_block = None + + if (blockstart_result or blocktext_result): + # start of a new block + blockindent = indent + arg_list = bool(blockstart_result) + section_block = bool(blocktext_result) - out.append(line) - elif line.strip().endswith("::"): - # Literal Block Support: https://docutils.sourceforge.io/docs/user/rst/quickref.html#literal-blocks - literal_block = True - out.append(line.replace("::", ":\n```")) - elif quote_block: - out.append(line.strip()) - elif line.strip().startswith("-"): - # Allow bullet lists - out.append("\n" + (" " * indent) + line) - elif indent > blockindent: + if prev_blank_line_count <= 1: + out.append("\n") + out.append("**{}**\n".format(line.strip())) + elif indent > blockindent and (arg_list or section_block): if arg_list and not literal_block and _RE_TYPED_ARGSTART.match(line): # start of new argument out.append( - "\n" - + " " * blockindent - + " - " + "- " + _RE_TYPED_ARGSTART.sub(r"`\1` (\2): \3", line) ) argindent = indent elif arg_list and not literal_block and _RE_ARGSTART.match(line): # start of an exception-type block out.append( - "\n" - + " " * blockindent - + " - " + "- " + _RE_ARGSTART.sub(r"`\1`: \2", line) ) argindent = indent elif indent > argindent: # attach docs text of argument # * (blockindent + 2) - out.append(" " + line) + padding = max(indent - argindent + offset, 0) + out.append(" " * padding + + line.replace("\n", + "\n" + " " * padding)) else: - out.append(line) + padding = max(indent - blockindent + offset, 0) + out.append(line.replace("\n", + "\n" + " " * padding)) + elif line: + padding = max(indent - blockindent + offset, 0) + out.append(" " * padding + + line.replace("\n", + "\n" + " " * padding)) else: - if line.strip() and literal_block: - # indent has changed, if not empty line, break literal block - line = "```\n" + line - literal_block = False out.append(line) - if md_code_snippet: - out.append("\n") - elif not line and not quote_block: - out.append("\n\n") - elif not line and quote_block: - out.append("\n>") - else: - out.append(" ") + out.append("\n") - return "".join(out) + if block_exit: + block_exit = False + if md_code_snippet: + md_code_snippet = False + if literal_block: + literal_block = None + elif doctest_block: + doctest_block = None + if line.lstrip(): + prev_blank_line_count = 0 + else: + prev_blank_line_count += 1 + return "".join(out) class MarkdownGenerator(object): """Markdown generator class.""" From 502e069c4413c2a4975e788faea8d56e121b1922 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 16 Feb 2025 19:13:48 +1000 Subject: [PATCH 05/13] Render class __init__ method as constructor using class name --- src/lazydocs/generation.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index ca1eefd..7be7562 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -136,7 +136,10 @@ def _get_function_signature( if owner_class: name_parts.append(owner_class.__name__) if hasattr(function, "__name__"): - name_parts.append(function.__name__) + if function.__name__ == "__init__": + name_parts.append(_get_class_that_defined_method(function).__name__) + else: + name_parts.append(function.__name__) else: name_parts.append(type(function).__name__) name_parts.append("__call__") @@ -735,7 +738,7 @@ def func2md(self, func: Callable, clsname: str = "", depth: int = 3, is_mdx: boo func_type = "function" else: # function of a class - func_type = "method" + func_type = "constructor" if escfuncname == "__init__" else "method" self.generated_objects.append( { From 5e938e58bcb4bd738bc577feb200e028f1a797b0 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 16 Feb 2025 17:39:46 +1000 Subject: [PATCH 06/13] Support including private modules with, aka files with "_" prefix. Add private_modules arg --- README.md | 1 + src/lazydocs/_cli.py | 8 ++++++-- src/lazydocs/generation.py | 11 ++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index d201cad..434cb2e 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,7 @@ lazydocs [OPTIONS] PATHS... * `--watermark / --no-watermark`: If `True`, add a watermark with a timestamp to bottom of the markdown files. [default: True] * `--validate / --no-validate`: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. [default: False] * `--output-format TEXT`: The output format for the creation of the markdown files. This may be 'md' or 'mdx'. Defaults to md. +* `--private-modules / --no-private-modules`: If `True`, includes modules with "_" prefix. [default: False] * `--install-completion`: Install completion for the current shell. * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. * `--help`: Show this message and exit. diff --git a/src/lazydocs/_cli.py b/src/lazydocs/_cli.py index ac139cd..a2d80de 100644 --- a/src/lazydocs/_cli.py +++ b/src/lazydocs/_cli.py @@ -45,8 +45,11 @@ def generate( output_format: Optional[str] = typer.Option( None, help="The output format for the creation of the markdown files. This may be 'md' or 'mdx'. Defaults to md.", - ) - + ), + private_modules: bool = typer.Option( + False, + help="If `True`, all packages starting with `_` will be included.", + ), ) -> None: """Generates markdown documentation for your Python project based on Google-style docstrings.""" @@ -61,6 +64,7 @@ def generate( overview_file=overview_file, watermark=watermark, validate=validate, + private_modules=private_modules, ) except Exception as ex: typer.echo(str(ex)) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 7be7562..088bc3e 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -313,9 +313,9 @@ def _is_object_ignored(obj: Any) -> bool: return False -def _is_module_ignored(module_name: str, ignored_modules: List[str]) -> bool: +def _is_module_ignored(module_name: str, ignored_modules: List[str], private_modules: bool = False) -> bool: """Checks if a given module is ignored.""" - if module_name.split(".")[-1].startswith("_"): + if module_name.split(".")[-1].startswith("_") and module_name[1] != "_" and not private_modules: return True for ignored_module in ignored_modules: @@ -1084,6 +1084,7 @@ def generate_docs( overview_file: Optional[str] = None, watermark: bool = True, validate: bool = False, + private_modules: bool = False, ) -> None: """Generates markdown documentation for provided paths based on Google-style docstrings. @@ -1098,6 +1099,7 @@ def generate_docs( overview_file: Filename of overview file. If not provided, no overview file will be generated. watermark: If `True`, add a watermark with a timestamp to bottom of the markdown files. validate: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. + private_modules: If `True`, includes modules with `_` prefix. """ stdout_mode = output_path.lower() == "stdout" @@ -1152,11 +1154,10 @@ def generate_docs( # Generate one file for every discovered module for loader, module_name, _ in pkgutil.walk_packages([path]): - if _is_module_ignored(module_name, ignored_modules): + if _is_module_ignored(module_name, ignored_modules, private_modules): # Add module to ignore list, so submodule will also be ignored ignored_modules.append(module_name) continue - try: try: mod_spec = importlib.util.spec_from_loader(module_name, loader) @@ -1236,7 +1237,7 @@ def generate_docs( path=obj.__path__, # type: ignore prefix=obj.__name__ + ".", # type: ignore ): - if _is_module_ignored(module_name, ignored_modules): + if _is_module_ignored(module_name, ignored_modules, private_modules): # Add module to ignore list, so submodule will also be ignored ignored_modules.append(module_name) continue From b6a650bc7e82e14a4105eb1e5a26e2e847929831 Mon Sep 17 00:00:00 2001 From: Levi Date: Sun, 23 Feb 2025 18:33:44 +1000 Subject: [PATCH 07/13] Add table of contents feature to module file --- README.md | 1 + src/lazydocs/_cli.py | 5 +++++ src/lazydocs/generation.py | 44 ++++++++++++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 434cb2e..4d4db68 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ lazydocs [OPTIONS] PATHS... * `--validate / --no-validate`: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. [default: False] * `--output-format TEXT`: The output format for the creation of the markdown files. This may be 'md' or 'mdx'. Defaults to md. * `--private-modules / --no-private-modules`: If `True`, includes modules with "_" prefix. [default: False] +* `--toc / --no-toc`: If `True`, includes table of contents in generated module markdown files. [default: False] * `--install-completion`: Install completion for the current shell. * `--show-completion`: Show completion for the current shell, to copy it or customize the installation. * `--help`: Show this message and exit. diff --git a/src/lazydocs/_cli.py b/src/lazydocs/_cli.py index a2d80de..98fb414 100644 --- a/src/lazydocs/_cli.py +++ b/src/lazydocs/_cli.py @@ -50,6 +50,10 @@ def generate( False, help="If `True`, all packages starting with `_` will be included.", ), + toc: bool = typer.Option( + False, + help="Include table of contents in module file. Defaults to False.", + ), ) -> None: """Generates markdown documentation for your Python project based on Google-style docstrings.""" @@ -65,6 +69,7 @@ def generate( watermark=watermark, validate=validate, private_modules=private_modules, + include_toc=toc, ) except Exception as ex: typer.echo(str(ex)) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 088bc3e..fbd229f 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -62,6 +62,11 @@ """ +_TOC_TEMPLATE = """ +## Table of Contents +{toc} +""" + _CLASS_TEMPLATE = """ {section} class `{header}` {doc} @@ -74,6 +79,7 @@ _MODULE_TEMPLATE = """ {section} module `{header}` {doc} +{toc} {global_vars} {functions} {classes} @@ -883,13 +889,14 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: return markdown - def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = False) -> str: + def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = False, include_toc: bool = False) -> str: """Takes an imported module object and create a Markdown string containing functions and classes. Args: module (types.ModuleType): Selected module for markdown generation. depth (int, optional): Number of # to append before module heading. Defaults to 1. is_mdx (bool, optional): JSX support. Default to False. + include_toc (bool, optional): Include table of contents in module file. Defaults to False. Returns: str: Markdown documentation for selected module. @@ -965,10 +972,13 @@ def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = Fal new_list = ["\n**Global Variables**", "---------------", *variables] variables = new_list + toc = self.toc2md(module=module, is_mdx=is_mdx) if include_toc else "" + markdown = _MODULE_TEMPLATE.format( header=modname, section="#" * depth, doc=doc, + toc=toc, global_vars="\n".join(variables) if variables else "", functions="\n".join(functions) if functions else "", classes="".join(classes) if classes else "", @@ -982,13 +992,14 @@ def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = Fal return markdown - def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False) -> str: + def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False, include_toc: bool = False) -> str: """Generates markdown documentation for a selected object/import. Args: obj (Any): Selcted object for markdown docs generation. depth (int, optional): Number of # to append before heading. Defaults to 1. is_mdx (bool, optional): JSX support. Default to False. + include_toc(bool, Optional): Include table of contents for module file. Defaults to False. Returns: str: Markdown documentation of selected object. @@ -996,7 +1007,7 @@ def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False) -> str: if inspect.isclass(obj): return self.class2md(obj, depth=depth, is_mdx=is_mdx) elif isinstance(obj, types.ModuleType): - return self.module2md(obj, depth=depth, is_mdx=is_mdx) + return self.module2md(obj, depth=depth, is_mdx=is_mdx, include_toc=include_toc) elif callable(obj): return self.func2md(obj, depth=depth, is_mdx=is_mdx) else: @@ -1072,6 +1083,26 @@ def overview2md(self, is_mdx: bool = False) -> str: modules=modules_md, classes=classes_md, functions=functions_md ) + def toc2md(self, module: types.ModuleType = None, is_mdx: bool = False) -> str: + """Generates table of contents for imported object.""" + toc = [] + for obj in self.generated_objects: + if module and (module.__name__ != obj["module"] or obj["type"] == "module"): + continue + # module_name = obj["module"].split(".")[-1] + full_name = obj["full_name"] + name = obj["name"] + if is_mdx: + link = "./" + obj["module"] + ".mdx#" + obj["anchor_tag"] + else: + link = "./" + obj["module"] + ".md#" + obj["anchor_tag"] + line = f"- [`{name}`]({link})" + depth = max(len(full_name.split(".")) - 1, 0) + if depth: + line = "\t" * depth + line + toc.append(line) + return _TOC_TEMPLATE.format(toc="\n".join(toc)) + def generate_docs( paths: List[str], @@ -1085,6 +1116,7 @@ def generate_docs( watermark: bool = True, validate: bool = False, private_modules: bool = False, + include_toc: bool = False, ) -> None: """Generates markdown documentation for provided paths based on Google-style docstrings. @@ -1166,7 +1198,7 @@ def generate_docs( except AttributeError: # For older python version compatibility mod = loader.find_module(module_name).load_module(module_name) # type: ignore - module_md = generator.module2md(mod, is_mdx=is_mdx) + module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc) if not module_md: # Module md is empty -> ignore module and all submodules # Add module to ignore list, so submodule will also be ignored @@ -1205,7 +1237,7 @@ def generate_docs( spec.loader.exec_module(mod) # type: ignore if mod: - module_md = generator.module2md(mod, is_mdx=is_mdx) + module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc) if stdout_mode: print(module_md) else: @@ -1250,7 +1282,7 @@ def generate_docs( except AttributeError: # For older python version compatibility mod = loader.find_module(module_name).load_module(module_name) # type: ignore - module_md = generator.module2md(mod, is_mdx=is_mdx) + module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc) if not module_md: # Module MD is empty -> ignore module and all submodules From 6ce29fa7141eb4a48e2cdff5585097e7ad0075fd Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Feb 2025 14:13:42 +1000 Subject: [PATCH 08/13] Fixed incorrect and unsafe src href link --- src/lazydocs/generation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index fbd229f..7942d6f 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -12,6 +12,7 @@ from dataclasses import dataclass from pydoc import locate from typing import Any, Callable, Dict, List, Optional +from urllib.parse import quote _RE_BLOCKSTART_LIST = re.compile( r"^(Args:|Arg:|Arguments:|Parameters:|Kwargs:|Attributes:|Returns:|Yields:|Kwargs:|Raises:).{0,2}$", @@ -683,7 +684,7 @@ def _get_src_path(self, obj: Any, append_base: bool = True) -> str: if append_base and self.src_base_url: relative_path = os.path.join(self.src_base_url, relative_path) - return relative_path + return quote("/".join(relative_path.split("\\")), safe=":/#") def func2md(self, func: Callable, clsname: str = "", depth: int = 3, is_mdx: bool = False) -> str: """Takes a function (or method) and generates markdown docs. From d8d309418fc341ddf7c0d88a4d4727e6bb4488ce Mon Sep 17 00:00:00 2001 From: Levi Date: Mon, 24 Feb 2025 23:08:36 +1000 Subject: [PATCH 09/13] Added feature for user override of url line anchor notation (#74) --- README.md | 1 + src/lazydocs/_cli.py | 5 +++++ src/lazydocs/generation.py | 14 +++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d4db68..73ffc38 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ lazydocs [OPTIONS] PATHS... * `--output-path TEXT`: The output path for the creation of the markdown files. Set this to `stdout` to print all markdown to stdout. [default: ./docs/] * `--src-base-url TEXT`: The base repo link used as prefix for all source links. Should also include the branch name. +* `--url-line-prefix TEXT`: Line prefix for git repository line url anchors #{prefix}line. If None provided, defaults to Github style notation. * `--overview-file TEXT`: Filename of overview file. If not provided, no API overview file will be generated. * `--remove-package-prefix / --no-remove-package-prefix`: If `True`, the package prefix will be removed from all functions and methods. [default: True] * `--ignored-modules TEXT`: A list of modules that should be ignored. [default: ] diff --git a/src/lazydocs/_cli.py b/src/lazydocs/_cli.py index 98fb414..47c0e36 100644 --- a/src/lazydocs/_cli.py +++ b/src/lazydocs/_cli.py @@ -54,6 +54,10 @@ def generate( False, help="Include table of contents in module file. Defaults to False.", ), + url_line_prefix: Optional[str] = typer.Option( + None, + help="Line prefix for git repository line url anchors #{prefix}line. If none provided, defaults to Github style notation.", + ), ) -> None: """Generates markdown documentation for your Python project based on Google-style docstrings.""" @@ -70,6 +74,7 @@ def generate( validate=validate, private_modules=private_modules, include_toc=toc, + url_line_prefix=url_line_prefix, ) except Exception as ex: typer.echo(str(ex)) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 7942d6f..18fccb3 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -624,6 +624,7 @@ def __init__( src_root_path: Optional[str] = None, src_base_url: Optional[str] = None, remove_package_prefix: bool = False, + url_line_prefix: Optional[str] = None, ): """Initializes the markdown API generator. @@ -632,10 +633,12 @@ def __init__( src_base_url: The base github link. Should include branch name. All source links are generated with this prefix. remove_package_prefix: If `True`, the package prefix will be removed from all functions and methods. + url_line_prefix: Line prefix for git repository line url anchors. Default: None - github "L". """ self.src_root_path = src_root_path self.src_base_url = src_base_url self.remove_package_prefix = remove_package_prefix + self.url_line_prefix = url_line_prefix self.generated_objects: List[Dict] = [] @@ -677,7 +680,13 @@ def _get_src_path(self, obj: Any, append_base: bool = True) -> str: relative_path = os.path.relpath(path, src_root_path) lineno = _get_line_no(obj) - lineno_hashtag = "" if lineno is None else "#L{}".format(lineno) + if self.url_line_prefix is None: + lineno_hashtag = "" if lineno is None else "#L{}".format(lineno) + else: + lineno_hashtag = "" if lineno is None else "#{}{}".format( + self.url_line_prefix, + lineno + ) # add line hash relative_path = relative_path + lineno_hashtag @@ -1118,6 +1127,7 @@ def generate_docs( validate: bool = False, private_modules: bool = False, include_toc: bool = False, + url_line_prefix: Optional[str] = None, ) -> None: """Generates markdown documentation for provided paths based on Google-style docstrings. @@ -1133,6 +1143,7 @@ def generate_docs( watermark: If `True`, add a watermark with a timestamp to bottom of the markdown files. validate: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. private_modules: If `True`, includes modules with `_` prefix. + url_line_prefix: Line prefix for git repository line url anchors. Default: None - github "L". """ stdout_mode = output_path.lower() == "stdout" @@ -1173,6 +1184,7 @@ def generate_docs( src_root_path=src_root_path, src_base_url=src_base_url, remove_package_prefix=remove_package_prefix, + url_line_prefix=url_line_prefix, ) pydocstyle_cmd = "pydocstyle --convention=google --add-ignore=D100,D101,D102,D103,D104,D105,D107,D202" From 8adaff582b7e9c0f08a369942729232432ab4730 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 25 Feb 2025 21:38:57 +1000 Subject: [PATCH 10/13] Forced generated markdown file to be platform independent --- src/lazydocs/generation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 18fccb3..0b7b594 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -259,7 +259,7 @@ def to_md_file( ) print("Writing {}.".format(md_file)) - with open(os.path.join(out_path, md_file), "w", encoding="utf-8") as f: + with open(os.path.join(out_path, md_file), "w", encoding="utf-8", newline="\n") as f: f.write(markdown_str) @@ -1348,5 +1348,5 @@ def generate_docs( # Write mkdocs pages file print("Writing mkdocs .pages file.") # TODO: generate navigation items to fix problem with naming - with open(os.path.join(output_path, ".pages"), "w") as f: + with open(os.path.join(output_path, ".pages"), "w", encoding="utf-8", newline="\n") as f: f.write(_MKDOCS_PAGES_TEMPLATE.format(overview_file=overview_file)) From 2a0a5de8db30d080b926c06aeed4b13009b0c9cf Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 26 Feb 2025 22:25:41 +1000 Subject: [PATCH 11/13] Crude workaround for AttributeError, no attribute "__create_fn__" (#72) Observed in python 3.8, constructor for dataclasses had different function signature --- src/lazydocs/generation.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 0b7b594..140d7f3 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -9,7 +9,7 @@ import re import subprocess import types -from dataclasses import dataclass +from dataclasses import dataclass, is_dataclass from pydoc import locate from typing import Any, Callable, Dict, List, Optional from urllib.parse import quote @@ -297,12 +297,19 @@ def _get_class_that_defined_method(meth: Any) -> Any: mod = inspect.getmodule(meth) if mod is None: return None - cls = getattr( - inspect.getmodule(meth), - meth.__qualname__.split(".", 1)[0].rsplit(".", 1)[0], - ) - if isinstance(cls, type): - return cls + try: + cls = getattr( + inspect.getmodule(meth), + meth.__qualname__.split(".", 1)[0].rsplit(".", 1)[0], + ) + except AttributeError: + # workaround for AttributeError("module '' has no attribute '__create_fn__'") + for obj in meth.__globals__.values(): + if is_dataclass(obj): + return obj + else: + if isinstance(cls, type): + return cls return getattr(meth, "__objclass__", None) # handle special descriptor objects From d9d60d0727a60b7aca3a8bd4a3bf8b88fff4d42c Mon Sep 17 00:00:00 2001 From: Levi Date: Wed, 26 Feb 2025 22:35:00 +1000 Subject: [PATCH 12/13] Added rendering support for enum, dataclass, and exception --- src/lazydocs/generation.py | 60 +++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/lazydocs/generation.py b/src/lazydocs/generation.py index 140d7f3..e773445 100755 --- a/src/lazydocs/generation.py +++ b/src/lazydocs/generation.py @@ -10,6 +10,7 @@ import subprocess import types from dataclasses import dataclass, is_dataclass +from enum import Enum from pydoc import locate from typing import Any, Callable, Dict, List, Optional from urllib.parse import quote @@ -69,7 +70,7 @@ """ _CLASS_TEMPLATE = """ -{section} class `{header}` +{section} {kind} `{header}` {doc} {init} {variables} @@ -807,6 +808,7 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: return "" section = "#" * depth + sectionheader = "#" * (depth + 1) subsection = "#" * (depth + 2) clsname = cls.__name__ modname = cls.__module__ @@ -814,6 +816,27 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: path = self._get_src_path(cls) doc = _doc2md(cls) summary = _get_doc_summary(cls) + variables = [] + + # Handle different kinds of classes + if issubclass(cls, Enum): + kind = cls.__base__.__name__ + if kind != "Enum": + kind = "enum[%s]" % (kind) + else: + kind = kind.lower() + variables.append( + "%s symbols\n" % (sectionheader) + ) + elif is_dataclass(cls): + kind = "dataclass" + variables.append( + "%s attributes\n" % (sectionheader) + ) + elif issubclass(cls, Exception): + kind = "exception" + else: + kind = "class" self.generated_objects.append( { @@ -821,7 +844,7 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: "name": header, "full_name": header, "module": modname, - "anchor_tag": _get_anchor_tag("class-" + header), + "anchor_tag": _get_anchor_tag("%s-%s" % (kind, header)), "description": summary, } ) @@ -839,21 +862,31 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: # this happens if __init__ is outside the repo init = "" - variables = [] for name, obj in inspect.getmembers( cls, lambda a: not (inspect.isroutine(a) or inspect.ismethod(a)) ): - if not name.startswith("_") and type(obj) == property: - comments = _doc2md(obj) or inspect.getcomments(obj) - comments = "\n\n%s" % comments if comments else "" - property_name = f"{clsname}.{name}" + if not name.startswith("_"): + full_name = f"{clsname}.{name}" if self.remove_package_prefix: - property_name = name - variables.append( - _SEPARATOR - + "\n%s property %s%s\n" - % (subsection, property_name, comments) - ) + full_name = name + if isinstance(obj, property): + comments = _doc2md(obj) or inspect.getcomments(obj) + comments = "\n\n%s" % comments if comments else "" + variables.append( + _SEPARATOR + + "\n%s property %s%s\n" + % (subsection, full_name, comments) + ) + elif isinstance(obj, Enum): + variables.append( + "- **%s** = %s\n" % (full_name, obj.value) + ) + elif name == "__dataclass_fields__": + for name, field in sorted((obj).items()): + variables.append( + "- ```%s``` (%s)\n" % (name, + field.type.__name__) + ) handlers = [] for name, obj in inspect.getmembers(cls, inspect.ismethoddescriptor): @@ -890,6 +923,7 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str: markdown = _CLASS_TEMPLATE.format( section=section, + kind=kind, header=header, doc=doc if doc else "", init=init, From fc350c8041225b0e7418bbbd75ac29646727e1d6 Mon Sep 17 00:00:00 2001 From: Levi Date: Tue, 25 Feb 2025 21:40:18 +1000 Subject: [PATCH 13/13] Updated API document example and bump version to 0.6.0 --- docs/lazydocs.generation.md | 224 +++++++++++++++++++++++------------- src/lazydocs/_about.py | 2 +- 2 files changed, 147 insertions(+), 79 deletions(-) diff --git a/docs/lazydocs.generation.md b/docs/lazydocs.generation.md index 1b21093..1f2d163 100644 --- a/docs/lazydocs.generation.md +++ b/docs/lazydocs.generation.md @@ -1,14 +1,28 @@ - + # module `lazydocs.generation` -Main module for markdown generation. +Main module for markdown generation. + + +## Table of Contents +- [`MarkdownGenerator`](./lazydocs.generation.md#class-markdowngenerator) + - [`__init__`](./lazydocs.generation.md#constructor-__init__) + - [`class2md`](./lazydocs.generation.md#method-class2md) + - [`func2md`](./lazydocs.generation.md#method-func2md) + - [`import2md`](./lazydocs.generation.md#method-import2md) + - [`module2md`](./lazydocs.generation.md#method-module2md) + - [`overview2md`](./lazydocs.generation.md#method-overview2md) + - [`toc2md`](./lazydocs.generation.md#method-toc2md) +- [`generate_docs`](./lazydocs.generation.md#function-generate_docs) +- [`to_md_file`](./lazydocs.generation.md#function-to_md_file) + --- - + ## function `to_md_file` @@ -18,26 +32,28 @@ to_md_file( filename: str, out_path: str = '.', watermark: bool = True, - disable_markdownlint: bool = True + disable_markdownlint: bool = True, + is_mdx: bool = False ) → None ``` -Creates an API docs file from a provided text. - +Creates an API docs file from a provided text. **Args:** - - - `markdown_str` (str): Markdown string with line breaks to write to file. - - `filename` (str): Filename without the .md - - `watermark` (bool): If `True`, add a watermark with a timestamp to bottom of the markdown files. - - `disable_markdownlint` (bool): If `True`, an inline tag is added to disable markdownlint for this file. - - `out_path` (str): The output directory + +- `markdown_str` (str): Markdown string with line breaks to write to file. +- `filename` (str): Filename without the .md +- `out_path` (str): The output directory. +- `watermark` (bool): If `True`, add a watermark with a timestamp to bottom of the markdown files. +- `disable_markdownlint` (bool): If `True`, an inline tag is added to disable markdownlint for this file. +- `is_mdx` (bool, optional): JSX support. Default to False. + --- - + ## function `generate_docs` @@ -49,173 +65,225 @@ generate_docs( src_base_url: Optional[str] = None, remove_package_prefix: bool = False, ignored_modules: Optional[List[str]] = None, + output_format: Optional[str] = None, overview_file: Optional[str] = None, watermark: bool = True, - validate: bool = False + validate: bool = False, + private_modules: bool = False, + include_toc: bool = False, + url_line_prefix: Optional[str] = None ) → None ``` -Generates markdown documentation for provided paths based on Google-style docstrings. - +Generates markdown documentation for provided paths based on Google-style docstrings. **Args:** - - - `paths`: Selected paths or import name for markdown generation. - - `output_path`: The output path for the creation of the markdown files. Set this to `stdout` to print all markdown to stdout. - - `src_root_path`: The root folder name containing all the sources. Fallback to git repo root. - - `src_base_url`: The base url of the github link. Should include branch name. All source links are generated with this prefix. - - `remove_package_prefix`: If `True`, the package prefix will be removed from all functions and methods. - - `ignored_modules`: A list of modules that should be ignored. - - `overview_file`: Filename of overview file. If not provided, no overview file will be generated. - - `watermark`: If `True`, add a watermark with a timestamp to bottom of the markdown files. - - `validate`: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. + +- `paths`: Selected paths or import name for markdown generation. +- `output_path`: The output path for the creation of the markdown files. Set this to `stdout` to print all markdown to stdout. +- `src_root_path`: The root folder name containing all the sources. Fallback to git repo root. +- `src_base_url`: The base url of the github link. Should include branch name. All source links are generated with this prefix. +- `remove_package_prefix`: If `True`, the package prefix will be removed from all functions and methods. +- `ignored_modules`: A list of modules that should be ignored. +- `output_format`: Markdown file extension and format. +- `overview_file`: Filename of overview file. If not provided, no overview file will be generated. +- `watermark`: If `True`, add a watermark with a timestamp to bottom of the markdown files. +- `validate`: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. +- `private_modules`: If `True`, includes modules with `_` prefix. +- `url_line_prefix: Line prefix for git repository line url anchors. Default`: None - github "L". + --- - + ## class `MarkdownGenerator` -Markdown generator class. +Markdown generator class. - -### method `__init__` + + +### constructor `__init__` ```python -__init__( +MarkdownGenerator( src_root_path: Optional[str] = None, src_base_url: Optional[str] = None, - remove_package_prefix: bool = False + remove_package_prefix: bool = False, + url_line_prefix: Optional[str] = None ) ``` -Initializes the markdown API generator. - +Initializes the markdown API generator. **Args:** - - - `src_root_path`: The root folder name containing all the sources. - - `src_base_url`: The base github link. Should include branch name. All source links are generated with this prefix. - - `remove_package_prefix`: If `True`, the package prefix will be removed from all functions and methods. + +- `src_root_path`: The root folder name containing all the sources. +- `src_base_url`: The base github link. Should include branch name. + All source links are generated with this prefix. +- `remove_package_prefix`: If `True`, the package prefix will be removed from all functions and methods. +- `url_line_prefix: Line prefix for git repository line url anchors. Default`: None - github "L". + --- - + ### method `class2md` ```python -class2md(cls: Any, depth: int = 2) → str +class2md(cls: Any, depth: int = 2, is_mdx: bool = False) → str ``` -Takes a class and creates markdown text to document its methods and variables. - +Takes a class and creates markdown text to document its methods and variables. **Args:** - - - `cls` (class): Selected class for markdown generation. - - `depth` (int, optional): Number of # to append to function name. Defaults to 2. +- `cls` (class): Selected class for markdown generation. +- `depth` (int, optional): Number of # to append to function name. Defaults to 2. +- `is_mdx` (bool, optional): JSX support. Default to False. **Returns:** - - - `str`: Markdown documentation for selected class. + +- `str`: Markdown documentation for selected class. + --- - + ### method `func2md` ```python -func2md(func: Callable, clsname: str = '', depth: int = 3) → str +func2md( + func: Callable, + clsname: str = '', + depth: int = 3, + is_mdx: bool = False +) → str ``` -Takes a function (or method) and generates markdown docs. - +Takes a function (or method) and generates markdown docs. **Args:** - - - `func` (Callable): Selected function (or method) for markdown generation. - - `clsname` (str, optional): Class name to prepend to funcname. Defaults to "". - - `depth` (int, optional): Number of # to append to class name. Defaults to 3. +- `func` (Callable): Selected function (or method) for markdown generation. +- `clsname` (str, optional): Class name to prepend to funcname. Defaults to "". +- `depth` (int, optional): Number of # to append to class name. Defaults to 3. +- `is_mdx` (bool, optional): JSX support. Default to False. **Returns:** - - - `str`: Markdown documentation for selected function. + +- `str`: Markdown documentation for selected function. + --- - + ### method `import2md` ```python -import2md(obj: Any, depth: int = 1) → str +import2md( + obj: Any, + depth: int = 1, + is_mdx: bool = False, + include_toc: bool = False +) → str ``` -Generates markdown documentation for a selected object/import. - +Generates markdown documentation for a selected object/import. **Args:** - - - `obj` (Any): Selcted object for markdown docs generation. - - `depth` (int, optional): Number of # to append before heading. Defaults to 1. +- `obj` (Any): Selcted object for markdown docs generation. +- `depth` (int, optional): Number of # to append before heading. Defaults to 1. +- `is_mdx` (bool, optional): JSX support. Default to False. +- `include_toc` (bool, Optional): Include table of contents for module file. Defaults to False. **Returns:** - - - `str`: Markdown documentation of selected object. + +- `str`: Markdown documentation of selected object. + --- - + ### method `module2md` ```python -module2md(module: module, depth: int = 1) → str +module2md( + module: module, + depth: int = 1, + is_mdx: bool = False, + include_toc: bool = False +) → str ``` -Takes an imported module object and create a Markdown string containing functions and classes. - +Takes an imported module object and create a Markdown string containing functions and classes. **Args:** - - - `module` (types.ModuleType): Selected module for markdown generation. - - `depth` (int, optional): Number of # to append before module heading. Defaults to 1. +- `module` (types.ModuleType): Selected module for markdown generation. +- `depth` (int, optional): Number of # to append before module heading. Defaults to 1. +- `is_mdx` (bool, optional): JSX support. Default to False. +- `include_toc` (bool, optional): Include table of contents in module file. Defaults to False. **Returns:** - - - `str`: Markdown documentation for selected module. + +- `str`: Markdown documentation for selected module. + --- - + ### method `overview2md` ```python -overview2md() → str +overview2md(is_mdx: bool = False) → str +``` + +Generates a documentation overview file based on the generated docs. + + +**Args:** + +- `is_mdx` (bool, optional): JSX support. Default to False. + + +**Returns:** + +- `str`: Markdown documentation of overview file. + + +--- + + + +### method `toc2md` + +```python +toc2md(module: module = None, is_mdx: bool = False) → str ``` -Generates a documentation overview file based on the generated docs. +Generates table of contents for imported object. + diff --git a/src/lazydocs/_about.py b/src/lazydocs/_about.py index a7b66d7..1092556 100644 --- a/src/lazydocs/_about.py +++ b/src/lazydocs/_about.py @@ -1,5 +1,5 @@ """Information about this library. This file will automatically changed.""" -__version__ = "0.5.1" +__version__ = "0.6.0" # __author__ # __email__