├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── doc ├── Makefile ├── requirements.txt └── source │ ├── conf.py │ ├── docpkg.config.rst │ ├── docpkg.main.rst │ └── index.rst ├── docpkg ├── __init__.py ├── config.py └── main.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.8" 4 | dist: bionic # https://docs.travis-ci.com/user/languages/python/#python-37-and-higher 5 | install: 6 | - travis_retry pip install -U -r doc/requirements.txt 7 | - travis_retry pip install -e . 8 | script: 9 | - 'true' # no tests to run 10 | after_success: 11 | - make -C doc html 12 | branches: 13 | only: 14 | - main 15 | deploy: 16 | provider: pages 17 | skip_cleanup: true 18 | github_token: $GH_TOKEN 19 | keep_history: true 20 | on: 21 | branch: main 22 | local_dir: doc/build/html 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Python Continuous Documentation 2 | =============================== 3 | 4 | [![Build Status](https://travis-ci.com/icgood/continuous-docs.svg?branch=main)](https://travis-ci.com/icgood/continuous-docs) 5 | [![Docs](https://img.shields.io/badge/docs-latest-informational)](https://icgood.github.io/continuous-docs/) 6 | 7 | ## Introduction 8 | 9 | If you own a Python library that uses [Sphinx formatted][1] docstrings, it's 10 | easy to get started turning these docstrings into beautiful HTML, hosted on 11 | [GitHub Pages][4], updated every time you push to GitHub. 12 | 13 | This repository is intended to be a working example of this method, check out 14 | [the docs][2]. 15 | 16 | This tutorial uses [Travis CI][9], but there is an older version 17 | [written for Jenkins](https://github.com/icgood/continuous-docs/tree/jenkins). 18 | 19 | #### Why not use [ReadTheDocs][8]? 20 | 21 | Please do! This tutorial simply provides an alternative. 22 | 23 | ## Setting Up Your Project 24 | 25 | ### Installation 26 | 27 | As always, I suggest using a [virtualenv][3] for local Python development. 28 | Inside your virtualenv, run: 29 | 30 | pip install sphinx 31 | 32 | ### Setup 33 | 34 | Create a `doc` directory in your project, we'll add it to git later: 35 | 36 | ```bash 37 | mkdir -p doc 38 | cd doc 39 | ``` 40 | 41 | Create the basic configuration and file structure: 42 | 43 | sphinx-quickstart 44 | 45 | There are several questions you need to give a non-default answer to: 46 | 47 | > Separate source and build directories (y/N) [n]: y 48 | > Project name: yourproject 49 | > Author name(s): Your Name 50 | > Project version: 1.2.3 51 | > autodoc: automatically insert docstrings from modules (y/N) [n]: y 52 | > intersphinx: link between Sphinx documentation of different projects (y/N) [n]: y 53 | 54 | ### Creating the Doc Layout 55 | 56 | When running `sphinx-quickstart`, you specified `index.rst` as the starting 57 | point for your documentation pages. With Sphinx, you'll need every page linked 58 | either directly or indirectly from `index.rst` using the `.. toctree::` 59 | directive. Let's consider the following Python package: 60 | 61 | docpkg/__init__.py 62 | docpkg/main.py 63 | docpkg/config.py 64 | 65 | One package, three modules. Replace your `index.rst` with the following: 66 | 67 | ```rst 68 | ``docpkg`` Package 69 | ================== 70 | 71 | .. automodule:: docpkg 72 | :members: 73 | 74 | ------------------- 75 | 76 | **Sub-Modules:** 77 | 78 | .. toctree:: 79 | 80 | docpkg.main 81 | docpkg.config 82 | ``` 83 | 84 | Now we're going to create two more files, `docpkg.main.rst` and 85 | `docpkg.config.rst`. I'll give you `docpkg.main.rst`, create 86 | `docpkg.config.rst` the same way: 87 | 88 | ```rst 89 | ``docpkg.main`` Module 90 | ======================== 91 | 92 | .. automodule:: docpkg.main 93 | :members: 94 | ``` 95 | 96 | As you add more modules to your project, they need to be added to the 97 | documentation structure. You can obviously put more than one `.. automodule::` 98 | on a page, at your discretion. 99 | 100 | ### Building the Docs Locally 101 | 102 | Once you have your doc layout created, you can build your documentation from 103 | the `doc/` directory with: 104 | 105 | make html 106 | 107 | Try navigating to `doc/build/html/index.html` in your browser! 108 | 109 | ### Add Documentation Requirements 110 | 111 | I usually keep a `doc/requirements.txt` file around so that I don't have to 112 | hard-code the dependencies necessary to build the documentation anywhere. For 113 | now, this may only contain one: 114 | 115 | sphinx 116 | 117 | In the future, you might add others with themes or sphinx extensions. 118 | 119 | ### Committing to Git 120 | 121 | The entire `doc/` directory tree does not necessarily need to be put into git. 122 | The following should suffice: 123 | 124 | ```bash 125 | git add doc/requirements.txt doc/Makefile doc/source/conf.py doc/source/*.rst 126 | git commit 127 | ``` 128 | 129 | ### Creating GitHub Pages Branch 130 | 131 | GitHub will generate a [GitHub Pages][4] site for any 132 | repository that has a `gh-pages` branch. Let's set ours up now: 133 | 134 | ```bash 135 | git checkout --orphan gh-pages 136 | git rm -rf . 137 | ``` 138 | 139 | Now let's add the `.nojekyll` file. This tells GitHub that our content does not 140 | use Jekyll for rendering. Finally, commit and push: 141 | 142 | ```bash 143 | touch .nojekyll 144 | git add .nojekyll 145 | git commit -m 'initial commit' 146 | git push origin gh-pages 147 | ``` 148 | 149 | ## Setting up Travis CI 150 | 151 | Thus far we have only built docs locally. We will now configure [Travis CI][9] 152 | to build and deploy the docs any time we merge changes to our default branch. 153 | It does so by committing the files under `doc/build/html` into the `gh-pages` 154 | branch and pushing using your [GitHub Personal Access Token][10]. 155 | 156 | All the steps below operate on the `.travis.yml` file in your project 157 | repository. It is assumed that you have already setup Travis to build your 158 | project, otherwise [start there][12]. 159 | 160 | ### Setting the Version 161 | 162 | Because Python projects usually declare their version in `setup.py`, we want 163 | Sphinx to look there to find the project version so our API docs reflect the 164 | correct value. 165 | 166 | In `doc/source/conf.py`, you likely see this value: 167 | 168 | ```python 169 | release = '1.2.3' 170 | ``` 171 | 172 | Replace that line with one that reads the version correctly: 173 | 174 | ```python 175 | import pkg_resources 176 | 177 | # Read the project version from setup.py 178 | release = pkg_resources.require(project)[0].version 179 | ``` 180 | 181 | ### Building the Docs 182 | 183 | To start, we need to make sure our build has Sphinx and other doc dependencies 184 | installed, so add a new `install` step: 185 | 186 | ```yaml 187 | install: 188 | - travis_retry pip install -U -r doc/requirements.txt 189 | # ... 190 | ``` 191 | 192 | Next, add an `after_success` step to build the docs: 193 | 194 | ```yaml 195 | after_success: 196 | - make -C doc html 197 | # ... 198 | ``` 199 | 200 | ### Deploying to GitHub Pages 201 | 202 | Travis can [deploy to GitHub Pages][11] on build with the simple addition of a 203 | `deploy` section: 204 | 205 | ```yaml 206 | deploy: 207 | provider: pages 208 | skip_cleanup: true 209 | github_token: $GITHUB_TOKEN 210 | keep_history: true 211 | on: 212 | branch: main # the default branch name 213 | local_dir: doc/build/html 214 | ``` 215 | 216 | If you have not already, turn on [GitHub Pages][4] for your repository using 217 | the `gh-pages` branch under *Settings* → *GitHub Pages*. 218 | 219 | ### Setting Your GitHub Token 220 | 221 | **This step should be taken very carefully!** 222 | 223 | First, you should have a [token][10] with `repo` scope, so that Travis may 224 | access and update your `gh-pages` branch. You will need to copy this token 225 | shortly. 226 | 227 | Navigate to your project settings in Travis. Under the *Environment Variables* 228 | section, add a new variable: 229 | 230 | | NAME | VALUE | BRANCH | DISPLAY VALUE IN BUILD LOG | 231 | | ---- | ----- | ------ | -------------------------- | 232 | | `GITHUB_TOKEN` | *paste your token here* | `main` | **LEAVE UNCHECKED** | 233 | 234 | Change `main` if that is not the name of your default branch. Be absolutely sure 235 | not to check *Display value in build log* or your API token may be leaked and 236 | should be deleted. 237 | 238 | ### Commit 239 | 240 | Commit and push your `.travis.yml` updates, and watch your Travis build logs to 241 | watch it in action. If all goes well, your project's GitHub Pages site should be 242 | now contain your latest and greatest API documentation. 243 | 244 | :tada: 245 | 246 | [1]: https://pythonhosted.org/an_example_pypi_project/sphinx.html#full-code-example 247 | [2]: https://icgood.github.io/continuous-docs/ 248 | [3]: https://docs.python.org/3/library/venv.html 249 | [4]: https://pages.github.com/ 250 | [6]: https://pypi.python.org/pypi 251 | [7]: https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html 252 | [8]: https://readthedocs.org/ 253 | [9]: https://travis-ci.com/ 254 | [10]: https://github.com/settings/tokens 255 | [11]: https://docs.travis-ci.com/user/deployment/pages/ 256 | [12]: https://docs.travis-ci.com/user/tutorial/ 257 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx-autodoc-typehints 3 | cloud_sptheme 4 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | import pkg_resources 18 | 19 | import cloud_sptheme as csp 20 | 21 | 22 | # -- Project information ----------------------------------------------------- 23 | 24 | project = 'docpkg' 25 | copyright = '2020, Ian Good' 26 | author = 'Ian Good' 27 | 28 | # The short X.Y version 29 | project_version = pkg_resources.require(project)[0].version 30 | version_parts = project_version.split('.') 31 | version = '.'.join(version_parts[0:2]) 32 | # The full version, including alpha/beta/rc tags 33 | release = project_version 34 | 35 | 36 | # -- General configuration --------------------------------------------------- 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.intersphinx', 44 | 'sphinx.ext.napoleon', 45 | 'sphinx.ext.githubpages', 46 | 'sphinx_autodoc_typehints', 47 | ] 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | templates_path = ['_templates'] 51 | 52 | # List of patterns, relative to source directory, that match files and 53 | # directories to ignore when looking for source files. 54 | # This pattern also affects html_static_path and html_extra_path. 55 | exclude_patterns = [] 56 | 57 | 58 | # -- Options for HTML output ------------------------------------------------- 59 | 60 | # The theme to use for HTML and HTML Help pages. See the documentation for 61 | # a list of builtin themes. 62 | # 63 | html_theme = 'cloud' 64 | 65 | # set the theme path to point to cloud's theme data 66 | html_theme_path = [csp.get_theme_dir()] 67 | 68 | # Add any paths that contain custom static files (such as style sheets) here, 69 | # relative to this directory. They are copied after the builtin static files, 70 | # so a file named "default.css" will overwrite the builtin "default.css". 71 | html_static_path = ['_static'] 72 | 73 | # -- Extension configuration ------------------------------------------------- 74 | 75 | autodoc_member_order = 'bysource' 76 | autodoc_default_flags = ['show-inheritance'] 77 | napoleon_numpy_docstring = False 78 | 79 | intersphinx_mapping = {'https://docs.python.org/3/': None} 80 | -------------------------------------------------------------------------------- /doc/source/docpkg.config.rst: -------------------------------------------------------------------------------- 1 | ``docpkg.config`` Module 2 | ======================== 3 | 4 | .. automodule:: docpkg.config 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/source/docpkg.main.rst: -------------------------------------------------------------------------------- 1 | ``docpkg.main`` Module 2 | ====================== 3 | 4 | .. automodule:: docpkg.main 5 | :members: 6 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | ``docpkg`` Package 2 | =================== 3 | 4 | .. automodule:: docpkg 5 | :members: 6 | 7 | ------------------- 8 | 9 | **Sub-Modules:** 10 | 11 | .. toctree:: 12 | 13 | docpkg.main 14 | docpkg.config 15 | -------------------------------------------------------------------------------- /docpkg/__init__.py: -------------------------------------------------------------------------------- 1 | """This is the base package for ``docpkg``. 2 | 3 | """ 4 | 5 | 6 | class DocpkgError(Exception): 7 | """The base error for the package. All custom exceptions raised will derive 8 | from this exception. 9 | 10 | """ 11 | pass 12 | -------------------------------------------------------------------------------- /docpkg/config.py: -------------------------------------------------------------------------------- 1 | """This module contains the configuration routines.""" 2 | 3 | from typing import Optional, TypeVar 4 | 5 | T = TypeVar('T') 6 | 7 | 8 | class MyConfig: 9 | """Loads and manages the configuration. 10 | 11 | Args: 12 | filename: The filename to load configs from. 13 | 14 | """ 15 | 16 | def __init__(self, filename: str) -> None: 17 | pass 18 | 19 | def get_option(self, name: str, default: Optional[T] = None) -> T: 20 | """Returns the requested option from the loaded configs. 21 | 22 | :param name: The option name to get. 23 | :param default: The default to return, if the option was not found 24 | in the configs. 25 | 26 | """ 27 | pass 28 | -------------------------------------------------------------------------------- /docpkg/main.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def main() -> None: 4 | """This is the entry point of the application! 5 | 6 | Raises: 7 | :class:`KeyboardInterrupt`, :class:`SystemExit` 8 | 9 | """ 10 | pass 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | from setuptools import setup, find_packages 3 | 4 | 5 | setup(name='docpkg', 6 | version='0.1.0', 7 | author='Ian Good', 8 | author_email='icgood@gmail.com', 9 | packages=find_packages()) 10 | --------------------------------------------------------------------------------