From 3888169fecb99392c9cfa35fa9a1cbd70a328f69 Mon Sep 17 00:00:00 2001 From: "Benjamin R. J. Schwedler" Date: Fri, 24 Apr 2026 15:18:24 -0500 Subject: [PATCH] Add inline_methods config for method page splitting Control whether class methods get their own pages or stay inline on the class page via the inline_methods setting in great-docs.yml: inline_methods: true # always inline (never split) inline_methods: false # always split to separate pages inline_methods: 10 # inline up to 10, split above (default: 5) Replaces three hardcoded threshold = 5 checks with a single config-driven method on GreatDocsConfig. --- great_docs/config.py | 27 +++++++++++++++++++++++++++ great_docs/core.py | 17 ++++------------- tests/test_config.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/great_docs/config.py b/great_docs/config.py index c1dfaea..b76f8cb 100644 --- a/great_docs/config.py +++ b/great_docs/config.py @@ -94,6 +94,11 @@ # API Reference configuration (explicit section ordering) # If not provided, auto-generates sections from discovered exports "reference": [], + # Control whether class methods get their own pages or stay inline. + # true: always inline methods on the class page (never split) + # false: always give methods their own pages (always split) + # int: inline up to N methods, split above N (default: 5) + "inline_methods": 5, # Logo configuration # str: path to a single logo file (used for all contexts) # dict: {"light": "...", "dark": "...", "alt": "...", "height": "...", "href": "...", "show_title": False} @@ -789,6 +794,28 @@ def reference_desc(self) -> str | None: return val.get("desc") return None + def should_split_methods(self, method_count: int) -> bool: + """Whether a class with this many methods should split them to separate pages. + + Controlled by `inline_methods` in great-docs.yml: + - true: never split (always inline) + - false: always split + - int N: split when method_count > N (default: 5) + + Items with no methods are never split regardless of the setting. + """ + if method_count == 0: + return False + val = self.get("inline_methods", 5) + if val is True: + return False + if val is False: + return True + try: + return method_count > int(val) + except (TypeError, ValueError): + return method_count > 5 + @property def authors(self) -> list[dict[str, Any]]: """Get the rich author metadata.""" diff --git a/great_docs/core.py b/great_docs/core.py index b5da846..74b84bb 100644 --- a/great_docs/core.py +++ b/great_docs/core.py @@ -6925,9 +6925,6 @@ def _create_api_sections(self, package_name: str) -> list | None: def _t(key: str, fallback: str) -> str: return get_translation(key, lang) if lang != "en" else fallback - # Use static threshold of 5 methods for large class separation - method_threshold = 5 - # ── Helper: build a class section with big-class splitting ──────── def _make_class_section(title: str, desc: str, class_names: list) -> list[dict]: """Return section dicts (possibly with a Methods companion section).""" @@ -6939,7 +6936,7 @@ def _make_class_section(title: str, desc: str, class_names: list) -> list[dict]: for class_name in class_names: method_count = categories["class_methods"].get(class_name, 0) - if method_count > method_threshold: + if self._config.should_split_methods(method_count): class_contents.append({"name": class_name, "members": []}) separate_methods.append(class_name) else: @@ -7306,9 +7303,6 @@ def _create_api_sections_from_config(self, package_name: str) -> list | None: mod_prefix = qualified.split(".")[0] module_members.setdefault(mod_prefix, []).append(qualified) - # Use the same big-class threshold as auto-discovery - method_threshold = 5 - sections = [] for section_config in reference_config: @@ -7331,7 +7325,7 @@ def _create_api_sections_from_config(self, package_name: str) -> list | None: # This is a module name — expand into its individual members for member in module_members[item]: method_count = categories["class_methods"].get(member, 0) - if method_count > method_threshold: + if self._config.should_split_methods(method_count): section_contents.append( {"name": member, "members": []} ) # pragma: no cover @@ -7341,7 +7335,7 @@ def _create_api_sections_from_config(self, package_name: str) -> list | None: else: # Regular item (class, function, etc.) — use as-is method_count = categories["class_methods"].get(item, 0) - if method_count > method_threshold: + if self._config.should_split_methods(method_count): section_contents.append({"name": item, "members": []}) large_classes_in_section.append(item) else: @@ -8044,9 +8038,6 @@ def _generate_config_with_reference( class_methods = categories.get("class_methods", {}) class_method_names = categories.get("class_method_names", {}) - # Use static threshold of 5 methods for large class separation - threshold = 5 - # Track large classes that need separate method sections large_classes: list[str] = [] @@ -8072,7 +8063,7 @@ def _generate_config_with_reference( lines.append(" contents:") for class_name in sorted(items): method_count = class_methods.get(class_name, 0) - if method_count > threshold: + if self._config.should_split_methods(method_count): lines.append(f" - name: {class_name}") lines.append(f" members: false # {method_count} methods listed below") large_classes.append(class_name) diff --git a/tests/test_config.py b/tests/test_config.py index 0da908b..beff636 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -839,3 +839,42 @@ def test_tags_index_page_false_when_disabled(tmp_path): """tags_index_page returns False when tags disabled.""" cfg = Config(tmp_path) assert cfg.tags_index_page is False + + +# ── inline_methods / should_split_methods ───────────────────────────── + + +class TestShouldSplitMethods: + def test_default_splits_above_5(self, tmp_project: Path): + cfg = Config(tmp_project) + assert cfg.should_split_methods(5) is False + assert cfg.should_split_methods(6) is True + + def test_true_never_splits(self, tmp_project: Path): + cfg = _make_config(tmp_project, "inline_methods: true\n") + assert cfg.should_split_methods(0) is False + assert cfg.should_split_methods(100) is False + + def test_false_always_splits(self, tmp_project: Path): + cfg = _make_config(tmp_project, "inline_methods: false\n") + assert cfg.should_split_methods(1) is True + assert cfg.should_split_methods(100) is True + + def test_false_does_not_split_zero_methods(self, tmp_project: Path): + cfg = _make_config(tmp_project, "inline_methods: false\n") + assert cfg.should_split_methods(0) is False + + def test_custom_threshold(self, tmp_project: Path): + cfg = _make_config(tmp_project, "inline_methods: 10\n") + assert cfg.should_split_methods(10) is False + assert cfg.should_split_methods(11) is True + + def test_invalid_value_falls_back_to_default(self, tmp_project: Path): + cfg = _make_config(tmp_project, 'inline_methods: "abc"\n') + assert cfg.should_split_methods(5) is False + assert cfg.should_split_methods(6) is True + + def test_null_falls_back_to_default(self, tmp_project: Path): + cfg = _make_config(tmp_project, "inline_methods: null\n") + assert cfg.should_split_methods(5) is False + assert cfg.should_split_methods(6) is True