├── .gitignore ├── .isort.cfg ├── .travis.yml ├── README.rst ├── docs ├── Makefile ├── conf.py ├── index.rst ├── spec.rst └── steps.rst ├── integration_tests ├── __init__.py ├── bad_encode_project │ └── schaue p�laylist an.py ├── minimal_project │ ├── Makefile │ ├── conf.py │ ├── index.rst │ ├── make.bat │ └── readthedocs.yml ├── test_minimal_project.py ├── test_with_python_package.py └── with_python_package │ ├── .gitignore │ ├── docs │ ├── Makefile │ ├── conf.py │ ├── index.rst │ └── make.bat │ ├── my_python_package │ └── __init__.py │ ├── readthedocs.yml │ └── setup.py ├── prospector.yml ├── readthedocs.yml ├── readthedocs_build ├── __init__.py ├── build.py ├── builder │ ├── __init__.py │ ├── base.py │ ├── sphinx.py │ ├── test_base.py │ ├── test_sphinx.py │ ├── test_virtualenv.py │ ├── utils.py │ └── virtualenv.py ├── cli.py ├── config │ ├── __init__.py │ ├── config.py │ ├── find.py │ ├── parser.py │ ├── test_config.py │ ├── test_find.py │ ├── test_parser.py │ ├── test_validation.py │ └── validation.py ├── test_build.py ├── test_cli.py ├── testing │ ├── __init__.py │ ├── test_utils.py │ └── utils.py └── utils.py ├── requirements ├── linting.txt └── tests.txt ├── setup.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | _build 4 | _readthedocs_build 5 | autoapi 6 | _build_rtd 7 | .idea/ 8 | .tox 9 | .cache 10 | .python-version 11 | dist 12 | tags 13 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | line_length=80 3 | indent=' ' 4 | multi_line_output=4 5 | default_section=THIRDPARTY 6 | known_third_party=mock 7 | sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER 8 | add_imports=from __future__ import division, from __future__ import print_function, from __future__ import unicode_literals 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.4 5 | - 3.5 6 | - 3.6 7 | sudo: false 8 | install: 9 | - pip install tox-travis 10 | script: 11 | - tox 12 | notifications: 13 | slack: 14 | rooms: 15 | - readthedocs:y3hjODOi7EIz1JAbD1Zb41sz#random 16 | on_success: change 17 | on_failure: always 18 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Read the Docs Builder 2 | ===================== 3 | 4 | .. important:: 5 | 6 | This repo was moved to the 7 | `Read the Docs code `__. 8 | Issues and pull requests should be done there. 9 | 10 | .. warning:: 11 | This repo is only used as a configuration parser for readthedocs.org builds, 12 | and it's unsupported for other uses. 13 | 14 | Install 15 | ------- 16 | 17 | Install with pip:: 18 | 19 | pip install readthedocs-build 20 | 21 | Library Use 22 | ----------- 23 | 24 | Example uses of this library is: 25 | 26 | * https://github.com/rtfd/readthedocs.org/blob/0e1112f96e3ba344271a44305028a811072fd10a/readthedocs/doc_builder/config.py#L8-L9 27 | * https://github.com/rtfd/readthedocs.org/blob/0e1112f96e3ba344271a44305028a811072fd10a/readthedocs/projects/tasks.py#L27 28 | 29 | 30 | Development 31 | ----------- 32 | 33 | Just run:: 34 | 35 | pip install -e . 36 | 37 | This will install the project into your environment, and have it pick up 38 | changes as you edit them in the code. 39 | 40 | To run the tests:: 41 | 42 | tox 43 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ReadtheDocsBuild.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ReadtheDocsBuild.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/ReadtheDocsBuild" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ReadtheDocsBuild" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Read the Docs Build documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Mar 12 20:30:01 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = ['sphinx.ext.doctest'] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | source_suffix = ['.rst'] 39 | 40 | # The encoding of source files. 41 | #source_encoding = 'utf-8-sig' 42 | 43 | # The master toctree document. 44 | master_doc = 'index' 45 | 46 | # General information about the project. 47 | project = u'Read the Docs Build' 48 | copyright = u'2015, Read the Docs' 49 | author = u'Read the Docs' 50 | 51 | # The version info for the project you're documenting, acts as replacement for 52 | # |version| and |release|, also used in various other places throughout the 53 | # built documents. 54 | # 55 | # The short X.Y version. 56 | version = '2.0' 57 | # The full version, including alpha/beta/rc tags. 58 | release = '2.0.x' 59 | 60 | # The language for content autogenerated by Sphinx. Refer to documentation 61 | # for a list of supported languages. 62 | # 63 | # This is also used if you do content translation via gettext catalogs. 64 | # Usually you set "language" from the command line for these cases. 65 | language = None 66 | 67 | # There are two options for replacing |today|: either, you set today to some 68 | # non-false value, then it is used: 69 | #today = '' 70 | # Else, today_fmt is used as the format for a strftime call. 71 | #today_fmt = '%B %d, %Y' 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | exclude_patterns = ['_build'] 76 | 77 | # The reST default role (used for this markup: `text`) to use for all 78 | # documents. 79 | #default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | #add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | #add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | #show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | #modindex_common_prefix = [] 97 | 98 | # If true, keep warnings as "system message" paragraphs in the built documents. 99 | #keep_warnings = False 100 | 101 | # If true, `todo` and `todoList` produce output, else they produce nothing. 102 | todo_include_todos = False 103 | 104 | 105 | # -- Options for HTML output ---------------------------------------------- 106 | 107 | # The theme to use for HTML and HTML Help pages. See the documentation for 108 | # a list of builtin themes. 109 | html_theme = 'default' 110 | 111 | # Theme options are theme-specific and customize the look and feel of a theme 112 | # further. For a list of options available for each theme, see the 113 | # documentation. 114 | #html_theme_options = {} 115 | 116 | # Add any paths that contain custom themes here, relative to this directory. 117 | #html_theme_path = [] 118 | 119 | # The name for this set of Sphinx documents. If None, it defaults to 120 | # " v documentation". 121 | #html_title = None 122 | 123 | # A shorter title for the navigation bar. Default is the same as html_title. 124 | #html_short_title = None 125 | 126 | # The name of an image file (relative to this directory) to place at the top 127 | # of the sidebar. 128 | #html_logo = None 129 | 130 | # The name of an image file (within the static path) to use as favicon of the 131 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 132 | # pixels large. 133 | #html_favicon = None 134 | 135 | # Add any paths that contain custom static files (such as style sheets) here, 136 | # relative to this directory. They are copied after the builtin static files, 137 | # so a file named "default.css" will overwrite the builtin "default.css". 138 | html_static_path = ['_static'] 139 | 140 | # Add any extra paths that contain custom files (such as robots.txt or 141 | # .htaccess) here, relative to this directory. These files are copied 142 | # directly to the root of the documentation. 143 | #html_extra_path = [] 144 | 145 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 146 | # using the given strftime format. 147 | #html_last_updated_fmt = '%b %d, %Y' 148 | 149 | # If true, SmartyPants will be used to convert quotes and dashes to 150 | # typographically correct entities. 151 | #html_use_smartypants = True 152 | 153 | # Custom sidebar templates, maps document names to template names. 154 | #html_sidebars = {} 155 | 156 | # Additional templates that should be rendered to pages, maps page names to 157 | # template names. 158 | #html_additional_pages = {} 159 | 160 | # If false, no module index is generated. 161 | #html_domain_indices = True 162 | 163 | # If false, no index is generated. 164 | #html_use_index = True 165 | 166 | # If true, the index is split into individual pages for each letter. 167 | #html_split_index = False 168 | 169 | # If true, links to the reST sources are added to the pages. 170 | #html_show_sourcelink = True 171 | 172 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 173 | #html_show_sphinx = True 174 | 175 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 176 | #html_show_copyright = True 177 | 178 | # If true, an OpenSearch description file will be output, and all pages will 179 | # contain a tag referring to it. The value of this option must be the 180 | # base URL from which the finished HTML is served. 181 | #html_use_opensearch = '' 182 | 183 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 184 | #html_file_suffix = None 185 | 186 | # Language to be used for generating the HTML full-text search index. 187 | # Sphinx supports the following languages: 188 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 189 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 190 | #html_search_language = 'en' 191 | 192 | # A dictionary with options for the search language support, empty by default. 193 | # Now only 'ja' uses this config value 194 | #html_search_options = {'type': 'default'} 195 | 196 | # The name of a javascript file (relative to the configuration directory) that 197 | # implements a search results scorer. If empty, the default will be used. 198 | #html_search_scorer = 'scorer.js' 199 | 200 | # Output file base name for HTML help builder. 201 | htmlhelp_basename = 'ReadtheDocsBuilddoc' 202 | 203 | # -- Options for LaTeX output --------------------------------------------- 204 | 205 | latex_elements = { 206 | # The paper size ('letterpaper' or 'a4paper'). 207 | #'papersize': 'letterpaper', 208 | 209 | # The font size ('10pt', '11pt' or '12pt'). 210 | #'pointsize': '10pt', 211 | 212 | # Additional stuff for the LaTeX preamble. 213 | #'preamble': '', 214 | 215 | # Latex figure (float) alignment 216 | #'figure_align': 'htbp', 217 | } 218 | 219 | # Grouping the document tree into LaTeX files. List of tuples 220 | # (source start file, target name, title, 221 | # author, documentclass [howto, manual, or own class]). 222 | latex_documents = [ 223 | (master_doc, 'ReadtheDocsBuild.tex', u'Read the Docs Build Documentation', 224 | u'Read the Docs', 'manual'), 225 | ] 226 | 227 | # The name of an image file (relative to this directory) to place at the top of 228 | # the title page. 229 | #latex_logo = None 230 | 231 | # For "manual" documents, if this is true, then toplevel headings are parts, 232 | # not chapters. 233 | #latex_use_parts = False 234 | 235 | # If true, show page references after internal links. 236 | #latex_show_pagerefs = False 237 | 238 | # If true, show URL addresses after external links. 239 | #latex_show_urls = False 240 | 241 | # Documents to append as an appendix to all manuals. 242 | #latex_appendices = [] 243 | 244 | # If false, no module index is generated. 245 | #latex_domain_indices = True 246 | 247 | 248 | # -- Options for manual page output --------------------------------------- 249 | 250 | # One entry per manual page. List of tuples 251 | # (source start file, name, description, authors, manual section). 252 | man_pages = [ 253 | (master_doc, 'readthedocsbuild', u'Read the Docs Build Documentation', 254 | [author], 1) 255 | ] 256 | 257 | # If true, show URL addresses after external links. 258 | #man_show_urls = False 259 | 260 | 261 | # -- Options for Texinfo output ------------------------------------------- 262 | 263 | # Grouping the document tree into Texinfo files. List of tuples 264 | # (source start file, target name, title, author, 265 | # dir menu entry, description, category) 266 | texinfo_documents = [ 267 | (master_doc, 'ReadtheDocsBuild', u'Read the Docs Build Documentation', 268 | author, 'ReadtheDocsBuild', 'One line description of project.', 269 | 'Miscellaneous'), 270 | ] 271 | 272 | # Documents to append as an appendix to all manuals. 273 | #texinfo_appendices = [] 274 | 275 | # If false, no module index is generated. 276 | #texinfo_domain_indices = True 277 | 278 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 279 | #texinfo_show_urls = 'footnote' 280 | 281 | # If true, do not generate a @detailmenu in the "Top" node's menu. 282 | #texinfo_no_detailmenu = False 283 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Read the Docs Build documentation master file, created by 2 | sphinx-quickstart on Thu Mar 12 20:30:01 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Read the Docs Build's documentation! 7 | =============================================== 8 | 9 | This is a fine set of docs 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :glob: 14 | 15 | steps 16 | spec 17 | 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | 27 | -------------------------------------------------------------------------------- /docs/spec.rst: -------------------------------------------------------------------------------- 1 | Specification 2 | ============= 3 | 4 | ``rtd-build`` is a tool that can build Sphinx documentation. It is used 5 | internally by readthedocs.org to build all Sphinx based documentation. 6 | 7 | Creating a build works like this: 8 | 9 | - change into the root of the project that you want to build the documentation 10 | for 11 | - run ``rtd-build`` 12 | 13 | ``rtd-build`` will then perform the following actions: 14 | 15 | - it searches for all ``readthedocs.yml`` files below the current directory 16 | and merges all found files into a list of configurations 17 | - it iterates over all configurations (order is not garuanteed) and performs 18 | the following actions for each: 19 | 20 | - create a fresh virtualenv 21 | - ``cd`` into the base directory of the documentation 22 | - ``pip install ...`` whatever is configured in the config 23 | - ``python setup.py install`` if configured (from the path specified in 24 | ``python.setup_path``. 25 | - run ``sphinx-build`` 26 | 27 | ``readthedocs.yml`` spec 28 | ------------------------ 29 | 30 | A ``readthedocs.yml`` file must be in YAML format. If the top level is a block 31 | sequence (i.e. a list), then the file describes multiple configurations. If 32 | the top level is mapping, then the file describes a single configuration. 33 | 34 | A few complete examples: 35 | 36 | - config file living at the root of the repository, configuring only one 37 | documentation: 38 | 39 | .. code-block:: yaml 40 | 41 | # in /readthedocs.yml 42 | base: docs/ 43 | type: sphinx 44 | formats: 45 | html: true 46 | pdf: true 47 | python: 48 | setup_install: true 49 | 50 | 51 | - A project with multiple documentations. The on in ``docs/`` is the english 52 | one and considered the main documentation. ``docs-de/`` contains a second 53 | documentation which is the german translation of ``docs/``. 54 | 55 | .. code-block:: yaml 56 | 57 | - name: en 58 | type: sphinx 59 | language: en 60 | base: docs/ 61 | python: 62 | requirements: 63 | - "-rrequirements.txt" 64 | setup_install: true 65 | 66 | - name: de 67 | extend: en 68 | language: de 69 | base: docs-de/ 70 | 71 | 72 | Following mapping keys are supported (all but the marked once are optional): 73 | 74 | ``name`` 75 | An identifier of the documentation that this config is about. It might 76 | simply be ``docs``, or ``docs-de``. It's arbitrary, but must be unique 77 | with in all readthedocs configs in one project. It can be used to refer to 78 | a different configuration. 79 | 80 | It defaults to the path of the file relative to the project root. E.g. the 81 | config in ``api-docs/readthedocs.yml`` will get the name 82 | ``api-docs/readthedocs.yml`` by default. Since the ``name`` must be 83 | unique, it's an error to have two configurations without a name in the 84 | same file. 85 | 86 | ``base`` 87 | The path to the root directory of the documentation that this config is 88 | about. That is usually the path that contains the ``conf.py`` file. It 89 | defaults to the directory that the ``readthedocs.yml`` file lives in. All 90 | commands for building the documentation will have the ``base`` set as 91 | working directory. 92 | 93 | ``type``, *required* 94 | The documentation framework that this documentation is written in. Allowed 95 | values are: 96 | 97 | - ``sphinx`` 98 | - ``mkdocs`` 99 | 100 | ``formats`` 101 | A mapping of format types to shall be built. The following formats are 102 | supported: 103 | 104 | - ``html``, default: ``true`` 105 | - ``pdf``, default: ``false`` 106 | - ``epub``, default: ``false`` 107 | 108 | ``build`` 109 | Options for setting the docker configuration. 110 | 111 | ``image`` 112 | This sets the build image to use on the build, as defined `here `_. 113 | 114 | ``python`` 115 | Python specific configuration. All builds are executed inside a 116 | virtualenv. This config can customize the virtualenv before running the 117 | build. The following subkeys are allowed: 118 | 119 | ``pip_requirements`` 120 | A list of arguments that will be passed down to a ``pip install`` 121 | call. You can specify requirements files with ``-r 122 | path/to/requirements.txt``. Accepts version modifiers like 123 | ``setuptools>=18.0``. 124 | 125 | ``pip_install`` 126 | If ``true``, ``pip install .`` will be executed before building the 127 | docs. Mutually exclusive with ``setup_install``. 128 | 129 | ``extra_requirements`` 130 | A list of `extra requirements`_ sections to install in addition to 131 | the `package default dependencies`_. Only used if the ``pip_install`` 132 | option above is ``true``. 133 | 134 | ``setup_install`` 135 | If ``true``, then ``python setup.py install`` will be executed before 136 | building the docs. Mutually exclusive with ``pip_install``. 137 | 138 | ``version`` 139 | The Python interpreter version to use for all build calls. This value 140 | should be a float or integer value. 141 | 142 | Supported versions can be configured on config instantiation by passing 143 | in the following to the `env_config`:: 144 | 145 | { 146 | 'python': { 147 | 'supported_versions': [2, 2.7, 3, 3.5], 148 | } 149 | } 150 | 151 | ``language`` 152 | The language the doc is written in. Defaults to empty string. 153 | 154 | 155 | .. _extra requirements: http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras-optional-features-with-their-own-dependencies 156 | .. _package default dependencies: http://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-dependencies 157 | -------------------------------------------------------------------------------- /docs/steps.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /integration_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readthedocs/readthedocs-build/e271e1861b1e68f9c15e08e84cc2b85fbfb45310/integration_tests/__init__.py -------------------------------------------------------------------------------- /integration_tests/bad_encode_project/schaue p�laylist an.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readthedocs/readthedocs-build/e271e1861b1e68f9c15e08e84cc2b85fbfb45310/integration_tests/bad_encode_project/schaue p�laylist an.py -------------------------------------------------------------------------------- /integration_tests/minimal_project/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/minimal_project.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/minimal_project.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/minimal_project" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/minimal_project" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /integration_tests/minimal_project/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # minimal_project documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Aug 10 17:33:06 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'minimal_project' 50 | copyright = u'2015, Gregor Müllegger' 51 | author = u'Gregor Müllegger' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '0.0.1' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '0.0.1' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = False 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = 'alabaster' 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | #html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | #html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | #html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | #html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | #html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | #html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | html_static_path = ['_static'] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | #html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Language to be used for generating the HTML full-text search index. 189 | # Sphinx supports the following languages: 190 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 191 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 192 | #html_search_language = 'en' 193 | 194 | # A dictionary with options for the search language support, empty by default. 195 | # Now only 'ja' uses this config value 196 | #html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | #html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = 'minimal_projectdoc' 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | 217 | # Latex figure (float) alignment 218 | #'figure_align': 'htbp', 219 | } 220 | 221 | # Grouping the document tree into LaTeX files. List of tuples 222 | # (source start file, target name, title, 223 | # author, documentclass [howto, manual, or own class]). 224 | latex_documents = [ 225 | (master_doc, 'minimal_project.tex', u'minimal\\_project Documentation', 226 | u'Gregor Müllegger', 'manual'), 227 | ] 228 | 229 | # The name of an image file (relative to this directory) to place at the top of 230 | # the title page. 231 | #latex_logo = None 232 | 233 | # For "manual" documents, if this is true, then toplevel headings are parts, 234 | # not chapters. 235 | #latex_use_parts = False 236 | 237 | # If true, show page references after internal links. 238 | #latex_show_pagerefs = False 239 | 240 | # If true, show URL addresses after external links. 241 | #latex_show_urls = False 242 | 243 | # Documents to append as an appendix to all manuals. 244 | #latex_appendices = [] 245 | 246 | # If false, no module index is generated. 247 | #latex_domain_indices = True 248 | 249 | 250 | # -- Options for manual page output --------------------------------------- 251 | 252 | # One entry per manual page. List of tuples 253 | # (source start file, name, description, authors, manual section). 254 | man_pages = [ 255 | (master_doc, 'minimal_project', u'minimal_project Documentation', 256 | [author], 1) 257 | ] 258 | 259 | # If true, show URL addresses after external links. 260 | #man_show_urls = False 261 | 262 | 263 | # -- Options for Texinfo output ------------------------------------------- 264 | 265 | # Grouping the document tree into Texinfo files. List of tuples 266 | # (source start file, target name, title, author, 267 | # dir menu entry, description, category) 268 | texinfo_documents = [ 269 | (master_doc, 'minimal_project', u'minimal_project Documentation', 270 | author, 'minimal_project', 'One line description of project.', 271 | 'Miscellaneous'), 272 | ] 273 | 274 | # Documents to append as an appendix to all manuals. 275 | #texinfo_appendices = [] 276 | 277 | # If false, no module index is generated. 278 | #texinfo_domain_indices = True 279 | 280 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 281 | #texinfo_show_urls = 'footnote' 282 | 283 | # If true, do not generate a @detailmenu in the "Top" node's menu. 284 | #texinfo_no_detailmenu = False 285 | -------------------------------------------------------------------------------- /integration_tests/minimal_project/index.rst: -------------------------------------------------------------------------------- 1 | .. minimal_project documentation master file, created by 2 | sphinx-quickstart on Mon Aug 10 17:33:06 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to minimal_project's documentation! 7 | =========================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /integration_tests/minimal_project/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\minimal_project.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\minimal_project.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /integration_tests/minimal_project/readthedocs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | type: sphinx 3 | -------------------------------------------------------------------------------- /integration_tests/test_minimal_project.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import subprocess 4 | 5 | from readthedocs_build.utils import cd 6 | 7 | 8 | def test_minimal_project(tmpdir): 9 | base_path = os.path.dirname(os.path.abspath(__file__)) 10 | out_dir = tmpdir.join('out') 11 | with cd(base_path): 12 | process = subprocess.Popen([ 13 | 'rtd-build', 14 | 'minimal_project', 15 | '--outdir={}'.format(out_dir) 16 | ]) 17 | process.wait() 18 | assert process.returncode == 0 19 | 20 | # Assert that the index was built. 21 | index_html = out_dir.join('docs', 'html', 'index.html') 22 | assert index_html.exists() 23 | assert re.search( 24 | 'Welcome to minimal_project.*', 25 | index_html.read()) 26 | -------------------------------------------------------------------------------- /integration_tests/test_with_python_package.py: -------------------------------------------------------------------------------- 1 | import re 2 | import os 3 | import subprocess 4 | 5 | from readthedocs_build.utils import cd 6 | 7 | 8 | def test_with_python_package(tmpdir): 9 | base_path = os.path.dirname(os.path.abspath(__file__)) 10 | out_dir = tmpdir.join('out') 11 | with cd(base_path): 12 | process = subprocess.Popen([ 13 | 'rtd-build', 14 | 'with_python_package', 15 | '--outdir={}'.format(out_dir) 16 | ]) 17 | process.wait() 18 | assert process.returncode == 0 19 | 20 | # Assert that the index was built. 21 | index_html = out_dir.join('docs', 'html', 'index.html') 22 | assert index_html.exists() 23 | assert re.search( 24 | 'Welcome to with_python_package.*', 25 | index_html.read()) 26 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/with_python_package.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/with_python_package.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/with_python_package" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/with_python_package" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # with_python_package documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Aug 14 14:22:26 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import shlex 18 | 19 | # If extensions (or modules to document with autodoc) are in another directory, 20 | # add these directories to sys.path here. If the directory is relative to the 21 | # documentation root, use os.path.abspath to make it absolute, like shown here. 22 | #sys.path.insert(0, os.path.abspath('.')) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | #needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [] 33 | 34 | # Add any paths that contain templates here, relative to this directory. 35 | templates_path = ['_templates'] 36 | 37 | # The suffix(es) of source filenames. 38 | # You can specify multiple suffix as a list of string: 39 | # source_suffix = ['.rst', '.md'] 40 | source_suffix = '.rst' 41 | 42 | # The encoding of source files. 43 | #source_encoding = 'utf-8-sig' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = u'with_python_package' 50 | copyright = u'2015, Gregor Müllegger' 51 | author = u'Gregor Müllegger' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '0.1' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '0.1.0' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # There are two options for replacing |today|: either, you set today to some 70 | # non-false value, then it is used: 71 | #today = '' 72 | # Else, today_fmt is used as the format for a strftime call. 73 | #today_fmt = '%B %d, %Y' 74 | 75 | # List of patterns, relative to source directory, that match files and 76 | # directories to ignore when looking for source files. 77 | exclude_patterns = ['_build'] 78 | 79 | # The reST default role (used for this markup: `text`) to use for all 80 | # documents. 81 | #default_role = None 82 | 83 | # If true, '()' will be appended to :func: etc. cross-reference text. 84 | #add_function_parentheses = True 85 | 86 | # If true, the current module name will be prepended to all description 87 | # unit titles (such as .. function::). 88 | #add_module_names = True 89 | 90 | # If true, sectionauthor and moduleauthor directives will be shown in the 91 | # output. They are ignored by default. 92 | #show_authors = False 93 | 94 | # The name of the Pygments (syntax highlighting) style to use. 95 | pygments_style = 'sphinx' 96 | 97 | # A list of ignored prefixes for module index sorting. 98 | #modindex_common_prefix = [] 99 | 100 | # If true, keep warnings as "system message" paragraphs in the built documents. 101 | #keep_warnings = False 102 | 103 | # If true, `todo` and `todoList` produce output, else they produce nothing. 104 | todo_include_todos = False 105 | 106 | 107 | # -- Options for HTML output ---------------------------------------------- 108 | 109 | # The theme to use for HTML and HTML Help pages. See the documentation for 110 | # a list of builtin themes. 111 | html_theme = 'alabaster' 112 | 113 | # Theme options are theme-specific and customize the look and feel of a theme 114 | # further. For a list of options available for each theme, see the 115 | # documentation. 116 | #html_theme_options = {} 117 | 118 | # Add any paths that contain custom themes here, relative to this directory. 119 | #html_theme_path = [] 120 | 121 | # The name for this set of Sphinx documents. If None, it defaults to 122 | # " v documentation". 123 | #html_title = None 124 | 125 | # A shorter title for the navigation bar. Default is the same as html_title. 126 | #html_short_title = None 127 | 128 | # The name of an image file (relative to this directory) to place at the top 129 | # of the sidebar. 130 | #html_logo = None 131 | 132 | # The name of an image file (within the static path) to use as favicon of the 133 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 134 | # pixels large. 135 | #html_favicon = None 136 | 137 | # Add any paths that contain custom static files (such as style sheets) here, 138 | # relative to this directory. They are copied after the builtin static files, 139 | # so a file named "default.css" will overwrite the builtin "default.css". 140 | html_static_path = ['_static'] 141 | 142 | # Add any extra paths that contain custom files (such as robots.txt or 143 | # .htaccess) here, relative to this directory. These files are copied 144 | # directly to the root of the documentation. 145 | #html_extra_path = [] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Language to be used for generating the HTML full-text search index. 189 | # Sphinx supports the following languages: 190 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 191 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 192 | #html_search_language = 'en' 193 | 194 | # A dictionary with options for the search language support, empty by default. 195 | # Now only 'ja' uses this config value 196 | #html_search_options = {'type': 'default'} 197 | 198 | # The name of a javascript file (relative to the configuration directory) that 199 | # implements a search results scorer. If empty, the default will be used. 200 | #html_search_scorer = 'scorer.js' 201 | 202 | # Output file base name for HTML help builder. 203 | htmlhelp_basename = 'with_python_packagedoc' 204 | 205 | # -- Options for LaTeX output --------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | 217 | # Latex figure (float) alignment 218 | #'figure_align': 'htbp', 219 | } 220 | 221 | # Grouping the document tree into LaTeX files. List of tuples 222 | # (source start file, target name, title, 223 | # author, documentclass [howto, manual, or own class]). 224 | latex_documents = [ 225 | (master_doc, 'with_python_package.tex', u'with\\_python\\_package Documentation', 226 | u'Gregor Müllegger', 'manual'), 227 | ] 228 | 229 | # The name of an image file (relative to this directory) to place at the top of 230 | # the title page. 231 | #latex_logo = None 232 | 233 | # For "manual" documents, if this is true, then toplevel headings are parts, 234 | # not chapters. 235 | #latex_use_parts = False 236 | 237 | # If true, show page references after internal links. 238 | #latex_show_pagerefs = False 239 | 240 | # If true, show URL addresses after external links. 241 | #latex_show_urls = False 242 | 243 | # Documents to append as an appendix to all manuals. 244 | #latex_appendices = [] 245 | 246 | # If false, no module index is generated. 247 | #latex_domain_indices = True 248 | 249 | 250 | # -- Options for manual page output --------------------------------------- 251 | 252 | # One entry per manual page. List of tuples 253 | # (source start file, name, description, authors, manual section). 254 | man_pages = [ 255 | (master_doc, 'with_python_package', u'with_python_package Documentation', 256 | [author], 1) 257 | ] 258 | 259 | # If true, show URL addresses after external links. 260 | #man_show_urls = False 261 | 262 | 263 | # -- Options for Texinfo output ------------------------------------------- 264 | 265 | # Grouping the document tree into Texinfo files. List of tuples 266 | # (source start file, target name, title, author, 267 | # dir menu entry, description, category) 268 | texinfo_documents = [ 269 | (master_doc, 'with_python_package', u'with_python_package Documentation', 270 | author, 'with_python_package', 'One line description of project.', 271 | 'Miscellaneous'), 272 | ] 273 | 274 | # Documents to append as an appendix to all manuals. 275 | #texinfo_appendices = [] 276 | 277 | # If false, no module index is generated. 278 | #texinfo_domain_indices = True 279 | 280 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 281 | #texinfo_show_urls = 'footnote' 282 | 283 | # If true, do not generate a @detailmenu in the "Top" node's menu. 284 | #texinfo_no_detailmenu = False 285 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/docs/index.rst: -------------------------------------------------------------------------------- 1 | .. with_python_package documentation master file, created by 2 | sphinx-quickstart on Fri Aug 14 14:22:26 2015. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to with_python_package's documentation! 7 | =============================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\with_python_package.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\with_python_package.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/my_python_package/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readthedocs/readthedocs-build/e271e1861b1e68f9c15e08e84cc2b85fbfb45310/integration_tests/with_python_package/my_python_package/__init__.py -------------------------------------------------------------------------------- /integration_tests/with_python_package/readthedocs.yml: -------------------------------------------------------------------------------- 1 | base: docs 2 | name: docs 3 | type: sphinx 4 | python: 5 | use_system_site_packages: true 6 | setup_py_install: true 7 | setup_py_path: ./setup.py 8 | -------------------------------------------------------------------------------- /integration_tests/with_python_package/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import find_packages 3 | from setuptools import setup 4 | 5 | 6 | setup( 7 | name='my_python_package', 8 | version='0.1.0', 9 | author='Eric Holscher', 10 | author_email='eric@ericholscher.com', 11 | url='https://readthedocs.org', 12 | license='MIT', 13 | description='For testing', 14 | packages=find_packages(), 15 | include_package_data=True, 16 | long_description='', 17 | zip_safe=True, 18 | ) 19 | -------------------------------------------------------------------------------- /prospector.yml: -------------------------------------------------------------------------------- 1 | strictness: low 2 | 3 | test-warnings: false 4 | doc-warnings: true 5 | 6 | ignore-patterns: 7 | - /migrations/ 8 | - /integration_tests/ 9 | 10 | pep8: 11 | full: true 12 | options: 13 | max-line-length: 100 14 | 15 | pylint: 16 | options: 17 | dummy-variables-rgx: '_$|__$|dummy' 18 | max-line-length: 100 19 | disable: 20 | - logging-format-interpolation 21 | 22 | mccabe: 23 | run: false 24 | 25 | pep257: 26 | run: false 27 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | name: read-the-docs 2 | type: sphinx 3 | python: 4 | version: 3.6 5 | -------------------------------------------------------------------------------- /readthedocs_build/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readthedocs/readthedocs-build/e271e1861b1e68f9c15e08e84cc2b85fbfb45310/readthedocs_build/__init__.py -------------------------------------------------------------------------------- /readthedocs_build/build.py: -------------------------------------------------------------------------------- 1 | from .builder import builder_types 2 | 3 | 4 | def build(project_config): 5 | for build_config in project_config: 6 | builder_type = build_config['type'] 7 | builder_class = builder_types[builder_type] 8 | builder = builder_class( 9 | build_config=build_config) 10 | builder.build() 11 | -------------------------------------------------------------------------------- /readthedocs_build/builder/__init__.py: -------------------------------------------------------------------------------- 1 | from .sphinx import SphinxBuilder 2 | 3 | 4 | builder_types = { 5 | 'sphinx': SphinxBuilder, 6 | } 7 | -------------------------------------------------------------------------------- /readthedocs_build/builder/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from .virtualenv import VirtualEnv 4 | 5 | 6 | class BaseBuilder(object): 7 | python_dependencies = () 8 | 9 | def __init__(self, build_config): 10 | self.build_config = build_config 11 | 12 | def get_source_directory(self): 13 | return self.build_config['base'] 14 | 15 | def setup(self): 16 | self.setup_virtualenv() 17 | 18 | def setup_virtualenv(self): 19 | python_config = self.build_config['python'] 20 | self.venv = VirtualEnv(python_config) 21 | for package in self.python_dependencies: 22 | self.venv.install(package) 23 | if python_config['setup_py_install']: 24 | setup_py_path = python_config['setup_py_path'] 25 | self.venv.python_run(setup_py_path, ['install']) 26 | 27 | def get_output_directory(self, format): 28 | out_dir = os.path.join( 29 | self.build_config['output_base'], 30 | self.build_config['name'], 31 | format) 32 | if not os.path.exists(out_dir): 33 | os.makedirs(out_dir) 34 | return out_dir 35 | 36 | def build(self): 37 | """ 38 | Initializes the build. 39 | """ 40 | self.setup() 41 | self.build_html() 42 | self.build_search_data() 43 | self.cleanup() 44 | 45 | def build_html(self): 46 | # Must be overriden by subclass. 47 | pass 48 | 49 | def build_search_data(self): 50 | """ 51 | Subclasses should override this method and build search data in a JSON 52 | format in the ``search_data`` output directory. 53 | """ 54 | # Must be overriden by subclass. 55 | pass 56 | 57 | def cleanup(self): 58 | self.venv.cleanup() 59 | -------------------------------------------------------------------------------- /readthedocs_build/builder/sphinx.py: -------------------------------------------------------------------------------- 1 | from .base import BaseBuilder 2 | 3 | 4 | class SphinxBuilder(BaseBuilder): 5 | """ 6 | TODO: 7 | 8 | - Build HTML in dirhtml format 9 | - Build HTML in singlehtml format + local media build 10 | - Build PDF 11 | - Build ePUB 12 | """ 13 | 14 | python_dependencies = BaseBuilder.python_dependencies + ( 15 | 'Sphinx>=1.5.2', 16 | ) 17 | 18 | def _run_sphinx_build(self, format, out_dir): 19 | source_dir = self.get_source_directory() 20 | self.venv.python_run( 21 | 'sphinx-build', [ 22 | '-b', 23 | format, 24 | source_dir, 25 | out_dir, 26 | ]) 27 | 28 | def build_html(self): 29 | out_dir = self.get_output_directory('html') 30 | self._run_sphinx_build('html', out_dir) 31 | 32 | def build_search_data(self): 33 | out_dir = self.get_output_directory('search_data') 34 | self._run_sphinx_build('json', out_dir) 35 | -------------------------------------------------------------------------------- /readthedocs_build/builder/test_base.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | 3 | from .base import BaseBuilder 4 | 5 | 6 | def get_config(extra=None): 7 | defaults = { 8 | 'name': 'docs', 9 | 'type': 'sphinx', 10 | 'python': { 11 | 'use_system_site_packages': False, 12 | 'setup_py_install': False, 13 | 'setup_py_path': '', 14 | }, 15 | 'output_base': '/tmp', 16 | } 17 | if extra is not None: 18 | defaults.update(extra) 19 | return defaults 20 | 21 | 22 | def test_build_calls_setup(): 23 | build_config = get_config() 24 | with patch.object(BaseBuilder, 'setup') as setup: 25 | with patch.object(BaseBuilder, 'cleanup'): 26 | builder = BaseBuilder(build_config=build_config) 27 | builder.build() 28 | setup.assert_called_with() 29 | 30 | 31 | def test_build_calls_cleanup(): 32 | build_config = get_config() 33 | with patch('readthedocs_build.builder.base.VirtualEnv'): 34 | with patch.object(BaseBuilder, 'cleanup') as cleanup: 35 | builder = BaseBuilder(build_config=build_config) 36 | builder.build() 37 | cleanup.assert_called_with() 38 | # Do real cleanup. 39 | builder.cleanup() 40 | 41 | 42 | def test_build_calls_build_html(): 43 | build_config = get_config() 44 | with patch('readthedocs_build.builder.base.VirtualEnv'): 45 | with patch.object(BaseBuilder, 'build_html') as build_html: 46 | builder = BaseBuilder(build_config=build_config) 47 | builder.build() 48 | build_html.assert_called_with() 49 | 50 | 51 | def test_build_calls_build_search_data(): 52 | build_config = get_config() 53 | mock_venv = patch('readthedocs_build.builder.base.VirtualEnv') 54 | mock_build_html = patch.object(BaseBuilder, 'build_html') 55 | mock_build_search_data = patch.object(BaseBuilder, 'build_search_data') 56 | with mock_venv, mock_build_html: 57 | with mock_build_search_data as build_search_data: 58 | builder = BaseBuilder(build_config=build_config) 59 | builder.build() 60 | build_search_data.assert_called_with() 61 | 62 | 63 | def test_setup_creates_virtualenv(): 64 | build_config = get_config() 65 | builder = BaseBuilder(build_config=build_config) 66 | with patch('readthedocs_build.builder.base.VirtualEnv') as VirtualEnv: 67 | builder.setup() 68 | assert VirtualEnv.call_count == 1 69 | 70 | 71 | def describe_setup_virtualenv(): 72 | def it_respects_use_system_site_packages_config(): 73 | build_config = get_config() 74 | build_config['python'].update({ 75 | 'use_system_site_packages': False 76 | }) 77 | with patch('readthedocs_build.builder.base.VirtualEnv') as VirtualEnv: 78 | builder = BaseBuilder(build_config=build_config) 79 | builder.setup_virtualenv() 80 | VirtualEnv.assert_called_with(build_config['python']) 81 | 82 | build_config = get_config() 83 | build_config['python'].update({ 84 | 'use_system_site_packages': True 85 | }) 86 | with patch('readthedocs_build.builder.base.VirtualEnv') as VirtualEnv: 87 | builder = BaseBuilder(build_config=build_config) 88 | builder.setup_virtualenv() 89 | VirtualEnv.assert_called_with(build_config['python']) 90 | 91 | def it_executes_setup_py_install(tmpdir): 92 | setup_py = str(tmpdir.join('setup.py')) 93 | build_config = get_config() 94 | build_config['python'].update({ 95 | 'setup_py_install': True, 96 | 'setup_py_path': setup_py, 97 | }) 98 | with patch('readthedocs_build.builder.base.VirtualEnv') as VirtualEnv: 99 | VirtualEnv.return_value = VirtualEnv 100 | builder = BaseBuilder(build_config=build_config) 101 | builder.setup_virtualenv() 102 | VirtualEnv.python_run.assert_called_with( 103 | setup_py, 104 | ['install']) 105 | 106 | 107 | def test_cleanup_removes_virtualenv(tmpdir): 108 | build_config = get_config() 109 | builder = BaseBuilder(build_config=build_config) 110 | with patch('readthedocs_build.builder.base.VirtualEnv'): 111 | builder.setup() 112 | builder.cleanup() 113 | builder.venv.cleanup.assert_called_with() 114 | -------------------------------------------------------------------------------- /readthedocs_build/builder/test_sphinx.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | import os 3 | 4 | from .sphinx import SphinxBuilder 5 | from .test_base import get_config 6 | 7 | 8 | def test_setup_installs_sphinx(): 9 | build_config = get_config() 10 | builder = SphinxBuilder(build_config=build_config) 11 | with patch('readthedocs_build.builder.base.VirtualEnv'): 12 | builder.setup() 13 | assert any([ 14 | args[0].startswith('Sphinx') 15 | for args, kwargs in builder.venv.install.call_args_list]) 16 | 17 | 18 | def test_build_html_calls_sphinx_build(tmpdir): 19 | build_config = get_config({ 20 | 'name': 'docs', 21 | 'base': str(tmpdir), 22 | 'output_base': str(tmpdir.join('out')), 23 | }) 24 | builder = SphinxBuilder(build_config=build_config) 25 | with patch('readthedocs_build.builder.base.VirtualEnv'): 26 | builder.build() 27 | source_dir = str(tmpdir) 28 | out_dir = str(tmpdir.join('out', 'docs', 'html')) 29 | builder.venv.python_run.assert_any_call( 30 | 'sphinx-build', [ 31 | '-b', 32 | 'html', 33 | source_dir, 34 | out_dir, 35 | ]) 36 | 37 | 38 | def test_build_creates_search_data(tmpdir): 39 | build_config = get_config({ 40 | 'name': 'docs', 41 | 'base': str(tmpdir), 42 | 'output_base': str(tmpdir.join('out')), 43 | }) 44 | builder = SphinxBuilder(build_config=build_config) 45 | with patch('readthedocs_build.builder.base.VirtualEnv'): 46 | builder.build() 47 | source_dir = str(tmpdir) 48 | out_dir = str(tmpdir.join('out', 'docs', 'search_data')) 49 | builder.venv.python_run.assert_any_call( 50 | 'sphinx-build', [ 51 | '-b', 52 | 'json', 53 | source_dir, 54 | out_dir, 55 | ]) 56 | -------------------------------------------------------------------------------- /readthedocs_build/builder/test_virtualenv.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | import os 3 | 4 | from .virtualenv import VirtualEnv 5 | 6 | 7 | def test_creates_default_virtualenv(): 8 | with patch('readthedocs_build.builder.virtualenv.run') as run: 9 | run.return_value = 0 10 | 11 | venv = VirtualEnv() 12 | assert not venv.system_site_packages 13 | run.assert_called_with([ 14 | 'virtualenv', 15 | venv.base_path, 16 | '--python=/usr/bin/python2.7', 17 | ]) 18 | 19 | 20 | def test_it_respects_system_site_packages_flag(): 21 | with patch('readthedocs_build.builder.virtualenv.run') as run: 22 | run.return_value = 0 23 | 24 | python_config = { 25 | 'use_system_site_packages': True, 26 | } 27 | 28 | venv = VirtualEnv(python_config) 29 | assert venv.system_site_packages 30 | run.assert_called_with([ 31 | 'virtualenv', 32 | venv.base_path, 33 | '--python=/usr/bin/python2.7', 34 | '--system-site-packages', 35 | ]) 36 | 37 | 38 | def test_cleanup_deletes_virtualenv(tmpdir): 39 | venv_dir = str(tmpdir.mkdir('venv')) 40 | with patch.object(VirtualEnv, '__init__') as __init__: 41 | __init__.return_value = None 42 | venv = VirtualEnv() 43 | venv.base_path = venv_dir 44 | venv.cleanup() 45 | assert not tmpdir.join('venv').exists() 46 | 47 | 48 | def describe_python_run(): 49 | def it_takes_relative_executable(): 50 | with patch('readthedocs_build.builder.virtualenv.run') as run: 51 | with patch.object(VirtualEnv, 'setup'): 52 | venv = VirtualEnv() 53 | venv.python_run('pip', args=['freeze']) 54 | run.assert_called_with([ 55 | os.path.join(venv.base_path, 'bin', 'python'), 56 | os.path.join(venv.base_path, 'bin', 'pip'), 57 | 'freeze', 58 | ]) 59 | 60 | def it_takes_absolute_path_to_script(tmpdir): 61 | with patch('readthedocs_build.builder.virtualenv.run') as run: 62 | with patch.object(VirtualEnv, 'setup'): 63 | setup_py = str(tmpdir.join('setup.py')) 64 | venv = VirtualEnv() 65 | venv.python_run(setup_py, []) 66 | run.assert_called_with([ 67 | os.path.join(venv.base_path, 'bin', 'python'), 68 | setup_py, 69 | ]) 70 | 71 | 72 | def test_install(tmpdir): 73 | with patch.object(VirtualEnv, 'setup'): 74 | with patch.object(VirtualEnv, 'python_run') as python_run: 75 | python_run.return_value = 0 76 | venv = VirtualEnv() 77 | venv.install('FooBar') 78 | python_run.assert_called_with('pip', ['install', 'FooBar']) 79 | 80 | venv.install('FooBar>=1.2') 81 | python_run.assert_called_with('pip', ['install', 'FooBar>=1.2']) 82 | 83 | venv.install('-rrequirements.txt') 84 | python_run.assert_called_with( 85 | 'pip', ['install', '-rrequirements.txt']) 86 | -------------------------------------------------------------------------------- /readthedocs_build/builder/utils.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | 4 | def run(args): 5 | popen = subprocess.Popen(args) 6 | return popen.wait() 7 | -------------------------------------------------------------------------------- /readthedocs_build/builder/virtualenv.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import tempfile 4 | 5 | from .utils import run 6 | 7 | 8 | class VirtualEnv(object): 9 | """ 10 | Light abstraction of a virtualenv. 11 | """ 12 | 13 | def __init__(self, python_config=None): 14 | self.base_path = tempfile.mkdtemp() 15 | 16 | if python_config is None: 17 | python_config = {} 18 | self.system_site_packages = python_config.get('use_system_site_packages', False) 19 | self.python_version = python_config.get('version', '2.7') 20 | 21 | self.setup() 22 | 23 | def python_run(self, command_bin, args): 24 | """ 25 | Execute a script from the virtualenv by using the bin/python from the 26 | virtualenv. That prevents an issue with too long shbangs. See 27 | https://github.com/rtfd/readthedocs.org/issues/994 for details. 28 | 29 | ``command_bin`` can be the name of an executable installed in the 30 | virtualenv or an absolute path to a python script. 31 | """ 32 | python_bin = os.path.join(self.base_path, 'bin', 'python') 33 | command_bin = os.path.join(self.base_path, 'bin', command_bin) 34 | return run([ 35 | python_bin, 36 | command_bin, 37 | ] + list(args)) 38 | 39 | def setup(self): 40 | params = [ 41 | 'virtualenv', 42 | self.base_path, 43 | '--python=/usr/bin/python{}'.format(self.python_version), 44 | ] 45 | if self.system_site_packages: 46 | params.append('--system-site-packages') 47 | 48 | exit_code = run(params) 49 | assert exit_code == 0, 'virtualenv setup failed' 50 | 51 | def cleanup(self): 52 | if os.path.exists(self.base_path): 53 | shutil.rmtree(self.base_path) 54 | 55 | def __del__(self): 56 | self.cleanup() 57 | 58 | def install(self, package): 59 | exit_code = self.python_run('pip', ['install', package]) 60 | assert exit_code == 0 61 | -------------------------------------------------------------------------------- /readthedocs_build/cli.py: -------------------------------------------------------------------------------- 1 | import click 2 | import os 3 | import sys 4 | 5 | from .build import build 6 | from .config import load 7 | from .config import ConfigError 8 | from .utils import cd 9 | 10 | 11 | @click.command() 12 | @click.argument('path', 13 | required=False, 14 | type=click.Path(exists=True, file_okay=False, readable=True), 15 | default='.') 16 | @click.option('--outdir', 17 | type=click.Path(file_okay=False, writable=True), 18 | default='_readthedocs_build', 19 | help='build output directory') 20 | def main(path, outdir): 21 | """ 22 | Exit codes: 23 | 24 | 0 -- success 25 | 1 -- bad config 26 | """ 27 | 28 | if path is None: 29 | path = os.getcwd() 30 | 31 | env_config = { 32 | 'output_base': outdir, 33 | } 34 | 35 | with cd(path): 36 | try: 37 | project_config = load(os.getcwd(), env_config) 38 | except ConfigError as error: 39 | sys.stderr.write('Error: {error}'.format(error=error)) 40 | sys.exit(1) 41 | build(project_config) 42 | -------------------------------------------------------------------------------- /readthedocs_build/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import * # noqa 2 | from .parser import * # noqa 3 | -------------------------------------------------------------------------------- /readthedocs_build/config/config.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | import re 3 | import os 4 | 5 | from .find import find_one 6 | from .parser import ParseError 7 | from .parser import parse 8 | from .validation import (ValidationError, validate_bool, validate_choice, 9 | validate_directory, validate_file, validate_list, 10 | validate_string) 11 | 12 | 13 | __all__ = ( 14 | 'load', 'BuildConfig', 'ConfigError', 'InvalidConfig', 'ProjectConfig') 15 | 16 | 17 | CONFIG_FILENAMES = ('readthedocs.yml', '.readthedocs.yml') 18 | 19 | 20 | BASE_INVALID = 'base-invalid' 21 | BASE_NOT_A_DIR = 'base-not-a-directory' 22 | CONFIG_SYNTAX_INVALID = 'config-syntax-invalid' 23 | CONFIG_REQUIRED = 'config-required' 24 | NAME_REQUIRED = 'name-required' 25 | NAME_INVALID = 'name-invalid' 26 | CONF_FILE_REQUIRED = 'conf-file-required' 27 | TYPE_REQUIRED = 'type-required' 28 | PYTHON_INVALID = 'python-invalid' 29 | 30 | DOCKER_DEFAULT_IMAGE = 'readthedocs/build' 31 | DOCKER_DEFAULT_VERSION = '2.0' 32 | # These map to coordisponding settings in the .org, 33 | # so they haven't been renamed. 34 | DOCKER_IMAGE = '{}:{}'.format(DOCKER_DEFAULT_IMAGE, DOCKER_DEFAULT_VERSION) 35 | DOCKER_IMAGE_SETTINGS = { 36 | 'readthedocs/build:1.0': { 37 | 'python': {'supported_versions': [2, 2.7, 3, 3.4]}, 38 | }, 39 | 'readthedocs/build:2.0': { 40 | 'python': {'supported_versions': [2, 2.7, 3, 3.5]}, 41 | }, 42 | 'readthedocs/build:latest': { 43 | 'python': {'supported_versions': [2, 2.7, 3, 3.3, 3.4, 3.5, 3.6]}, 44 | }, 45 | } 46 | 47 | 48 | class ConfigError(Exception): 49 | 50 | def __init__(self, message, code): 51 | self.code = code 52 | super(ConfigError, self).__init__(message) 53 | 54 | 55 | class InvalidConfig(ConfigError): 56 | message_template = 'Invalid "{key}": {error}' 57 | 58 | def __init__(self, key, code, error_message, source_file=None, 59 | source_position=None): 60 | self.key = key 61 | self.code = code 62 | self.source_file = source_file 63 | self.source_position = source_position 64 | message = self.message_template.format( 65 | key=key, 66 | code=code, 67 | error=error_message) 68 | super(InvalidConfig, self).__init__(message, code=code) 69 | 70 | 71 | class BuildConfig(dict): 72 | 73 | """ 74 | Config that handles the build of one particular documentation. Config keys 75 | can be accessed with a dictionary lookup:: 76 | 77 | >>> build_config['type'] 78 | 'sphinx' 79 | 80 | You need to call ``validate`` before the config is ready to use. Also 81 | setting the ``output_base`` is required before using it for a build. 82 | """ 83 | 84 | BASE_INVALID_MESSAGE = 'Invalid value for base: {base}' 85 | BASE_NOT_A_DIR_MESSAGE = '"base" is not a directory: {base}' 86 | NAME_REQUIRED_MESSAGE = 'Missing key "name"' 87 | NAME_INVALID_MESSAGE = ( 88 | 'Invalid name "{name}". Valid values must match {name_re}') 89 | TYPE_REQUIRED_MESSAGE = 'Missing key "type"' 90 | CONF_FILE_REQUIRED_MESSAGE = 'Missing key "conf_file"' 91 | PYTHON_INVALID_MESSAGE = '"python" section must be a mapping.' 92 | PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE = ( 93 | '"python.extra_requirements" section must be a list.') 94 | 95 | PYTHON_SUPPORTED_VERSIONS = [2, 2.7, 3, 3.5] 96 | DOCKER_SUPPORTED_VERSIONS = ['1.0', '2.0', 'latest'] 97 | 98 | def __init__(self, env_config, raw_config, source_file, source_position): 99 | self.env_config = env_config 100 | self.raw_config = raw_config 101 | self.source_file = source_file 102 | self.source_position = source_position 103 | 104 | def error(self, key, message, code): 105 | source = '{file} [{pos}]'.format( 106 | file=self.source_file, 107 | pos=self.source_position) 108 | raise InvalidConfig( 109 | key=key, 110 | code=code, 111 | error_message='{source}: {message}'.format(source=source, 112 | message=message), 113 | source_file=self.source_file, 114 | source_position=self.source_position) 115 | 116 | @contextmanager 117 | def catch_validation_error(self, key): 118 | try: 119 | yield 120 | except ValidationError as error: 121 | raise InvalidConfig( 122 | key=key, 123 | code=error.code, 124 | error_message=str(error), 125 | source_file=self.source_file, 126 | source_position=self.source_position) 127 | 128 | def get_valid_types(self): 129 | return ( 130 | 'sphinx', 131 | ) 132 | 133 | def get_valid_python_versions(self): 134 | try: 135 | return self.env_config['python']['supported_versions'] 136 | except (KeyError, TypeError): 137 | pass 138 | return self.PYTHON_SUPPORTED_VERSIONS 139 | 140 | def get_valid_formats(self): 141 | return ( 142 | 'htmlzip', 143 | 'pdf', 144 | 'epub', 145 | ) 146 | 147 | def validate(self): 148 | """ 149 | Validate and process config into ``config`` attribute that contains the 150 | ready to use build configuration. 151 | 152 | It makes sure that: 153 | 154 | - ``type`` is set and is a valid builder 155 | - ``base`` is a valid directory and defaults to the directory of the 156 | ``readthedocs.yml`` config file if not set 157 | """ 158 | 159 | # Validate env_config. 160 | self.validate_output_base() 161 | 162 | # Validate the build environment first 163 | self.validate_build() # Must happen before `validate_python`! 164 | 165 | # Validate raw_config. Order matters. 166 | self.validate_name() 167 | self.validate_type() 168 | self.validate_base() 169 | self.validate_python() 170 | self.validate_formats() 171 | 172 | self.validate_conda() 173 | self.validate_requirements_file() 174 | self.validate_conf_file() 175 | 176 | def validate_output_base(self): 177 | assert 'output_base' in self.env_config, ( 178 | '"output_base" required in "env_config"') 179 | base_path = os.path.dirname(self.source_file) 180 | self['output_base'] = os.path.abspath( 181 | os.path.join( 182 | self.env_config.get('output_base', base_path), 183 | ) 184 | ) 185 | 186 | def validate_name(self): 187 | name = self.raw_config.get('name', None) 188 | if not name: 189 | name = self.env_config.get('name', None) 190 | if not name: 191 | self.error('name', self.NAME_REQUIRED_MESSAGE, code=NAME_REQUIRED) 192 | name_re = r'^[-_.0-9a-zA-Z]+$' 193 | if not re.match(name_re, name): 194 | self.error( 195 | 'name', 196 | self.NAME_INVALID_MESSAGE.format( 197 | name=name, 198 | name_re=name_re), 199 | code=NAME_INVALID) 200 | 201 | self['name'] = name 202 | 203 | def validate_type(self): 204 | type = self.raw_config.get('type', None) 205 | if not type: 206 | type = self.env_config.get('type', None) 207 | if not type: 208 | self.error('type', self.TYPE_REQUIRED_MESSAGE, code=TYPE_REQUIRED) 209 | 210 | with self.catch_validation_error('type'): 211 | validate_choice(type, self.get_valid_types()) 212 | 213 | self['type'] = type 214 | 215 | def validate_base(self): 216 | if 'base' in self.raw_config: 217 | base = self.raw_config['base'] 218 | else: 219 | base = os.path.dirname(self.source_file) 220 | with self.catch_validation_error('base'): 221 | base_path = os.path.dirname(self.source_file) 222 | base = validate_directory(base, base_path) 223 | self['base'] = base 224 | 225 | def validate_build(self): 226 | """ 227 | Validate the build config settings. 228 | 229 | This is a bit complex, 230 | so here is the logic: 231 | 232 | * We take the default image & version if it's specific in the environment 233 | * Then update the _version_ from the users config 234 | * Then append the default _image_, since users can't change this 235 | * Then update the env_config with the settings for that specific image 236 | - This is currently used for a build image -> python version mapping 237 | 238 | This means we can use custom docker _images_, 239 | but can't change the supported _versions_ that users have defined. 240 | """ 241 | # Defaults 242 | if 'build' in self.env_config: 243 | build = self.env_config['build'] 244 | else: 245 | build = {'image': DOCKER_IMAGE} 246 | 247 | # User specified 248 | if 'build' in self.raw_config: 249 | _build = self.raw_config['build'] 250 | if 'image' in _build: 251 | with self.catch_validation_error('build'): 252 | build['image'] = validate_choice( 253 | str(_build['image']), 254 | self.DOCKER_SUPPORTED_VERSIONS, 255 | ) 256 | if ':' not in build['image']: 257 | # Prepend proper image name to user's image name 258 | build['image'] = '{}:{}'.format( 259 | DOCKER_DEFAULT_IMAGE, 260 | build['image'] 261 | ) 262 | # Update docker default settings from image name 263 | if build['image'] in DOCKER_IMAGE_SETTINGS: 264 | self.env_config.update( 265 | DOCKER_IMAGE_SETTINGS[build['image']] 266 | ) 267 | # Update docker settings from user config 268 | if 'DOCKER_IMAGE_SETTINGS' in self.env_config and \ 269 | build['image'] in self.env_config['DOCKER_IMAGE_SETTINGS']: 270 | self.env_config.update( 271 | self.env_config['DOCKER_IMAGE_SETTINGS'][build['image']] 272 | ) 273 | self['build'] = build 274 | 275 | def validate_python(self): 276 | python = { 277 | 'use_system_site_packages': False, 278 | 'pip_install': False, 279 | 'extra_requirements': [], 280 | 'setup_py_install': False, 281 | 'setup_py_path': os.path.join( 282 | os.path.dirname(self.source_file), 283 | 'setup.py'), 284 | 'version': 2, 285 | } 286 | 287 | if 'python' in self.raw_config: 288 | raw_python = self.raw_config['python'] 289 | if not isinstance(raw_python, dict): 290 | self.error( 291 | 'python', 292 | self.PYTHON_INVALID_MESSAGE, 293 | code=PYTHON_INVALID) 294 | 295 | # Validate use_system_site_packages. 296 | if 'use_system_site_packages' in raw_python: 297 | with self.catch_validation_error( 298 | 'python.use_system_site_packages'): 299 | python['use_system_site_packages'] = validate_bool( 300 | raw_python['use_system_site_packages']) 301 | 302 | # Validate pip_install. 303 | if 'pip_install' in raw_python: 304 | with self.catch_validation_error('python.pip_install'): 305 | python['pip_install'] = validate_bool( 306 | raw_python['pip_install']) 307 | 308 | # Validate extra_requirements. 309 | if 'extra_requirements' in raw_python: 310 | raw_extra_requirements = raw_python['extra_requirements'] 311 | if not isinstance(raw_extra_requirements, list): 312 | self.error( 313 | 'python.extra_requirements', 314 | self.PYTHON_EXTRA_REQUIREMENTS_INVALID_MESSAGE, 315 | code=PYTHON_INVALID) 316 | for extra_name in raw_extra_requirements: 317 | with self.catch_validation_error( 318 | 'python.extra_requirements'): 319 | python['extra_requirements'].append( 320 | validate_string(extra_name)) 321 | 322 | # Validate setup_py_install. 323 | if 'setup_py_install' in raw_python: 324 | with self.catch_validation_error('python.setup_py_install'): 325 | python['setup_py_install'] = validate_bool( 326 | raw_python['setup_py_install']) 327 | 328 | # Validate setup_py_path. 329 | if 'setup_py_path' in raw_python: 330 | with self.catch_validation_error('python.setup_py_path'): 331 | base_path = os.path.dirname(self.source_file) 332 | python['setup_py_path'] = validate_file( 333 | raw_python['setup_py_path'], base_path) 334 | 335 | if 'version' in raw_python: 336 | with self.catch_validation_error('python.version'): 337 | # Try to convert strings to an int first, to catch '2', then 338 | # a float, to catch '2.7' 339 | version = raw_python['version'] 340 | if isinstance(version, str): 341 | try: 342 | version = int(version) 343 | except ValueError: 344 | try: 345 | version = float(version) 346 | except ValueError: 347 | pass 348 | python['version'] = validate_choice( 349 | version, 350 | self.get_valid_python_versions(), 351 | ) 352 | 353 | self['python'] = python 354 | 355 | def validate_conda(self): 356 | conda = {} 357 | 358 | if 'conda' in self.raw_config: 359 | raw_conda = self.raw_config['conda'] 360 | if not isinstance(raw_conda, dict): 361 | self.error( 362 | 'conda', 363 | self.PYTHON_INVALID_MESSAGE, 364 | code=PYTHON_INVALID) 365 | 366 | if 'file' in raw_conda: 367 | with self.catch_validation_error('conda.file'): 368 | base_path = os.path.dirname(self.source_file) 369 | conda['file'] = validate_file( 370 | raw_conda['file'], base_path) 371 | 372 | self['conda'] = conda 373 | 374 | def validate_requirements_file(self): 375 | if 'requirements_file' not in self.raw_config: 376 | return None 377 | 378 | requirements_file = self.raw_config['requirements_file'] 379 | base_path = os.path.dirname(self.source_file) 380 | with self.catch_validation_error('requirements_file'): 381 | validate_file(requirements_file, base_path) 382 | self['requirements_file'] = requirements_file 383 | 384 | return True 385 | 386 | def validate_conf_file(self): 387 | if 'conf_file' not in self.raw_config: 388 | # self.error('conf_file', self.CONF_FILE_REQUIRED_MESSAGE, code=CONF_FILE_REQUIRED) 389 | return None 390 | 391 | conf_file = self.raw_config['conf_file'] 392 | base_path = os.path.dirname(self.source_file) 393 | with self.catch_validation_error('conf_file'): 394 | validate_file(conf_file, base_path) 395 | self['conf_file'] = conf_file 396 | 397 | return True 398 | 399 | def validate_formats(self): 400 | _formats = self.raw_config.get('formats') 401 | if 'formats' not in self.raw_config or _formats == []: 402 | return None 403 | 404 | with self.catch_validation_error('format'): 405 | validate_list(_formats) 406 | for _format in _formats: 407 | validate_choice(_format, self.get_valid_formats()) 408 | self['formats'] = _formats 409 | 410 | return True 411 | 412 | 413 | class ProjectConfig(list): 414 | 415 | """ 416 | Wrapper for multiple build configs. 417 | """ 418 | 419 | def validate(self): 420 | for build in self: 421 | build.validate() 422 | 423 | def set_output_base(self, directory): 424 | for build in self: 425 | build['output_base'] = os.path.abspath(directory) 426 | 427 | 428 | def load(path, env_config): 429 | """ 430 | Load a project configuration and the top-most build config for a 431 | given path. That is usually the root of the project, but will look deeper. 432 | 433 | The config will be validated. 434 | """ 435 | 436 | filename = find_one(path, CONFIG_FILENAMES) 437 | 438 | if not filename: 439 | files = '{}'.format(', '.join(map(repr, CONFIG_FILENAMES[:-1]))) 440 | if files: 441 | files += ' or ' 442 | files += '{!r}'.format(CONFIG_FILENAMES[-1]) 443 | raise ConfigError('No files {} found'.format(files), 444 | code=CONFIG_REQUIRED) 445 | build_configs = [] 446 | with open(filename, 'r') as file: 447 | try: 448 | configs = parse(file.read()) 449 | except ParseError as error: 450 | raise ConfigError( 451 | 'Parse error in {filename}: {message}'.format( 452 | filename=filename, 453 | message=str(error)), 454 | code=CONFIG_SYNTAX_INVALID) 455 | for i, config in enumerate(configs): 456 | build_config = BuildConfig( 457 | env_config, 458 | config, 459 | source_file=filename, 460 | source_position=i) 461 | build_configs.append(build_config) 462 | 463 | project_config = ProjectConfig(build_configs) 464 | project_config.validate() 465 | return project_config 466 | -------------------------------------------------------------------------------- /readthedocs_build/config/find.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | 4 | def find_all(path, filenames): 5 | path = os.path.abspath(path) 6 | for root, dirs, files in os.walk(path, topdown=True): 7 | dirs.sort() 8 | for filename in filenames: 9 | if filename in files: 10 | yield os.path.abspath(os.path.join(root, filename)) 11 | 12 | 13 | def find_one(path, filenames): 14 | for _path in find_all(path, filenames): 15 | return _path 16 | return '' 17 | -------------------------------------------------------------------------------- /readthedocs_build/config/parser.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | 4 | __all__ = ('parse', 'ParseError') 5 | 6 | 7 | class ParseError(Exception): 8 | pass 9 | 10 | 11 | def parse(stream): 12 | """ 13 | Take file-like object and return a list of project configurations. 14 | 15 | The files need be valid YAML and only contain mappings as documents. 16 | Everything else raises a ``ParseError``. 17 | """ 18 | try: 19 | configs = list(yaml.safe_load_all(stream)) 20 | except yaml.YAMLError as error: 21 | raise ParseError('YAML: {message}'.format(message=error)) 22 | if not configs: 23 | raise ParseError('Empty config') 24 | for config in configs: 25 | if not isinstance(config, dict): 26 | raise ParseError('Expected mapping') 27 | return configs 28 | -------------------------------------------------------------------------------- /readthedocs_build/config/test_config.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from mock import DEFAULT 3 | from pytest import raises 4 | import os 5 | 6 | from ..testing.utils import apply_fs 7 | from .config import ConfigError 8 | from .config import InvalidConfig 9 | from .config import load 10 | from .config import BuildConfig 11 | from .config import ProjectConfig 12 | from .config import TYPE_REQUIRED 13 | from .config import NAME_REQUIRED 14 | from .config import NAME_INVALID 15 | from .config import PYTHON_INVALID 16 | from .validation import INVALID_BOOL 17 | from .validation import INVALID_CHOICE 18 | from .validation import INVALID_DIRECTORY 19 | from .validation import INVALID_LIST 20 | from .validation import INVALID_PATH 21 | from .validation import INVALID_STRING 22 | 23 | 24 | env_config = { 25 | 'output_base': '/tmp' 26 | } 27 | 28 | 29 | minimal_config = { 30 | 'name': 'docs', 31 | 'type': 'sphinx', 32 | } 33 | 34 | 35 | config_with_explicit_empty_list = { 36 | 'readthedocs.yml': ''' 37 | name: docs 38 | type: sphinx 39 | formats: [] 40 | ''' 41 | } 42 | 43 | 44 | minimal_config_dir = { 45 | 'readthedocs.yml': '''\ 46 | name: docs 47 | type: sphinx 48 | ''' 49 | } 50 | 51 | 52 | multiple_config_dir = { 53 | 'readthedocs.yml': ''' 54 | name: first 55 | type: sphinx 56 | --- 57 | name: second 58 | type: sphinx 59 | ''', 60 | 'nested': minimal_config_dir, 61 | } 62 | 63 | 64 | def get_build_config(config, env_config=None, source_file='readthedocs.yml', 65 | source_position=0): 66 | return BuildConfig( 67 | env_config or {}, 68 | config, 69 | source_file=source_file, 70 | source_position=source_position) 71 | 72 | 73 | def test_load_no_config_file(tmpdir): 74 | base = str(tmpdir) 75 | with raises(ConfigError): 76 | load(base, env_config) 77 | 78 | 79 | def test_load_empty_config_file(tmpdir): 80 | apply_fs(tmpdir, { 81 | 'readthedocs.yml': '' 82 | }) 83 | base = str(tmpdir) 84 | with raises(ConfigError): 85 | load(base, env_config) 86 | 87 | 88 | def test_minimal_config(tmpdir): 89 | apply_fs(tmpdir, minimal_config_dir) 90 | base = str(tmpdir) 91 | config = load(base, env_config) 92 | assert isinstance(config, ProjectConfig) 93 | assert len(config) == 1 94 | build = config[0] 95 | assert isinstance(build, BuildConfig) 96 | 97 | 98 | def test_build_config_has_source_file(tmpdir): 99 | base = str(apply_fs(tmpdir, minimal_config_dir)) 100 | build = load(base, env_config)[0] 101 | assert build.source_file == os.path.join(base, 'readthedocs.yml') 102 | assert build.source_position == 0 103 | 104 | 105 | def test_build_config_has_source_position(tmpdir): 106 | base = str(apply_fs(tmpdir, multiple_config_dir)) 107 | builds = load(base, env_config) 108 | assert len(builds) == 2 109 | first, second = filter( 110 | lambda b: not b.source_file.endswith('nested/readthedocs.yml'), 111 | builds) 112 | assert first.source_position == 0 113 | assert second.source_position == 1 114 | 115 | 116 | def test_build_config_has_list_with_single_null_value(tmpdir): 117 | base = str(apply_fs(tmpdir, config_with_explicit_empty_list)) 118 | build = load(base, env_config)[0] 119 | assert isinstance(build, BuildConfig) 120 | assert 'formats' not in build 121 | 122 | 123 | def test_config_requires_name(): 124 | build = BuildConfig({}, 125 | {}, 126 | source_file=None, 127 | source_position=None) 128 | with raises(InvalidConfig) as excinfo: 129 | build.validate_name() 130 | assert excinfo.value.key == 'name' 131 | assert excinfo.value.code == NAME_REQUIRED 132 | 133 | 134 | def test_build_requires_valid_name(): 135 | build = BuildConfig({}, 136 | {'name': 'with/slashes'}, 137 | source_file=None, 138 | source_position=None) 139 | with raises(InvalidConfig) as excinfo: 140 | build.validate_name() 141 | assert excinfo.value.key == 'name' 142 | assert excinfo.value.code == NAME_INVALID 143 | 144 | 145 | def test_config_requires_type(): 146 | build = BuildConfig({}, 147 | {'name': 'docs'}, 148 | source_file=None, 149 | source_position=None) 150 | with raises(InvalidConfig) as excinfo: 151 | build.validate_type() 152 | assert excinfo.value.key == 'type' 153 | assert excinfo.value.code == TYPE_REQUIRED 154 | 155 | 156 | def test_build_requires_valid_type(): 157 | build = BuildConfig({}, 158 | {'type': 'unknown'}, 159 | source_file=None, 160 | source_position=None) 161 | with raises(InvalidConfig) as excinfo: 162 | build.validate_type() 163 | assert excinfo.value.key == 'type' 164 | assert excinfo.value.code == INVALID_CHOICE 165 | 166 | 167 | def test_empty_python_section_is_valid(): 168 | build = get_build_config({'python': {}}) 169 | build.validate_python() 170 | assert 'python' in build 171 | 172 | 173 | def test_python_section_must_be_dict(): 174 | build = get_build_config({'python': 123}) 175 | with raises(InvalidConfig) as excinfo: 176 | build.validate_python() 177 | assert excinfo.value.key == 'python' 178 | assert excinfo.value.code == PYTHON_INVALID 179 | 180 | 181 | def test_use_system_site_packages_defaults_to_false(): 182 | build = get_build_config({'python': {}}) 183 | build.validate_python() 184 | # Default is False. 185 | assert not build['python']['use_system_site_packages'] 186 | 187 | 188 | def test_python_pip_install_default(): 189 | build = get_build_config({'python': {}}) 190 | build.validate_python() 191 | # Default is False. 192 | assert build['python']['pip_install'] is False 193 | 194 | 195 | def describe_validate_python_extra_requirements(): 196 | 197 | def it_defaults_to_list(): 198 | build = get_build_config({'python': {}}) 199 | build.validate_python() 200 | # Default is an empty list. 201 | assert build['python']['extra_requirements'] == [] 202 | 203 | def it_validates_is_a_list(): 204 | build = get_build_config( 205 | {'python': {'extra_requirements': 'invalid'}}) 206 | with raises(InvalidConfig) as excinfo: 207 | build.validate_python() 208 | assert excinfo.value.key == 'python.extra_requirements' 209 | assert excinfo.value.code == PYTHON_INVALID 210 | 211 | @patch('readthedocs_build.config.config.validate_string') 212 | def it_uses_validate_string(validate_string): 213 | validate_string.return_value = True 214 | build = get_build_config( 215 | {'python': {'extra_requirements': ['tests']}}) 216 | build.validate_python() 217 | validate_string.assert_any_call('tests') 218 | 219 | 220 | def describe_validate_use_system_site_packages(): 221 | def it_defaults_to_false(): 222 | build = get_build_config({'python': {}}) 223 | build.validate_python() 224 | assert build['python']['setup_py_install'] is False 225 | 226 | def it_validates_value(): 227 | build = get_build_config( 228 | {'python': {'use_system_site_packages': 'invalid'}}) 229 | with raises(InvalidConfig) as excinfo: 230 | build.validate_python() 231 | excinfo.value.key = 'python.use_system_site_packages' 232 | excinfo.value.code = INVALID_BOOL 233 | 234 | @patch('readthedocs_build.config.config.validate_bool') 235 | def it_uses_validate_bool(validate_bool): 236 | validate_bool.return_value = True 237 | build = get_build_config( 238 | {'python': {'use_system_site_packages': 'to-validate'}}) 239 | build.validate_python() 240 | validate_bool.assert_any_call('to-validate') 241 | 242 | 243 | def describe_validate_setup_py_install(): 244 | 245 | def it_defaults_to_false(): 246 | build = get_build_config({'python': {}}) 247 | build.validate_python() 248 | assert build['python']['setup_py_install'] is False 249 | 250 | def it_validates_value(): 251 | build = get_build_config({'python': {'setup_py_install': 'this-is-string'}}) 252 | with raises(InvalidConfig) as excinfo: 253 | build.validate_python() 254 | assert excinfo.value.key == 'python.setup_py_install' 255 | assert excinfo.value.code == INVALID_BOOL 256 | 257 | @patch('readthedocs_build.config.config.validate_bool') 258 | def it_uses_validate_bool(validate_bool): 259 | validate_bool.return_value = True 260 | build = get_build_config( 261 | {'python': {'setup_py_install': 'to-validate'}}) 262 | build.validate_python() 263 | validate_bool.assert_any_call('to-validate') 264 | 265 | 266 | def describe_validate_python_version(): 267 | 268 | def it_defaults_to_a_valid_version(): 269 | build = get_build_config({'python': {}}) 270 | build.validate_python() 271 | assert build['python']['version'] is 2 272 | 273 | def it_supports_other_versions(): 274 | build = get_build_config({'python': {'version': 3.5}}) 275 | build.validate_python() 276 | assert build['python']['version'] is 3.5 277 | 278 | def it_validates_versions_out_of_range(): 279 | build = get_build_config({'python': {'version': 1.0}}) 280 | with raises(InvalidConfig) as excinfo: 281 | build.validate_python() 282 | assert excinfo.value.key == 'python.version' 283 | assert excinfo.value.code == INVALID_CHOICE 284 | 285 | def it_validates_wrong_type(): 286 | build = get_build_config({'python': {'version': 'this-is-string'}}) 287 | with raises(InvalidConfig) as excinfo: 288 | build.validate_python() 289 | assert excinfo.value.key == 'python.version' 290 | assert excinfo.value.code == INVALID_CHOICE 291 | 292 | def it_validates_wrong_type_right_value(): 293 | build = get_build_config({'python': {'version': '3.5'}}) 294 | build.validate_python() 295 | assert build['python']['version'] == 3.5 296 | 297 | build = get_build_config({'python': {'version': '3'}}) 298 | build.validate_python() 299 | assert build['python']['version'] == 3 300 | 301 | def it_validates_env_supported_versions(): 302 | build = get_build_config( 303 | {'python': {'version': 3.6}}, 304 | env_config={'python': {'supported_versions': [3.5]}} 305 | ) 306 | with raises(InvalidConfig) as excinfo: 307 | build.validate_python() 308 | assert excinfo.value.key == 'python.version' 309 | assert excinfo.value.code == INVALID_CHOICE 310 | 311 | build = get_build_config( 312 | {'python': {'version': 3.6}}, 313 | env_config={'python': {'supported_versions': [3.5, 3.6]}} 314 | ) 315 | build.validate_python() 316 | assert build['python']['version'] == 3.6 317 | 318 | 319 | def describe_validate_formats(): 320 | 321 | def it_defaults_to_not_being_included(): 322 | build = get_build_config({}) 323 | build.validate_formats() 324 | assert 'formats' not in build 325 | 326 | def it_gets_set_correctly(): 327 | build = get_build_config({'formats': ['pdf']}) 328 | build.validate_formats() 329 | assert build['formats'] == ['pdf'] 330 | 331 | def formats_can_be_empty(): 332 | build = get_build_config({'formats': []}) 333 | build.validate_formats() 334 | assert 'formats' not in build 335 | 336 | def all_valid_formats(): 337 | build = get_build_config({'formats': ['pdf', 'htmlzip', 'epub']}) 338 | build.validate_formats() 339 | assert build['formats'] == ['pdf', 'htmlzip', 'epub'] 340 | 341 | def cant_have_none_as_format(): 342 | build = get_build_config({'formats': ['htmlzip', None]}) 343 | with raises(InvalidConfig) as excinfo: 344 | build.validate_formats() 345 | assert excinfo.value.key == 'format' 346 | assert excinfo.value.code == INVALID_CHOICE 347 | 348 | def formats_have_only_allowed_values(): 349 | build = get_build_config({'formats': ['htmlzip', 'csv']}) 350 | with raises(InvalidConfig) as excinfo: 351 | build.validate_formats() 352 | assert excinfo.value.key == 'format' 353 | assert excinfo.value.code == INVALID_CHOICE 354 | 355 | def only_list_type(): 356 | build = get_build_config({'formats': None}) 357 | with raises(InvalidConfig) as excinfo: 358 | build.validate_formats() 359 | assert excinfo.value.key == 'format' 360 | assert excinfo.value.code == INVALID_LIST 361 | 362 | 363 | def describe_validate_setup_py_path(): 364 | 365 | def it_defaults_to_source_file_directory(tmpdir): 366 | with tmpdir.as_cwd(): 367 | source_file = tmpdir.join('subdir', 'readthedocs.yml') 368 | setup_py = tmpdir.join('subdir', 'setup.py') 369 | build = get_build_config({}, source_file=str(source_file)) 370 | build.validate_python() 371 | assert build['python']['setup_py_path'] == str(setup_py) 372 | 373 | def it_validates_value(tmpdir): 374 | with tmpdir.as_cwd(): 375 | build = get_build_config({'python': {'setup_py_path': 'this-is-string'}}) 376 | with raises(InvalidConfig) as excinfo: 377 | build.validate_python() 378 | assert excinfo.value.key == 'python.setup_py_path' 379 | assert excinfo.value.code == INVALID_PATH 380 | 381 | def it_uses_validate_file(tmpdir): 382 | path = tmpdir.join('setup.py') 383 | path.write('content') 384 | path = str(path) 385 | patcher = patch('readthedocs_build.config.config.validate_file') 386 | with patcher as validate_file: 387 | validate_file.return_value = path 388 | build = get_build_config( 389 | {'python': {'setup_py_path': 'setup.py'}}) 390 | build.validate_python() 391 | args, kwargs = validate_file.call_args 392 | assert args[0] == 'setup.py' 393 | 394 | 395 | def test_valid_build_config(): 396 | build = BuildConfig(env_config, 397 | minimal_config, 398 | source_file='readthedocs.yml', 399 | source_position=0) 400 | build.validate() 401 | assert build['name'] == 'docs' 402 | assert build['type'] == 'sphinx' 403 | assert build['base'] 404 | assert build['python'] 405 | assert 'setup_py_install' in build['python'] 406 | assert 'use_system_site_packages' in build['python'] 407 | assert build['output_base'] 408 | 409 | 410 | def describe_validate_base(): 411 | 412 | def it_validates_to_abspath(tmpdir): 413 | apply_fs(tmpdir, {'configs': minimal_config, 'docs': {}}) 414 | with tmpdir.as_cwd(): 415 | source_file = str(tmpdir.join('configs', 'readthedocs.yml')) 416 | build = BuildConfig( 417 | {}, 418 | {'base': '../docs'}, 419 | source_file=source_file, 420 | source_position=0) 421 | build.validate_base() 422 | assert build['base'] == str(tmpdir.join('docs')) 423 | 424 | @patch('readthedocs_build.config.config.validate_directory') 425 | def it_uses_validate_directory(validate_directory): 426 | validate_directory.return_value = 'path' 427 | build = get_build_config({'base': '../my-path'}) 428 | build.validate_base() 429 | # Test for first argument to validate_directory 430 | args, kwargs = validate_directory.call_args 431 | assert args[0] == '../my-path' 432 | 433 | def it_fails_if_base_is_not_a_string(tmpdir): 434 | apply_fs(tmpdir, minimal_config) 435 | with tmpdir.as_cwd(): 436 | build = BuildConfig( 437 | {}, 438 | {'base': 1}, 439 | source_file=str(tmpdir.join('readthedocs.yml')), 440 | source_position=0) 441 | with raises(InvalidConfig) as excinfo: 442 | build.validate_base() 443 | assert excinfo.value.key == 'base' 444 | assert excinfo.value.code == INVALID_STRING 445 | 446 | def it_fails_if_base_does_not_exist(tmpdir): 447 | apply_fs(tmpdir, minimal_config) 448 | build = BuildConfig( 449 | {}, 450 | {'base': 'docs'}, 451 | source_file=str(tmpdir.join('readthedocs.yml')), 452 | source_position=0) 453 | with raises(InvalidConfig) as excinfo: 454 | build.validate_base() 455 | assert excinfo.value.key == 'base' 456 | assert excinfo.value.code == INVALID_PATH 457 | 458 | 459 | def describe_validate_build(): 460 | 461 | def it_fails_if_build_is_invalid_option(tmpdir): 462 | apply_fs(tmpdir, minimal_config) 463 | build = BuildConfig( 464 | {}, 465 | {'build': {'image': 3.0}}, 466 | source_file=str(tmpdir.join('readthedocs.yml')), 467 | source_position=0) 468 | with raises(InvalidConfig) as excinfo: 469 | build.validate_build() 470 | assert excinfo.value.key == 'build' 471 | assert excinfo.value.code == INVALID_CHOICE 472 | 473 | def it_fails_on_python_validation(tmpdir): 474 | apply_fs(tmpdir, minimal_config) 475 | build = BuildConfig( 476 | {}, 477 | { 478 | 'build': {'image': 1.0}, 479 | 'python': {'version': '3.3'}, 480 | }, 481 | source_file=str(tmpdir.join('readthedocs.yml')), 482 | source_position=0) 483 | build.validate_build() 484 | with raises(InvalidConfig) as excinfo: 485 | build.validate_python() 486 | assert excinfo.value.key == 'python.version' 487 | assert excinfo.value.code == INVALID_CHOICE 488 | 489 | def it_works_on_python_validation(tmpdir): 490 | apply_fs(tmpdir, minimal_config) 491 | build = BuildConfig( 492 | {}, 493 | { 494 | 'build': {'image': 'latest'}, 495 | 'python': {'version': '3.3'}, 496 | }, 497 | source_file=str(tmpdir.join('readthedocs.yml')), 498 | source_position=0) 499 | build.validate_build() 500 | build.validate_python() 501 | 502 | def it_works(tmpdir): 503 | apply_fs(tmpdir, minimal_config) 504 | build = BuildConfig( 505 | {}, 506 | {'build': {'image': 'latest'}}, 507 | source_file=str(tmpdir.join('readthedocs.yml')), 508 | source_position=0) 509 | build.validate_build() 510 | assert build['build']['image'] == 'readthedocs/build:latest' 511 | 512 | def default(tmpdir): 513 | apply_fs(tmpdir, minimal_config) 514 | build = BuildConfig( 515 | {}, 516 | {}, 517 | source_file=str(tmpdir.join('readthedocs.yml')), 518 | source_position=0) 519 | build.validate_build() 520 | assert build['build']['image'] == 'readthedocs/build:2.0' 521 | 522 | 523 | def test_build_validate_calls_all_subvalidators(tmpdir): 524 | apply_fs(tmpdir, minimal_config) 525 | build = BuildConfig( 526 | {}, 527 | {}, 528 | source_file=str(tmpdir.join('readthedocs.yml')), 529 | source_position=0) 530 | with patch.multiple(BuildConfig, 531 | validate_base=DEFAULT, 532 | validate_name=DEFAULT, 533 | validate_type=DEFAULT, 534 | validate_python=DEFAULT, 535 | validate_output_base=DEFAULT): 536 | build.validate() 537 | BuildConfig.validate_base.assert_called_with() 538 | BuildConfig.validate_name.assert_called_with() 539 | BuildConfig.validate_type.assert_called_with() 540 | BuildConfig.validate_python.assert_called_with() 541 | BuildConfig.validate_output_base.assert_called_with() 542 | 543 | 544 | def test_validate_project_config(): 545 | with patch.object(BuildConfig, 'validate') as build_validate: 546 | project = ProjectConfig([ 547 | BuildConfig( 548 | env_config, 549 | minimal_config, 550 | source_file='readthedocs.yml', 551 | source_position=0) 552 | ]) 553 | project.validate() 554 | assert build_validate.call_count == 1 555 | 556 | 557 | def test_load_calls_validate(tmpdir): 558 | apply_fs(tmpdir, minimal_config_dir) 559 | base = str(tmpdir) 560 | with patch.object(BuildConfig, 'validate') as build_validate: 561 | load(base, env_config) 562 | assert build_validate.call_count == 1 563 | 564 | 565 | def test_project_set_output_base(): 566 | project = ProjectConfig([ 567 | BuildConfig( 568 | env_config, 569 | minimal_config, 570 | source_file='readthedocs.yml', 571 | source_position=0), 572 | BuildConfig( 573 | env_config, 574 | minimal_config, 575 | source_file='readthedocs.yml', 576 | source_position=1), 577 | ]) 578 | project.set_output_base('random') 579 | for build_config in project: 580 | assert ( 581 | build_config['output_base'] == os.path.join(os.getcwd(), 'random')) 582 | -------------------------------------------------------------------------------- /readthedocs_build/config/test_find.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | import six 5 | 6 | from .find import find_all, find_one 7 | from ..testing.utils import apply_fs 8 | 9 | 10 | def test_find_no_files(tmpdir): 11 | with tmpdir.as_cwd(): 12 | paths = list(find_all(os.getcwd(), ('readthedocs.yml',))) 13 | assert len(paths) == 0 14 | 15 | 16 | def test_find_at_root(tmpdir): 17 | apply_fs(tmpdir, {'readthedocs.yml': '', 'otherfile.txt': ''}) 18 | 19 | base = str(tmpdir) 20 | paths = list(find_all(base, ('readthedocs.yml',))) 21 | assert paths == [ 22 | os.path.abspath(os.path.join(base, 'readthedocs.yml')) 23 | ] 24 | 25 | 26 | def test_find_nested(tmpdir): 27 | apply_fs(tmpdir, { 28 | 'first': { 29 | 'readthedocs.yml': '', 30 | }, 31 | 'second': { 32 | 'confuser.txt': 'content', 33 | }, 34 | 'third': { 35 | 'readthedocs.yml': 'content', 36 | 'Makefile': '', 37 | }, 38 | }) 39 | apply_fs(tmpdir, {'first/readthedocs.yml': ''}) 40 | 41 | base = str(tmpdir) 42 | paths = list(find_all(base, ('readthedocs.yml',))) 43 | assert set(paths) == set([ 44 | str(tmpdir.join('first', 'readthedocs.yml')), 45 | str(tmpdir.join('third', 'readthedocs.yml')), 46 | ]) 47 | 48 | 49 | def test_find_multiple_files(tmpdir): 50 | apply_fs(tmpdir, { 51 | 'first': { 52 | 'readthedocs.yml': '', 53 | '.readthedocs.yml': 'content', 54 | }, 55 | 'second': { 56 | 'confuser.txt': 'content', 57 | }, 58 | 'third': { 59 | 'readthedocs.yml': 'content', 60 | 'Makefile': '', 61 | }, 62 | }) 63 | apply_fs(tmpdir, {'first/readthedocs.yml': ''}) 64 | 65 | base = str(tmpdir) 66 | paths = list(find_all(base, ('readthedocs.yml', 67 | '.readthedocs.yml'))) 68 | assert paths == [ 69 | str(tmpdir.join('first', 'readthedocs.yml')), 70 | str(tmpdir.join('first', '.readthedocs.yml')), 71 | str(tmpdir.join('third', 'readthedocs.yml')), 72 | ] 73 | 74 | paths = list(find_all(base, ('.readthedocs.yml', 75 | 'readthedocs.yml'))) 76 | assert paths == [ 77 | str(tmpdir.join('first', '.readthedocs.yml')), 78 | str(tmpdir.join('first', 'readthedocs.yml')), 79 | str(tmpdir.join('third', 'readthedocs.yml')), 80 | ] 81 | 82 | 83 | @pytest.mark.skipif(not six.PY2, reason='Only for python2') 84 | @pytest.mark.xfail(raises=UnicodeDecodeError) 85 | def test_find_unicode_path(tmpdir): 86 | base_path = os.path.abspath('integration_tests/bad_encode_project') 87 | assert isinstance(base_path, str) 88 | unicode_base_path = base_path.decode('utf-8') 89 | assert isinstance(unicode_base_path, unicode) 90 | path = find_one(unicode_base_path, ('readthedocs.yml',)) 91 | assert path == '' 92 | assert False, 'The UnicodeDecodeError was not raised' 93 | -------------------------------------------------------------------------------- /readthedocs_build/config/test_parser.py: -------------------------------------------------------------------------------- 1 | from io import StringIO 2 | from pytest import raises 3 | 4 | from .parser import parse 5 | from .parser import ParseError 6 | 7 | 8 | def test_parse_empty_config_file(): 9 | buf = StringIO(u'') 10 | with raises(ParseError): 11 | parse(buf) 12 | 13 | 14 | def test_parse_invalid_yaml(): 15 | buf = StringIO(u'- - !asdf') 16 | with raises(ParseError): 17 | parse(buf) 18 | 19 | 20 | def test_parse_bad_type(): 21 | buf = StringIO(u'Hello') 22 | with raises(ParseError): 23 | parse(buf) 24 | 25 | 26 | def test_parse_single_config(): 27 | buf = StringIO(u'base: path') 28 | config = parse(buf) 29 | assert isinstance(config, list) 30 | assert len(config) == 1 31 | assert config[0]['base'] == 'path' 32 | 33 | 34 | def test_parse_empty_list(): 35 | buf = StringIO(u'base: []') 36 | config = parse(buf) 37 | assert config[0]['base'] == [] 38 | 39 | 40 | def test_parse_multiple_configs_in_one_file(): 41 | buf = StringIO( 42 | u''' 43 | base: path 44 | --- 45 | base: other_path 46 | name: second 47 | nested: 48 | works: true 49 | ''') 50 | configs = parse(buf) 51 | assert isinstance(configs, list) 52 | assert len(configs) == 2 53 | assert configs[0]['base'] == 'path' 54 | assert configs[1]['nested'] == {'works': True} 55 | -------------------------------------------------------------------------------- /readthedocs_build/config/test_validation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from mock import patch 3 | from pytest import raises 4 | import os 5 | from six import text_type 6 | 7 | from .validation import validate_bool 8 | from .validation import validate_choice 9 | from .validation import validate_list 10 | from .validation import validate_directory 11 | from .validation import validate_file 12 | from .validation import validate_path 13 | from .validation import validate_string 14 | from .validation import ValidationError 15 | from .validation import INVALID_BOOL 16 | from .validation import INVALID_CHOICE 17 | from .validation import INVALID_LIST 18 | from .validation import INVALID_DIRECTORY 19 | from .validation import INVALID_FILE 20 | from .validation import INVALID_PATH 21 | from .validation import INVALID_STRING 22 | 23 | 24 | def describe_validate_bool(): 25 | def it_accepts_true(): 26 | assert validate_bool(True) is True 27 | 28 | def it_accepts_false(): 29 | assert validate_bool(False) is False 30 | 31 | def it_accepts_0(): 32 | assert validate_bool(0) is False 33 | 34 | def it_accepts_1(): 35 | assert validate_bool(1) is True 36 | 37 | def it_fails_on_string(): 38 | with raises(ValidationError) as excinfo: 39 | validate_bool('random string') 40 | assert excinfo.value.code == INVALID_BOOL 41 | 42 | 43 | def describe_validate_choice(): 44 | 45 | def it_accepts_valid_choice(): 46 | result = validate_choice('choice', ('choice', 'another_choice')) 47 | assert result is 'choice' 48 | 49 | with raises(ValidationError) as excinfo: 50 | validate_choice('c', 'abc') 51 | assert excinfo.value.code == INVALID_LIST 52 | 53 | def it_rejects_invalid_choice(): 54 | with raises(ValidationError) as excinfo: 55 | validate_choice('not-a-choice', ('choice', 'another_choice')) 56 | assert excinfo.value.code == INVALID_CHOICE 57 | 58 | 59 | def describe_validate_list(): 60 | 61 | def it_accepts_list_types(): 62 | result = validate_list(['choice', 'another_choice']) 63 | assert result == ['choice', 'another_choice'] 64 | 65 | result = validate_list(('choice', 'another_choice')) 66 | assert result == ['choice', 'another_choice'] 67 | 68 | def iterator(): 69 | yield 'choice' 70 | 71 | result = validate_list(iterator()) 72 | assert result == ['choice'] 73 | 74 | with raises(ValidationError) as excinfo: 75 | validate_choice('c', 'abc') 76 | assert excinfo.value.code == INVALID_LIST 77 | 78 | def it_rejects_string_types(): 79 | with raises(ValidationError) as excinfo: 80 | result = validate_list('choice') 81 | assert excinfo.value.code == INVALID_LIST 82 | 83 | 84 | def describe_validate_directory(): 85 | 86 | def it_uses_validate_path(tmpdir): 87 | patcher = patch('readthedocs_build.config.validation.validate_path') 88 | with patcher as validate_path: 89 | path = text_type(tmpdir.mkdir('a directory')) 90 | validate_path.return_value = path 91 | validate_directory(path, str(tmpdir)) 92 | validate_path.assert_called_with(path, str(tmpdir)) 93 | 94 | def it_rejects_files(tmpdir): 95 | tmpdir.join('file').write('content') 96 | with raises(ValidationError) as excinfo: 97 | validate_directory('file', str(tmpdir)) 98 | assert excinfo.value.code == INVALID_DIRECTORY 99 | 100 | 101 | def describe_validate_file(): 102 | 103 | def it_uses_validate_path(tmpdir): 104 | patcher = patch('readthedocs_build.config.validation.validate_path') 105 | with patcher as validate_path: 106 | path = tmpdir.join('a file') 107 | path.write('content') 108 | path = str(path) 109 | validate_path.return_value = path 110 | validate_file(path, str(tmpdir)) 111 | validate_path.assert_called_with(path, str(tmpdir)) 112 | 113 | def it_rejects_directories(tmpdir): 114 | tmpdir.mkdir('directory') 115 | with raises(ValidationError) as excinfo: 116 | validate_file('directory', str(tmpdir)) 117 | assert excinfo.value.code == INVALID_FILE 118 | 119 | 120 | def describe_validate_path(): 121 | 122 | def it_accepts_relative_path(tmpdir): 123 | tmpdir.mkdir('a directory') 124 | validate_path('a directory', str(tmpdir)) 125 | 126 | def it_accepts_files(tmpdir): 127 | tmpdir.join('file').write('content') 128 | validate_path('file', str(tmpdir)) 129 | 130 | def it_accepts_absolute_path(tmpdir): 131 | path = str(tmpdir.mkdir('a directory')) 132 | validate_path(path, 'does not matter') 133 | 134 | def it_returns_absolute_path(tmpdir): 135 | tmpdir.mkdir('a directory') 136 | path = validate_path('a directory', str(tmpdir)) 137 | assert path == os.path.abspath(path) 138 | 139 | def it_only_accepts_strings(): 140 | with raises(ValidationError) as excinfo: 141 | validate_path(None, '') 142 | assert excinfo.value.code == INVALID_STRING 143 | 144 | def it_rejects_non_existent_path(tmpdir): 145 | with raises(ValidationError) as excinfo: 146 | validate_path('does not exist', str(tmpdir)) 147 | assert excinfo.value.code == INVALID_PATH 148 | 149 | 150 | def describe_validate_string(): 151 | 152 | def it_accepts_unicode(): 153 | result = validate_string(u'Unicöde') 154 | assert isinstance(result, text_type) 155 | 156 | def it_accepts_nonunicode(): 157 | result = validate_string('Unicode') 158 | assert isinstance(result, text_type) 159 | 160 | def it_rejects_float(): 161 | with raises(ValidationError) as excinfo: 162 | validate_string(123.456) 163 | assert excinfo.value.code == INVALID_STRING 164 | 165 | def it_rejects_none(): 166 | with raises(ValidationError) as excinfo: 167 | validate_string(None) 168 | assert excinfo.value.code == INVALID_STRING 169 | -------------------------------------------------------------------------------- /readthedocs_build/config/validation.py: -------------------------------------------------------------------------------- 1 | import os 2 | from six import string_types, text_type 3 | 4 | 5 | INVALID_BOOL = 'invalid-bool' 6 | INVALID_CHOICE = 'invalid-choice' 7 | INVALID_LIST = 'invalid-list' 8 | INVALID_DIRECTORY = 'invalid-directory' 9 | INVALID_FILE = 'invalid-file' 10 | INVALID_PATH = 'invalid-path' 11 | INVALID_STRING = 'invalid-string' 12 | 13 | 14 | class ValidationError(Exception): 15 | messages = { 16 | INVALID_BOOL: 'expected one of (0, 1, true, false), got {value}', 17 | INVALID_CHOICE: 'expected one of ({choices}), got {value}', 18 | INVALID_DIRECTORY: '{value} is not a directory', 19 | INVALID_FILE: '{value} is not a file', 20 | INVALID_PATH: 'path {value} does not exist', 21 | INVALID_STRING: 'expected string', 22 | INVALID_LIST: 'expected list', 23 | } 24 | 25 | def __init__(self, value, code, format_kwargs=None): 26 | self.value = value 27 | self.code = code 28 | defaults = { 29 | 'value': value, 30 | } 31 | if format_kwargs is not None: 32 | defaults.update(format_kwargs) 33 | message = self.messages[code].format(**defaults) 34 | super(ValidationError, self).__init__(message) 35 | 36 | 37 | def validate_list(value): 38 | if isinstance(value, str): 39 | raise ValidationError(value, INVALID_LIST) 40 | if not hasattr(value, '__iter__'): 41 | raise ValidationError(value, INVALID_LIST) 42 | return list(value) 43 | 44 | 45 | def validate_choice(value, choices): 46 | choices = validate_list(choices) 47 | if value not in choices: 48 | raise ValidationError(value, INVALID_CHOICE, { 49 | 'choices': ', '.join(map(str, choices)) 50 | }) 51 | return value 52 | 53 | 54 | def validate_bool(value): 55 | if value not in (0, 1, False, True): 56 | raise ValidationError(value, INVALID_BOOL) 57 | return bool(value) 58 | 59 | 60 | def validate_directory(value, base_path): 61 | path = validate_path(value, base_path) 62 | if not os.path.isdir(path): 63 | raise ValidationError(value, INVALID_DIRECTORY) 64 | return path 65 | 66 | 67 | def validate_file(value, base_path): 68 | path = validate_path(value, base_path) 69 | if not os.path.isfile(path): 70 | raise ValidationError(value, INVALID_FILE) 71 | return path 72 | 73 | 74 | def validate_path(value, base_path): 75 | string_value = validate_string(value) 76 | pathed_value = os.path.join(base_path, string_value) 77 | final_value = os.path.abspath(pathed_value) 78 | if not os.path.exists(final_value): 79 | raise ValidationError(value, INVALID_PATH) 80 | return final_value 81 | 82 | 83 | def validate_string(value): 84 | if not isinstance(value, string_types): 85 | raise ValidationError(value, INVALID_STRING) 86 | return text_type(value) 87 | -------------------------------------------------------------------------------- /readthedocs_build/test_build.py: -------------------------------------------------------------------------------- 1 | from mock import patch 2 | from mock import Mock 3 | 4 | from .build import build 5 | from .builder import builder_types 6 | 7 | 8 | def test_build_triggers_sphinx_builder(tmpdir): 9 | project_config = [{ 10 | 'type': 'sphinx', 11 | }] 12 | with tmpdir.as_cwd(): 13 | sphinx_mock = Mock() 14 | sphinx_mock.return_value = sphinx_mock 15 | with patch.dict(builder_types, {'sphinx': sphinx_mock}): 16 | build(project_config) 17 | sphinx_mock.assert_called_with(build_config=project_config[0]) 18 | sphinx_mock.build.assert_called_with() 19 | -------------------------------------------------------------------------------- /readthedocs_build/test_cli.py: -------------------------------------------------------------------------------- 1 | from click.testing import CliRunner 2 | from mock import patch 3 | import os 4 | 5 | from .builder.base import BaseBuilder 6 | from .cli import main 7 | from .testing.utils import apply_fs 8 | 9 | 10 | minimal_config = { 11 | 'readthedocs.yml': ''' 12 | name: docs 13 | type: sphinx 14 | ''', 15 | } 16 | 17 | 18 | def run(params): 19 | runner = CliRunner() 20 | # Patch ``build`` function to not test the actual build but the config 21 | # parsing etc. 22 | with patch.object(BaseBuilder, 'build'): 23 | return runner.invoke(main, params) 24 | 25 | 26 | def test_main_fails_with_exit_code_1_if_no_config_found(tmpdir): 27 | with tmpdir.as_cwd(): 28 | result = run([]) 29 | assert result.exit_code == 1 30 | 31 | 32 | def test_main_resets_cwd(tmpdir): 33 | tmpdir = apply_fs(tmpdir, { 34 | 'goodpath': { 35 | 'readthedocs.yml': ''' 36 | 37 | ''' 38 | } 39 | }) 40 | 41 | with tmpdir.as_cwd(): 42 | old_cwd = os.getcwd() 43 | run(['goodpath']) 44 | assert os.getcwd() == old_cwd 45 | 46 | 47 | def test_main_takes_path_argument(tmpdir): 48 | tmpdir = apply_fs(tmpdir, { 49 | 'badpath': {}, 50 | 'goodpath': minimal_config, 51 | }) 52 | 53 | with tmpdir.as_cwd(): 54 | result = run(['badpath']) 55 | assert result.exit_code == 1 56 | 57 | result = run(['goodpath']) 58 | assert result.exit_code == 0, result.output 59 | 60 | 61 | def test_main_attaches_outdir_to_env_config(tmpdir): 62 | with apply_fs(tmpdir, minimal_config).as_cwd(): 63 | with patch('readthedocs_build.cli.build') as build: 64 | run(['--outdir=out']) 65 | args, kwargs = build.call_args 66 | project_config = args[0] 67 | assert project_config[0]['output_base'] == str(tmpdir.join('out')) 68 | 69 | 70 | def test_outdir_default(tmpdir): 71 | with apply_fs(tmpdir, minimal_config).as_cwd(): 72 | with patch('readthedocs_build.cli.build') as build: 73 | run([]) 74 | args, kwargs = build.call_args 75 | project_config = args[0] 76 | outdir = str(tmpdir.join('_readthedocs_build')) 77 | assert project_config[0]['output_base'] == outdir 78 | -------------------------------------------------------------------------------- /readthedocs_build/testing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/readthedocs/readthedocs-build/e271e1861b1e68f9c15e08e84cc2b85fbfb45310/readthedocs_build/testing/__init__.py -------------------------------------------------------------------------------- /readthedocs_build/testing/test_utils.py: -------------------------------------------------------------------------------- 1 | from .utils import apply_fs 2 | 3 | 4 | def test_apply_fs_with_empty_contents(tmpdir): 5 | # Doesn't do anything if second paramter is empty. 6 | apply_fs(tmpdir, {}) 7 | assert tmpdir.listdir() == [] 8 | 9 | 10 | def test_apply_fs_create_empty_file(tmpdir): 11 | # Create empty file. 12 | apply_fs(tmpdir, {'file': ''}) 13 | assert len(tmpdir.listdir()) == 1 14 | assert tmpdir.join('file').read() == '' 15 | 16 | 17 | def test_apply_fs_create_file_with_content(tmpdir): 18 | # Create file with content. 19 | apply_fs(tmpdir, {'file': 'content'}) 20 | assert tmpdir.join('file').read() == 'content' 21 | 22 | 23 | def test_apply_fs_create_subdirectory(tmpdir): 24 | # Create file with content. 25 | apply_fs(tmpdir, {'subdir': {'file': 'content'}}) 26 | assert tmpdir.join('subdir', 'file').read() == 'content' 27 | -------------------------------------------------------------------------------- /readthedocs_build/testing/utils.py: -------------------------------------------------------------------------------- 1 | def apply_fs(tmpdir, contents): 2 | """ 3 | Create the directory structure specified in ``contents``. It's a dict of 4 | filenames as keys and the file contents as values. If the value is another 5 | dict, it's a subdirectory. 6 | """ 7 | for filename, content in contents.items(): 8 | if hasattr(content, 'items'): 9 | apply_fs(tmpdir.mkdir(filename), content) 10 | else: 11 | file = tmpdir.join(filename) 12 | file.write(content) 13 | return tmpdir 14 | -------------------------------------------------------------------------------- /readthedocs_build/utils.py: -------------------------------------------------------------------------------- 1 | import contextlib 2 | import os 3 | 4 | 5 | @contextlib.contextmanager 6 | def cd(directory): 7 | """ 8 | :: 9 | 10 | with cd(new_cwd): 11 | os.walk('.') 12 | """ 13 | old_path = os.getcwd() 14 | os.chdir(directory) 15 | try: 16 | yield 17 | finally: 18 | os.chdir(old_path) 19 | -------------------------------------------------------------------------------- /requirements/linting.txt: -------------------------------------------------------------------------------- 1 | prospector 2 | pylint-django 3 | -------------------------------------------------------------------------------- /requirements/tests.txt: -------------------------------------------------------------------------------- 1 | mock==1.3.0 2 | pytest==3.2.5 3 | pytest-describe==0.11.0 4 | pytest-xdist>=1.12 5 | tox>=2.1 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import codecs 3 | from setuptools import find_packages 4 | from setuptools import setup 5 | 6 | 7 | setup( 8 | name='readthedocs-build', 9 | version='2.0.9', 10 | author='Eric Holscher', 11 | author_email='eric@ericholscher.com', 12 | url='https://readthedocs.org', 13 | license='MIT', 14 | description='Build infrastructure for Read the Docs', 15 | packages=find_packages(), 16 | include_package_data=True, 17 | long_description=codecs.open("README.rst", "r", "utf-8").read(), 18 | install_requires=[ 19 | "PyYAML>=3.0", 20 | "Sphinx>=1.5.2", 21 | "Docutils", 22 | "readthedocs-sphinx-ext", 23 | "recommonmark", 24 | "click>=4.0", 25 | "virtualenv", 26 | "six", 27 | "mock" 28 | ], 29 | entry_points={ 30 | 'console_scripts': [ 31 | 'rtd-build=readthedocs_build.cli:main', 32 | ] 33 | }, 34 | zip_safe=True, 35 | ) 36 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py{27,34,35,36} 4 | py{27,34,35,36}-integration 5 | lint 6 | 7 | [tox:travis] 8 | 2.7 = py27, py27-integration, lint 9 | 3.4 = py34, py34-integration 10 | 3.5 = py35, py35-integration 11 | 3.6 = py36, py36-integration 12 | 13 | [testenv] 14 | deps = 15 | -r{toxinidir}/requirements/tests.txt 16 | ipdb 17 | commands = 18 | py.test readthedocs_build/ {posargs} 19 | integration: py.test integration_tests/ -s 20 | 21 | [testenv:docs] 22 | changedir = {toxinidir}/docs 23 | commands = 24 | sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html 25 | 26 | [testenv:lint] 27 | deps = 28 | {[testenv]deps} 29 | -r{toxinidir}/requirements/linting.txt 30 | commands = 31 | prospector \ 32 | --profile-path={toxinidir} \ 33 | --profile=prospector \ 34 | --die-on-tool-error 35 | --------------------------------------------------------------------------------