Skip to content

Commit b798de3

Browse files
authored
Merge pull request #187 from machow/feat-subtitles
feat: add subtitle option to section
2 parents bb33500 + 45d05d4 commit b798de3

File tree

12 files changed

+259
-77
lines changed

12 files changed

+259
-77
lines changed

docs/_quarto.yml

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -131,38 +131,33 @@ quartodoc:
131131
- create_inventory
132132
- convert_inventory
133133

134-
- title: "Data models: structural"
134+
- title: "Data models"
135+
136+
- subtitle: "Structural"
135137
desc: |
136138
Classes for specifying the broad structure your docs.
137139
contents:
138-
- kind: "page"
139-
path: "layouts-structure"
140-
flatten: true
141-
contents:
142-
- layout.Layout
143-
- layout.Section
144-
- layout.Page
145-
- layout.SectionElement
146-
- layout.ContentElement
140+
- layout.Layout
141+
- layout.Section
142+
- layout.Page
143+
- layout.SectionElement
144+
- layout.ContentElement
147145

148-
- title: "Data models: docable"
146+
- subtitle: "Docable"
149147
desc: |
150148
Classes representing python objects to be rendered.
151149
contents:
152-
- kind: "page"
153-
path: "layouts-docable"
154-
flatten: true
155-
contents:
156-
- name: layout.Doc
157-
members: []
158-
- layout.DocFunction
159-
- layout.DocAttribute
160-
- layout.DocModule
161-
- layout.DocClass
162-
- layout.Link
163-
- layout.Item
164-
- layout.ChoicesChildren
165-
- title: "Data models: docstring patches"
150+
- name: layout.Doc
151+
members: []
152+
- layout.DocFunction
153+
- layout.DocAttribute
154+
- layout.DocModule
155+
- layout.DocClass
156+
- layout.Link
157+
- layout.Item
158+
- layout.ChoicesChildren
159+
160+
- subtitle: "Docstring patches"
166161
desc: |
167162
Most of the classes for representing python objects live
168163
in [](`griffe.dataclasses`) or [](`griffe.docstrings.dataclasses`).

docs/get-started/basic-content.qmd

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,36 @@ quartodoc:
9595
# set the children option, so that methods get documented
9696
# on separate pages. MdRenderer's docs will include a summary
9797
# table that links to each page.
98-
- name: quartodoc.MdRenderer
98+
- name: MdRenderer
9999
children: separate
100100
```
101101

102+
## Reference page sub-sections
103+
104+
quartodoc supports two levels of grouping on the reference page.
105+
Use the `subtitle:` option to add an additional level of grouping.
106+
107+
For example, the code below creates an API reference page with one top-level section ("Some section"),
108+
with two sub-sections inside it.
109+
110+
111+
```yaml
112+
quartodoc:
113+
package: quartodoc
114+
sections:
115+
- title: Some section
116+
117+
- subtitle: Stuff A
118+
desc: This is subsection A
119+
contents:
120+
- MdRenderer
121+
122+
- subtitle: Stuff B
123+
desc: This is subsection B
124+
contents:
125+
- get_object
126+
```
127+
102128
## Grouping on a page
103129

104130
By default, content in each section gets included in the same index table,

docs/get-started/basic-docs.qmd

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ print(renderer.render(doc_params))
4646

4747
The `sections` field defines which functions to document.
4848

49-
It requires three pieces of configuration:
49+
It commonly requires three pieces of configuration:
5050

5151
* `title`: a title for the section
5252
* `desc`: a description for the section
5353
* `contents`: a list of functions to document
5454

55+
You can also replace `title` with `subtitle` to create a sub-section.

docs/styles.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,29 @@ html { font-family: 'Inter', sans-serif; }
3636
line-height: 22px;
3737
}
3838

39+
.sidebar-item {
40+
margin-top: 0px;
41+
}
42+
3943
.sidebar-item-section {
4044
padding-top: 16px;
4145
}
4246

4347
.sidebar-section {
4448
padding-left: 0px !important;
4549
}
50+
51+
.sidebar-item-section .sidebar-item-section {
52+
padding-top: 0px;
53+
padding-left: 10px;
54+
}
55+
56+
57+
td:first-child {
58+
text-wrap: nowrap;
59+
text-size-adjust: 100%;
60+
}
61+
62+
td:first-child a {
63+
word-break: normal;
64+
}

quartodoc/autosummary.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,30 @@ def create_inventory(self, items):
572572

573573
def _generate_sidebar(self, blueprint: layout.Layout):
574574
contents = [f"{self.dir}/index{self.out_page_suffix}"]
575+
in_subsection = False
576+
crnt_entry = {}
575577
for section in blueprint.sections:
578+
if section.title:
579+
if crnt_entry:
580+
contents.append(crnt_entry)
581+
582+
in_subsection = False
583+
crnt_entry = {"section": section.title, "contents": []}
584+
elif section.subtitle:
585+
in_subsection = True
586+
576587
links = []
577588
for entry in section.contents:
578589
links.extend(self._page_to_links(entry))
579590

580-
contents.append({"section": section.title, "contents": links})
591+
if in_subsection:
592+
sub_entry = {"section": section.subtitle, "contents": links}
593+
crnt_entry["contents"].append(sub_entry)
594+
else:
595+
crnt_entry["contents"].extend(links)
596+
597+
if crnt_entry:
598+
contents.append(crnt_entry)
581599

582600
entries = [{"id": self.dir, "contents": contents}, {"id": "dummy-sidebar"}]
583601
return {"website": {"sidebar": entries}}

quartodoc/layout.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ class Section(_Structural):
6161
kind:
6262
title:
6363
Title of the section on the index.
64+
subtitle:
65+
Subtitle of the section on the index. Note that either title or subtitle,
66+
but not both, may be set.
6467
desc:
6568
Description of the section on the index.
6669
package:
@@ -70,10 +73,22 @@ class Section(_Structural):
7073
"""
7174

7275
kind: Literal["section"] = "section"
73-
title: str
74-
desc: str
76+
title: Optional[str] = None
77+
subtitle: Optional[str] = None
78+
desc: Optional[str] = None
7579
package: Union[str, None, MISSING] = MISSING()
76-
contents: ContentList
80+
contents: ContentList = []
81+
82+
def __init__(self, **data):
83+
super().__init__(**data)
84+
85+
# TODO: should these be a custom type? Or can we use pydantic's ValidationError?
86+
if self.title is None and self.subtitle is None and not self.contents:
87+
raise ValueError(
88+
"Section must specify a title, subtitle, or contents field"
89+
)
90+
elif self.title is not None and self.subtitle is not None:
91+
raise ValueError("Section cannot specify both title and subtitle fields.")
7792

7893

7994
class SummaryDetails(_Base):

quartodoc/renderers/md_renderer.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,16 +547,23 @@ def summarize(self, el: layout.Layout):
547547

548548
@dispatch
549549
def summarize(self, el: layout.Section):
550-
header = f"## {el.title}\n\n{el.desc}"
550+
desc = f"\n\n{el.desc}" if el.desc is not None else ""
551+
if el.title is not None:
552+
header = f"## {el.title}{desc}"
553+
elif el.subtitle is not None:
554+
header = f"### {el.subtitle}{desc}"
551555

552-
thead = "| | |\n| --- | --- |"
556+
if el.contents:
557+
thead = "| | |\n| --- | --- |"
553558

554-
rendered = []
555-
for child in el.contents:
556-
rendered.append(self.summarize(child))
559+
rendered = []
560+
for child in el.contents:
561+
rendered.append(self.summarize(child))
557562

558-
str_func_table = "\n".join([thead, *rendered])
559-
return f"{header}\n\n{str_func_table}"
563+
str_func_table = "\n".join([thead, *rendered])
564+
return f"{header}\n\n{str_func_table}"
565+
566+
return header
560567

561568
@dispatch
562569
def summarize(self, el: layout.Page):
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# serializer version: 1
2+
# name: test_builder_generate_sidebar
3+
'''
4+
website:
5+
sidebar:
6+
- contents:
7+
- reference/index.qmd
8+
- contents:
9+
- reference/a_func.qmd
10+
section: first section
11+
- contents:
12+
- contents:
13+
- reference/a_attr.qmd
14+
section: a subsection
15+
section: second section
16+
id: reference
17+
- id: dummy-sidebar
18+
19+
'''
20+
# ---

quartodoc/tests/test_builder.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import pytest
2+
import yaml
23

34
from pathlib import Path
45
from quartodoc import layout as lo
5-
from quartodoc import Builder
6+
from quartodoc import Builder, blueprint
67

78

89
@pytest.fixture
@@ -40,3 +41,28 @@ def test_builder_build_filter_wildcard_methods(builder):
4041
builder.build(filter="MdRenderer.*")
4142

4243
len(list(Path(builder.dir).glob("Mdrenderer.*"))) == 2
44+
45+
46+
def test_builder_generate_sidebar(tmp_path, snapshot):
47+
cfg = yaml.safe_load(
48+
"""
49+
quartodoc:
50+
package: quartodoc.tests.example
51+
sections:
52+
- title: first section
53+
desc: some description
54+
contents: [a_func]
55+
- title: second section
56+
desc: title description
57+
- subtitle: a subsection
58+
desc: subtitle description
59+
contents:
60+
- a_attr
61+
"""
62+
)
63+
64+
builder = Builder.from_quarto_config(cfg)
65+
bp = blueprint(builder.layout)
66+
d_sidebar = builder._generate_sidebar(bp)
67+
68+
assert yaml.dump(d_sidebar) == snapshot

quartodoc/tests/test_layout.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ def test_layout_from_config(cfg, res):
3030
assert layout.sections[0] == res
3131

3232

33+
@pytest.mark.parametrize(
34+
"kwargs, msg_part",
35+
[
36+
({}, "must specify a title, subtitle, or contents field"),
37+
({"title": "x", "subtitle": "y"}, "cannot specify both"),
38+
],
39+
)
40+
def test_section_validation_fails(kwargs, msg_part):
41+
with pytest.raises(ValueError) as exc_info:
42+
Section(**kwargs)
43+
44+
assert msg_part in exc_info.value.args[0]
45+
46+
3347
def test_layout_extra_forbidden():
3448
with pytest.raises(ValidationError) as exc_info:
3549
Section(title="abc", desc="xyz", contents=[], zzzzz=1)

0 commit comments

Comments
 (0)