diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py index 6119673b8fa..ab542943d05 100644 --- a/readthedocs/doc_builder/backends/mkdocs.py +++ b/readthedocs/doc_builder/backends/mkdocs.py @@ -114,7 +114,7 @@ def append_conf(self, **__): # Handle custom docs dirs user_docs_dir = user_config.get('docs_dir') docs_dir = self.docs_dir(docs_dir=user_docs_dir) - self.create_index(extension='md') + self.create_index(extension='md', force_index=True) user_config['docs_dir'] = docs_dir # Set mkdocs config values diff --git a/readthedocs/doc_builder/base.py b/readthedocs/doc_builder/base.py index 83aac0da617..fc774f97e6b 100644 --- a/readthedocs/doc_builder/base.py +++ b/readthedocs/doc_builder/base.py @@ -2,14 +2,20 @@ """Base classes for Builders.""" from __future__ import ( - absolute_import, division, print_function, unicode_literals) + absolute_import, + division, + print_function, + unicode_literals, +) import logging import os import shutil -from builtins import object +import textwrap from functools import wraps +from builtins import open + log = logging.getLogger(__name__) @@ -94,37 +100,46 @@ def docs_dir(self, docs_dir=None, **__): docs_dir = checkout_path return docs_dir - def create_index(self, extension='md', **__): - """Create an index file if it needs it.""" + def create_index(self, extension='md', force_index=False, **__): + """ + Create an index file if it needs it. + + If force_index is True and there isn't an index file, + one is created whether or not there is a README file. + """ docs_dir = self.docs_dir() index_filename = os.path.join( - docs_dir, 'index.{ext}'.format(ext=extension)) - if not os.path.exists(index_filename): - readme_filename = os.path.join( - docs_dir, 'README.{ext}'.format(ext=extension)) - if os.path.exists(readme_filename): - return 'README' - - index_file = open(index_filename, 'w+') - index_text = """ - -Welcome to Read the Docs ------------------------- - -This is an autogenerated index file. - -Please create an ``index.{ext}`` or ``README.{ext}`` file with your own content -under the root (or ``/docs``) directory in your repository. - -If you want to use another markup, choose a different builder in your settings. -Check out our `Getting Started Guide -`_ to become more -familiar with Read the Docs. - """ - - index_file.write(index_text.format(dir=docs_dir, ext=extension)) - index_file.close() + docs_dir, 'index.{ext}'.format(ext=extension) + ) + readme_filename = os.path.join( + docs_dir, 'README.{ext}'.format(ext=extension) + ) + if os.path.exists(index_filename): + return 'index' + if not force_index and os.path.exists(readme_filename): + return 'README' + + index_text = textwrap.dedent( + """ + Welcome to Read the Docs + ------------------------ + + This is an autogenerated index file. + + Please create an ``index.{ext}`` or ``README.{ext}`` (Sphinx only) file with + your own content under the root (or ``/docs``) directory in your repository. + + If you want to use another markup, choose a different builder in your settings. + Check out our `Getting Started Guide + `_ to become more + familiar with Read the Docs. + """ + ) + with open(index_filename, 'w+') as index_file: + index_file.write( + index_text.format(dir=docs_dir, ext=extension) + ) return 'index' def run(self, *args, **kwargs): diff --git a/readthedocs/rtd_tests/mocks/paths.py b/readthedocs/rtd_tests/mocks/paths.py index 34fa7e5953f..8fa96af0a18 100644 --- a/readthedocs/rtd_tests/mocks/paths.py +++ b/readthedocs/rtd_tests/mocks/paths.py @@ -58,3 +58,21 @@ def check(path): return None return fake_paths(check) + + +def fake_paths_by_endswith(path_dict, default_return=None): + """ + Usage: + + >>> paths = {'my.txt': True, 'no.txt': False} + >>> with fake_paths_by_endswith(paths): + ... assert os.path.exists('/usr/dev/my.txt') == True + ... assert os.path.exists('/home/no.txt') == False + """ + def check(path): + for path_to_look, resp in path_dict.items(): + if path.endswith(path_to_look): + return resp + return default_return + + return fake_paths(check) diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py index 57939fad374..449ac97e920 100644 --- a/readthedocs/rtd_tests/tests/test_builds.py +++ b/readthedocs/rtd_tests/tests/test_builds.py @@ -5,17 +5,21 @@ unicode_literals, ) -import mock import os + +import mock from django.test import TestCase from django_dynamic_fixture import fixture, get from readthedocs.builds.models import Build, Version +from readthedocs.doc_builder.backends.mkdocs import MkdocsHTML +from readthedocs.doc_builder.backends.sphinx import HtmlBuilder as SphinxHTML from readthedocs.doc_builder.config import load_yaml_config from readthedocs.doc_builder.environments import LocalBuildEnvironment from readthedocs.doc_builder.python_environments import Virtualenv -from readthedocs.projects.models import Project, EnvironmentVariable +from readthedocs.projects.models import EnvironmentVariable, Project from readthedocs.projects.tasks import UpdateDocsTaskStep +from readthedocs.rtd_tests.mocks.paths import fake_paths_by_endswith from readthedocs.rtd_tests.tests.test_config_integration import create_load from ..mocks.environment import EnvironmentMockGroup @@ -59,6 +63,165 @@ def test_build(self, load_config): self.assertRegexpMatches(cmd[0][0], r'python') self.assertRegexpMatches(cmd[0][1], r'sphinx-build') + @mock.patch('readthedocs.doc_builder.config.load_config') + def test_build_generate_index_file_force(self, load_config): + load_config.side_effect = create_load() + project = get( + Project, + slug='project-1', + documentation_type='mkdocs', + conf_py_file='', + enable_pdf_build=False, + enable_epub_build=False, + versions=[fixture()] + ) + version = project.versions.all()[0] + build_env = LocalBuildEnvironment( + project=project, + version=version, + build={} + ) + python_env = Virtualenv(version=version, build_env=build_env) + base_builder = MkdocsHTML(build_env, python_env) + + paths = { + 'README.md': True, + 'index.md': False, + } + + with mock.patch('readthedocs.doc_builder.base.open', mock.mock_open()) as mock_open, fake_paths_by_endswith(paths): + result = base_builder.create_index(extension='md', force_index=True) + mock_open.assert_called_once_with( + os.path.join(base_builder.docs_dir(), 'index.md'), 'w+' + ) + self.assertEqual(result, 'index') + + @mock.patch('readthedocs.doc_builder.config.load_config') + def test_build_get_index_file_force(self, load_config): + load_config.side_effect = create_load() + project = get( + Project, + slug='project-1', + documentation_type='mkdocs', + conf_py_file='', + enable_pdf_build=False, + enable_epub_build=False, + versions=[fixture()] + ) + version = project.versions.all()[0] + build_env = LocalBuildEnvironment( + project=project, + version=version, + build={} + ) + python_env = Virtualenv(version=version, build_env=build_env) + base_builder = MkdocsHTML(build_env, python_env) + + paths = { + 'README.md': True, + 'index.md': True, + } + + with mock.patch('readthedocs.doc_builder.base.open', mock.mock_open()) as mock_open, fake_paths_by_endswith(paths): + result = base_builder.create_index(extension='md', force_index=True) + self.assertEqual(len(mock_open.mock_calls), 0) + self.assertEqual(result, 'index') + + @mock.patch('readthedocs.doc_builder.config.load_config') + def test_build_generate_index_file(self, load_config): + load_config.side_effect = create_load() + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='', + enable_pdf_build=False, + enable_epub_build=False, + versions=[fixture()] + ) + version = project.versions.all()[0] + build_env = LocalBuildEnvironment( + project=project, + version=version, + build={} + ) + python_env = Virtualenv(version=version, build_env=build_env) + base_builder = SphinxHTML(build_env, python_env) + + paths = { + 'README.md': False, + 'index,md': False, + } + + with mock.patch('readthedocs.doc_builder.base.open', mock.mock_open()) as mock_open, fake_paths_by_endswith(paths): + result = base_builder.create_index(extension='md') + mock_open.assert_called_once_with( + os.path.join(base_builder.docs_dir(), 'index.md'), 'w+' + ) + self.assertEqual(result, 'index') + + @mock.patch('readthedocs.doc_builder.config.load_config') + def test_get_readme_file(self, load_config): + load_config.side_effect = create_load() + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='', + enable_pdf_build=False, + enable_epub_build=False, + versions=[fixture()] + ) + version = project.versions.all()[0] + build_env = LocalBuildEnvironment( + project=project, + version=version, + build={} + ) + python_env = Virtualenv(version=version, build_env=build_env) + base_builder = SphinxHTML(build_env, python_env) + + paths = { + 'README.md': True, + 'index.md': False, + } + + with mock.patch('readthedocs.doc_builder.base.open', mock.mock_open()) as mock_open, fake_paths_by_endswith(paths): + result = base_builder.create_index(extension='md') + self.assertEqual(len(mock_open.mock_calls), 0) + self.assertEqual(result, 'README') + + @mock.patch('readthedocs.doc_builder.config.load_config') + def test_get_index_file(self, load_config): + load_config.side_effect = create_load() + project = get( + Project, + slug='project-1', + documentation_type='sphinx', + conf_py_file='', + enable_pdf_build=False, + enable_epub_build=False, + versions=[fixture()] + ) + version = project.versions.all()[0] + build_env = LocalBuildEnvironment( + project=project, + version=version, + build={} + ) + python_env = Virtualenv(version=version, build_env=build_env) + base_builder = SphinxHTML(build_env, python_env) + + paths = { + 'README.md': False, + 'index.md': True, + } + + with mock.patch('readthedocs.doc_builder.base.open', mock.mock_open()) as mock_open, fake_paths_by_endswith(paths): + result = base_builder.create_index(extension='md') + self.assertEqual(len(mock_open.mock_calls), 0) + self.assertEqual(result, 'index') + @mock.patch('readthedocs.doc_builder.config.load_config') def test_build_respects_pdf_flag(self, load_config): '''Build output format control'''