Skip to content

Commit 334bdc1

Browse files
authored
Merge pull request #370 from gadenbuie/feat/sidebar-options
feat: Allow addings additional options to `sidebar`
2 parents 8d7ade2 + 07d4a0f commit 334bdc1

File tree

4 files changed

+169
-11
lines changed

4 files changed

+169
-11
lines changed

docs/get-started/sidebar.qmd

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,36 @@ quartodoc:
1919
```
2020

2121
Note that running `python -m quartodoc build` will now produce a file called `_sidebar.yml`,
22-
with a [quarto sidebar configuration](https://quarto.org/docs/websites/website-navigation.html#side-navigation).
23-
The quarto [`metadata-files` option](https://quarto.org/docs/projects/quarto-projects.html#metadata-includes) ensures
22+
with a [Quarto sidebar configuration](https://quarto.org/docs/websites/website-navigation.html#side-navigation).
23+
The Quarto [`metadata-files` option](https://quarto.org/docs/projects/quarto-projects.html#metadata-includes) ensures
2424
it's included with the configuration in `_quarto.yml`.
2525

2626
Here is what the sidebar for the [quartodoc reference page](/api) looks like:
2727

2828
<div class="sourceCode">
29-
<pre class="sourceCode yaml"><code class="sourceCode yaml">
30-
{{{< include /api/_sidebar.yml >}}}
29+
<pre class="sourceCode yaml"><code class="sourceCode yaml">{{< include /api/_sidebar.yml >}}
3130
</code></pre>
3231
</div>
32+
33+
## Customizing the sidebar
34+
35+
`sidebar` can also accept additional [sidebar options from the choices available in Quarto](https://quarto.org/docs/websites/website-navigation.html#side-navigation). These options are passed directly to Quarto, and can be used to customize the sidebar's appearance and behavior, or include additional content.
36+
37+
When using a dictionary for `sidebar`, use `file` to specify the sidebar file (defaults to `_quartodoc-sidebar.yml` if not provided). You can also provide additional content in the sidebar. Tell quartodoc where to include your package documentation in the sidebar with the `"{{ contents }}"` placeholder.
38+
39+
```yaml
40+
quartodoc:
41+
sidebar:
42+
file: "_sidebar.yml"
43+
style: docked
44+
search: true
45+
collapse-level: 2
46+
contents:
47+
- text: "Introduction"
48+
href: introduction.qmd
49+
- section: "Reference"
50+
contents:
51+
- "{{ contents }}"
52+
- text: "Basics"
53+
href: basics-summary.qmd
54+
```

quartodoc/autosummary.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,38 @@ def _is_valueless(obj: dc.Object):
399399
return False
400400

401401

402+
def _insert_contents(
403+
x: dict | list,
404+
contents: list,
405+
sentinel: str = "{{ contents }}",
406+
):
407+
"""Splice `contents` into a list.
408+
409+
Splices `contents` into the first element in `x` that exactly matches
410+
`sentinel`. This functions recurses into dictionaries, but because
411+
`contents` is a list, we only look within lists in `x` for the sentinel
412+
value where `contents` should be inserted.
413+
414+
Returns
415+
-------
416+
:
417+
Returns `True` if `contents` was inserted into `x`, otherwise returns
418+
`False`. Note that `x` is modified in place.
419+
"""
420+
if isinstance(x, dict):
421+
for value in x.values():
422+
if _insert_contents(value, contents):
423+
return True
424+
elif isinstance(x, list):
425+
for i, item in enumerate(x):
426+
if item == sentinel:
427+
x[i : i + 1] = contents # noqa: E203
428+
return True
429+
elif _insert_contents(item, contents):
430+
return True
431+
return False
432+
433+
402434
# pkgdown =====================================================================
403435

404436

@@ -426,7 +458,11 @@ class Builder:
426458
out_index:
427459
The output path of the index file, used to list all API functions.
428460
sidebar:
429-
The output path for a sidebar yaml config (by default no config generated).
461+
The output path for a sidebar yaml config (by default no config
462+
generated). Alternatively, can be a dictionary of [Quarto sidebar
463+
options](https://quarto.org/docs/websites/website-navigation.html#side-navigation)
464+
with an additional `file` key containing the output path for the sidebar
465+
YAML config file (by default `_quartodoc-sidebar.yml` if not specified).
430466
css:
431467
The output path for the default css styles.
432468
rewrite_all_pages:
@@ -487,7 +523,7 @@ def __init__(
487523
title: str = "Function reference",
488524
renderer: "dict | Renderer | str" = "markdown",
489525
out_index: str = None,
490-
sidebar: "str | None" = None,
526+
sidebar: "str | dict[str, Any] | None" = None,
491527
css: "str | None" = None,
492528
rewrite_all_pages=False,
493529
source_dir: "str | None" = None,
@@ -504,7 +540,13 @@ def __init__(
504540
self.version = None
505541
self.dir = dir
506542
self.title = title
507-
self.sidebar = sidebar
543+
544+
if isinstance(sidebar, str):
545+
sidebar = {"file": sidebar}
546+
elif isinstance(sidebar, dict) and "file" not in sidebar:
547+
sidebar["file"] = "_quartodoc-sidebar.yml"
548+
self.sidebar: "dict[str, Any] | None" = sidebar
549+
508550
self.css = css
509551
self.parser = parser
510552

@@ -588,7 +630,7 @@ def build(self, filter: str = "*"):
588630
# sidebar ----
589631

590632
if self.sidebar:
591-
_log.info(f"Writing sidebar yaml to {self.sidebar}")
633+
_log.info(f"Writing sidebar yaml to {self.sidebar['file']}")
592634
self.write_sidebar(blueprint)
593635

594636
# css ----
@@ -659,7 +701,9 @@ def create_inventory(self, items):
659701

660702
# sidebar ----
661703

662-
def _generate_sidebar(self, blueprint: layout.Layout):
704+
def _generate_sidebar(
705+
self, blueprint: layout.Layout, options: "dict | None" = None
706+
):
663707
contents = [f"{self.dir}/index{self.out_page_suffix}"]
664708
in_subsection = False
665709
crnt_entry = {}
@@ -686,14 +730,33 @@ def _generate_sidebar(self, blueprint: layout.Layout):
686730
if crnt_entry:
687731
contents.append(crnt_entry)
688732

689-
entries = [{"id": self.dir, "contents": contents}, {"id": "dummy-sidebar"}]
733+
# Create sidebar with user options, ensuring we control `id` and `contents`
734+
if self.sidebar is None:
735+
sidebar = {}
736+
else:
737+
sidebar = {k: v for k, v in self.sidebar.items() if k != "file"}
738+
739+
if "id" not in sidebar:
740+
sidebar["id"] = self.dir
741+
742+
if "contents" not in sidebar:
743+
sidebar["contents"] = contents
744+
else:
745+
if not isinstance(sidebar["contents"], list):
746+
raise TypeError("`sidebar.contents` must be a list")
747+
748+
if not _insert_contents(sidebar["contents"], contents):
749+
# otherwise append contents to existing list
750+
sidebar["contents"].extend(contents)
751+
752+
entries = [sidebar, {"id": "dummy-sidebar"}]
690753
return {"website": {"sidebar": entries}}
691754

692755
def write_sidebar(self, blueprint: layout.Layout):
693756
"""Write a yaml config file for API sidebar."""
694757

695758
d_sidebar = self._generate_sidebar(blueprint)
696-
yaml.dump(d_sidebar, open(self.sidebar, "w"))
759+
yaml.dump(d_sidebar, open(self.sidebar["file"], "w"))
697760

698761
def write_css(self):
699762
"""Write default css styles to a file."""

quartodoc/tests/__snapshots__/test_builder.ambr

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,30 @@
1818

1919
'''
2020
# ---
21+
# name: test_builder_generate_sidebar_options
22+
'''
23+
website:
24+
sidebar:
25+
- contents:
26+
- href: introduction.qmd
27+
text: Introduction
28+
- contents:
29+
- reference/index.qmd
30+
- contents:
31+
- reference/a_func.qmd
32+
section: first section
33+
- contents:
34+
- contents:
35+
- reference/a_attr.qmd
36+
section: a subsection
37+
section: second section
38+
section: Reference
39+
- href: basics-summary.qmd
40+
text: Basics
41+
id: reference
42+
search: true
43+
style: docked
44+
- id: dummy-sidebar
45+
46+
'''
47+
# ---

quartodoc/tests/test_builder.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,49 @@ def test_builder_generate_sidebar(tmp_path, snapshot):
8282
d_sidebar = builder._generate_sidebar(bp)
8383

8484
assert yaml.dump(d_sidebar) == snapshot
85+
86+
87+
def test_builder_generate_sidebar_options(tmp_path, snapshot):
88+
cfg = yaml.safe_load(
89+
"""
90+
quartodoc:
91+
package: quartodoc.tests.example
92+
sidebar:
93+
style: docked
94+
search: true
95+
contents:
96+
- text: "Introduction"
97+
href: introduction.qmd
98+
- section: "Reference"
99+
contents:
100+
- "{{ contents }}"
101+
- text: "Basics"
102+
href: basics-summary.qmd
103+
sections:
104+
- title: first section
105+
desc: some description
106+
contents: [a_func]
107+
- title: second section
108+
desc: title description
109+
- subtitle: a subsection
110+
desc: subtitle description
111+
contents:
112+
- a_attr
113+
"""
114+
)
115+
116+
builder = Builder.from_quarto_config(cfg)
117+
assert builder.sidebar["file"] == "_quartodoc-sidebar.yml" # default value
118+
119+
bp = blueprint(builder.layout)
120+
121+
d_sidebar = builder._generate_sidebar(bp)
122+
assert "website" in d_sidebar
123+
assert "sidebar" in d_sidebar["website"]
124+
125+
qd_sidebar = d_sidebar["website"]["sidebar"][0]
126+
assert "file" not in qd_sidebar
127+
assert qd_sidebar["style"] == "docked"
128+
assert qd_sidebar["search"]
129+
130+
assert yaml.dump(d_sidebar) == snapshot

0 commit comments

Comments
 (0)