From 8641b6dc4573321721543b5fb80a95071f498157 Mon Sep 17 00:00:00 2001 From: Zach Mueller Date: Thu, 8 Jun 2023 20:27:23 -0400 Subject: [PATCH] Enable semantic versioned sidebar (#6) --- docs/_quarto.yml | 6 +- docs/package_reference/processor.qmd | 5 +- docs/package_reference/processors.qmd | 49 ++++++-- nbs/package_reference/processors.ipynb | 34 ++++-- src/nbquarto/processors/__init__.py | 1 + .../processors/semantic_versioning.py | 109 ++++++++++++++++++ tests/test_process.py | 45 +++++++- 7 files changed, 220 insertions(+), 29 deletions(-) create mode 100644 src/nbquarto/processors/semantic_versioning.py diff --git a/docs/_quarto.yml b/docs/_quarto.yml index 6ccd59d..a1d8c9a 100755 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -5,11 +5,7 @@ project: website: sidebar: search: true - contents: - - index.qmd - - getting_started.qmd - - section: "Package Reference" - contents: package_reference/* + contents: auto margin-header: | ![](/logo.png) favicon: /logo.png diff --git a/docs/package_reference/processor.qmd b/docs/package_reference/processor.qmd index be4069b..eba8b3b 100755 --- a/docs/package_reference/processor.qmd +++ b/docs/package_reference/processor.qmd @@ -131,7 +131,7 @@ Processes notebook cells and comments in a notebook
#### `process_cell` {#nbquarto.processor.NotebookProcessor.process_cell} -[\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processor.py#L154){style="float:right;font-size:.875rem;"} +[\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processor.py#L155){style="float:right;font-size:.875rem;"}

(**`processor`**`: callable`, **`cell`**`: AttributeDictionary`)

@@ -151,7 +151,7 @@ explicitly and instead a user should use `process_notebook`
#### `process_notebook` {#nbquarto.processor.NotebookProcessor.process_notebook} -[\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processor.py#L172){style="float:right;font-size:.875rem;"} +[\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processor.py#L173){style="float:right;font-size:.875rem;"}

()

@@ -164,4 +164,3 @@ Processes the content of the notebook
- diff --git a/docs/package_reference/processors.qmd b/docs/package_reference/processors.qmd index 83e9b4e..b0c1e1e 100755 --- a/docs/package_reference/processors.qmd +++ b/docs/package_reference/processors.qmd @@ -22,7 +22,7 @@ Example usage: ```python #| process -def my_function(): +def my_function(): return "Hello world!" ``` @@ -36,10 +36,9 @@ Example outcome: def my_function(): return "Hello world!" ``` -
-### `class AutoDocProcessor` {#nbquarto.processors.autodoc.AutoDocProcessor} +### `class AutoDocProcessor` {#nbquarto.processors.AutoDocProcessor} [\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processors/autodoc.py#L250){style="float:right;font-size:.875rem;"}

(**`notebook`**, **`processor_args`**`: dict = {}`) @@ -48,7 +47,7 @@ def my_function():

A processor which will automatically generate API documentation for a given class or method. -Largely relies on the implementation in [hf-doc-builder](https://github.comn/huggingface/doc-builder), +Largely relies on the implementation in [hf-doc-builder](https://github.com/huggingface/doc-builder), while adding some customizations for Quarto. This processor expects the following directives: @@ -86,10 +85,9 @@ To expose all public methods and include special or hidden methods: Example outcome: (See the auto-generated docs that made this!) -
-### `class CodeNoteProcessor` {#nbquarto.processors.codenotes.CodeNoteProcessor} +### `class CodeNoteProcessor` {#nbquarto.processors.CodeNoteProcessor} [\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processors/codenotes.py#L124){style="float:right;font-size:.875rem;"}

(**`notebook`**`: AttributeDictionary`) @@ -139,8 +137,43 @@ def addition(a,b): ```{.python} addition(a,b) ``` -:::{style='padding-top: 0px;'} +::::{style='padding-top: 0px;'} This function adds two numbers together +:::: ::: -::: +

+ +### `class SemanticVersioningProcessor` {#nbquarto.processors.SemanticVersioningProcessor} +[\](https://github.com/muellerzr/nbquarto/blob/main/src/nbquarto/processors/semantic_versioning.py#L65){style="float:right;font-size:.875rem;"} +

+(**`notebook`**`: AttributeDictionary`) +

+ +
+ +A processor which will inject javascript into the top of the `qmd` or +notebook to enable semantic versioning of the documentation. + +Assumes your documentation structure is as follows: + +``` +- docs/ + - version_1 + - page_1.qmd + - page_2.qmd + - version_2 + - page_1.qmd + - page_2.qmd +``` + +From here, the sidebar will be populated based on the +current opened page and its semantic version. So if +you are on `/docs/version_1/page_1`, the sidebar will +hide all of `version_2`'s pages (and any others there may be), +and only show `version_1`. + +
+
+ +This processor also requires that your project is configured in a particular way. To read more be sure to check out the usage guide. **TO BE COMPLETED** \ No newline at end of file diff --git a/nbs/package_reference/processors.ipynb b/nbs/package_reference/processors.ipynb index a645ce2..2e9937a 100755 --- a/nbs/package_reference/processors.ipynb +++ b/nbs/package_reference/processors.ipynb @@ -36,7 +36,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#| autodoc nbquarto.processors.autodoc.AutoDocProcessor" + "#| autodoc nbquarto.processors.AutoDocProcessor" ] }, { @@ -54,15 +54,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#| autodoc nbquarto.processors.codenotes.CodeNoteProcessor" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Example outcome:" + "#| autodoc nbquarto.processors.CodeNoteProcessor" ] }, { @@ -70,6 +62,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "Example outcome:\n", + "\n", "::: {.panel-tabset}\n", "## Code\n", "```python\n", @@ -85,11 +79,27 @@ "```{.python}\n", "addition(a,b)\n", "```\n", - ":::{style='padding-top: 0px;'}\n", + "::::{style='padding-top: 0px;'}\n", "This function adds two numbers together\n", - ":::\n", + "::::\n", ":::\n" ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#| autodoc nbquarto.processors.SemanticVersioningProcessor" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This processor also requires that your project is configured in a particular way. To read more be sure to check out the usage guide. **TO BE COMPLETED**" + ] } ], "metadata": { diff --git a/src/nbquarto/processors/__init__.py b/src/nbquarto/processors/__init__.py index 7e4cb46..0f3220c 100755 --- a/src/nbquarto/processors/__init__.py +++ b/src/nbquarto/processors/__init__.py @@ -1,2 +1,3 @@ from .autodoc import AutoDocProcessor from .codenotes import CodeNoteProcessor +from .semantic_versioning import SemanticVersioningProcessor diff --git a/src/nbquarto/processors/semantic_versioning.py b/src/nbquarto/processors/semantic_versioning.py new file mode 100644 index 0000000..7a6cd2f --- /dev/null +++ b/src/nbquarto/processors/semantic_versioning.py @@ -0,0 +1,109 @@ +# Copyright 2023 Zachary Mueller. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from ..notebook import make_cell +from ..processor import Processor + + +logger = logging.getLogger(__name__) + +# This is the javascript that will be injected into the top of the notebook +# to enable semantic versioning of the documentation + +REFERENCE_JAVASCRIPT = """/** + * Enables semantic versioning through careful sidebar menu item selection. + * Hide sidebar menu items that are not related to the current page that is open. + * Assumes a directory structure of: + * - version_1 + * - page_1 + * - page_2 + * - version_2 + * - page_2 + * - page_3 + * + * If version_1 is open, then version_2 and it's pages will not be visible to the sidebar. + * These will also link to {url}/{version_num}/page_{num}. + * + * In the `_quarto.yml` sidebar *must* be set to `auto` for this to work. + */ +var all_versioned_menus = $(".sidebar-menu-container > .list-unstyled").children() +// Get the current url, which should be something like: /branch_name/{version_number}/{something} +// the latter parts after the version number are not important nor will be in there. +// eventually need to handle a special case when we use the latest stable version +var url = window.location.pathname.split("/")[1] +for (var versioned_menu of all_versioned_menus){ + // Check if the current url extension is in the sidebar menu + var active_sidebar = $(versioned_menu).find(`a[href*="${url}"]`)[0] + // If it is, wrap it in a div so it's easily recognizeable + if (active_sidebar !== undefined){ + $(active_sidebar).parent().parent().wrap("
") + } + // Else hide the additional menus + else { + versioned_menu.style.display = "none" + } +}""" + +# We inject it directly to the markdown so there doesn't have to be other random files we need to worry about +REFERENCE_JQUERY = '' +REFERENCE_JAVASCRIPT = f"" + + +class SemanticVersioningProcessor(Processor): + """ + A processor which will inject javascript into the top of the `qmd` or + notebook to enable semantic versioning of the documentation. + + Assumes your documentation structure is as follows: + + ``` + - docs/ + - version_1 + - page_1.qmd + - page_2.qmd + - version_2 + - page_1.qmd + - page_2.qmd + ``` + + From here, the sidebar will be populated based on the + current opened page and its semantic version. So if + you are on `/docs/version_1/page_1`, the sidebar will + hide all of `version_2`'s pages (and any others there may be), + and only show `version_1`. + """ + + cell_types = "markdown" + found_markdown_cell = False + markdown_cell_index = None + + def process(self, cell): + if self.is_first_markdown(cell): + # Create the new markdown cell + new_cell = make_cell("\n".join([REFERENCE_JQUERY, REFERENCE_JAVASCRIPT]), "markdown") + # Insert the new cell just after the first markdown cell + self.notebook.cells.insert(self.markdown_cell_index + 1, new_cell) + # Update the notebook order + for i, cell in enumerate(self.notebook.cells): + cell.index_ = i + + def is_first_markdown(self, cell): + if not self.found_markdown_cell: + self.found_markdown_cell = True + self.markdown_cell_index = cell["index_"] + return True + else: + return False diff --git a/tests/test_process.py b/tests/test_process.py index 34a080a..d835730 100755 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -2,7 +2,8 @@ from nbquarto.notebook import make_cell, new_notebook from nbquarto.processor import NotebookProcessor, Processor -from nbquarto.processors import AutoDocProcessor, CodeNoteProcessor +from nbquarto.processors import AutoDocProcessor, CodeNoteProcessor, SemanticVersioningProcessor +from nbquarto.processors.semantic_versioning import REFERENCE_JAVASCRIPT, REFERENCE_JQUERY class BasicProcessor(Processor): @@ -117,3 +118,45 @@ def test_codenotes(self): self.assertTrue( "Applies the processor to a cell if the cell is of the" in self.notebook_processor.notebook.cells[2].source ) + + +class TestSemanticVersioning(unittest.TestCase): + processor = SemanticVersioningProcessor + + def reset_cells(self): + test_cells = [ + make_cell("# Test Notebook", "markdown"), + make_cell("Here's some text!", "markdown"), + ] + self.test_notebooks = [ + new_notebook(cells=test_cells), + new_notebook(cells=[make_cell("print('Some code!')")] + test_cells), + ] + + def setUp(self): + self.reset_cells() + + def test_base_case(self): + "Checks if we can insert directly to the first cell" + self.notebook_processor = NotebookProcessor( + processors=[self.processor], + notebook=self.test_notebooks[0], + ) + self.notebook_processor.process_notebook() + self.assertEqual(self.notebook_processor.notebook.cells[0].source, "# Test Notebook") + self.assertEqual( + self.notebook_processor.notebook.cells[1].source, f"{REFERENCE_JQUERY}\n{REFERENCE_JAVASCRIPT}" + ) + + def test_around_code_cell(self): + "Checks that it can be inserted under a markdown cell after a code cell in the very start" + self.notebook_processor = NotebookProcessor( + processors=[self.processor], + notebook=self.test_notebooks[1], + ) + self.notebook_processor.process_notebook() + self.assertEqual(self.notebook_processor.notebook.cells[0].source, "print('Some code!')") + self.assertEqual(self.notebook_processor.notebook.cells[1].source, "# Test Notebook") + self.assertEqual( + self.notebook_processor.notebook.cells[2].source, f"{REFERENCE_JQUERY}\n{REFERENCE_JAVASCRIPT}" + )