diff --git a/readthedocs/config/config.py b/readthedocs/config/config.py index 0607fd3eea1..0d949cba486 100644 --- a/readthedocs/config/config.py +++ b/readthedocs/config/config.py @@ -31,7 +31,7 @@ ) ALL = 'all' -CONFIG_FILENAMES = ('readthedocs.yml', '.readthedocs.yml') +CONFIG_FILENAME_REGEX = r'\.?readthedocs.ya?ml' CONFIG_NOT_SUPPORTED = 'config-not-supported' VERSION_INVALID = 'version-invalid' @@ -48,7 +48,7 @@ DOCKER_DEFAULT_IMAGE = 'readthedocs/build' DOCKER_DEFAULT_VERSION = '2.0' -# These map to coordisponding settings in the .org, +# These map to corresponding settings in the .org, # so they haven't been renamed. DOCKER_IMAGE = '{}:{}'.format(DOCKER_DEFAULT_IMAGE, DOCKER_DEFAULT_VERSION) DOCKER_IMAGE_SETTINGS = { @@ -903,16 +903,12 @@ def load(path, env_config): the version of the configuration a build object would be load and validated, ``BuildConfigV1`` is the default. """ - filename = find_one(path, CONFIG_FILENAMES) + filename = find_one(path, CONFIG_FILENAME_REGEX) if not filename: - files = '{}'.format(', '.join(map(repr, CONFIG_FILENAMES[:-1]))) - if files: - files += ' or ' - files += '{!r}'.format(CONFIG_FILENAMES[-1]) raise ConfigError( - 'No files {} found'.format(files), - code=CONFIG_REQUIRED, + 'No configuration file found', + code=CONFIG_REQUIRED ) build_configs = [] with open(filename, 'r') as configuration_file: diff --git a/readthedocs/config/find.py b/readthedocs/config/find.py index df83d098785..e518da13f2c 100644 --- a/readthedocs/config/find.py +++ b/readthedocs/config/find.py @@ -3,20 +3,21 @@ from __future__ import division, print_function, unicode_literals import os +import re -def find_all(path, filenames): - """Find all files in ``path`` that match in ``filenames``.""" +def find_all(path, filename_regex): + """Find all files in ``path`` that match ``filename_regex`` regex.""" path = os.path.abspath(path) for root, dirs, files in os.walk(path, topdown=True): dirs.sort() - for filename in filenames: - if filename in files: + for filename in files: + if re.match(filename_regex, filename): yield os.path.abspath(os.path.join(root, filename)) -def find_one(path, filenames): - """Find the first file in ``path`` that match in ``filenames``.""" - for _path in find_all(path, filenames): +def find_one(path, filename_regex): + """Find the first file in ``path`` that match ``filename_regex`` regex.""" + for _path in find_all(path, filename_regex): return _path return '' diff --git a/readthedocs/config/tests/test_config.py b/readthedocs/config/tests/test_config.py index 7c2755d94a4..88f35f58c41 100644 --- a/readthedocs/config/tests/test_config.py +++ b/readthedocs/config/tests/test_config.py @@ -3,6 +3,7 @@ import os import textwrap +import re import pytest from mock import DEFAULT, patch @@ -13,7 +14,7 @@ ConfigOptionNotSupportedError, InvalidConfig, ProjectConfig, load) from readthedocs.config.config import ( CONFIG_NOT_SUPPORTED, NAME_INVALID, NAME_REQUIRED, PYTHON_INVALID, - VERSION_INVALID) + VERSION_INVALID, CONFIG_FILENAME_REGEX) from readthedocs.config.models import Conda from readthedocs.config.validation import ( INVALID_BOOL, INVALID_CHOICE, INVALID_LIST, INVALID_PATH, INVALID_STRING) @@ -50,6 +51,13 @@ 'nested': minimal_config_dir, } +yaml_extension_config_dir = { + 'readthedocs.yaml': '''\ +name: docs +type: sphinx +''' +} + def get_build_config(config, env_config=None, source_file='readthedocs.yml', source_position=0): @@ -138,6 +146,14 @@ def test_load_unknow_version(tmpdir): assert excinfo.value.code == VERSION_INVALID +def test_yaml_extension(tmpdir): + """Make sure it's capable of loading the 'readthedocs' file with a 'yaml' extension.""" + apply_fs(tmpdir, yaml_extension_config_dir) + base = str(tmpdir) + config = load(base, env_config) + assert len(config) == 1 + + def test_build_config_has_source_file(tmpdir): base = str(apply_fs(tmpdir, minimal_config_dir)) build = load(base, env_config)[0] @@ -766,6 +782,13 @@ def test_raise_config_not_supported(): assert excinfo.value.code == CONFIG_NOT_SUPPORTED +@pytest.mark.parametrize('correct_config_filename', + [prefix + 'readthedocs.' + extension for prefix in {"", "."} + for extension in {"yml", "yaml"}]) +def test_config_filenames_regex(correct_config_filename): + assert re.match(CONFIG_FILENAME_REGEX, correct_config_filename) + + class TestBuildConfigV2(object): def get_build_config(self, config, env_config=None, diff --git a/readthedocs/config/tests/test_find.py b/readthedocs/config/tests/test_find.py index 3a74d027fbb..2005d839277 100644 --- a/readthedocs/config/tests/test_find.py +++ b/readthedocs/config/tests/test_find.py @@ -12,7 +12,7 @@ def test_find_no_files(tmpdir): with tmpdir.as_cwd(): - paths = list(find_all(os.getcwd(), ('readthedocs.yml',))) + paths = list(find_all(os.getcwd(), r'readthedocs.yml')) assert len(paths) == 0 @@ -20,7 +20,7 @@ def test_find_at_root(tmpdir): apply_fs(tmpdir, {'readthedocs.yml': '', 'otherfile.txt': ''}) base = str(tmpdir) - paths = list(find_all(base, ('readthedocs.yml',))) + paths = list(find_all(base, r'readthedocs\.yml')) assert paths == [ os.path.abspath(os.path.join(base, 'readthedocs.yml')) ] @@ -42,11 +42,11 @@ def test_find_nested(tmpdir): apply_fs(tmpdir, {'first/readthedocs.yml': ''}) base = str(tmpdir) - paths = list(find_all(base, ('readthedocs.yml',))) - assert set(paths) == set([ + paths = set(find_all(base, r'readthedocs\.yml')) + assert paths == { str(tmpdir.join('first', 'readthedocs.yml')), str(tmpdir.join('third', 'readthedocs.yml')), - ]) + } def test_find_multiple_files(tmpdir): @@ -66,21 +66,12 @@ def test_find_multiple_files(tmpdir): apply_fs(tmpdir, {'first/readthedocs.yml': ''}) base = str(tmpdir) - paths = list(find_all(base, ('readthedocs.yml', - '.readthedocs.yml'))) - assert paths == [ + paths = set(find_all(base, r'\.?readthedocs\.yml')) + assert paths == { str(tmpdir.join('first', 'readthedocs.yml')), str(tmpdir.join('first', '.readthedocs.yml')), str(tmpdir.join('third', 'readthedocs.yml')), - ] - - paths = list(find_all(base, ('.readthedocs.yml', - 'readthedocs.yml'))) - assert paths == [ - str(tmpdir.join('first', '.readthedocs.yml')), - str(tmpdir.join('first', 'readthedocs.yml')), - str(tmpdir.join('third', 'readthedocs.yml')), - ] + } @pytest.mark.skipif(not six.PY2, reason='Only for python2') @@ -88,9 +79,9 @@ def test_find_unicode_path(tmpdir): base_path = os.path.abspath( os.path.join(os.path.dirname(__file__), 'fixtures/bad_encode_project') ) - path = find_one(base_path, ('readthedocs.yml',)) + path = find_one(base_path, r'readthedocs\.yml') assert path == '' unicode_base_path = base_path.decode('utf-8') assert isinstance(unicode_base_path, unicode) - path = find_one(unicode_base_path, ('readthedocs.yml',)) + path = find_one(unicode_base_path, r'readthedocs\.yml') assert path == ''