Skip to content

plugin

mkdocs_terok.plugin

ProperDocs plugin that wraps mkdocs-terok generators into a single terok plugin.

Adds File.generated() entries for CI maps, quality reports, test maps, module maps, and API reference pages — eliminating the need for mkdocs-gen-files shim scripts. Asset injection (CSS / JS) is handled automatically via on_config.

TerokPlugin

Bases: BasePlugin[TerokPluginConfig]

ProperDocs plugin that drives mkdocs-terok generators.

Source code in src/mkdocs_terok/plugin.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
class TerokPlugin(BasePlugin[TerokPluginConfig]):
    """ProperDocs plugin that drives mkdocs-terok generators."""

    def on_config(self, config: ProperDocsConfig) -> ProperDocsConfig:
        """Inject brand CSS and Mermaid zoom JS into the site configuration."""
        css_uri = "_assets/extra.css"
        js_uri = "_assets/mermaid_zoom.js"

        if self.config.inject_css and css_uri not in config.extra_css:
            config.extra_css.append(css_uri)

        if self.config.inject_js and js_uri not in [
            s if isinstance(s, str) else getattr(s, "path", s) for s in config.extra_javascript
        ]:
            config.extra_javascript.append(js_uri)

        return config

    def on_files(self, files: Files, /, *, config: ProperDocsConfig) -> Files:
        """Generate virtual files for each enabled generator.

        When [`INVENTORY_ONLY_ENV`][mkdocs_terok.INVENTORY_ONLY_ENV]
        is set, the four generators that don't feed ``objects.inv``
        (``ci_map``, ``code_metrics``, ``test_map``, ``module_map``)
        are skipped, so a stripped-down ``poetry install --only main,docs``
        environment can still produce the inventory without ``pytest``,
        ``scc``, ``vulture``, etc. on PATH.  ``ref_pages`` always runs
        (when enabled in user config) because every inventory entry
        comes from an mkdocstrings render of those pages.
        """
        if self.config.inject_css:
            files.append(
                File.generated(config, "_assets/extra.css", abs_src_path=str(brand_css_path()))
            )
        if self.config.inject_js:
            files.append(
                File.generated(
                    config, "_assets/mermaid_zoom.js", abs_src_path=str(mermaid_zoom_js_path())
                )
            )

        inventory_only = os.environ.get(INVENTORY_ONLY_ENV) == "1"

        if not inventory_only:
            if self.config.ci_map:
                self._generate_ci_map(files, config)
            if self.config.code_metrics:
                self._generate_code_metrics(files, config)
            if self.config.test_map:
                self._generate_test_map(files, config)
            if self.config.module_map:
                self._generate_module_map(files, config)

        if self.config.ref_pages:
            self._generate_ref_pages(files, config)

        return files

    # -- private generators -------------------------------------------------

    def _generate_ci_map(self, files: Files, config: ProperDocsConfig) -> None:
        """Emit a virtual CI map page from GitHub Actions workflows."""
        from mkdocs_terok.ci_map import generate_ci_map

        markdown = generate_ci_map()
        files.append(File.generated(config, self.config.ci_map_path, content=markdown))
        log.info(_LOG_GENERATED, self.config.ci_map_path)

    def _generate_code_metrics(self, files: Files, config: ProperDocsConfig) -> None:
        """Emit quality report page and companion files (e.g. treemap SVGs)."""
        from mkdocs_terok.code_metrics import CodeMetricsConfig, generate_code_metrics

        coverage_json_path = (
            Path(self.config.code_metrics_coverage_json_path)
            if self.config.code_metrics_coverage_json_path
            else None
        )
        qr_config = CodeMetricsConfig(
            complexity_threshold=self.config.code_metrics_complexity_threshold,
            graph_depth=self.config.code_metrics_graph_depth,
            vulture_min_confidence=self.config.code_metrics_vulture_min_confidence,
            file_level_loc=self.config.code_metrics_file_level_loc,
            include_layer_overview=self.config.code_metrics_include_layer_overview,
            include_graph_coarsening=self.config.code_metrics_include_graph_coarsening,
            coverage_json_path=coverage_json_path,
            treemap_group_depth=self.config.code_metrics_treemap_group_depth,
            codecov_repo=self.config.code_metrics_codecov_repo,
            src_label=self.config.code_metrics_src_label,
            tests_label=self.config.code_metrics_tests_label,
        )
        result = generate_code_metrics(qr_config)
        report_path = self.config.code_metrics_path
        files.append(File.generated(config, report_path, content=result.markdown))

        # Place companion files (e.g. treemap SVG) as siblings of the rendered page.
        # The generator references them by bare filename; the plugin places them so
        # that the bare name resolves correctly regardless of use_directory_urls.
        report_posix = PurePosixPath(report_path)
        for name, content in result.companion_files.items():
            if config.use_directory_urls and report_posix.stem != "index":
                companion_base = report_posix.with_suffix("")
            else:
                companion_base = report_posix.parent
            files.append(File.generated(config, str(companion_base / name), content=content))

        log.info(_LOG_GENERATED, report_path)

    def _generate_test_map(self, files: Files, config: ProperDocsConfig) -> None:
        """Emit a virtual test map page from pytest collection."""
        from mkdocs_terok.test_map import TestMapConfig, generate_test_map

        integration_dir = (
            Path(self.config.test_map_integration_dir)
            if self.config.test_map_integration_dir
            else None
        )
        tm_config = TestMapConfig(
            show_markers=self.config.test_map_show_markers,
            title=self.config.test_map_title,
            integration_dir=integration_dir,
        )
        markdown = generate_test_map(config=tm_config)
        files.append(File.generated(config, self.config.test_map_path, content=markdown))
        log.info(_LOG_GENERATED, self.config.test_map_path)

    def _generate_module_map(self, files: Files, config: ProperDocsConfig) -> None:
        """Emit a virtual module map page from source docstrings."""
        from mkdocs_terok.module_map import ModuleMapConfig, generate_module_map

        mm_config = ModuleMapConfig(title=self.config.module_map_title)
        markdown = generate_module_map(mm_config)
        files.append(File.generated(config, self.config.module_map_path, content=markdown))
        log.info(_LOG_GENERATED, self.config.module_map_path)

    def _generate_ref_pages(self, files: Files, config: ProperDocsConfig) -> None:
        """Emit API reference stubs and a literate-nav SUMMARY.md."""
        from mkdocs_terok.ref_pages import RefPagesConfig, generate_ref_pages

        output_prefix = self.config.ref_pages_path.rstrip("/")
        rp_config = RefPagesConfig(
            skip_patterns=tuple(self.config.ref_pages_skip_patterns),
            output_prefix=output_prefix,
        )

        def write_file(doc_path: str, content: str) -> None:
            """Callback that appends a generated File to the files collection."""
            files.append(File.generated(config, doc_path, content=content))

        entries = generate_ref_pages(
            rp_config,
            write_file=write_file,
            set_edit_path=lambda _doc, _src: None,
        )

        # Build literate-nav SUMMARY.md for the reference tree
        prefix = rp_config.output_prefix + "/"
        nav_lines = _build_literate_nav(entries, prefix)
        summary_path = f"{rp_config.output_prefix}/SUMMARY.md"
        files.append(File.generated(config, summary_path, content="".join(nav_lines)))
        log.info("Generated %d reference pages", len(entries))

on_config(config)

Inject brand CSS and Mermaid zoom JS into the site configuration.

Source code in src/mkdocs_terok/plugin.py
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def on_config(self, config: ProperDocsConfig) -> ProperDocsConfig:
    """Inject brand CSS and Mermaid zoom JS into the site configuration."""
    css_uri = "_assets/extra.css"
    js_uri = "_assets/mermaid_zoom.js"

    if self.config.inject_css and css_uri not in config.extra_css:
        config.extra_css.append(css_uri)

    if self.config.inject_js and js_uri not in [
        s if isinstance(s, str) else getattr(s, "path", s) for s in config.extra_javascript
    ]:
        config.extra_javascript.append(js_uri)

    return config

on_files(files, /, *, config)

Generate virtual files for each enabled generator.

When INVENTORY_ONLY_ENV is set, the four generators that don't feed objects.inv (ci_map, code_metrics, test_map, module_map) are skipped, so a stripped-down poetry install --only main,docs environment can still produce the inventory without pytest, scc, vulture, etc. on PATH. ref_pages always runs (when enabled in user config) because every inventory entry comes from an mkdocstrings render of those pages.

Source code in src/mkdocs_terok/plugin.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
def on_files(self, files: Files, /, *, config: ProperDocsConfig) -> Files:
    """Generate virtual files for each enabled generator.

    When [`INVENTORY_ONLY_ENV`][mkdocs_terok.INVENTORY_ONLY_ENV]
    is set, the four generators that don't feed ``objects.inv``
    (``ci_map``, ``code_metrics``, ``test_map``, ``module_map``)
    are skipped, so a stripped-down ``poetry install --only main,docs``
    environment can still produce the inventory without ``pytest``,
    ``scc``, ``vulture``, etc. on PATH.  ``ref_pages`` always runs
    (when enabled in user config) because every inventory entry
    comes from an mkdocstrings render of those pages.
    """
    if self.config.inject_css:
        files.append(
            File.generated(config, "_assets/extra.css", abs_src_path=str(brand_css_path()))
        )
    if self.config.inject_js:
        files.append(
            File.generated(
                config, "_assets/mermaid_zoom.js", abs_src_path=str(mermaid_zoom_js_path())
            )
        )

    inventory_only = os.environ.get(INVENTORY_ONLY_ENV) == "1"

    if not inventory_only:
        if self.config.ci_map:
            self._generate_ci_map(files, config)
        if self.config.code_metrics:
            self._generate_code_metrics(files, config)
        if self.config.test_map:
            self._generate_test_map(files, config)
        if self.config.module_map:
            self._generate_module_map(files, config)

    if self.config.ref_pages:
        self._generate_ref_pages(files, config)

    return files

TerokPluginConfig

Bases: Config

Typed ProperDocs configuration for the terok plugin.

Source code in src/mkdocs_terok/plugin.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class TerokPluginConfig(Config):
    """Typed ProperDocs configuration for the ``terok`` plugin."""

    # Asset injection
    inject_css = c.Type(bool, default=True)
    inject_js = c.Type(bool, default=True)

    # CI map
    ci_map = c.Type(bool, default=False)
    ci_map_path = c.Type(str, default="ci-map.md")

    # Quality report
    code_metrics = c.Type(bool, default=False)
    code_metrics_path = c.Type(str, default="code-metrics.md")
    code_metrics_complexity_threshold = c.Type(int, default=15)
    code_metrics_graph_depth = c.Type(int, default=3)
    code_metrics_vulture_min_confidence = c.Type(int, default=80)
    code_metrics_file_level_loc = c.Type(bool, default=True)
    code_metrics_include_layer_overview = c.Type(bool, default=False)
    code_metrics_include_graph_coarsening = c.Type(bool, default=False)
    code_metrics_coverage_json_path = c.Optional(c.Type(str))
    code_metrics_treemap_group_depth = c.Type(int, default=3)
    code_metrics_codecov_repo = c.Type(str, default="")
    code_metrics_src_label = c.Type(str, default="Source")
    code_metrics_tests_label = c.Type(str, default="Tests")

    # Test map
    test_map = c.Type(bool, default=False)
    test_map_path = c.Type(str, default="test-map.md")
    test_map_show_markers = c.Type(bool, default=True)
    test_map_title = c.Type(str, default="Integration Test Map")
    test_map_integration_dir = c.Optional(c.Type(str))

    # Module map
    module_map = c.Type(bool, default=False)
    module_map_path = c.Type(str, default="module-map.md")
    module_map_title = c.Type(str, default="Module Map")

    # Reference pages
    ref_pages = c.Type(bool, default=False)
    ref_pages_path = c.Type(str, default="reference")
    ref_pages_skip_patterns = c.ListOfItems(c.Type(str), default=["__main__", "resources"])