├── .gitignore ├── .travis.yml ├── LICENSE ├── README.rst ├── demo.gif ├── requirements.txt ├── setup.py └── sphinx_reload.py /.gitignore: -------------------------------------------------------------------------------- 1 | Skip to content 2 | This repository 3 | Search 4 | Pull requests 5 | Issues 6 | Marketplace 7 | Explore 8 | @prkumar 9 | Sign out 10 | Watch 2,234 11 | Star 55,603 12 | Fork 24,485 github/gitignore 13 | Code Pull requests 157 Projects 0 Insights 14 | Branch: master Find file Copy pathgitignore/Python.gitignore 15 | 2ec038e 21 days ago 16 | @shiftkey shiftkey Merge pull request #2464 from Cadmus/master 17 | 61 contributors @arcresu @shiftkey @Lucretiel @Harrison-G @jwg4 @Metallicow @misaelnieto @pmsosa @svkampen @vltr @matheussl @hugovk @ghisvail @EvandroLG @DSIW @toanant @2Cubed @weinihou @Visgean @nvie @skuschel @sigo @scari @borntyping @roll @rmax and others 18 | RawBlameHistory 19 | 105 lines (84 sloc) 1.17 KB 20 | # Byte-compiled / optimized / DLL files 21 | __pycache__/ 22 | *.py[cod] 23 | *$py.class 24 | 25 | # C extensions 26 | *.so 27 | 28 | # Distribution / packaging 29 | .Python 30 | build/ 31 | develop-eggs/ 32 | dist/ 33 | downloads/ 34 | eggs/ 35 | .eggs/ 36 | lib/ 37 | lib64/ 38 | parts/ 39 | sdist/ 40 | var/ 41 | wheels/ 42 | *.egg-info/ 43 | .installed.cfg 44 | *.egg 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .coverage 61 | .coverage.* 62 | .cache 63 | nosetests.xml 64 | coverage.xml 65 | *.cover 66 | .hypothesis/ 67 | 68 | # Translations 69 | *.mo 70 | *.pot 71 | 72 | # Django stuff: 73 | *.log 74 | .static_storage/ 75 | .media/ 76 | local_settings.py 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # pyenv 95 | .python-version 96 | 97 | # celery beat schedule file 98 | celerybeat-schedule 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | © 2017 GitHub, Inc. 125 | Terms 126 | Privacy 127 | Security 128 | Status 129 | Help 130 | Contact GitHub 131 | API 132 | Training 133 | Shop 134 | Blog 135 | About 136 | 137 | # Jetbrains 138 | .idea/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | # - '3.3' 5 | - '3.4' 6 | - '3.5' 7 | - '3.6' 8 | - '3.7' 9 | script: 10 | - pip install . 11 | deploy: 12 | provider: pypi 13 | user: prkumar 14 | password: 15 | secure: vZosiQMk80yl4FBgbgg/0S8SuNrI0PfCVNskxpl82E09wtW8gip2wNf+wbcxGqC0GuvBbty5kXyTtLNDWnaW+zmevathz04txsUxL4EPpyl4au6L2jidZcILnpnC4Nn5OYGbHJmtFoOLS4BsKbH9PugJvOgKdkLssGgD/gksVIj2TEihthFAPmnZQCrGzebMfFKT6h9pB5Dt0NntSMmWdF88h4dbogJeI3uEF0XRlno9+Q0Ntz6eM0I3Lw8DxYznfjtxAHGcXrpDkpJhINmORRtadloNrU7lLrMMyTn866AnR9VHWQr5P8KckEQZ/vkt5FC87AtrV2bkuImkrSDrD5+ujQoJxa9a8FMFIhwmpfelgB0aLKhtCnwdjxKFf9kLH6q6nLAhBkol5GoFWfbfXPrvg6zicXnQ11nNbOMNn1K7PVfeDTF0Sq9lyoZHViX8EuP+DBo8dY2cbd3dvTpPy9242bODwdr4kstN0yzIuNBAxw2PXihBWKIQNNqjxRAnbAPn/DiC6XEYaTysht12JV71NdaHdN6gb5P0sAOaHExgWLfBBcWHwktzt5tVbyax+bP1R30xenZ5+cEsR2GvHYFjmGt5tr7WEXm49MCWxNa5hijy5y/XdE3+daSrlKyvSchi4QoiF7XWhOklV/YA3e1KipZxqzhv1IZ0Zv9omE8= 16 | on: 17 | tags: true 18 | python: '3.7' 19 | distributions: "sdist bdist_wheel" 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 P. Raj Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Sphinx-Reload 2 | ************* 3 | 4 | |PyPI Version| |Build Status| 5 | 6 | .. image:: demo.gif 7 | 8 | 9 | Installation 10 | ============ 11 | 12 | To install, use ``pip`` (or ``easy_install``): 13 | 14 | :: 15 | 16 | $ pip install sphinx-reload 17 | 18 | 19 | The package installs the command-line program ``sphinx-reload``: 20 | 21 | :: 22 | 23 | $ sphinx-reload --version 24 | v0.2.0 25 | 26 | Getting Started 27 | =============== 28 | 29 | To begin live previewing your documentation, simply pass the path of your 30 | documentation's root as a command-line argument with ``sphinx-reload``. 31 | Here's an example assuming your documentation's root is under the current 32 | directory and named ``docs``: 33 | 34 | :: 35 | 36 | $ sphinx-reload docs/ 37 | 38 | ``sphinx-reload`` will open a preview in a new tab of your favorite browser 39 | and watch for changes in your documentation's source 40 | files (e.g., any `reStructuredText 41 | `__ files under the documentation's 42 | root). 43 | 44 | To view further usage details, use the script's ``--help`` option: 45 | 46 | :: 47 | 48 | $ sphinx-reload --help 49 | 50 | .. |Build Status| image:: https://travis-ci.org/prkumar/sphinx-reload.svg?branch=master 51 | :target: https://travis-ci.org/prkumar/sphinx-reload 52 | .. |PyPI Version| image:: https://img.shields.io/pypi/v/sphinx-reload.svg 53 | :target: https://pypi.python.org/pypi/sphinx-reload 54 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prkumar/sphinx-reload/dd08a2c3f8bb0954ecacfea6a7a8d41db80b77f0/demo.gif -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | livereload==2.6.3 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Standard library imports 2 | from setuptools import setup, find_packages 3 | 4 | # Local imports 5 | import sphinx_reload 6 | 7 | 8 | def read(filename): 9 | with open(filename) as stream: 10 | return stream.read() 11 | 12 | 13 | metadata = dict({ 14 | "name": "sphinx-reload", 15 | "author": "P. Raj Kumar", 16 | "author_email": "raj.pritvi.kumar@gmail.com", 17 | "version": sphinx_reload.__version__, 18 | "url": "https://github.com/prkumar/sphinx-reload", 19 | "license": "MIT", 20 | "description": "Live preview your Sphinx documentation", 21 | "long_description": read("README.rst"), 22 | "classifiers": [ 23 | "Development Status :: 2 - Pre-Alpha", 24 | "Environment :: Console", 25 | "Intended Audience :: Developers", 26 | "License :: OSI Approved :: MIT License", 27 | "Programming Language :: Python :: 2", 28 | "Programming Language :: Python :: 2.7", 29 | "Programming Language :: Python :: 3", 30 | "Programming Language :: Python :: 3.3", 31 | "Programming Language :: Python :: 3.4", 32 | "Programming Language :: Python :: 3.5", 33 | "Programming Language :: Python :: 3.6", 34 | "Programming Language :: Python :: 3.7", 35 | "Topic :: Software Development :: Documentation" 36 | ], 37 | "keywords": "sphinx live preview sync reload documentation", 38 | "install_requires": [ 39 | "livereload >= 2.5.1", 40 | ], 41 | "py_modules": ["sphinx_reload"], 42 | "entry_points": { 43 | "console_scripts": [ 44 | "sphinx-reload = sphinx_reload:main" 45 | ] 46 | } 47 | }) 48 | 49 | if __name__ == "__main__": 50 | setup(**metadata) 51 | -------------------------------------------------------------------------------- /sphinx_reload.py: -------------------------------------------------------------------------------- 1 | """ 2 | Live reload your Sphinx documentation. 3 | 4 | TODO: 5 | * Support more powerful file patterns (e.g., "**") 6 | """ 7 | # Standard library imports 8 | import argparse 9 | import glob 10 | import os 11 | import sys 12 | 13 | # Third-party imports 14 | import livereload 15 | import livereload.watcher 16 | 17 | __version__ = "0.3.0" 18 | 19 | 20 | class _RecursiveGlobWatcher(livereload.watcher.Watcher): 21 | def is_glob_changed(self, path, ignore=None): 22 | files = glob.glob(path, recursive=True) 23 | return any(self.is_file_changed(f, ignore) for f in files) 24 | 25 | 26 | class _SphinxResourceFactory(object): 27 | _MAKE_CMD = "make html" 28 | _SPHINX_BUILD_CMD_TEMPLATE = "sphinx-build %s %s" 29 | _DEFAULT_BUILD_DIRECTORY = "_build" 30 | 31 | @staticmethod 32 | def get_documentation_root(makefile_path): 33 | if os.path.isfile(makefile_path): 34 | makefile_path = os.path.dirname(makefile_path) 35 | return makefile_path 36 | 37 | @staticmethod 38 | def estimate_source_directory(doc_root): 39 | if os.path.isfile(os.path.join(doc_root, "source", "conf.py")): 40 | return os.path.join(doc_root, "source") 41 | elif os.path.isfile(os.path.join(doc_root, "conf.py")): 42 | return doc_root 43 | else: 44 | raise ValueError( 45 | "Failed to estimate documentation source directory from " 46 | "path '%s'" % doc_root 47 | ) 48 | 49 | def get_build_directory(self, doc_root): 50 | return os.path.join(doc_root, self._DEFAULT_BUILD_DIRECTORY) 51 | 52 | def get_make_command(self, build_directory): 53 | make_cmd = self._MAKE_CMD 54 | if build_directory is not None: 55 | make_cmd += " BUILDDIR=%s" % build_directory 56 | return make_cmd 57 | 58 | def get_sphinx_build_command(self, source_directory, build_directory): 59 | return self._SPHINX_BUILD_CMD_TEMPLATE % ( 60 | source_directory, build_directory 61 | ) 62 | 63 | @staticmethod 64 | def get_html_directory(build_directory): 65 | return os.path.join(build_directory, "html") 66 | 67 | 68 | class SphinxReload(object): 69 | 70 | def __init__(self): 71 | self._spy_on = [] 72 | self._sphinx = _SphinxResourceFactory() 73 | 74 | def watch(self, *glob_names): 75 | self._spy_on.extend(glob_names) 76 | 77 | def _run(self, build_func, root, port, host): 78 | watcher = _RecursiveGlobWatcher() if sys.version_info >= (3, 5) else None 79 | server = livereload.Server(watcher=watcher) 80 | for pattern in self._spy_on: 81 | server.watch(pattern, build_func) 82 | build_func() # Do an initial build. 83 | server.serve( 84 | root=root, 85 | port=port, 86 | open_url_delay=2, 87 | restart_delay=0.3, 88 | host=host 89 | ) 90 | 91 | def run(self, doc_root, build_dir=None, host="localhost", port=5500, 92 | use_makefile=True): 93 | # Set up sphinx resources 94 | doc_root = self._sphinx.get_documentation_root(doc_root) 95 | doc_root = os.path.abspath(doc_root) 96 | if build_dir is None: 97 | build_dir = self._sphinx.get_build_directory(doc_root) 98 | html_dir = self._sphinx.get_html_directory(build_dir) 99 | 100 | if use_makefile: 101 | build_cmd = self._sphinx.get_make_command(build_dir) 102 | else: 103 | source_dir = self._sphinx.estimate_source_directory(doc_root) 104 | build_cmd = self._sphinx.get_sphinx_build_command( 105 | source_dir, build_dir 106 | ) 107 | build_func = livereload.shell(build_cmd, cwd=doc_root) 108 | self._run(build_func, html_dir, port=port, host=host) 109 | 110 | 111 | def _create_parser(): 112 | parser = argparse.ArgumentParser(prog="sphinx-reload") 113 | parser.add_argument( 114 | '--version', 115 | action='version', 116 | version='v%s' % __version__ 117 | ) 118 | parser.add_argument( 119 | "--host", 120 | help="The host to serve files", 121 | default="localhost" 122 | ) 123 | parser.add_argument( 124 | "--build-dir", 125 | help="The desired build directory.", 126 | default=None 127 | ) 128 | parser.add_argument( 129 | "--watch", 130 | metavar="PATTERN", 131 | default=[], 132 | action="append", 133 | help="File patterns to watch for changes, on which documentation " 134 | "should be rebuilt and served again." 135 | ) 136 | parser.add_argument( 137 | "-p", 138 | "--port", 139 | default=5500, 140 | type=int, 141 | help="The port number from which to serve your documentation." 142 | ) 143 | parser.add_argument( 144 | "documentation_root", 145 | help="Your documentation's root directory (i.e., the place where " 146 | "`sphinx-build` put the Makefile)." 147 | ) 148 | return parser 149 | 150 | 151 | def main(): 152 | parser = _create_parser() 153 | namespace = parser.parse_args() 154 | reload = SphinxReload() 155 | 156 | if namespace.watch: 157 | reload.watch(*namespace.watch) 158 | else: 159 | sphinx = _SphinxResourceFactory() 160 | src = sphinx.estimate_source_directory(namespace.documentation_root) 161 | reload.watch(os.path.abspath(src)) 162 | 163 | reload.run( 164 | namespace.documentation_root, 165 | build_dir=namespace.build_dir, 166 | port=namespace.port, 167 | host=namespace.host, 168 | ) 169 | 170 | 171 | if __name__ == "__main__": 172 | main() 173 | --------------------------------------------------------------------------------