diff --git a/.gitignore b/.gitignore index 448db6ae..c3d54ada 100644 --- a/.gitignore +++ b/.gitignore @@ -83,7 +83,9 @@ target/ # Jupyter Notebook .ipynb_checkpoints PPO_UC2/ -docs/source/notebooks/*.ipynb +# ignore everything but the executed notebooks rst in the docs/source/notebooks directory +!docs/source/notebooks/executed_notebooks.rst +docs/source/notebooks/**/* # IPython profile_default/ diff --git a/docs/Makefile b/docs/Makefile index 2346738f..82719283 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -6,6 +6,8 @@ SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build +AUTOSUMMARY="source/_autosummary" + # Remove command is different depending on OS ifdef OS RM = IF exist $(AUTOSUMMARY) ( RMDIR $(AUTOSUMMARY) /s /q ) diff --git a/docs/conf.py b/docs/conf.py index 008c23a1..2d3aa7ba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,7 +12,7 @@ import os import shutil import sys from pathlib import Path -from typing import Any +from typing import Any, List, Optional import furo # noqa @@ -67,7 +67,7 @@ html_theme = "furo" html_static_path = ["_static"] html_theme_options = {"globaltoc_collapse": True, "globaltoc_maxdepth": 2} html_copy_source = False -nbsphinx_allow_errors = True +nbsphinx_allow_errors = False # set to True to take shortcuts def replace_token(app: Any, docname: Any, source: Any): @@ -83,19 +83,78 @@ tokens = { } # Token VERSION is replaced by the value of the PrimAITE version in the version file """Dict containing the tokens that need to be replaced in documentation.""" -temp_ignored_notebooks = ["Training-an-RLLib-Agent.ipynb", "Training-an-RLLIB-MARL-System.ipynb"] + +def notebook_assets(ignored_files: Optional[List[str]] = [], include_file_types: Optional[List[str]] = []) -> Any: + """ + Creates a function to be used with `shutil.copytree`'s `ignore` parameter. + + :param ignored_files: A list of specific file names to ignore. If a file in the directory matches one of these + names, it will be excluded from the copy process. + :type ignored_files: Optional[List[str]] + :param include_file_types: A list of file extensions to include in the copy process. Files that do not match these + extensions will be excluded. If this list is empty, all files will be excluded, effectively copying only + directories. + :type include_file_types: Optional[List[str]] + """ + + def ignore_items(directory: List[str], contents: List[str]) -> List[str]: + """ + Determines which files and directories should be ignored during the copy process. + + :param directory: The directory being copied. + :type directory: str + :param contents: A list of contents in the directory. + :type contents: List[str] + :return: A list of items to exclude from the copy process. + :rtype: List[str] + """ + exclude_items = [] + + for item in contents: + if item in ignored_files: + exclude_items.append(item) + continue + + if len(include_file_types) > 0: + if not any(item.lower().endswith(ext.lower()) for ext in include_file_types) and os.path.isdir(item): + exclude_items.append(item) + else: + # if we dont specify which files to include, exclude everything + exclude_items.append(item) + + # exclude files but not directories + return [path for path in exclude_items if not (Path(directory) / path).is_dir()] + + return ignore_items -def copy_notebooks_to_docs(): - """Copies the notebooks to a directory within docs directory so that they can be included.""" +def copy_notebooks_to_docs() -> Any: + """ + Incredibly over-engineered method that copies the notebooks and its assets to a directory within the docs directory. + + This allows developers to create new notebooks without having to worry about updating documentation when + a new notebook is included within PrimAITE. + """ + # temporarily ignore these notebooks because we love RLlib and its quirks + ignored_notebooks = ["Training-an-RLLib-Agent.ipynb", "Training-an-RLLIB-MARL-System.ipynb"] + + notebook_asset_types = [".ipynb", ".png"] + notebook_directories = [] + + # find paths where notebooks are contained for notebook in Path("../src/primaite").rglob("*.ipynb"): - if notebook.name not in temp_ignored_notebooks: - dest = Path("source") / "notebooks" - Path(dest).mkdir(parents=True, exist_ok=True) - shutil.copy2(src=notebook, dst=dest) + # add parent path to notebook directory if not already added + if notebook.parent not in notebook_directories: + notebook_directories.append(notebook.parent) - # copy any images - # TODO + # go through the notebook directories and copy the notebooks and extra assets + for notebook_parent in notebook_directories: + shutil.copytree( + src=notebook_parent, + dst=Path("source") / "notebooks" / notebook_parent.name, + ignore=notebook_assets(ignored_files=ignored_notebooks, include_file_types=notebook_asset_types), + dirs_exist_ok=True, + ) def setup(app: Any): diff --git a/docs/make.bat b/docs/make.bat index 7c1cf0cc..0d4049f0 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -13,7 +13,6 @@ set SOURCEDIR=. set BUILDDIR=_build set AUTOSUMMARYDIR="%cd%\source\_autosummary\" -set JUPYTEROUTPUTPATH="%cd%\_static\notebooks\html" %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( diff --git a/docs/source/notebooks/executed_notebooks.rst b/docs/source/notebooks/executed_notebooks.rst index f785a598..f99b13bb 100644 --- a/docs/source/notebooks/executed_notebooks.rst +++ b/docs/source/notebooks/executed_notebooks.rst @@ -13,4 +13,4 @@ Below is a list of available pre-executed notebooks. :maxdepth: 1 :glob: - * + **/*