Skip to content

Commit b6a650b

Browse files
committed
Add table of contents feature to module file
1 parent 5e938e5 commit b6a650b

File tree

3 files changed

+44
-6
lines changed

3 files changed

+44
-6
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ lazydocs [OPTIONS] PATHS...
178178
* `--validate / --no-validate`: If `True`, validate the docstrings via pydocstyle. Requires pydocstyle to be installed. [default: False]
179179
* `--output-format TEXT`: The output format for the creation of the markdown files. This may be 'md' or 'mdx'. Defaults to md.
180180
* `--private-modules / --no-private-modules`: If `True`, includes modules with "_" prefix. [default: False]
181+
* `--toc / --no-toc`: If `True`, includes table of contents in generated module markdown files. [default: False]
181182
* `--install-completion`: Install completion for the current shell.
182183
* `--show-completion`: Show completion for the current shell, to copy it or customize the installation.
183184
* `--help`: Show this message and exit.

src/lazydocs/_cli.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ def generate(
5050
False,
5151
help="If `True`, all packages starting with `_` will be included.",
5252
),
53+
toc: bool = typer.Option(
54+
False,
55+
help="Include table of contents in module file. Defaults to False.",
56+
),
5357
) -> None:
5458
"""Generates markdown documentation for your Python project based on Google-style docstrings."""
5559

@@ -65,6 +69,7 @@ def generate(
6569
watermark=watermark,
6670
validate=validate,
6771
private_modules=private_modules,
72+
include_toc=toc,
6873
)
6974
except Exception as ex:
7075
typer.echo(str(ex))

src/lazydocs/generation.py

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@
6262
"""
6363

6464

65+
_TOC_TEMPLATE = """
66+
## Table of Contents
67+
{toc}
68+
"""
69+
6570
_CLASS_TEMPLATE = """
6671
{section} <kbd>class</kbd> `{header}`
6772
{doc}
@@ -74,6 +79,7 @@
7479
_MODULE_TEMPLATE = """
7580
{section} <kbd>module</kbd> `{header}`
7681
{doc}
82+
{toc}
7783
{global_vars}
7884
{functions}
7985
{classes}
@@ -883,13 +889,14 @@ def class2md(self, cls: Any, depth: int = 2, is_mdx: bool = False) -> str:
883889

884890
return markdown
885891

886-
def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = False) -> str:
892+
def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = False, include_toc: bool = False) -> str:
887893
"""Takes an imported module object and create a Markdown string containing functions and classes.
888894
889895
Args:
890896
module (types.ModuleType): Selected module for markdown generation.
891897
depth (int, optional): Number of # to append before module heading. Defaults to 1.
892898
is_mdx (bool, optional): JSX support. Default to False.
899+
include_toc (bool, optional): Include table of contents in module file. Defaults to False.
893900
894901
Returns:
895902
str: Markdown documentation for selected module.
@@ -965,10 +972,13 @@ def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = Fal
965972
new_list = ["\n**Global Variables**", "---------------", *variables]
966973
variables = new_list
967974

975+
toc = self.toc2md(module=module, is_mdx=is_mdx) if include_toc else ""
976+
968977
markdown = _MODULE_TEMPLATE.format(
969978
header=modname,
970979
section="#" * depth,
971980
doc=doc,
981+
toc=toc,
972982
global_vars="\n".join(variables) if variables else "",
973983
functions="\n".join(functions) if functions else "",
974984
classes="".join(classes) if classes else "",
@@ -982,21 +992,22 @@ def module2md(self, module: types.ModuleType, depth: int = 1, is_mdx: bool = Fal
982992

983993
return markdown
984994

985-
def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False) -> str:
995+
def import2md(self, obj: Any, depth: int = 1, is_mdx: bool = False, include_toc: bool = False) -> str:
986996
"""Generates markdown documentation for a selected object/import.
987997
988998
Args:
989999
obj (Any): Selcted object for markdown docs generation.
9901000
depth (int, optional): Number of # to append before heading. Defaults to 1.
9911001
is_mdx (bool, optional): JSX support. Default to False.
1002+
include_toc(bool, Optional): Include table of contents for module file. Defaults to False.
9921003
9931004
Returns:
9941005
str: Markdown documentation of selected object.
9951006
"""
9961007
if inspect.isclass(obj):
9971008
return self.class2md(obj, depth=depth, is_mdx=is_mdx)
9981009
elif isinstance(obj, types.ModuleType):
999-
return self.module2md(obj, depth=depth, is_mdx=is_mdx)
1010+
return self.module2md(obj, depth=depth, is_mdx=is_mdx, include_toc=include_toc)
10001011
elif callable(obj):
10011012
return self.func2md(obj, depth=depth, is_mdx=is_mdx)
10021013
else:
@@ -1072,6 +1083,26 @@ def overview2md(self, is_mdx: bool = False) -> str:
10721083
modules=modules_md, classes=classes_md, functions=functions_md
10731084
)
10741085

1086+
def toc2md(self, module: types.ModuleType = None, is_mdx: bool = False) -> str:
1087+
"""Generates table of contents for imported object."""
1088+
toc = []
1089+
for obj in self.generated_objects:
1090+
if module and (module.__name__ != obj["module"] or obj["type"] == "module"):
1091+
continue
1092+
# module_name = obj["module"].split(".")[-1]
1093+
full_name = obj["full_name"]
1094+
name = obj["name"]
1095+
if is_mdx:
1096+
link = "./" + obj["module"] + ".mdx#" + obj["anchor_tag"]
1097+
else:
1098+
link = "./" + obj["module"] + ".md#" + obj["anchor_tag"]
1099+
line = f"- [`{name}`]({link})"
1100+
depth = max(len(full_name.split(".")) - 1, 0)
1101+
if depth:
1102+
line = "\t" * depth + line
1103+
toc.append(line)
1104+
return _TOC_TEMPLATE.format(toc="\n".join(toc))
1105+
10751106

10761107
def generate_docs(
10771108
paths: List[str],
@@ -1085,6 +1116,7 @@ def generate_docs(
10851116
watermark: bool = True,
10861117
validate: bool = False,
10871118
private_modules: bool = False,
1119+
include_toc: bool = False,
10881120
) -> None:
10891121
"""Generates markdown documentation for provided paths based on Google-style docstrings.
10901122
@@ -1166,7 +1198,7 @@ def generate_docs(
11661198
except AttributeError:
11671199
# For older python version compatibility
11681200
mod = loader.find_module(module_name).load_module(module_name) # type: ignore
1169-
module_md = generator.module2md(mod, is_mdx=is_mdx)
1201+
module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc)
11701202
if not module_md:
11711203
# Module md is empty -> ignore module and all submodules
11721204
# Add module to ignore list, so submodule will also be ignored
@@ -1205,7 +1237,7 @@ def generate_docs(
12051237
spec.loader.exec_module(mod) # type: ignore
12061238

12071239
if mod:
1208-
module_md = generator.module2md(mod, is_mdx=is_mdx)
1240+
module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc)
12091241
if stdout_mode:
12101242
print(module_md)
12111243
else:
@@ -1250,7 +1282,7 @@ def generate_docs(
12501282
except AttributeError:
12511283
# For older python version compatibility
12521284
mod = loader.find_module(module_name).load_module(module_name) # type: ignore
1253-
module_md = generator.module2md(mod, is_mdx=is_mdx)
1285+
module_md = generator.module2md(mod, is_mdx=is_mdx, include_toc=include_toc)
12541286

12551287
if not module_md:
12561288
# Module MD is empty -> ignore module and all submodules

0 commit comments

Comments
 (0)