Merge pull request #4436 from stephenfin/directory-validation

app: Centralize directory validation
This commit is contained in:
Takeshi KOMIYA 2018-01-21 01:02:45 +09:00 committed by GitHub
commit 3413590edd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 53 additions and 43 deletions

View File

@ -28,7 +28,8 @@ from docutils.parsers.rst import directives, roles
import sphinx
from sphinx import package_dir, locale
from sphinx.config import Config
from sphinx.errors import ConfigError, ExtensionError, VersionRequirementError
from sphinx.errors import (
ApplicationError, ConfigError, ExtensionError, VersionRequirementError)
from sphinx.deprecation import RemovedInSphinx20Warning
from sphinx.environment import BuildEnvironment
from sphinx.events import EventManager
@ -43,6 +44,7 @@ from sphinx.util.osutil import ENOENT, ensuredir
from sphinx.util.console import bold # type: ignore
from sphinx.util.docutils import is_html5_writer_available, directive_helper
from sphinx.util.i18n import find_catalog_source_files
from sphinx.util.osutil import abspath
if False:
# For type annotation
@ -123,10 +125,24 @@ class Sphinx(object):
self.enumerable_nodes = {} # type: Dict[nodes.Node, Tuple[unicode, Callable]] # NOQA
self.html_themes = {} # type: Dict[unicode, unicode]
self.srcdir = srcdir
# validate provided directories
self.srcdir = abspath(srcdir)
self.outdir = abspath(outdir)
self.doctreedir = abspath(doctreedir)
self.confdir = confdir
self.outdir = outdir
self.doctreedir = doctreedir
if self.confdir: # confdir is optional
self.confdir = abspath(self.confdir)
if not path.isfile(path.join(self.confdir, 'conf.py')):
raise ApplicationError("config directory doesn't contain a "
"conf.py file (%s)" % confdir)
if not path.isdir(self.srcdir):
raise ApplicationError('Cannot find source directory (%s)' %
self.srcdir)
if self.srcdir == self.outdir:
raise ApplicationError('Source directory and destination '
'directory cannot be identical')
self.parallel = parallel

View File

@ -11,9 +11,9 @@
from __future__ import print_function
import argparse
import os
import sys
import traceback
from os import path
from docutils.utils import SystemMessage
from six import text_type, binary_type
@ -24,7 +24,6 @@ from sphinx.application import Sphinx
from sphinx.util import Tee, format_exception_cut_frames, save_traceback
from sphinx.util.console import red, nocolor, color_terminal # type: ignore
from sphinx.util.docutils import docutils_namespace, patch_docutils
from sphinx.util.osutil import abspath, fs_encoding
from sphinx.util.pycompat import terminal_safe
if False:
@ -184,31 +183,19 @@ def main(argv=sys.argv[1:]): # type: ignore
parser = get_parser()
args = parser.parse_args(argv)
# get paths (first and second positional argument)
try:
srcdir = abspath(args.sourcedir)
confdir = abspath(args.confdir or srcdir)
if args.noconfig:
confdir = None
if args.noconfig:
args.confdir = None
elif not args.confdir:
args.confdir = args.sourcedir
if not path.isdir(srcdir):
parser.error('cannot find source directory (%s)' % srcdir)
if not args.noconfig and not path.isfile(path.join(confdir, 'conf.py')):
parser.error("config directory doesn't contain a conf.py file "
"(%s)" % confdir)
outdir = abspath(args.outputdir)
if srcdir == outdir:
parser.error('source directory and destination directory are same')
except UnicodeError:
parser.error('multibyte filename not supported on this filesystem '
'encoding (%r)' % fs_encoding)
if not args.doctreedir:
args.doctreedir = os.path.join(args.sourcedir, '.doctrees')
# handle remaining filename arguments
filenames = args.filenames
missing_files = []
for filename in filenames:
if not path.isfile(filename):
if not os.path.isfile(filename):
missing_files.append(filename)
if missing_files:
parser.error('cannot find files %r' % missing_files)
@ -226,8 +213,6 @@ def main(argv=sys.argv[1:]): # type: ignore
if args.color == 'no' or (args.color == 'auto' and not color_terminal()):
nocolor()
doctreedir = abspath(args.doctreedir or path.join(outdir, '.doctrees'))
status = sys.stdout
warning = sys.stderr
error = sys.stderr
@ -281,9 +266,10 @@ def main(argv=sys.argv[1:]): # type: ignore
app = None
try:
with patch_docutils(), docutils_namespace():
app = Sphinx(srcdir, confdir, outdir, doctreedir, args.builder,
confoverrides, status, warning, args.freshenv,
args.warningiserror, args.tags, args.verbosity, args.jobs)
app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
args.doctreedir, args.builder, confoverrides, status,
warning, args.freshenv, args.warningiserror,
args.tags, args.verbosity, args.jobs)
app.build(args.force_all, filenames)
return app.statuscode
except (Exception, KeyboardInterrupt) as exc:

View File

@ -43,6 +43,11 @@ class SphinxWarning(SphinxError):
category = 'Warning, treated as error'
class ApplicationError(SphinxError):
"""Application initialization error."""
category = 'Application error'
class ExtensionError(SphinxError):
"""Extension error."""
category = 'Extension error'

View File

@ -116,7 +116,7 @@ class BuildDoc(Command):
for root, dirnames, filenames in os.walk(guess):
if 'conf.py' in filenames:
return root
return None
return os.curdir
# Overriding distutils' Command._ensure_stringlike which doesn't support
# unicode, causing finalize_options to fail if invoked again. Workaround
@ -134,30 +134,26 @@ class BuildDoc(Command):
def finalize_options(self):
# type: () -> None
self.ensure_string_list('builder')
if self.source_dir is None:
self.source_dir = self._guess_source_dir()
self.announce('Using source directory %s' % self.source_dir)
self.ensure_dirname('source_dir')
if self.source_dir is None:
self.source_dir = os.curdir
self.source_dir = abspath(self.source_dir)
if self.config_dir is None:
self.config_dir = self.source_dir
self.config_dir = abspath(self.config_dir)
self.ensure_string_list('builder')
if self.build_dir is None:
build = self.get_finalized_command('build')
self.build_dir = os.path.join(abspath(build.build_base), 'sphinx') # type: ignore
self.mkpath(self.build_dir) # type: ignore
self.build_dir = abspath(self.build_dir)
self.doctree_dir = os.path.join(self.build_dir, 'doctrees')
self.mkpath(self.doctree_dir) # type: ignore
self.builder_target_dirs = [
(builder, os.path.join(self.build_dir, builder))
for builder in self.builder] # type: List[Tuple[str, unicode]]
for _, builder_target_dir in self.builder_target_dirs:
self.mkpath(builder_target_dir) # type: ignore
def run(self):
# type: () -> None

View File

@ -137,12 +137,14 @@ def warning(app):
@pytest.fixture()
def make_app(test_params):
def make_app(test_params, monkeypatch):
"""
provides make_app function to initialize SphinxTestApp instance.
if you want to initialize 'app' in your test function. please use this
instead of using SphinxTestApp class directory.
"""
monkeypatch.setattr('sphinx.application.abspath', lambda x: x)
apps = []
syspath = sys.path[:]

View File

@ -219,7 +219,12 @@ def abspath(pathdir):
# type: (unicode) -> unicode
pathdir = path.abspath(pathdir)
if isinstance(pathdir, bytes):
pathdir = pathdir.decode(fs_encoding)
try:
pathdir = pathdir.decode(fs_encoding)
except UnicodeDecodeError:
raise UnicodeDecodeError('multibyte filename not supported on '
'this filesystem encoding '
'(%r)' % fs_encoding)
return pathdir