diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c39aa38a..a998b6343 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,31 @@ repos: -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: "v5.0.0" hooks: - - id: trailing-whitespace + - id: check-builtin-literals + - id: check-case-conflict + - id: check-toml + - id: check-yaml + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.7 + hooks: + - id: ruff-format + exclude: examples + - id: ruff + args: ["--fix", "--show-fixes"] + + - repo: https://github.com/rbubley/mirrors-prettier + rev: "v3.4.2" + hooks: + - id: prettier + types_or: [yaml, markdown, html, css, scss, javascript, json] + + - repo: https://github.com/abravalheri/validate-pyproject + rev: "v0.23" + hooks: + - id: validate-pyproject + additional_dependencies: ["validate-pyproject-schema-store[all]"] diff --git a/Makefile b/Makefile index dba1164f0..8cf13b9e9 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: docs clean +make.PHONY: docs clean SPHINXOPTS = diff --git a/docs/_scripts/prep_docs.py b/docs/_scripts/prep_docs.py index 1098567d1..bc502f407 100644 --- a/docs/_scripts/prep_docs.py +++ b/docs/_scripts/prep_docs.py @@ -1,39 +1,70 @@ """ALL pre-rendering and pre-preparation of docs should occur in this file. +This script is called **before** Sphinx builds the documentation. + Note: make no assumptions about the working directory from which this script will be called. """ + +import os import sys -from pathlib import Path from importlib.metadata import version +from pathlib import Path from packaging.version import parse +from scripts_logger import setup_logger + +logger = setup_logger(__name__) + +# Set up paths to docs and npe2 docs source DOCS = Path(__file__).parent.parent.absolute() -NPE = DOCS.parent.absolute() / 'npe2' +logger.debug(f"DOCS: {DOCS}") +NPE = DOCS.parent.absolute() / "npe2" +logger.debug(f"NPE: {NPE}") + def prep_npe2(): - # some plugin docs live in npe2 for testing purposes + """Preps the npe2 plugin engine prior to Sphinx docs build. + + Some plugin-related docs live in the npe2 repo to simplify + plugin testing. + """ + logger.debug("Preparing npe2 plugin") + # Checks if the path to npe2 repo exist. If so, bail. if NPE.exists(): + logger.debug("NPE2 plugin already present") return from subprocess import check_call npe2_version = version("npe2") - + logger.debug(f"npe2 version: {npe2_version}") check_call(f"rm -rf {NPE}".split()) + logger.debug("removing NPE directory succeeded") check_call(f"git clone https://github.com/napari/npe2 {NPE}".split()) + if not parse(npe2_version).is_devrelease: check_call(f"git checkout tags/v{npe2_version}".split(), cwd=NPE) - check_call([sys.executable, f"{NPE}/_docs/render.py", DOCS / 'plugins']) + + check_call([sys.executable, f"{NPE}/_docs/render.py", DOCS / "plugins"]) check_call(f"rm -rf {NPE}".split()) def main(): prep_npe2() - __import__('update_preference_docs').main() - __import__('update_event_docs').main() - __import__('update_ui_sections_docs').main() + logger.debug("Prep npe2 complete") + __import__("update_preference_docs").main() + logger.debug("update_preference_docs succeeded") + __import__("update_event_docs").main() + logger.debug("update_event_docs succeeded") + __import__("update_ui_sections_docs").main() + logger.debug("update_ui_sections_docs succeeded") if __name__ == "__main__": + # Example usage within a script + current_script_name = os.path.basename(__file__) + # Get the name of the current script + logger = setup_logger(current_script_name) + main() diff --git a/docs/_scripts/scripts_logger.py b/docs/_scripts/scripts_logger.py new file mode 100644 index 000000000..8ba3a4923 --- /dev/null +++ b/docs/_scripts/scripts_logger.py @@ -0,0 +1,59 @@ +"""Create a logger for a directory of scripts to aid in debugging.""" + +import logging +import os +import sys + + +def setup_logger(script_name, log_directory="logs"): + """Sets up a logger for a specific script. + + Args: + script_name (str): The name of the script (e.g., "my_script.py"). + log_directory (str, optional): The directory to store log files. Defaults to "logs". + + Returns: + logging.Logger: A configured logger instance. + """ + # Create log directory if it doesn't exist + if not os.path.exists(log_directory): + os.makedirs(log_directory) + + # Extract the script name without the extension + script_name_no_ext = os.path.splitext(script_name)[0] + + # Create a logger + logger = logging.getLogger(script_name_no_ext) + logger.setLevel(logging.DEBUG) # Set the minimum logging level + + # Create a file handler + # log_file_path = os.path.join(log_directory, f"{script_name_no_ext}.log") + # file_handler = logging.FileHandler(log_file_path) + # file_handler.setLevel(logging.DEBUG) + + handler = logging.StreamHandler(sys.stdout) + + # Create a formatter + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) + # file_handler.setFormatter(formatter) + handler.setFormatter(formatter) + + # Add the file handler to the logger + # logger.addHandler(file_handler) + logger.addHandler(handler) + return logger + + +if __name__ == "__main__": + # Example usage within a script + current_script_name = os.path.basename(__file__) + # Get the name of the current script + logger = setup_logger(current_script_name) + + logger.debug("This is a debug message.") + logger.info("This is an info message.") + logger.warning("This is a warning message.") + logger.error("This is an error message.") + logger.critical("This is a critical message.") diff --git a/docs/_scripts/update_event_docs.py b/docs/_scripts/update_event_docs.py index 572c5469c..5e1f40b15 100644 --- a/docs/_scripts/update_event_docs.py +++ b/docs/_scripts/update_event_docs.py @@ -1,5 +1,6 @@ import ast import inspect +import os from dataclasses import dataclass from pathlib import Path from types import ModuleType @@ -15,6 +16,10 @@ from napari.components.viewer_model import ViewerModel from napari.utils.events import EventedModel +from scripts_logger import setup_logger + +logger = setup_logger(__name__) + DOCS = Path(__file__).parent.parent @@ -28,49 +33,50 @@ class Ev: def access_at(self): """Where this event can be accessed (in code)""" if issubclass(self.model, layers.Layer): - return f'layer.events.{self.name}' + return f"layer.events.{self.name}" if issubclass(self.model, LayerList): - if self.name.startswith('selection.'): - return f'layers.selection.events.{self.name[10:]}' - return f'layers.events.{self.name}' + if self.name.startswith("selection."): + return f"layers.selection.events.{self.name[10:]}" + return f"layers.events.{self.name}" if issubclass(self.model, ViewerModel): - return f'viewer.events.{self.name}' + return f"viewer.events.{self.name}" for name, field_ in napari.Viewer.__fields__.items(): if field_.type_ is self.model: - return f'viewer.{name}.events.{self.name}' - return '' + return f"viewer.{name}.events.{self.name}" + return "" def type_name(self): - if cls_name := getattr(self.type_, '__name__', None): + if cls_name := getattr(self.type_, "__name__", None): return cls_name - name = str(self.type_) if self.type_ else '' + name = str(self.type_) if self.type_ else "" return name.replace("typing.", "") def ev_model_row(self) -> List[str]: return [ - f'`{self.model.__name__}`', - f'`{self.name}`', - f'`{self.access_at()}`', - self.description or '', - f'`{self.type_name()}`', + f"`{self.model.__name__}`", + f"`{self.name}`", + f"`{self.access_at()}`", + self.description or "", + f"`{self.type_name()}`", ] def layer_row(self) -> List[str]: return [ - f'`{self.model.__name__}`', - f'`{self.name}`', - f'`{self.access_at()}`', - self.description or '', - '', + f"`{self.model.__name__}`", + f"`{self.name}`", + f"`{self.access_at()}`", + self.description or "", + "", ] def walk_modules( - module: ModuleType, pkg='napari', _walked=None + module: ModuleType, pkg="napari", _walked=None ) -> Iterator[ModuleType]: """walk all modules in pkg, starting with `module`.""" + logger.debug(f"walking {pkg}") if not _walked: _walked = set() yield module @@ -87,6 +93,7 @@ def walk_modules( def iter_classes(module: ModuleType) -> Iterator[Type]: """iter all classes in module""" + logger.debug(f"walking {module}") for name in dir(module): attr = getattr(module, name) if inspect.isclass(attr) and attr.__module__ == module.__name__: @@ -94,14 +101,14 @@ def iter_classes(module: ModuleType) -> Iterator[Type]: def class_doc_attrs(kls: Type) -> Dict[str, Parameter]: - docs = {p.name: " ".join(p.desc) for p in ClassDoc(kls).get('Attributes')} - docs.update( - {p.name: " ".join(p.desc) for p in ClassDoc(kls).get('Parameters')} - ) + logger.debug(f"walking {kls}") + docs = {p.name: " ".join(p.desc) for p in ClassDoc(kls).get("Attributes")} + docs.update({p.name: " ".join(p.desc) for p in ClassDoc(kls).get("Parameters")}) return docs def iter_evented_model_events(module: ModuleType = napari) -> Iterator[Ev]: + logger.debug(f"walking evented model events {module}") for mod in walk_modules(module): for kls in iter_classes(mod): if not issubclass(kls, EventedModel): @@ -110,17 +117,14 @@ def iter_evented_model_events(module: ModuleType = napari) -> Iterator[Ev]: for name, field_ in kls.__fields__.items(): finfo = field_.field_info if finfo.allow_mutation: - descr = ( - f"{finfo.title.lower()}" - if finfo.title - else docs.get(name) - ) + descr = f"{finfo.title.lower()}" if finfo.title else docs.get(name) yield Ev(name, kls, descr, field_.type_) def iter_evented_container_events( module: ModuleType = napari, container_class=LayerList ) -> Iterator[Ev]: + logger.debug(f"walking evented container events {module}") for mod in walk_modules(module): for kls in iter_classes(mod): if not issubclass(kls, container_class): @@ -130,13 +134,13 @@ def iter_evented_container_events( for name, emitter in kls_instance.events._emitters.items(): descr = docs.get(name) yield Ev(name, kls, descr, type_=None) - if hasattr(kls_instance, 'selection'): + if hasattr(kls_instance, "selection"): selection = kls_instance.selection for name, emitter in selection.events._emitters.items(): - if name.startswith('_'): + if name.startswith("_"): # skip private emitters continue - name = 'selection.' + name + name = "selection." + name descr = docs.get(name) yield Ev(name, kls, descr, type_=None) @@ -147,11 +151,12 @@ def __init__(self) -> None: self._emitters: List[str] = [] def visit_Call(self, node: ast.Call): - if getattr(node.func, 'id', None) == 'EmitterGroup': + if getattr(node.func, "id", None) == "EmitterGroup": self._emitters.extend([name.arg for name in node.keywords]) # type: ignore def base_event_names() -> List[str]: + logger.debug("walking base event names") from napari.layers.base import base root = ast.parse(Path(base.__file__).read_text()) @@ -161,6 +166,7 @@ def base_event_names() -> List[str]: def iter_layer_events() -> Iterator[Ev]: + logger.debug("walking layer events") basenames = base_event_names() docs = class_doc_attrs(layers.Layer) for name in basenames: @@ -194,10 +200,11 @@ def iter_layer_events() -> Iterator[Ev]: def merge_image_and_label_rows(rows: List[List[str]]): """Merge events common to _ImageBase or IntensityVisualizationMixin.""" + logger.debug(f"merging {len(rows)} rows") # find events that are common across both Image, Labels and Surface layers. - image_events = {r[1] for r in rows if r[0] == '`Image`'} - labels_events = {r[1] for r in rows if r[0] == '`Labels`'} - surface_events = {r[1] for r in rows if r[0] == '`Surface`'} + image_events = {r[1] for r in rows if r[0] == "`Image`"} + labels_events = {r[1] for r in rows if r[0] == "`Labels`"} + surface_events = {r[1] for r in rows if r[0] == "`Surface`"} common_events = image_events & labels_events & surface_events # common only to Image and Labels imagebase_events = (image_events & labels_events) - common_events @@ -206,24 +213,24 @@ def merge_image_and_label_rows(rows: List[List[str]]): rows = [ r for r in rows - if not (r[0] in ['`Labels`', '`Surface`'] and r[1] in common_events) + if not (r[0] in ["`Labels`", "`Surface`"] and r[1] in common_events) ] rows = [ r for r in rows - if not (r[0] in ['`Labels`', '`Surface`'] and r[1] in imagebase_events) + if not (r[0] in ["`Labels`", "`Surface`"] and r[1] in imagebase_events) ] # modify the class name of the Image entries to mention Labels, Surface rows = [ - ['`Image`, `Labels`'] + r[1:] - if r[0] == '`Image`' and r[1] in imagebase_events + ["`Image`, `Labels`"] + r[1:] + if r[0] == "`Image`" and r[1] in imagebase_events else r for r in rows ] rows = [ - ['`Image`, `Labels`, `Surface`'] + r[1:] - if r[0] == '`Image`' and r[1] in common_events + ["`Image`, `Labels`, `Surface`"] + r[1:] + if r[0] == "`Image`" and r[1] in common_events else r for r in rows ] @@ -232,45 +239,43 @@ def merge_image_and_label_rows(rows: List[List[str]]): def main(): HEADER = [ - 'Event', - 'Description', - 'Event.value type', + "Event", + "Description", + "Event.value type", ] # Do viewer events rows = [ - ev.ev_model_row()[2:] - for ev in iter_evented_model_events() - if ev.access_at() + ev.ev_model_row()[2:] for ev in iter_evented_model_events() if ev.access_at() ] table1 = table_repr(rows, padding=2, header=HEADER, divide_rows=False) - (DOCS / 'guides' / '_viewer_events.md').write_text(table1) + (DOCS / "guides" / "_viewer_events.md").write_text(table1) # Do LayerList events rows = [ ev.layer_row()[2:] - for ev in iter_evented_container_events( - napari, container_class=LayerList - ) + for ev in iter_evented_container_events(napari, container_class=LayerList) if ev.access_at() ] table2 = table_repr(rows, padding=2, header=HEADER, divide_rows=False) - (DOCS / 'guides' / '_layerlist_events.md').write_text(table2) + (DOCS / "guides" / "_layerlist_events.md").write_text(table2) # Do layer events HEADER = [ - 'Class', - 'Event', - 'Description', - 'Event.value type', - ] - rows = [ - [ev.layer_row()[0]] + ev.layer_row()[2:] for ev in iter_layer_events() + "Class", + "Event", + "Description", + "Event.value type", ] + rows = [[ev.layer_row()[0]] + ev.layer_row()[2:] for ev in iter_layer_events()] rows = merge_image_and_label_rows(rows) table3 = table_repr(rows, padding=2, header=HEADER, divide_rows=False) - (DOCS / 'guides' / '_layer_events.md').write_text(table3) + (DOCS / "guides" / "_layer_events.md").write_text(table3) -if __name__ == '__main__': +if __name__ == "__main__": + # Example usage within a script + current_script_name = os.path.basename(__file__) + # Get the name of the current script + logger = setup_logger(current_script_name) main() diff --git a/docs/_scripts/update_preference_docs.py b/docs/_scripts/update_preference_docs.py index ef60c7fef..5c15c16a5 100644 --- a/docs/_scripts/update_preference_docs.py +++ b/docs/_scripts/update_preference_docs.py @@ -1,15 +1,20 @@ +import os from pathlib import Path from jinja2 import Template -from napari._pydantic_compat import ModelMetaclass from qtpy.QtCore import QTimer from qtpy.QtWidgets import QMessageBox from napari._qt.dialogs.preferences_dialog import PreferencesDialog from napari._qt.qt_event_loop import get_qapp from napari._qt.qt_resources import get_stylesheet +from napari._pydantic_compat import ModelMetaclass from napari.settings import NapariSettings +from scripts_logger import setup_logger + +logger = setup_logger(__name__) + DOCS = REPO_ROOT_PATH = Path(__file__).resolve().parent.parent GUIDES_PATH = DOCS / "guides" IMAGES_PATH = DOCS / "images" / "_autogenerated" @@ -98,7 +103,7 @@ def generate_images(): Generate images from `CORE_SETTINGS`. and save them in the developer section of the docs. """ - + logger.debug("Generating images") app = get_qapp() pref = PreferencesDialog() pref.setStyleSheet(get_stylesheet("dark")) @@ -131,10 +136,10 @@ def grab(): def create_preferences_docs(): """Create preferences docs from SETTINGS using a jinja template.""" + logger.debug("Creating preferences docs") sections = {} for name, field in NapariSettings.__fields__.items(): - if not isinstance(field.type_, ModelMetaclass): continue @@ -142,7 +147,7 @@ def create_preferences_docs(): title = field.field_info.title or name sections[title.lower()] = { "title": title, - "description": field.field_info.description or '', + "description": field.field_info.description or "", "fields": [ { "field": n, @@ -150,10 +155,10 @@ def create_preferences_docs(): "description": f.field_info.description, "default": repr(f.get_default()), "ui": n not in excluded, - "type": repr(f._type_display()).replace('.typing', ''), + "type": repr(f._type_display()).replace(".typing", ""), } for n, f in sorted(field.type_.__fields__.items()) - if n not in ('schema_version') + if n not in ("schema_version") ], } @@ -176,4 +181,9 @@ def main(): if __name__ == "__main__": + # Example usage within a script + current_script_name = os.path.basename(__file__) + # Get the name of the current script + logger = setup_logger(current_script_name) + main() diff --git a/docs/_scripts/update_ui_sections_docs.py b/docs/_scripts/update_ui_sections_docs.py index 9fd8f6bb9..a5ab07246 100644 --- a/docs/_scripts/update_ui_sections_docs.py +++ b/docs/_scripts/update_ui_sections_docs.py @@ -1,21 +1,26 @@ # ---- Standard library imports import json +import os from pathlib import Path +# ---- Third-party imports +import seedir as sd +from pydeps import cli, colors, dot, py2depgraph +from pydeps.pydeps import depgraph_to_dotsrc +from pydeps.target import Target + # ---- Napari imports +from napari._qt import dialogs +from napari._qt import qt_viewer from napari._qt.containers import qt_layer_list from napari._qt.layer_controls import qt_layer_controls_container -from napari._qt.widgets import qt_viewer_status_bar from napari._qt._qapp_model import qactions -from napari._qt import qt_viewer -from napari._qt import dialogs +from napari._qt.widgets import qt_viewer_status_bar from napari_console import qt_console -# ---- Third-party imports -from pydeps import cli, colors, dot, py2depgraph -from pydeps.pydeps import depgraph_to_dotsrc -from pydeps.target import Target -import seedir as sd +from scripts_logger import setup_logger + +logger = setup_logger(__name__) # ---- General constants # Docs paths @@ -32,6 +37,8 @@ DIALOGS_MODULE_PATH = Path(dialogs.__file__).parent CONSOLE_MODULE_PATH = Path(qt_console.__file__).parent +logger.debug("paths successfully set in update ui sections") + # ---- Utility functions def generate_dependencies_graph(options): @@ -206,13 +213,8 @@ def generate_mermaid_diagram( if "imports" in dependency: dep_imports = dependency["imports"] for dep_import_name in dep_imports: - if ( - dep_import_name != dep_name - and dep_import_name not in dep_name - ): - mermaid_diagram_content += ( - f"\t{dep_name} --> {dep_import_name}\n" - ) + if dep_import_name != dep_name and dep_import_name not in dep_name: + mermaid_diagram_content += f"\t{dep_name} --> {dep_import_name}\n" if graph_urls_prefix and dependency["path"]: module_path = Path(dependency["path"]) # Check if module is outside napari, like @@ -222,9 +224,7 @@ def generate_mermaid_diagram( NAPARI_ROOT_DIRECTORY_PATH ).as_posix() module_url = f"{graph_urls_prefix}{module_relative_path}" - mermaid_diagram_content += ( - f'\tclick {dep_name} "{module_url}" _blank\n' - ) + mermaid_diagram_content += f'\tclick {dep_name} "{module_url}" _blank\n' dep_name_parent = ".".join(dep_name.split(".")[:-1]) if dep_name_parent not in subgraphs: subgraphs[dep_name_parent] = [dep_name] @@ -234,18 +234,14 @@ def generate_mermaid_diagram( external_nodes.append(dep_name) for subgraph_key, subgraph_value in subgraphs.items(): - mermaid_diagram_content += ( - f"\tsubgraph module.{subgraph_key}[{subgraph_key}]\n" - ) + mermaid_diagram_content += f"\tsubgraph module.{subgraph_key}[{subgraph_key}]\n" for dep_subgraph_name in subgraph_value: mermaid_diagram_content += f"\t\t {dep_subgraph_name}\n" mermaid_diagram_content += "\tend\n" mermaid_diagram_content += f"\tclass module.{subgraph_key} subgraphs\n" if external_nodes: - mermaid_diagram_content += ( - "\tsubgraph module.external[external]\n" - ) + mermaid_diagram_content += "\tsubgraph module.external[external]\n" for external_node in external_nodes: mermaid_diagram_content += f"\t\t {external_node}\n" mermaid_diagram_content += "\tend\n" @@ -256,17 +252,11 @@ def generate_mermaid_diagram( "\tclassDef subgraphs fill:white,strock:black,color:black;" ) if graph_node_default_style: - mermaid_diagram_content += ( - f"\tclassDef default {graph_node_default_style}\n" - ) + mermaid_diagram_content += f"\tclassDef default {graph_node_default_style}\n" if graph_link_default_style: - mermaid_diagram_content += ( - f"\tlinkStyle default {graph_link_default_style}\n" - ) + mermaid_diagram_content += f"\tlinkStyle default {graph_link_default_style}\n" if graph_node_external_style: - mermaid_diagram_content += ( - f"\tclassDef external {graph_node_external_style}\n" - ) + mermaid_diagram_content += f"\tclassDef external {graph_node_external_style}\n" for external_dep in external_nodes: mermaid_diagram_content += f"\tclass {external_dep} external\n" @@ -310,7 +300,9 @@ def generate_docs_ui_section_page( page_content = f"## {section_name}\n" page_content += "### Dependencies diagram (related `napari` modules)\n" page_content += mermaid_diagram - page_content += "### Source code directory layout (related to modules inside `napari`)\n" + page_content += ( + "### Source code directory layout (related to modules inside `napari`)\n" + ) page_content += directory_layout if output_file: output_file.parent.mkdir(exist_ok=True, parents=True) @@ -388,7 +380,10 @@ def generate_docs_ui_section( # ---- Main and UI sections parameters def main(): - # General 'settings' + logger.debug("Empty ui sections list created") + ui_sections = [] + + # --- mermaid settings mermaid_graph_base_settings = { "graph_orientation": "LR", "graph_node_default_style": "fill:#00c3ff,color:black;", @@ -396,9 +391,8 @@ def main(): "graph_link_default_style": "stroke:#00c3ff", "graph_urls_prefix": "https://github.com/napari/napari/tree/main/napari/", } - ui_sections = [] - # ---- Layer list section parameters + # --- Layer list section parameters layer_list_section_name = "Layers list" layer_list_output_page = UI_SECTIONS_DOCS_ROOT_PATH / "layers_list_ui.md" layer_list_pydeps_args = [ @@ -433,6 +427,8 @@ def main(): "--show-deps", "--no-output", ] + + logger.debug("adding layer list to ui section") ui_sections.append( ( layer_list_section_name, @@ -443,10 +439,9 @@ def main(): ) # ---- Layer controls section parameters + logger.debug("start layer controls") layer_controls_section_name = "Layers controls" - layer_controls_output_page = ( - UI_SECTIONS_DOCS_ROOT_PATH / "layers_controls_ui.md" - ) + layer_controls_output_page = UI_SECTIONS_DOCS_ROOT_PATH / "layers_controls_ui.md" layer_controls_pydeps_args = [ f"{LAYER_CONTROLS_MODULE_PATH}", "--exclude", @@ -475,6 +470,8 @@ def main(): "--show-deps", "--no-output", ] + + logger.debug("adding layer controls to ui section") ui_sections.append( ( layer_controls_section_name, @@ -514,6 +511,7 @@ def main(): "--show-deps", "--no-output", ] + logger.debug("adding application status bar to ui section") ui_sections.append( ( application_status_bar_section_name, @@ -565,6 +563,7 @@ def main(): "--show-deps", "--no-output", ] + logger.debug("adding application menus to ui section") ui_sections.append( ( application_menus_section_name, @@ -606,6 +605,7 @@ def main(): "--show-deps", "--no-output", ] + logger.debug("adding viewer to ui section") ui_sections.append( ( viewer_section_name, @@ -660,6 +660,7 @@ def main(): "--show-deps", "--no-output", ] + logger.debug("adding dialogs to ui section") ui_sections.append( ( dialogs_section_name, @@ -687,6 +688,7 @@ def main(): "--show-deps", "--no-output", ] + logger.debug("adding console to ui section") ui_sections.append( ( console_section_name, @@ -696,6 +698,8 @@ def main(): ) ) + logger.debug("getting ready to iterate over sections") + logger.debug(f"ui sections {ui_sections}") for ( section_name, output_page, @@ -711,5 +715,9 @@ def main(): if __name__ == "__main__": - # ---- Call main + # Example usage within a script + current_script_name = os.path.basename(__file__) + # Get the name of the current script + logger = setup_logger(current_script_name) + main() diff --git a/docs/_static/custom.css b/docs/_static/custom.css index ecd8fabcf..d4c258b44 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -1,3 +1,5 @@ +@import url("https://fonts.googleapis.com/css2?family=Barlow:wght@400;500;600;700&display=swap"); + /* Sphinx-Gallery has compatible CSS to fix default sphinx themes Tested for Sphinx 1.3.1 for all themes: default, alabaster, sphinxdoc, @@ -6,7 +8,11 @@ Tested for Read the Docs theme 0.1.7 */ div.sphx-glr-download a { background-color: rgb(255, 255, 255) !important; - background-image: linear-gradient(to bottom, rgb(255, 255, 255), #ffffff) !important; + background-image: linear-gradient( + to bottom, + rgb(255, 255, 255), + #ffffff + ) !important; border-radius: 4px; border: 1px solid #ffffff !important; color: #000; @@ -17,7 +23,8 @@ div.sphx-glr-download a { } div.sphx-glr-download a:hover { - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 5px rgba(0, 0, 0, 0.25); text-decoration: none; background-image: none; @@ -25,18 +32,26 @@ div.sphx-glr-download a:hover { } /* Workaround for https: //github.com/napari/docs/pull/423#issuecomment-2141165872 */ -.bd-content .sd-tab-set>label { +.bd-content .sd-tab-set > label { background-color: var(--napari-gray); color: #222832; } /* Version warning banner color */ #bd-header-version-warning { - background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 30%); + background-color: color-mix( + in srgb, + var(--pst-color-secondary-bg), + transparent 30% + ); } #bd-header-version-warning .pst-button-link-to-stable-version { - background-color: color-mix(in srgb, var(--pst-color-secondary-bg), transparent 0%); + background-color: color-mix( + in srgb, + var(--pst-color-secondary-bg), + transparent 0% + ); border-color: var(--pst-color-secondary-bg); color: #222832; font-weight: 700; @@ -47,4 +62,972 @@ div.sphx-glr-download a:hover { border-color: var(--pst-color-secondary-bg); color: #222832; font-weight: 700; -} \ No newline at end of file +} + +/*************************** + napari custom colors +***************************/ + +html { + --napari-primary-blue: #80d1ff; + --napari-deep-blue: #009bf2; + --napari-light-blue: #d2efff; + --napari-dark-gray: #686868; + --napari-gray: #f7f7f7; + --pst-font-family-base: "Barlow", var(--pst-font-family-base-system); + --pst-font-family-heading: "Barlow", var(--pst-font-family-base-system); + --pst-font-family-monospace: "JetBrains Mono", + var(--pst-font-family-monospace-system); + --pst-font-size-base: 16px; + --pst-color-headerlink: var(--napari-dark-gray); + --pst-color-headerlink-hover: var(--napari-deep-blue); + --pst-color-surface: var(--napari-gray) !important; +} + +html[data-theme="light"] { + --pst-color-primary: black; + --pst-color-secondary: var(--napari-primary-blue); + --pst-color-background: white; + --napari-color-text-base: black; + --pst-color-text-base: var(--napari-color-text-base); + --pst-color-link: black; + --pst-color-link-hover: black !important; + --pst-color-inline-code: black !important; + --pst-color-inline-code-links: black !important; + --pst-color-on-background: white; + --pst-color-text-muted: var(--napari-dark-gray); + --pst-color-border: var(--napari-gray); + --pst-color-target: var(--napari-gray); +} + +/* Dark theme is currently unset - design/accessibility assessment is needed here */ +html[data-theme="dark"] { + --pst-color-primary: black; + --pst-color-secondary: var(--napari-primary-blue); + --pst-color-background: white; + --napari-color-text-base: black; + --pst-color-text-base: var(--napari-color-text-base); + --pst-color-link: black; + --pst-color-link-hover: black !important; + --pst-color-inline-code: black !important; + --pst-color-inline-code-links: black !important; + --pst-color-on-background: white; + --pst-color-text-muted: var(--napari-dark-gray); + --pst-color-border: var(--napari-gray); + --pst-color-target: var(--napari-gray); + --pst-color-secondary-bg: #e0c7ff; + --pst-color-primary-text: white; +} + +/* HTML elements */ +body { + margin: 0; + font-family: + "Barlow", + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "Helvetica Neue", + Arial, + "Noto Sans", + "Liberation Sans", + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + "Segoe UI Symbol", + "Noto Color Emoji"; + text-align: left; +} + +p, +.line-block .line { + line-height: 1.5; + font-size: 1.0625rem; +} + +h1 { + font-size: 2.1875rem; + line-height: 125%; + font-weight: bolder; +} + +/*************************** + Textual elements +***************************/ + +h1 { + font-weight: 700; + color: var(--napari-color-text-base) !important; +} + +h2 { + font-weight: 700; + color: var(--napari-color-text-base) !important; +} + +h3 { + font-weight: 700; + color: var(--napari-color-text-base) !important; +} + +h4 { + font-weight: 700; + color: var(--napari-color-text-base) !important; +} + +h5 { + font-weight: 700; + color: var(--napari-color-text-base) !important; +} + +h6 { + font-weight: 700; + color: var(--napari-color-text-base) !important; +} + +a.headerlink { + color: var(--napari-dark-gray); +} + +.sd-card-hover:hover { + border-color: var(--napari-color-text-base) !important; + transform: scale(1.01); +} + +.prev-next-area p { + color: var(--napari-color-text-base); +} + +/*************************** + napari footer +***************************/ + +.napari-footer { + display: flex; + flex-shrink: 0; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + align-items: center; + justify-content: space-between; + background-color: black; + padding-top: 1.5rem; + padding-bottom: 0.5rem; + margin-top: 75px; +} + +@media (min-width: 495px) { + .napari-footer { + padding-left: 3rem; + padding-right: 3rem; + } + + .napari-footer a > span { + font-size: 0.875rem; + line-height: 1.25rem; + } +} + +.napari-footer p { + color: white; +} + +.napari-footer a { + white-space: nowrap; + text-decoration-line: none; + margin-right: 1rem; + margin-bottom: 1rem; + color: white !important; + display: flex; + flex-direction: row; + align-items: center; +} + +.napari-footer a:hover { + color: white; +} + +.napari-footer a:last-child { + margin-right: 0; +} + +.napari-footer a > svg { + margin-right: 0.25rem; + display: inline-block; + height: 1em; + width: 1em; +} + +.napari-footer .footer-item { + display: flex; + flex-wrap: wrap; + align-items: center; +} + +.napari-footer .footer-item { + display: flex; + flex-wrap: wrap; + align-items: center; +} + +.napari-footer .footer-item--with-napari-copyright { + width: 100%; + justify-content: flex-end; +} + +.napari-footer .napari-copyright { + display: flex; + flex-direction: column; +} + +.napari-footer .napari-copyright, +.napari-footer .napari-copyright .copyright { + font-weight: 600; + font-size: 0.5625rem; + margin-bottom: auto; +} + +@media (min-width: 780px) { + .napari-footer .footer-item--with-napari-copyright { + width: max-content; + } + + .napari-footer .footer-item--with-napari-copyright { + justify-content: flex-start; + } +} + +@media (min-width: 495px) { + .napari-footer .napari-copyright, + .napari-footer .napari-copyright .copyright { + font-size: 0.875rem; + line-height: 1.25rem; + } +} + +.napari-footer .napari-copyright .sphinx-link, +.napari-footer .napari-copyright .sphinx-version { + display: flex; + justify-content: flex-start; +} + +.napari-footer, +.napari-copyright, +.sphinx-link, +> :not([hidden]), +~ :not([hidden]) { + margin-right: calc(0.25rem); + margin-left: calc(0.25rem); +} + +.napari-footer .napari-copyright .sphinx-version { + color: white; +} + +.napari-footer .footer-icon__hover-blue .footer-icon__light-blue { + display: none; +} + +.napari-footer .footer-icon__hover-blue:hover .footer-icon__regular { + display: none; +} + +.napari-footer .footer-icon__hover-blue:hover .footer-icon__light-blue { + display: block; +} + +/* Recommended by PST so that components will display horizontally */ +.footer-items__start, +.footer-items__end { + flex-direction: row; +} + +.bd-footer__inner { + display: flex; + flex-shrink: 0; + flex-direction: row; + flex-wrap: wrap; + width: 100%; + max-width: 100%; + align-items: center; + justify-content: space-between; + background-color: black; +} + +/**************************** + Nav bar section +*****************************/ + +.navbar { + background-color: var(--napari-primary-blue) !important; + box-shadow: none; +} + +.navbar-brand { + vertical-align: middle; + font-size: 1.25rem; + color: black !important; + position: relative; + font-weight: bolder; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none !important; + color: var(--napari-color-text-base) !important; +} + +/* Navbar text */ +.bd-header ul.navbar-nav > li.nav-item > .nav-link { + color: var(--napari-color-text-base); + font-size: 1.0625rem; + font-weight: 500 !important; + border-bottom: 3px solid transparent; + padding: 16px 1.0625rem 16px !important; +} + +.bd-header ul.navbar-nav > li.nav-item > .nav-link:hover { + color: var(--napari-color-text-base); + font-size: 1.0625rem; + font-weight: 500 !important; + border-bottom: 3px solid var(--napari-color-text-base); +} + +.bd-header ul.navbar-nav > li.nav-item.current > .nav-link::before { + border-bottom: 0 solid var(--napari-color-text-base); +} + +.bd-header ul.navbar-nav > li.nav-item.current > .nav-link { + border-bottom: 3px solid var(--napari-color-text-base); + font-weight: 700 !important; +} + +.bd-header ul.navbar-nav { + height: var(--pst-header-height); +} + +.bd-header ul.navbar-nav > li.nav-item { + margin-inline: 0; +} + +.bd-header ul.navbar-nav > li.nav-item.dropdown > .dropdown-toggle { + color: var(--napari-color-text-base); + font-size: 1.0625rem; + font-weight: 500 !important; + border-bottom: 3px solid transparent; + padding: 16px 1.0625rem 16px !important; + height: var(--pst-header-height); +} + +.bd-header ul.navbar-nav > li.nav-item.dropdown > .dropdown-toggle:hover { + box-shadow: none; + text-decoration: none; + border-bottom: 3px solid var(--napari-color-text-base); +} + +.bd-header ul.navbar-nav li a.nav-link.dropdown-item { + color: var(--napari-color-text-base); + font-weight: 500; +} + +html .pst-navbar-icon, +html .pst-navbar-icon:hover { + color: var(--napari-color-text-base); +} + +/*************************** + version switcher +***************************/ + +button.btn.version-switcher__button { + padding-top: 0; + font-size: 0.875rem; + font-weight: 600; + border-style: none; + color: var(--napari-color-text-base); +} + +button.btn.version-switcher__button:hover { + color: var(--napari-color-text-base); +} + +.version-switcher__menu a.list-group-item { + background-color: var(--pst-color-background); + color: var(--napari-color-text-base); + padding: 0.5rem 0.5rem; + font-size: 0.875rem; +} + +.version-switcher__menu a.list-group-item:hover { + color: var(--napari-color-text-base); +} + +.version-switcher__menu, +button.version-switcher__button { + min-width: max-content; + border-radius: unset; +} + +/*************************** + sidebar +***************************/ + +/* Remove "Section Navigation" caption */ +.bd-links__title { + display: none; +} + +/* Move chevron to the left */ +.bd-sidebar-primary li.has-children > details > summary .toctree-toggle { + right: unset; +} + +/* Fonts and styles */ +.bd-sidebar a.reference, +.bd-sidebar .caption-text { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.bd-sidebar-primary .sidebar-primary-items__end { + margin-bottom: 0; + margin-top: 0; +} + +.bd-sidebar .toctree-l1 a { + padding-left: 32px; +} + +.bd-sidebar .toctree-l2 { + margin-left: -0.2rem; + border-left: 1px solid var(--napari-color-text-base); +} + +.bd-sidebar .toctree-l2 label { + left: 4px; +} + +.bd-sidebar .toctree-l2 a { + font-size: 0.875rem; + line-height: 1.5rem; + text-decoration-line: none; + border-left: 2px solid transparent; + color: var(--napari-color-text-base) !important; +} + +.bd-sidebar .toctree-l2 a:hover, +.bd-sidebar .toctree-l2 a.current:hover { + border-left: 2px solid var(--napari-primary-blue); +} + +.bd-sidebar .toctree-l2 a.current { + border-left: 2px solid var(--napari-color-text-base); +} + +.bd-sidebar .toctree-l3 label { + left: 6px; +} + +.bd-sidebar .toctree-l3 a { + font-size: 0.875rem; + line-height: 1.5rem; + text-decoration-line: none; + border-left: 2px solid transparent; + color: var(--napari-color-text-base) !important; + padding-left: 36px; +} + +.bd-sidebar .toctree-l3 a:hover, +.bd-sidebar .toctree-l3 a.current:hover { + border-left: 2px solid var(--napari-primary-blue); + margin-left: -1rem; + padding-left: 52px; +} + +.bd-sidebar .toctree-l3 a.current { + border-left: 2px solid var(--napari-color-text-base); + margin-left: -1rem; + padding-left: 52px; +} + +.bd-sidebar .toctree-l4 label { + left: 8px; +} + +.bd-sidebar .toctree-l4 a { + font-size: 0.875rem; + line-height: 1.5rem; + text-decoration-line: none; + border-left: 2px solid transparent; + color: var(--napari-color-text-base) !important; +} + +.bd-sidebar .toctree-l4 a:hover, +.bd-sidebar .toctree-l4 a.current:hover { + border-left: 2px solid var(--napari-primary-blue); + margin-left: -2rem; + padding-left: 68px; +} + +.bd-sidebar .toctree-l4 a.current { + border-left: 2px solid var(--napari-color-text-base); + margin-left: -2rem; + padding-left: 68px; +} + +.bd-sidebar .toctree-l5 label { + left: 10px; +} + +.bd-sidebar .toctree-l5 a { + font-size: 0.875rem; + line-height: 1.5rem; + text-decoration-line: none; + border-left: 2px solid transparent; + color: var(--napari-color-text-base) !important; +} + +.bd-sidebar .toctree-l5 a:hover, +.bd-sidebar .toctree-l5 a.current:hover { + border-left: 2px solid var(--napari-primary-blue); +} + +.bd-sidebar .toctree-l5 a.current { + border-left: 2px solid var(--napari-color-text-base); +} + +/* also related to navbar */ +.navbar-nav li a:focus, +.navbar-nav li a:hover, +.navbar-nav li.current > a { + color: var(--napari-color-text-base); +} + +nav.bd-links li > a { + color: var(--napari-color-text-base); + display: block; + line-height: 1.25rem; +} + +nav.bd-links li > a:active, +nav.bd-links li > a:hover { + color: var(--napari-color-text-base); +} + +nav.bd-links li > a:hover { + text-decoration: none !important; +} + +nav.bd-links .current > a { + box-shadow: none !important; +} + +/*************************** +search +***************************/ + +.bd-search { + border: 1px solid transparent; +} + +.bd-search:focus-within { + box-shadow: 0 0 0 0.1875rem var(--napari-primary-blue); +} + +.form-control { + border: 1px transparent; +} + +.form-control:focus, +.form-control:focus-visible { + background-color: var(--pst-color-background); + border: none; + box-shadow: none; + color: var(--pst-color-text-muted); + outline: none; +} + +/*************************** +page toc sidebar +***************************/ + +.onthispage { + border-style: none; + padding: 1px 0 5px; + font-size: 0.875rem; + line-height: 1.25rem; + font-weight: 600; + text-transform: uppercase; + color: var(--napari-color-text-base) !important; + margin: 0 !important; +} + +.page-toc { + .section-nav { + padding-left: 0; + border-bottom: none; + } + + .onthispage { + color: var(--napari-color-text-base); + font-weight: var(--pst-sidebar-header-font-weight); + margin-bottom: 1rem; + } +} + +.toc-entry a.nav-link.active:hover { + color: var(--napari-color-text-base); +} + +.toc-entry a.nav-link:active, +.toc-entry a.nav-link:hover { + color: var(--napari-color-text-base); +} + +nav.page-toc { + border-left: 1px solid var(--napari-color-text-base); + padding-left: 1rem; +} + +.sidebar-secondary-item { + border-left: none !important; +} + +.toc-entry a.nav-link, +.toc-entry a > code { + color: var(--napari-color-text-base); +} + +.toc-entry > .nav-link { + border-left: 3px solid transparent; +} + +.toc-entry > .nav-link:hover { + border-left: 3px solid var(--napari-primary-blue); +} + +.toc-entry a.nav-link:hover { + text-decoration: none !important; +} + +.toc-entry a.nav-link.active { + box-shadow: none !important; + border-left: 3px solid var(--napari-color-text-base); +} + +.toc-h3.nav-item.toc-entry.active a { + margin-left: -2rem; + padding-left: 2rem; +} + +.toc-h3.nav-item.toc-entry a { + margin-left: -2rem; + padding-left: 2rem; +} + +.toc-h4.nav-item.toc-entry.active a { + margin-left: -3rem; + padding-left: 3rem; +} + +.toc-h4.nav-item.toc-entry a { + margin-left: -3rem; + padding-left: 3rem; +} + +.toc-h4.nav-item.toc-entry a:hover { + margin-left: -3rem; + padding-left: 3rem; +} + +/*************************** + napari calendar +***************************/ + +:root { + --fc-border-color: var(--napari-primary-blue); + --fc-daygrid-event-dot-width: 5px; + --fc-button-bg-color: var(--napari-primary-blue); + --fc-button-border-color: var(--napari-primary-blue); + --fc-button-text-color: var(--napari-color-text-base); + --fc-button-active-bg-color: var(--napari-deep-blue); + --fc-button-active-border-color: var(--napari-deep-blue); + --fc-button-hover-bg-color: var(--napari-deep-blue); + --fc-button-hover-border-color: var(--napari-deep-blue); + --fc-event-bg-color: var(--napari-light-blue); + --fc-event-border-color: var(--napari-light-blue); + --fc-event-text-color: var(--napari-color-text-base); +} + +.fc .fc-button:focus { + box-shadow: none; +} + +.fc-event-time { + margin-right: 3px; + min-width: fit-content; +} + +.fc-day-today .fc-daygrid-day-number { + background-color: var(--napari-primary-blue); +} + +.fc .fc-daygrid-day.fc-day-today { + background-color: unset; +} + +/*************************** + Admonitions +***************************/ + +.admonition, +div.admonition { + --color: #80d1ff; + border: var(--color) solid 1px !important; + border-radius: 0 !important; + box-shadow: none !important; + /*border-color: rgba(var(--pst-color-admonition-default), 1); */ + padding-left: 0 !important; + font-size: 0.938rem; + font-weight: 500; +} + +.admonition > .admonition-title, +div.admonition > .admonition-title { + text-transform: uppercase; + background: var(--color) !important; + font-size: 0.938rem !important; + font-weight: 700; +} + +/* Remove admonition title icon */ +div.admonition > .admonition-title:after, +.admonition > .admonition-title:after { + display: none; +} + +/* Padding and spacing */ +div.admonition.warning > ul.simple { + padding: 1.1rem !important; +} + +div.admonition > p, +div.admonition > ul.simple p { + font-size: 0.938rem; +} + +div.admonition > p { + padding-top: 0.5rem; + padding-bottom: 0.4rem; +} + +/* Toggle button */ +.admonition.toggle-hidden { + height: 40px; +} + +.admonition .toggle-button { + top: 0; + right: 0; + z-index: 10; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + float: unset; +} + +.admonition .toggle-button::before { + display: none; +} + +.admonition .toggle-button svg { + transition-property: transform; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; + transform: rotate(45deg); +} + +.admonition .toggle-button.toggle-button-hidden svg { + transform: rotate(0); +} + +/* Attention */ + +.admonition.attention { + --color: #d8f97d; +} + +/* Caution */ + +.admonition.caution { + --color: #ffc580; +} + +/* Warning */ + +.admonition.warning { + --color: #ffa680; +} + +/* Danger */ + +.admonition.danger { + --color: #ff8080; +} + +/* Error */ + +.admonition.error { + --color: #fade7d; +} + +/* Hint */ + +.admonition.hint { + --color: #8094ff; +} + +/* Tip */ + +.admonition.tip { + --color: #cf80ff; +} + +/* Important */ + +.admonition.important { + --color: #f1f379; +} + +/* Note */ + +.admonition.note { + --color: #80ffe0; +} + +/*************************** + Page container +***************************/ + +#pst-back-to-top { + background-color: var(--napari-light-blue); + color: var(--napari-dark-gray); +} + +/*************************** + Calendar popup +***************************/ + +/* The Modal (background) */ +.modal { + /* Hidden by default */ + position: fixed; + /* Stay in place */ + z-index: 1; + /* Sit on top */ + padding-top: 100px; + /* Location of the box */ + left: 0; + top: 0; + width: 100%; + /* Full width */ + height: 100%; + /* Full height */ + overflow: auto; + /* Enable scroll if needed */ + background-color: rgb(0, 0, 0); + /* Fallback color */ + background-color: rgba(0, 0, 0, 0.4); + /* Black w/ opacity */ +} + +/* Modal Content */ +.modal-content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #fefefe; + margin: auto; + padding: 0; + border: 1px solid #888; + box-shadow: + 0 4px 8px 0 rgba(0, 0, 0, 0.2), + 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + +/* Modal Content */ +.modal-content { + width: 30%; +} + +@media (max-width: 780px) { + .modal-content { + width: 50%; + } +} + +@media (max-width: 495px) { + .modal-content { + width: 80%; + } +} + +/* Add Animation */ +@-webkit-keyframes animatetop { + from { + top: -300px; + opacity: 0; + } + + to { + top: 0; + opacity: 1; + } +} + +@keyframes animatetop { + from { + top: -300px; + opacity: 0; + } + + to { + top: 0; + opacity: 1; + } +} + +/* The Close Button */ +.close { + color: white; + float: right; + font-size: 28px; + font-weight: bold; + padding-right: 12px; + padding-top: 4px; +} + +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; +} + +.modal-header { + padding: 0 0 0 12px; + background-color: var(--napari-primary-blue); + color: white; + display: block; +} + +.modal-header h3 { + margin-top: 1rem; +} + +.modal-body { + padding: 12px; +} diff --git a/docs/_templates/footer.html b/docs/_templates/footer.html new file mode 100644 index 000000000..ff6eee7b2 --- /dev/null +++ b/docs/_templates/footer.html @@ -0,0 +1,23 @@ +{% if theme_footer_start or theme_footer_center or theme_footer_end %} + +{% endif %} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html new file mode 100644 index 000000000..a715e0f18 --- /dev/null +++ b/docs/_templates/layout.html @@ -0,0 +1,3 @@ +{%- extends "pydata_sphinx_theme/layout.html" %} {%- block footer %} + +{%- endblock %} diff --git a/docs/_templates/napari-copyright.html b/docs/_templates/napari-copyright.html new file mode 100644 index 000000000..a6f1538b1 --- /dev/null +++ b/docs/_templates/napari-copyright.html @@ -0,0 +1,21 @@ + diff --git a/docs/_templates/napari-footer-links.html b/docs/_templates/napari-footer-links.html new file mode 100644 index 000000000..96dae323b --- /dev/null +++ b/docs/_templates/napari-footer-links.html @@ -0,0 +1,70 @@ + + {% include "partials/napari.html" %} + napari + + + + {% include "partials/napari-hub.html" %} {% include + "partials/napari-hub-light-blue.html" %} + Plugins + + + + {% include "partials/github.html" %} {% include + "partials/github-light-blue.html" %} + Code + + + + {% include "partials/mastodon.html" %} {% include + "partials/mastodon-light-blue.html" %} + Mastodon + + + + {% include "partials/image-sc.html" %} {% include + "partials/image-sc-light-blue.html" %} + Forum + + + + {% include "partials/zulip.html" %} {% include + "partials/zulip-light-blue.html" %} + Chat + diff --git a/docs/_templates/page-toc.html b/docs/_templates/page-toc.html new file mode 100644 index 000000000..dd6639ab1 --- /dev/null +++ b/docs/_templates/page-toc.html @@ -0,0 +1,4 @@ +{% set page_toc = generate_toc_html() %} {%- if page_toc | length >= 1 %} +
{{ _("On this page") }}
+ +{%- endif %} diff --git a/docs/_templates/partials/github-light-blue.html b/docs/_templates/partials/github-light-blue.html new file mode 100644 index 000000000..2735a7dc3 --- /dev/null +++ b/docs/_templates/partials/github-light-blue.html @@ -0,0 +1,13 @@ + + + diff --git a/docs/_templates/partials/github.html b/docs/_templates/partials/github.html new file mode 100644 index 000000000..6898a5df4 --- /dev/null +++ b/docs/_templates/partials/github.html @@ -0,0 +1,14 @@ + + Visit GitHub repository + + diff --git a/docs/_templates/partials/image-sc-light-blue.html b/docs/_templates/partials/image-sc-light-blue.html new file mode 100644 index 000000000..fc7c82f95 --- /dev/null +++ b/docs/_templates/partials/image-sc-light-blue.html @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/docs/_templates/partials/image-sc.html b/docs/_templates/partials/image-sc.html new file mode 100644 index 000000000..449a2f383 --- /dev/null +++ b/docs/_templates/partials/image-sc.html @@ -0,0 +1,40 @@ + + Visit image.sc forum + + + + + + + diff --git a/docs/_templates/partials/mastodon-light-blue.html b/docs/_templates/partials/mastodon-light-blue.html new file mode 100644 index 000000000..e5e7a32e6 --- /dev/null +++ b/docs/_templates/partials/mastodon-light-blue.html @@ -0,0 +1,13 @@ + + + diff --git a/docs/_templates/partials/mastodon.html b/docs/_templates/partials/mastodon.html new file mode 100644 index 000000000..563231f19 --- /dev/null +++ b/docs/_templates/partials/mastodon.html @@ -0,0 +1,14 @@ + + Visit Mastodon page + + diff --git a/docs/_templates/partials/menu.html b/docs/_templates/partials/menu.html new file mode 100644 index 000000000..63e56be20 --- /dev/null +++ b/docs/_templates/partials/menu.html @@ -0,0 +1,11 @@ + + + + + diff --git a/docs/_templates/partials/napari-hub-light-blue.html b/docs/_templates/partials/napari-hub-light-blue.html new file mode 100644 index 000000000..67d49111c --- /dev/null +++ b/docs/_templates/partials/napari-hub-light-blue.html @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_templates/partials/napari-hub.html b/docs/_templates/partials/napari-hub.html new file mode 100644 index 000000000..18e063444 --- /dev/null +++ b/docs/_templates/partials/napari-hub.html @@ -0,0 +1,90 @@ + + Visit napari hub + + + + + + + + + + + + + + diff --git a/docs/_templates/partials/napari.html b/docs/_templates/partials/napari.html new file mode 100644 index 000000000..d581265a7 --- /dev/null +++ b/docs/_templates/partials/napari.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_templates/partials/zulip-light-blue.html b/docs/_templates/partials/zulip-light-blue.html new file mode 100644 index 000000000..e91e7051c --- /dev/null +++ b/docs/_templates/partials/zulip-light-blue.html @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/docs/_templates/partials/zulip.html b/docs/_templates/partials/zulip.html new file mode 100644 index 000000000..37e8a5b77 --- /dev/null +++ b/docs/_templates/partials/zulip.html @@ -0,0 +1,21 @@ + + Visit Zulip chatroom + + + + + + + + + diff --git a/docs/_templates/search-button-field.html b/docs/_templates/search-button-field.html new file mode 100644 index 000000000..6ca02fb32 --- /dev/null +++ b/docs/_templates/search-button-field.html @@ -0,0 +1,10 @@ +{# Behaves the same as `search-button.html` but looks more like a search field. +# # As this function will only work when JavaScript is enabled, we add it +through JavaScript. #} + diff --git a/docs/_templates/search-field.html b/docs/_templates/search-field.html new file mode 100644 index 000000000..ab2682b1c --- /dev/null +++ b/docs/_templates/search-field.html @@ -0,0 +1,21 @@ +{# A bootstrap-styled field that will direct to the `search.html` page when +submitted #} + diff --git a/docs/conf.py b/docs/conf.py index f107e0c8c..7e0a8bb23 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,100 +1,164 @@ # Configuration file for the Sphinx documentation builder. # -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html +# For sphinx config settings available, see: +# See https://www.sphinx-doc.org/en/master/usage/configuration.html +# For theme specific config, see: +# https://pydata-sphinx-theme.readthedocs.io/en/stable/index.html -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# -# import os -# import sys -# sys.path.insert(0, os.path.abspath('.')) - -import re +import logging import os +import re from datetime import datetime from importlib import import_module from importlib.metadata import distribution from pathlib import Path from urllib.parse import urlparse, urlunparse -import logging from jinja2.filters import FILTERS +from packaging.version import parse as parse_version +from pygments.lexers import TOMLLexer +from qtpy.QtWidgets import QApplication +from sphinx_gallery import gen_rst from sphinx_gallery import scrapers from sphinx_gallery.sorting import ExampleTitleSortKey from sphinx.highlighting import lexers from sphinx.util import logging as sphinx_logging -from packaging.version import parse as parse_version -from pygments.lexers import TOMLLexer import napari +from napari.settings import get_settings from napari._version import __version_tuple__ -release = napari.__version__ -if "dev" in release: - version = "dev" -else: - version = release +logger = logging.getLogger(__name__) # -- Project information ----------------------------------------------------- -project = 'napari' -copyright = f'{datetime.now().year}, The napari team' -author = 'The napari team' +project = "napari" +copyright = f"{datetime.now().year}, The napari team" +author = "The napari team" # -- General configuration --------------------------------------------------- -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -autosummary_generate = True -autosummary_imported_members = True -comments_config = {'hypothesis': False, 'utterances': False} - -# execution_allow_errors = False -# execution_excludepatterns = [] -# execution_in_temp = False -# execution_timeout = 30 - +# Add sphinx extensions here, as strings. extensions = [ - "sphinx.ext.napoleon", "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.intersphinx", - "sphinx_external_toc", - "sphinx_design", - 'myst_nb', - # "sphinx_comments", + "sphinx.ext.napoleon", "sphinx.ext.viewcode", - "sphinx_favicon", + "myst_nb", "sphinx_copybutton", + "sphinx_design", + "sphinx_external_toc", + "sphinx_favicon", "sphinx_gallery.gen_gallery", "sphinx_tags", "sphinxcontrib.mermaid", ] +# Config for sphinx.ext.autosummary + +autosummary_generate = True +autosummary_imported_members = True +autosummary_ignore_module_all = False + +# Config for sphinx_copybutton + +# Specify how to identify the prompt when copying code snippets +copybutton_prompt_text = r">>> |\.\.\. " +copybutton_prompt_is_regexp = True +copybutton_exclude = "style" + +# Config for sphinx_external_toc + external_toc_path = "_toc.yml" external_toc_exclude_missing = False +# Config for sphinx.ext.intersphinx + +intersphinx_mapping = { + "python": ["https://docs.python.org/3", None], + "numpy": ["https://numpy.org/doc/stable/", None], + # napari_plugin_engine is deprecated + "napari_plugin_engine": [ + "https://napari-plugin-engine.readthedocs.io/en/latest/", + "https://napari-plugin-engine.readthedocs.io/en/latest/objects.inv", + ], + "magicgui": [ + "https://pyapp-kit.github.io/magicgui/", + "https://pyapp-kit.github.io/magicgui/objects.inv", + ], + "app-model": [ + "http://app-model.readthedocs.io/en/latest/", + "http://app-model.readthedocs.io/en/latest/objects.inv", + ], + "vispy": [ + "https://vispy.org/", + "https://vispy.org/objects.inv", + ], +} + +# Config for sphinx_tags + tags_create_tags = True tags_output_dir = "_tags" tags_overview_title = "Tags" tags_extension = ["md", "rst"] +# Config for sphinxcontrib.mermaid + mermaid_d3_zoom = True mermaid_version = "11.4.1" mermaid_include_elk = "" -# -- Options for HTML output ------------------------------------------------- +# Config for myst_nb -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = 'napari_sphinx_theme' +myst_enable_extensions = [ + "colon_fence", + "dollarmath", + "substitution", + "tasklist", + "attrs_inline", + "linkify", +] + +myst_heading_anchors = 4 + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [ + "_build", + "Thumbs.db", + ".DS_Store", + ".jupyter_cache", + "jupyter_execute", + "plugins/_*.md", + "plugins/building_a_plugin/_layer_data_guide.md", + "gallery/index.rst", + "refactor.md", +] + +myst_footnote_transition = False + +nb_output_stderr = "show" + +panels_add_bootstrap_css = False +pygments_style = "solarized-dark" +suppress_warnings = ["myst.header", "etoc.toctree", "config.cache"] + +napoleon_custom_sections = [("Events", "params_style")] +lexers["toml"] = TOMLLexer(startinline=True) + +# -- Config for versions ---------------------------------------------------- + +release = napari.__version__ +if "dev" in release: + version = "dev" +else: + version = release # Define the json_url for our version switcher. json_url = "https://napari.org/dev/_static/version_switcher.json" @@ -104,25 +168,73 @@ else: version_match = release + +def get_supported_python_versions(project_name): + """ + Get the supported Python versions for a given project + based on the classifiers in its distribution metadata. + """ + dist = distribution(project_name) + classifiers = [ + value + for key, value in dist.metadata.items() + if key == "Classifier" and value.startswith("Programming Language :: Python ::") + ] + return [ + parse_version(c.split(" :: ")[-1]) + for c in classifiers + if not c.endswith("Only") + ] + + +napari_supported_python_versions = get_supported_python_versions("napari") + +min_python_version = min(napari_supported_python_versions) +max_python_version = max(napari_supported_python_versions) + +version_string = ".".join(str(x) for x in __version_tuple__[:3]) +# when updating the version below, ensure to also update napari/napari README +python_version = "3.11" +python_version_range = f"{min_python_version}-{max_python_version}" + +myst_substitutions = { + "napari_conda_version": f"`napari={version_string}`", + "napari_version": version_string, + "python_version": python_version, + "python_version_range": python_version_range, + "python_version_code": f"`python={python_version}`", + "conda_create_env": f"```sh\nconda create -y -n napari-env -c conda-forge python={python_version}\nconda activate napari-env\n```", +} + +# -- Options for HTML output ------------------------------------------------- + +html_theme = "pydata_sphinx_theme" + html_theme_options = { "external_links": [ {"name": "napari hub", "url": "https://napari-hub.org"}, {"name": "Island Dispatch", "url": "https://napari.org/island-dispatch"}, {"name": "Community chat", "url": "https://napari.zulipchat.com"}, - {"name": "workshop template", "url": "https://napari.org/napari-workshop-template"}, + { + "name": "workshop template", + "url": "https://napari.org/napari-workshop-template", + }, ], "github_url": "https://github.com/napari/napari", + "navbar_align": "content", "navbar_start": ["navbar-logo", "navbar-project"], + "navbar_center": ["navbar-nav"], "navbar_end": ["version-switcher", "navbar-icon-links"], "switcher": { "json_url": json_url, "version_match": version_match, }, "navbar_persistent": [], + "navigation_with_keys": True, "header_links_before_dropdown": 6, - "secondary_sidebar_items": ["page-toc"], - "pygment_light_style": "napari", - "pygment_dark_style": "napari", + "secondary_sidebar_items": ["page-toc", "sourcelink"], + "pygments_light_style": "napari", + "pygments_dark_style": "napari", "announcement": "", "back_to_top_button": False, "analytics": { @@ -138,21 +250,21 @@ html_sidebars = { "**": ["search-field.html", "sidebar-nav-bs"], - "index": ["search-field.html" , "calendar-template"], + "index": ["search-field.html", "calendar-template"], } html_context = { - # use Light theme only, don't auto switch (default) - "default_mode": "light" + # use Light theme only, don't auto switch (default) + "default_mode": "light" } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] html_logo = "_static/images/logo.png" -html_sourcelink_suffix = '' -html_title = 'napari' +html_sourcelink_suffix = "" +html_title = "napari" favicons = [ { @@ -177,106 +289,13 @@ ] html_css_files = [ - 'custom.css', -] - -intersphinx_mapping = { - 'python': ['https://docs.python.org/3', None], - 'numpy': ['https://numpy.org/doc/stable/', None], - 'napari_plugin_engine': [ - 'https://napari-plugin-engine.readthedocs.io/en/latest/', - 'https://napari-plugin-engine.readthedocs.io/en/latest/objects.inv', - ], - 'magicgui': [ - 'https://pyapp-kit.github.io/magicgui/', - 'https://pyapp-kit.github.io/magicgui/objects.inv', - ], - 'app-model': [ - 'http://app-model.readthedocs.io/en/latest/', - 'http://app-model.readthedocs.io/en/latest/objects.inv', - ], - 'vispy': [ - 'https://vispy.org/', - 'https://vispy.org/objects.inv', - ], -} - -myst_enable_extensions = [ - 'colon_fence', - 'dollarmath', - 'substitution', - 'tasklist', - 'attrs_inline', - 'linkify', -] - -myst_heading_anchors = 4 - - -def get_supported_python_versions(project_name): - """ - Get the supported Python versions for a given project - based on the classifiers in its distribution metadata. - """ - dist = distribution(project_name) - classifiers = [value for key, value in dist.metadata.items() if key == 'Classifier' and value.startswith('Programming Language :: Python ::')] - return [parse_version(c.split(' :: ')[-1]) for c in classifiers if not c.endswith('Only')] - - -napari_supported_python_versions = get_supported_python_versions('napari') - -min_python_version = min(napari_supported_python_versions) -max_python_version = max(napari_supported_python_versions) - -version_string = '.'.join(str(x) for x in __version_tuple__[:3]) -# when updating the version below, ensure to also update napari/napari README -python_version = '3.11' -python_version_range = f"{min_python_version}-{max_python_version}" - -myst_substitutions = { - "napari_conda_version": f"`napari={version_string}`", - "napari_version": version_string, - "python_version": python_version, - "python_version_range": python_version_range, - "python_version_code": f"`python={python_version}`", - "conda_create_env": f"```sh\nconda create -y -n napari-env -c conda-forge python={python_version}\nconda activate napari-env\n```", -} - -myst_footnote_transition = False - -nb_output_stderr = 'show' - -panels_add_bootstrap_css = False -pygments_style = 'solarized-dark' -suppress_warnings = ['myst.header', 'etoc.toctree', 'config.cache'] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [ - '_build', - 'Thumbs.db', - '.DS_Store', - '.jupyter_cache', - 'jupyter_execute', - 'plugins/_*.md', - 'plugins/building_a_plugin/_layer_data_guide.md', - 'gallery/index.rst', + "custom.css", ] -napoleon_custom_sections = [('Events', 'params_style')] -lexers['toml'] = TOMLLexer(startinline=True) - def reset_napari(gallery_conf, fname): - from napari.settings import get_settings - from qtpy.QtWidgets import QApplication - settings = get_settings() - settings.appearance.theme = 'dark' + settings.appearance.theme = "dark" # Disabling `QApplication.exec_` means example scripts can call `exec_` # (scripts work when run normally) without blocking example execution by @@ -291,7 +310,7 @@ def napari_scraper(block, block_vars, gallery_conf): `app.processEvents()` allows Qt events to propagateo and prevents hanging. """ - imgpath_iter = block_vars['image_path_iterator'] + imgpath_iter = block_vars["image_path_iterator"] if app := napari.qt.get_qapp(): app.processEvents() @@ -309,10 +328,10 @@ def napari_scraper(block, block_vars, gallery_conf): napari.Viewer.close_all() app.processEvents() - return scrapers.figure_rst(img_paths, gallery_conf['src_dir']) + return scrapers.figure_rst(img_paths, gallery_conf["src_dir"]) -from sphinx_gallery import gen_rst +# -- Sphinx gallery ---------------------------------------------------- gen_rst.EXAMPLE_HEADER = """ .. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. @@ -338,22 +357,27 @@ def napari_scraper(block, block_vars, gallery_conf): sphinx_gallery_conf = { # path to your example scripts (this value is set in the Makefile) # 'examples_dirs': '../../napari/examples', - 'gallery_dirs': 'gallery', # path to where to save gallery generated output - 'filename_pattern': '/*.py', - 'ignore_pattern': 'README.rst|/*_.py', - 'default_thumb_file': Path(__file__).parent / '_static' / 'images' / 'logo.png', - 'plot_gallery': "'True'", # https://github.com/sphinx-gallery/sphinx-gallery/pull/304/files - 'download_all_examples': False, - 'min_reported_time': 10, - 'only_warn_on_example_error': False, - 'abort_on_example_error': True, - 'image_scrapers': ("matplotlib", napari_scraper,), - 'reset_modules': (reset_napari,), - 'reference_url': {'napari': None}, - 'within_subsection_order': ExampleTitleSortKey, + "gallery_dirs": "gallery", # path to where to save gallery generated output + "filename_pattern": "/*.py", + "ignore_pattern": "README.rst|/*_.py", + "default_thumb_file": Path(__file__).parent / "_static" / "images" / "logo.png", + "plot_gallery": "'True'", # https://github.com/sphinx-gallery/sphinx-gallery/pull/304/files + "download_all_examples": False, + "min_reported_time": 10, + "only_warn_on_example_error": False, + "abort_on_example_error": True, + "image_scrapers": ( + "matplotlib", + napari_scraper, + ), + "reset_modules": (reset_napari,), + "reference_url": {"napari": None}, + "within_subsection_order": ExampleTitleSortKey, } -GOOGLE_CALENDAR_API_KEY = os.environ.get('GOOGLE_CALENDAR_API_KEY', '') +# -- Google calendar integration ---------------------------------------- + +GOOGLE_CALENDAR_API_KEY = os.environ.get("GOOGLE_CALENDAR_API_KEY", "") def add_google_calendar_secrets(app, docname, source): @@ -363,8 +387,8 @@ def add_google_calendar_secrets(app, docname, source): source file. You can process the contents and replace this item to implement source-level transformations. """ - if docname == 'community/meeting_schedule': - source[0] = source[0].replace('{API_KEY}', GOOGLE_CALENDAR_API_KEY) + if docname == "community/meeting_schedule": + source[0] = source[0].replace("{API_KEY}", GOOGLE_CALENDAR_API_KEY) class FilterSphinxWarnings(logging.Filter): @@ -375,8 +399,8 @@ class FilterSphinxWarnings(logging.Filter): The warnings are not useful - they don't result in any missing documentation or rendering issues, so we can safely ignore them. - """ + def __init__(self, app): self.app = app super().__init__() @@ -384,9 +408,7 @@ def __init__(self, app): def filter(self, record: logging.LogRecord) -> bool: msg = record.getMessage() - filter_out = ( - "duplicate object description", - ) + filter_out = ("duplicate object description",) if msg.strip().startswith(filter_out): return False @@ -417,18 +439,20 @@ def setup(app): """ app.registry.source_suffix.pop(".ipynb", None) - app.connect('source-read', add_google_calendar_secrets) - app.connect('linkcheck-process-uri', rewrite_github_anchor) - app.connect('autodoc-process-docstring', qt_docstrings) + app.connect("source-read", add_google_calendar_secrets) + app.connect("linkcheck-process-uri", rewrite_github_anchor) + app.connect("autodoc-process-docstring", qt_docstrings) logger = logging.getLogger("sphinx") warning_handler, *_ = [ - h for h in logger.handlers - if isinstance(h, sphinx_logging.WarningStreamHandler) + h for h in logger.handlers if isinstance(h, sphinx_logging.WarningStreamHandler) ] warning_handler.filters.insert(0, FilterSphinxWarnings(app)) +# -- Attributes for autosummary -------------------------------------- + + def get_attributes(item, obj, modulename): """Filters attributes to be used in autosummary. @@ -447,9 +471,9 @@ def get_attributes(item, obj, modulename): FILTERS["get_attributes"] = get_attributes -autosummary_ignore_module_all = False +# -- Config for linkcheck --------------------------------------------------- -linkcheck_anchors_ignore = [r'^!', r'L\d+-L\d+', r'r\d+', r'issuecomment-\d+'] +linkcheck_anchors_ignore = [r"^!", r"L\d+-L\d+", r"r\d+", r"issuecomment-\d+"] linkcheck_ignore = [ "https://napari.zulipchat.com/", "../_tags", @@ -468,6 +492,8 @@ def get_attributes(item, obj, modulename): r"https://github\.com/napari/napari/releases/download/.*": r"https://objects\.githubusercontent\.com/.*", } +# -- GitHub Anchors for Links ----------------------------------------- + def rewrite_github_anchor(app, uri: str): """Rewrite anchor name of the hyperlink to github.com @@ -486,10 +512,10 @@ def rewrite_github_anchor(app, uri: str): ]: if parsed.fragment.startswith(text): return None - if re.match(r'r\d+', parsed.fragment): + if re.match(r"r\d+", parsed.fragment): return None - prefixed = parsed.fragment.startswith('user-content-') + prefixed = parsed.fragment.startswith("user-content-") if not prefixed: - fragment = f'user-content-{parsed.fragment}' + fragment = f"user-content-{parsed.fragment}" return urlunparse(parsed._replace(fragment=fragment)) return None diff --git a/docs/refactor.md b/docs/refactor.md new file mode 100644 index 000000000..1e4a13dd0 --- /dev/null +++ b/docs/refactor.md @@ -0,0 +1,21 @@ +# Refactor custom theme to pydata theme plus config + +## Motivation + +- Maintaining a separate theme adds more burden. +- Adding more complexity than needed. +- Frustrating to contribute since builds aren't clear to do locally. + +## Done + +- added logging to scripts +- fixed code copy + +## TODO + +- reduce warnings (no warnings when make no plot) +- ~~add scripts from nap theme here maybe~~ +- pygments highlighting +- headless on mac +- fix styling for This Page source +- search wtf keyboard shortcut (where did you go) diff --git a/requirements.txt b/requirements.txt index 515edfd9f..322b3170b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -sphinx<8 # break build docs because of warnings +sphinx<8 # break build docs because of warnings sphinx-autobuild sphinx-tabs sphinx-tags @@ -10,7 +10,7 @@ sphinx-gallery sphinx_autodoc_typehints==1.12.0 sphinxcontrib-mermaid>=1.0.0 myst-nb -napari-sphinx-theme>=0.3.0 +pydata-sphinx-theme matplotlib lxml_html_clean imageio-ffmpeg