From 84e1eeb8a2dd7092759471fd2e4525e7f0cbb8a5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 18 Jan 2018 22:20:48 +0900 Subject: [PATCH] Move env.doc2path() and env.path2doc() to Project class --- sphinx/environment/__init__.py | 30 ++++++-------------- sphinx/project.py | 50 +++++++++++++++++++++++++++++----- tests/test_environment.py | 44 ++++++++++++++++++++++++++++++ tests/test_project.py | 35 ++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 29 deletions(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index dc396ce11..7be84ab29 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -29,7 +29,6 @@ from sphinx.util import logging from sphinx.util.docutils import LoggingReporter from sphinx.util.i18n import find_catalog_files from sphinx.util.nodes import is_translatable -from sphinx.util.osutil import SEP, relpath from sphinx.util.websupport import is_commentable if False: @@ -325,12 +324,7 @@ class BuildEnvironment: *filename* should be absolute or relative to the source directory. """ - if filename.startswith(self.srcdir): - filename = relpath(filename, self.srcdir) - for suffix in self.config.source_suffix: - if filename.endswith(suffix): - return filename[:-len(suffix)] - return None + return self.project.path2doc(filename) def doc2path(self, docname, base=True, suffix=None): # type: (unicode, Union[bool, unicode], unicode) -> unicode @@ -348,21 +342,13 @@ class BuildEnvironment: warnings.warn('The string style base argument for doc2path() is deprecated.', RemovedInSphinx40Warning) - docname = docname.replace(SEP, path.sep) - if suffix is None: - # Use first candidate if there is not a file for any suffix - suffix = next(iter(self.config.source_suffix)) - for candidate_suffix in self.config.source_suffix: - if path.isfile(path.join(self.srcdir, docname) + - candidate_suffix): - suffix = candidate_suffix - break - if base is True: - return path.join(self.srcdir, docname) + suffix - elif base is None: - return docname + suffix - else: - return path.join(base, docname) + suffix # type: ignore + pathname = self.project.doc2path(docname, base is True) + if suffix: + filename, _ = path.splitext(pathname) + pathname = filename + suffix + if base and base is not True: + pathname = path.join(base, pathname) # type: ignore + return pathname def relfn2path(self, filename, docname=None): # type: (unicode, unicode) -> Tuple[unicode, unicode] diff --git a/sphinx/project.py b/sphinx/project.py index 676ea493d..ed910088e 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -16,6 +16,7 @@ from sphinx.locale import __ from sphinx.util import get_matching_files from sphinx.util import logging from sphinx.util.matching import compile_matchers +from sphinx.util.osutil import SEP, relpath if TYPE_CHECKING: from typing import Dict, List, Set # NOQA @@ -51,12 +52,47 @@ class Project(object): self.docnames = set() excludes = compile_matchers(exclude_paths + EXCLUDE_PATHS) for filename in get_matching_files(self.srcdir, excludes): # type: ignore - for suffix in self.source_suffix: - if filename.endswith(suffix): - docname = filename[:-len(suffix)] - if os.access(os.path.join(self.srcdir, filename), os.R_OK): - self.docnames.add(docname) - else: - logger.warning(__("document not readable. Ignored."), location=docname) + docname = self.path2doc(filename) + if docname: + if os.access(os.path.join(self.srcdir, filename), os.R_OK): + self.docnames.add(docname) + else: + logger.warning(__("document not readable. Ignored."), location=docname) return self.docnames + + def path2doc(self, filename): + # type: (unicode) -> unicode + """Return the docname for the filename if the file is document. + + *filename* should be absolute or relative to the source directory. + """ + if filename.startswith(self.srcdir): + filename = relpath(filename, self.srcdir) + for suffix in self.source_suffix: + if filename.endswith(suffix): + return filename[:-len(suffix)] + + # the file does not have docname + return None + + def doc2path(self, docname, basedir=True): + # type: (unicode, bool) -> unicode + """Return the filename for the document name. + + If *basedir* is True, return as an absolute path. + Else, return as a relative path to the source directory. + """ + docname = docname.replace(SEP, os.path.sep) + basename = os.path.join(self.srcdir, docname) + for suffix in self.source_suffix: + if os.path.isfile(basename + suffix): + break + else: + # document does not exist + suffix = list(self.source_suffix)[0] + + if basedir: + return basename + suffix + else: + return docname + suffix diff --git a/tests/test_environment.py b/tests/test_environment.py index 1ab60b539..993fa80a2 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -69,3 +69,47 @@ def test_object_inventory(app): assert app.env.domains['py'].data is app.env.domaindata['py'] assert app.env.domains['c'].data is app.env.domaindata['c'] + + +@pytest.mark.sphinx('dummy', testroot='basic') +def test_env_relfn2path(app): + # relative filename and root document + relfn, absfn = app.env.relfn2path('logo.jpg', 'index') + assert relfn == 'logo.jpg' + assert absfn == app.srcdir / 'logo.jpg' + + # absolute filename and root document + relfn, absfn = app.env.relfn2path('/logo.jpg', 'index') + assert relfn == 'logo.jpg' + assert absfn == app.srcdir / 'logo.jpg' + + # relative filename and a document in subdir + relfn, absfn = app.env.relfn2path('logo.jpg', 'subdir/index') + assert relfn == 'subdir/logo.jpg' + assert absfn == app.srcdir / 'subdir' / 'logo.jpg' + + # absolute filename and a document in subdir + relfn, absfn = app.env.relfn2path('/logo.jpg', 'subdir/index') + assert relfn == 'logo.jpg' + assert absfn == app.srcdir / 'logo.jpg' + + # relative filename having subdir + relfn, absfn = app.env.relfn2path('images/logo.jpg', 'index') + assert relfn == 'images/logo.jpg' + assert absfn == app.srcdir / 'images' / 'logo.jpg' + + # relative path traversal + relfn, absfn = app.env.relfn2path('../logo.jpg', 'index') + assert relfn == '../logo.jpg' + assert absfn == app.srcdir.parent / 'logo.jpg' + + # omit docname (w/ current docname) + app.env.temp_data['docname'] = 'subdir/document' + relfn, absfn = app.env.relfn2path('images/logo.jpg') + assert relfn == 'subdir/images/logo.jpg' + assert absfn == app.srcdir / 'subdir' / 'images' / 'logo.jpg' + + # omit docname (w/o current docname) + app.env.temp_data.clear() + with pytest.raises(KeyError): + app.env.relfn2path('images/logo.jpg') diff --git a/tests/test_project.py b/tests/test_project.py index a5e9933f9..3b48c2de3 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -9,6 +9,8 @@ :license: BSD, see LICENSE for details. """ +from collections import OrderedDict + import pytest from sphinx.project import Project @@ -47,3 +49,36 @@ def test_project_discovery(rootdir): '_templates/contentssb'} assert project.discovery(['_templates']) == set() + + +@pytest.mark.sphinx(testroot='basic') +def test_project_path2doc(app): + project = Project(app.srcdir, app.config.source_suffix) + assert project.path2doc('index.rst') == 'index' + assert project.path2doc('index.foo') is None # unknown extension + assert project.path2doc('index.foo.rst') == 'index.foo' + assert project.path2doc('index') is None + assert project.path2doc('/path/to/index.rst') == '/path/to/index' + assert project.path2doc(app.srcdir / '/to/index.rst') == '/to/index' + + +@pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic') +def test_project_doc2path(app): + source_suffix = OrderedDict([('.rst', 'restructuredtext'), + ('.txt', 'restructuredtext')]) + + project = Project(app.srcdir, source_suffix) + assert project.doc2path('index') == (app.srcdir / 'index.rst') + + # first source_suffix is used for missing file + assert project.doc2path('foo') == (app.srcdir / 'foo.rst') + + # matched source_suffix is used if exists + (app.srcdir / 'foo.txt').write_text('') + assert project.doc2path('foo') == (app.srcdir / 'foo.txt') + + # absolute path + assert project.doc2path('index', basedir=True) == (app.srcdir / 'index.rst') + + # relative path + assert project.doc2path('index', basedir=False) == 'index.rst'