diff --git a/template/docs/Makefile.jinja b/template/docs/Makefile.jinja new file mode 100644 index 0000000..bdc6a11 --- /dev/null +++ b/template/docs/Makefile.jinja @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = python -msphinx +SPHINXPROJ = {{ project_slug }} +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/template/docs/api_reference.rst.jinja b/template/docs/api_reference.rst.jinja new file mode 100644 index 0000000..73e9652 --- /dev/null +++ b/template/docs/api_reference.rst.jinja @@ -0,0 +1,22 @@ +.. _api_reference: + +API Reference +============= + +The following gives detailed information about all {{ project_slug }} functions sorted +according to their modules. + +For examples on how to use {{ project_slug }} refer to the notebooks in the +`examples gallery`_ and the documentation on classes and modules below. + + +Modules +------- + +.. toctree:: + :maxdepth: 1 + + modules/{{ project_slug }} + + +.. _examples gallery: https://pyfar-gallery.readthedocs.io/en/latest/examples_gallery.html \ No newline at end of file diff --git a/template/docs/conf.py.jinja b/template/docs/conf.py.jinja new file mode 100644 index 0000000..3cb867d --- /dev/null +++ b/template/docs/conf.py.jinja @@ -0,0 +1,231 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys +import urllib3 +import shutil +import numpy as np +sys.path.insert(0, os.path.abspath('..')) + +import {{ project_slug }} # noqa + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', + 'sphinx.ext.autosummary', + 'matplotlib.sphinxext.plot_directive', + 'sphinx.ext.mathjax', + 'sphinx.ext.intersphinx', + 'autodocsumm', + 'sphinx_design', + 'sphinx_favicon', + 'sphinx_reredirects', + 'sphinx_mdinclude', +] + +# show tocs for classes and functions of modules using the autodocsumm +# package +autodoc_default_options = {'autosummary': True} + +# show the code of plots that follows the command .. plot:: based on the +# package matplotlib.sphinxext.plot_directive +plot_include_source = True + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = '{{ project_slug }}' +copyright = "{{ copyright_statement }}" +author = "{{ author }}" + +# The version info for the project you're documenting, acts as replacement +# for |version| and |release|, also used in various other places throughout +# the built documents. +# +# The short X.Y version. +version = {{ project_slug }}.__version__ +# The full version, including alpha/beta/rc tags. +release = {{ project_slug }}.__version__ + +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# The name of the Pygments (syntax highlighting) style to use (Not defining +# uses the default style of the html_theme). +# pygments_style = 'sphinx' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = False + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# default language for highlighting in source code +highlight_language = "python3" + +# intersphinx mapping +intersphinx_mapping = { + {% if 'numpy' in dependencies -%} + 'numpy': ('https://numpy.org/doc/stable/', None),{% endif %} + {% if 'scipy' in dependencies -%} + 'scipy': ('https://docs.scipy.org/doc/scipy/', None),{% endif %} + {% if 'matplotlib' in dependencies -%} + 'matplotlib': ('https://matplotlib.org/stable/', None),{% endif %} + {% if 'pyfar' in dependencies -%} + 'pyfar': ('https://pyfar.readthedocs.io/en/stable/', None),{% endif %} + } + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'pydata_sphinx_theme' +html_static_path = ['_static'] +html_css_files = ['css/custom.css'] +html_js_files = ['js/custom.js'] +html_logo = '{{ logo_path_gallery }}' +html_title = "{{ project_slug }}" +html_favicon = '_static/favicon.ico' + +# -- HTML theme options +# https://pydata-sphinx-theme.readthedocs.io/en/stable/user_guide/layout.html +html_sidebars = { + "{{ project_slug }}": [] +} + +html_theme_options = { + "navbar_start": ["navbar-logo"], + "navbar_end": ["navbar-icon-links", "theme-switcher"], + "navbar_align": "content", + "header_links_before_dropdown": None, # will be automatically set later based on headers.rst + "header_dropdown_text": "Packages", # Change dropdown name from "More" to "Packages" + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/pyfar", + "icon": "fa-brands fa-square-github", + "type": "fontawesome", + }, + ], + # Configure secondary (right) side bar + "show_toc_level": 3, # Show all subsections of notebooks + "secondary_sidebar_items": ["page-toc"], # Omit 'show source' link that that shows notebook in json format + "navigation_with_keys": True, + # Configure navigation depth for section navigation + "navigation_depth": 2, +} + +html_context = { + "default_mode": "light" +} + +# redirect index to pyfar.html +redirects = { + "index": "{{ project_slug }}.html" +} + +# -- download navbar and style files from gallery ----------------------------- +branch = 'main' +link = f'https://github.com/pyfar/gallery/raw/{branch}/docs/' +folders_in = [ + '_static/css/custom.css', + '_static/js/custom.js', + '_static/favicon.ico', + '_static/header.rst', + '{{ logo_path_gallery }}', + ] + +def download_files_from_gallery(link, folders_in): + c = urllib3.PoolManager() + for file in folders_in: + url = link + file + filename = file + os.makedirs(os.path.dirname(filename), exist_ok=True) + with c.request('GET', url, preload_content=False) as res: + if res.status == 200: + with open(filename, 'wb') as out_file: + shutil.copyfileobj(res, out_file) + +download_files_from_gallery(link, folders_in) +# if logo does not exist, use pyfar logo +if not os.path.exists(html_logo): + download_files_from_gallery( + link, ['resources/logos/pyfar_logos_fixed_size_pyfar.png']) + shutil.copyfile( + 'resources/logos/pyfar_logos_fixed_size_pyfar.png', html_logo) + + +# -- modify downloaded header file from the gallery to ---------------------- +# -- aline with the local toctree --------------------------------------------- + +# read the header file from the gallery +with open("_static/header.rst", "rt") as fin: + lines = [line for line in fin] + +# replace readthedocs link with internal link to this documentation +lines_mod = [ + line.replace(f'https://{project}.readthedocs.io', project) for line in lines] + +# if not found, add this documentation link to the end of the list, so that +# it is in the doc tree +contains_project = any(project in line for line in lines_mod) +if not contains_project: + lines_mod.append(f' {project} <{project}>\n') + +# write the modified header file +# to the doc\header.rst folder, so that it can be used in the documentation +with open("header.rst", "wt") as fout: + fout.writelines(lines_mod) + + +# -- find position of pyfar package in toctree -------------------------------- +# -- this is required to define the dropdown of Packages in the header -------- + +# find line where pyfar package is mentioned, to determine the start of +# the packages list in the header +n_line_pyfar = 0 +for i, line in enumerate(lines): + if 'https://pyfar.readthedocs.io' in line: + n_line_pyfar = i + break + +# the first 4 lines of the header file are defining the settings and a empty +# line of the toctree, therefore we need to subtract 4 from the line number +# of the pyfar package to get the correct position in the toctree +n_toctree_pyfar = n_line_pyfar - 4 + +if n_toctree_pyfar < 1: + raise ValueError( + "Could not find the line where pyfar package is mentioned. " + "Please check the header.rst file in the gallery." + ) + +# set dropdown header at pyfar appearance, so that pyfar is the first item in +# the dropdown called Packages +html_theme_options['header_links_before_dropdown'] = n_toctree_pyfar \ No newline at end of file diff --git a/template/docs/contributing.rst b/template/docs/contributing.rst new file mode 100644 index 0000000..3bdd7dc --- /dev/null +++ b/template/docs/contributing.rst @@ -0,0 +1 @@ +.. include:: ../CONTRIBUTING.rst \ No newline at end of file diff --git a/template/docs/history.rst b/template/docs/history.rst new file mode 100644 index 0000000..bec23d8 --- /dev/null +++ b/template/docs/history.rst @@ -0,0 +1 @@ +.. include:: ../HISTORY.rst \ No newline at end of file diff --git a/template/docs/index.rst.jinja b/template/docs/index.rst.jinja new file mode 100644 index 0000000..c7d7db9 --- /dev/null +++ b/template/docs/index.rst.jinja @@ -0,0 +1,4 @@ +{{ project_slug }} +{% for _ in project_slug %}={% endfor %} + +.. include:: header.rst \ No newline at end of file diff --git a/template/docs/make.bat.jinja b/template/docs/make.bat.jinja new file mode 100644 index 0000000..0ee37c7 --- /dev/null +++ b/template/docs/make.bat.jinja @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=python -msphinx +) +set SOURCEDIR=. +set BUILDDIR=_build +set SPHINXPROJ={{ project_slug }} + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The Sphinx module was not found. Make sure you have Sphinx installed, + echo.then set the SPHINXBUILD environment variable to point to the full + echo.path of the 'sphinx-build' executable. Alternatively you may add the + echo.Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd \ No newline at end of file diff --git a/template/docs/modules/{{ project_slug }}.rst.jinja b/template/docs/modules/{{ project_slug }}.rst.jinja new file mode 100644 index 0000000..4447190 --- /dev/null +++ b/template/docs/modules/{{ project_slug }}.rst.jinja @@ -0,0 +1,7 @@ +{{ project_slug }} +{% for _ in project_slug %}={% endfor %} + +.. automodule:: {{ project_slug }} + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/template/docs/readme.rst b/template/docs/readme.rst new file mode 100644 index 0000000..e2618b8 --- /dev/null +++ b/template/docs/readme.rst @@ -0,0 +1 @@ +.. mdinclude:: ../README.md \ No newline at end of file diff --git a/template/docs/{{ project_slug }}.rst.jinja b/template/docs/{{ project_slug }}.rst.jinja new file mode 100644 index 0000000..d7d232c --- /dev/null +++ b/template/docs/{{ project_slug }}.rst.jinja @@ -0,0 +1,71 @@ +{{ project_slug }} +{% for _ in project_slug %}={% endfor %} + +.. toctree:: + :maxdepth: 3 + :hidden: + + readme + api_reference + contributing + history + + +{{ project_short_description }} + +{{ project_long_description }} + +.. grid:: 1 2 2 2 + :gutter: 4 + + .. grid-item-card:: + :link: readme.html + :text-align: center + :padding: 0 0 3 3 + + **Getting Started** + ^^^^ + + .. raw:: html + + + + + .. grid-item-card:: + :link: api_reference + :link-type: ref + :text-align: center + :padding: 0 0 3 3 + + **API Reference** + ^^^^ + + .. raw:: html + + + + + .. grid-item-card:: + :link: contributing.html + :text-align: center + :padding: 0 0 3 3 + + **Contributing** + ^^^^ + + .. raw:: html + + + + .. grid-item-card:: + :link: history.html + :text-align: center + :padding: 0 0 3 3 + + **History** + ^^^^ + + .. raw:: html + + + \ No newline at end of file diff --git a/tests/test_copier.py b/tests/test_copier.py index 9916393..071ad8c 100644 --- a/tests/test_copier.py +++ b/tests/test_copier.py @@ -13,6 +13,16 @@ def test_project_folder(default_project): "pyproject.toml", "my_project/__init__.py", "my_project/my_project.py", + "docs/modules/my_project.rst", + "docs/my_project.rst", + "docs/api_reference.rst", + "docs/conf.py", + "docs/contributing.rst", + "docs/history.rst", + "docs/index.rst", + "docs/make.bat", + "docs/Makefile", + "docs/readme.rst", ]) def test_generated_file_exists(default_project, file_name): assert default_project.project_dir.joinpath(file_name).exists() @@ -110,3 +120,30 @@ def test_content_project_slug_init(default_project, desired): content = default_project.project_dir.joinpath('my_project').joinpath( "__init__.py").read_text() assert desired in content, f"{desired!r} is not in content" + + +@pytest.mark.parametrize("desired", [ + "project = 'my_project'", + 'copyright = "2025, The pyfar developers"', + 'author = "The pyfar developers"', + "'numpy': ('https://numpy.org/doc/stable/', None)", + '\n "index": "my_project.html"', + +]) +def test_content_docs_conf(default_project, desired): + content = default_project.project_dir.joinpath('docs/conf.py').read_text() + assert desired in content, f"{desired!r} is not in content" + + +@pytest.mark.parametrize("file_name", [ + 'docs/modules/my_project.rst', + 'docs/my_project.rst', + 'docs/api_reference.rst', + 'docs/index.rst', + 'docs/make.bat', + 'docs/Makefile', +]) +def test_content_docs_multiple_files(default_project, file_name): + content = default_project.project_dir.joinpath(file_name).read_text() + desired = "my_project" + assert desired in content, f"{desired!r} is not in content"