diff --git a/site/Makefile b/site/Makefile index d46f551947..d944db545f 100644 --- a/site/Makefile +++ b/site/Makefile @@ -326,6 +326,8 @@ notebooks: @rm -rf $(DEST_DIR_NB)/templates @echo "Copying _metadata.yml into notebooks/ ..." @cp developer/_metadata.yml $(DEST_DIR_NB)/_metadata.yml + @echo "Updating developer/_sidebar.yaml with code_samples directories ..." + @python scripts/update_code_samples_sidebar.py @echo "Zip up notebooks.zip ..." @zip -r notebooks.zip $(DEST_DIR_NB) > /dev/null 2>&1 diff --git a/site/developer/_sidebar.yaml b/site/developer/_sidebar.yaml index 3cbeea5b45..3b1680ea69 100644 --- a/site/developer/_sidebar.yaml +++ b/site/developer/_sidebar.yaml @@ -53,7 +53,27 @@ website: - text: "Notebooks" - text: "Code samples" file: developer/samples-jupyter-notebooks.qmd - contents: "notebooks/code_samples/**" + contents: + - section: "Agents" + contents: "notebooks/code_samples/agents/**" + - section: "Capital markets" + contents: "notebooks/code_samples/capital_markets/**" + - section: "Code explainer" + contents: "notebooks/code_samples/code_explainer/**" + - section: "Credit risk" + contents: "notebooks/code_samples/credit_risk/**" + - section: "Custom tests" + contents: "notebooks/code_samples/custom_tests/**" + - section: "Model validation" + contents: "notebooks/code_samples/model_validation/**" + - section: "NLP and LLM" + contents: "notebooks/code_samples/nlp_and_llm/**" + - section: "Ongoing monitoring" + contents: "notebooks/code_samples/ongoing_monitoring/**" + - section: "Regression" + contents: "notebooks/code_samples/regression/**" + - section: "Time series" + contents: "notebooks/code_samples/time_series/**" - text: "---" - text: "Reference" - text: "{{< var validmind.api >}}" diff --git a/site/scripts/update_code_samples_sidebar.py b/site/scripts/update_code_samples_sidebar.py new file mode 100644 index 0000000000..c9d31fa5d3 --- /dev/null +++ b/site/scripts/update_code_samples_sidebar.py @@ -0,0 +1,94 @@ +# Copyright © 2023-2026 ValidMind Inc. All rights reserved. +# Refer to the LICENSE file in the root of this repository for details. +# SPDX-License-Identifier: AGPL-3.0 AND ValidMind Commercial +""" +Update developer/_sidebar.yaml so the Code samples entry lists each +subdirectory of notebooks/code_samples/ explicitly (with wildcards), +in alphabetical order, with fixed capitalization for NLP and LLM. + +Run from the site/ directory, e.g.: + cd site && python scripts/update_code_samples_sidebar.py +""" + +import os +from pathlib import Path + + +# Display title for directories that need fixed capitalization (e.g. acronyms) +SPECIAL_TITLES = { + "nlp_and_llm": "NLP and LLM", +} + + +def dir_to_title(dirname: str) -> str: + """Convert directory name to sidebar display title (sentence-style capitalization).""" + if dirname in SPECIAL_TITLES: + return SPECIAL_TITLES[dirname] + return dirname.replace("_", " ").capitalize() + + +def main() -> None: + # Run from site/ or repo root + cwd = Path.cwd() + if (cwd / "notebooks" / "code_samples").is_dir(): + base = cwd + elif (cwd / "site" / "notebooks" / "code_samples").is_dir(): + base = cwd / "site" + else: + raise SystemExit("Run from site/ or repo root (e.g. cd site && python scripts/update_code_samples_sidebar.py)") + code_samples = base / "notebooks" / "code_samples" + sidebar_path = base / "developer" / "_sidebar.yaml" + + if not code_samples.is_dir(): + raise SystemExit(f"Directory not found: {code_samples}") + if not sidebar_path.is_file(): + raise SystemExit(f"Sidebar file not found: {sidebar_path}") + + subdirs = sorted( + d for d in os.listdir(code_samples) + if (code_samples / d).is_dir() + ) + + # Build the new contents block (YAML). Use "section" so Quarto renders + # expandable accordion items; "text" alone does not expand. + lines = [ + ' contents:', + ] + for d in subdirs: + title = dir_to_title(d) + lines.append(f' - section: "{title}"') + lines.append(f' contents: "notebooks/code_samples/{d}/**"') + + new_block = "\n".join(lines) + + text = sidebar_path.read_text() + # Replace either the single wildcard or an existing expanded block + old_single = ' contents: "notebooks/code_samples/**"' + if old_single in text: + text = text.replace(old_single, new_block, 1) + else: + # Find the code_samples contents block and replace it (multi-line) + import re + # Match from " contents:" through all following lines that are + # " - ..." or " contents: ..." for code_samples + pattern = re.compile( + r'( - text: "Code samples"\n' + r' file: developer/samples-jupyter-notebooks\.qmd\n)' + r' contents:\n' + r'( - (?:text|section): "[^"]+"\n' + r' contents: "notebooks/code_samples/[^"]+\*\*"\n)*', + re.MULTILINE, + ) + match = pattern.search(text) + if not match: + raise SystemExit( + "Could not find Code samples contents block in sidebar. " + "Has the sidebar format changed?" + ) + text = text[: match.start()] + match.group(1) + new_block + "\n" + text[match.end() :] + sidebar_path.write_text(text) + print(f"Updated {sidebar_path} with {len(subdirs)} code sample directories.") + + +if __name__ == "__main__": + main()