Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions great_docs/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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."""
Expand Down
17 changes: 4 additions & 13 deletions great_docs/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)."""
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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] = []

Expand All @@ -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)
Expand Down
39 changes: 39 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading