diff options
| author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2016-11-08 14:05:58 +0900 |
|---|---|---|
| committer | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2016-11-16 12:06:22 +0900 |
| commit | ceec82451bfbefc0fd720bbf48e4b9a029cacd99 (patch) | |
| tree | 1d76b5d59db131fb574cd649e0c283534fd6710f | |
| parent | 3407ef0ca8a8ce41e67092d2605f8fc77bebb982 (diff) | |
| download | sphinx-git-ceec82451bfbefc0fd720bbf48e4b9a029cacd99.tar.gz | |
Add type-check annotations to sphinx.*
35 files changed, 510 insertions, 185 deletions
diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 368f9d8fe..2f126b6f6 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -39,7 +39,7 @@ if __version__.endswith('+'): stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if out: - __display_version__ += '/' + out.decode().strip() + __display_version__ += '/' + out.decode().strip() # type: ignore except Exception: pass diff --git a/sphinx/apidoc.py b/sphinx/apidoc.py index d4793ff4d..e48a527a5 100644 --- a/sphinx/apidoc.py +++ b/sphinx/apidoc.py @@ -26,6 +26,10 @@ from fnmatch import fnmatch from sphinx.util.osutil import FileAvoidWrite, walk from sphinx import __display_version__ +if False: + # For type annotation + from typing import Any, Tuple # NOQA + # automodule options if 'SPHINX_APIDOC_OPTIONS' in os.environ: OPTIONS = os.environ['SPHINX_APIDOC_OPTIONS'].split(',') @@ -42,6 +46,7 @@ PY_SUFFIXES = set(['.py', '.pyx']) def makename(package, module): + # type: (unicode, unicode) -> unicode """Join package and module with a dot.""" # Both package and module can be None/empty. if package: @@ -54,6 +59,7 @@ def makename(package, module): def write_file(name, text, opts): + # type: (unicode, unicode, Any) -> None """Write the output file for module/package <name>.""" fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix)) if opts.dryrun: @@ -68,12 +74,14 @@ def write_file(name, text, opts): def format_heading(level, text): + # type: (int, unicode) -> unicode """Create a heading of <level> [1, 2 or 3 supported].""" underlining = ['=', '-', '~', ][level - 1] * len(text) return '%s\n%s\n\n' % (text, underlining) def format_directive(module, package=None): + # type: (unicode, unicode) -> unicode """Create the automodule directive and add the options.""" directive = '.. automodule:: %s\n' % makename(package, module) for option in OPTIONS: @@ -82,6 +90,7 @@ def format_directive(module, package=None): def create_module_file(package, module, opts): + # type: (unicode, unicode, Any) -> None """Build the text of the file and write the file.""" if not opts.noheadings: text = format_heading(1, '%s module' % module) @@ -93,6 +102,7 @@ def create_module_file(package, module, opts): def create_package_file(root, master_package, subroot, py_files, opts, subs, is_namespace): + # type: (unicode, unicode, unicode, List[unicode], Any, List[unicode], bool) -> None """Build the text of the file and write the file.""" text = format_heading(1, ('%s package' if not is_namespace else "%s namespace") % makename(master_package, subroot)) @@ -148,13 +158,14 @@ def create_package_file(root, master_package, subroot, py_files, opts, subs, is_ def create_modules_toc_file(modules, opts, name='modules'): + # type: (List[unicode], Any, unicode) -> None """Create the module's index.""" text = format_heading(1, '%s' % opts.header) text += '.. toctree::\n' text += ' :maxdepth: %s\n\n' % opts.maxdepth modules.sort() - prev_module = '' + prev_module = '' # type: unicode for module in modules: # look if the module is a subpackage and, if yes, ignore it if module.startswith(prev_module + '.'): @@ -166,6 +177,7 @@ def create_modules_toc_file(modules, opts, name='modules'): def shall_skip(module, opts): + # type: (unicode, Any) -> bool """Check if we want to skip this module.""" # skip if the file doesn't exist and not using implicit namespaces if not opts.implicit_namespaces and not path.exists(module): @@ -184,6 +196,7 @@ def shall_skip(module, opts): def recurse_tree(rootpath, excludes, opts): + # type: (unicode, List[unicode], Any) -> List[unicode] """ Look for every file in the directory tree and create the corresponding ReST files. @@ -217,7 +230,7 @@ def recurse_tree(rootpath, excludes, opts): # remove hidden ('.') and private ('_') directories, as well as # excluded dirs if includeprivate: - exclude_prefixes = ('.',) + exclude_prefixes = ('.',) # type: Tuple[unicode, ...] else: exclude_prefixes = ('.', '_') subs[:] = sorted(sub for sub in subs if not sub.startswith(exclude_prefixes) and @@ -247,23 +260,26 @@ def recurse_tree(rootpath, excludes, opts): def normalize_excludes(rootpath, excludes): + # type: (unicode, List[unicode]) -> List[unicode] """Normalize the excluded directory list.""" return [path.abspath(exclude) for exclude in excludes] def is_excluded(root, excludes): + # type: (unicode, List[unicode]) -> bool """Check if the directory is in the exclude list. Note: by having trailing slashes, we avoid common prefix issues, like e.g. an exlude "foo" also accidentally excluding "foobar". """ for exclude in excludes: - if fnmatch(root, exclude): + if fnmatch(root, exclude): # type: ignore return True return False def main(argv=sys.argv): + # type: (List[str]) -> int """Parse and check the command line arguments.""" parser = optparse.OptionParser( usage="""\ @@ -359,7 +375,7 @@ Note: By default this script will not overwrite already created files.""") if opts.full: from sphinx import quickstart as qs modules.sort() - prev_module = '' + prev_module = '' # type: unicode text = '' for module in modules: if module.startswith(prev_module + '.'): diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 0d85767e4..7a97e10e2 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -16,17 +16,22 @@ import traceback from os import path from six import text_type, binary_type + from docutils.utils import SystemMessage from sphinx import __display_version__ from sphinx.errors import SphinxError 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 +from sphinx.util.console import red, nocolor, color_terminal # type: ignore from sphinx.util.docutils import docutils_namespace from sphinx.util.osutil import abspath, fs_encoding from sphinx.util.pycompat import terminal_safe +if False: + # For type annotation + from typing import Any, IO, Union # NOQA + USAGE = """\ Sphinx v%s @@ -45,18 +50,21 @@ For more information, visit <http://sphinx-doc.org/>. class MyFormatter(optparse.IndentedHelpFormatter): def format_usage(self, usage): + # type: (Any) -> Any return usage def format_help(self, formatter): - result = [] - if self.description: + # type: (Any) -> unicode + result = [] # type: List[unicode] + if self.description: # type: ignore result.append(self.format_description(formatter)) - if self.option_list: - result.append(self.format_option_help(formatter)) + if self.option_list: # type: ignore + result.append(self.format_option_help(formatter)) # type: ignore return "\n".join(result) def handle_exception(app, opts, exception, stderr=sys.stderr): + # type: (Sphinx, Any, Union[Exception, KeyboardInterrupt], IO) -> None if opts.pdb: import pdb print(red('Exception occurred while building, starting debugger:'), @@ -107,6 +115,7 @@ def handle_exception(app, opts, exception, stderr=sys.stderr): def main(argv): + # type: (List[unicode]) -> int if not color_terminal(): nocolor() @@ -210,11 +219,11 @@ def main(argv): # handle remaining filename arguments filenames = args[2:] - err = 0 + err = 0 # type: ignore for filename in filenames: if not path.isfile(filename): print('Error: Cannot find file %r.' % filename, file=sys.stderr) - err = 1 + err = 1 # type: ignore if err: return 1 @@ -249,7 +258,7 @@ def main(argv): print('Error: Cannot open warning file %r: %s' % (opts.warnfile, exc), file=sys.stderr) sys.exit(1) - warning = Tee(warning, warnfp) + warning = Tee(warning, warnfp) # type: ignore error = warning confoverrides = {} diff --git a/sphinx/config.py b/sphinx/config.py index 5741d66bf..9bfdd2976 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -16,9 +16,14 @@ from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integ from sphinx.errors import ConfigError from sphinx.locale import l_ +from sphinx.util.i18n import format_date from sphinx.util.osutil import cd from sphinx.util.pycompat import execfile_, NoneType -from sphinx.util.i18n import format_date + +if False: + # For type annotation + from typing import Any, Callable, Tuple # NOQA + from sphinx.util.tags import Tags # NOQA nonascii_re = re.compile(br'[\x80-\xff]') copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])') @@ -43,13 +48,15 @@ class ENUM(object): app.add_config_value('latex_show_urls', 'no', ENUM('no', 'footnote', 'inline')) """ def __init__(self, *candidates): + # type: (unicode) -> None self.candidates = candidates def match(self, value): + # type: (unicode) -> bool return value in self.candidates -string_classes = [text_type] +string_classes = [text_type] # type: List if PY2: string_classes.append(binary_type) # => [str, unicode] @@ -114,12 +121,13 @@ class Config(object): # pre-initialized confval for HTML builder html_translator_class = (None, 'html', string_classes), - ) + ) # type: Dict[unicode, Tuple] def __init__(self, dirname, filename, overrides, tags): + # type: (unicode, unicode, Dict, Tags) -> None self.overrides = overrides self.values = Config.config_values.copy() - config = {} + config = {} # type: Dict[unicode, Any] if dirname is not None: config_file = path.join(dirname, filename) config['__file__'] = config_file @@ -137,14 +145,14 @@ class Config(object): self._raw_config = config # these two must be preinitialized because extensions can add their # own config values - self.setup = config.get('setup', None) + self.setup = config.get('setup', None) # type: Callable if 'extensions' in overrides: if isinstance(overrides['extensions'], string_types): config['extensions'] = overrides.pop('extensions').split(',') else: config['extensions'] = overrides.pop('extensions') - self.extensions = config.get('extensions', []) + self.extensions = config.get('extensions', []) # type: List[unicode] # correct values of copyright year that are not coherent with # the SOURCE_DATE_EPOCH environment variable (if set) @@ -152,10 +160,11 @@ class Config(object): if getenv('SOURCE_DATE_EPOCH') is not None: for k in ('copyright', 'epub_copyright'): if k in config: - config[k] = copyright_year_re.sub('\g<1>%s' % format_date('%Y'), + config[k] = copyright_year_re.sub('\g<1>%s' % format_date('%Y'), # type: ignore # NOQA config[k]) def check_types(self, warn): + # type: (Callable) -> None # check all values for deviation from the default value's type, since # that can result in TypeErrors all over the place # NB. since config values might use l_() we have to wait with calling @@ -197,15 +206,17 @@ class Config(object): name=name, current=type(current), default=type(default))) def check_unicode(self, warn): + # type: (Callable) -> None # check all string values for non-ASCII characters in bytestrings, # since that can result in UnicodeErrors all over the place for name, value in iteritems(self._raw_config): - if isinstance(value, binary_type) and nonascii_re.search(value): + if isinstance(value, binary_type) and nonascii_re.search(value): # type: ignore warn('the config value %r is set to a string with non-ASCII ' 'characters; this can lead to Unicode errors occurring. ' 'Please use Unicode strings, e.g. %r.' % (name, u'Content')) def convert_overrides(self, name, value): + # type: (unicode, Any) -> Any if not isinstance(value, string_types): return value else: @@ -215,10 +226,10 @@ class Config(object): 'ignoring (use %r to set individual elements)' % (name, name + '.key=value')) elif isinstance(defvalue, list): - return value.split(',') + return value.split(',') # type: ignore elif isinstance(defvalue, integer_types): try: - return int(value) + return int(value) # type: ignore except ValueError: raise ValueError('invalid number %r for config value %r, ignoring' % (value, name)) @@ -231,6 +242,7 @@ class Config(object): return value def pre_init_values(self, warn): + # type: (Callable) -> None """Initialize some limited config variables before loading extensions""" variables = ['needs_sphinx', 'suppress_warnings', 'html_translator_class'] for name in variables: @@ -243,12 +255,13 @@ class Config(object): warn(exc) def init_values(self, warn): + # type: (Callable) -> None config = self._raw_config for valname, value in iteritems(self.overrides): try: if '.' in valname: realvalname, key = valname.split('.', 1) - config.setdefault(realvalname, {})[key] = value + config.setdefault(realvalname, {})[key] = value # type: ignore continue elif valname not in self.values: warn('unknown config value %r in override, ignoring' % valname) @@ -262,10 +275,11 @@ class Config(object): for name in config: if name in self.values: self.__dict__[name] = config[name] - if isinstance(self.source_suffix, string_types): - self.source_suffix = [self.source_suffix] + if isinstance(self.source_suffix, string_types): # type: ignore + self.source_suffix = [self.source_suffix] # type: ignore def __getattr__(self, name): + # type: (unicode) -> Any if name.startswith('_'): raise AttributeError(name) if name not in self.values: @@ -276,13 +290,17 @@ class Config(object): return default def __getitem__(self, name): + # type: (unicode) -> unicode return getattr(self, name) def __setitem__(self, name, value): + # type: (unicode, Any) -> None setattr(self, name, value) def __delitem__(self, name): + # type: (unicode) -> None delattr(self, name) def __contains__(self, name): + # type: (unicode) -> bool return name in self.values diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 76b54f9d6..ea09daff5 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -29,6 +29,12 @@ from sphinx.directives.patches import ( # noqa Figure, Meta ) +if False: + # For type annotation + from typing import Any # NOQA + from sphinx.application import Sphinx # NOQA + from sphinx.environment import BuildEnvironment # NOQA + # RE to strip backslash escapes nl_escape_re = re.compile(r'\\\n') @@ -51,9 +57,13 @@ class ObjectDescription(Directive): } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types = [] + doc_field_types = [] # type: List[Any] + domain = None # type: unicode + objtype = None # type: unicode + indexnode = None # type: addnodes.index def get_signatures(self): + # type: () -> List[unicode] """ Retrieve the signatures to document from the directive arguments. By default, signatures are given as arguments, one per line. @@ -65,6 +75,7 @@ class ObjectDescription(Directive): return [strip_backslash_re.sub(r'\1', line.strip()) for line in lines] def handle_signature(self, sig, signode): + # type: (unicode, addnodes.desc_signature) -> Any """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -77,6 +88,7 @@ class ObjectDescription(Directive): raise ValueError def add_target_and_index(self, name, sig, signode): + # type: (Any, unicode, addnodes.desc_signature) -> None """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -85,6 +97,7 @@ class ObjectDescription(Directive): return # do nothing by default def before_content(self): + # type: () -> None """ Called before parsing content. Used to set information about the current directive context on the build environment. @@ -92,6 +105,7 @@ class ObjectDescription(Directive): pass def after_content(self): + # type: () -> None """ Called after parsing content. Used to reset information about the current directive context on the build environment. @@ -99,6 +113,7 @@ class ObjectDescription(Directive): pass def run(self): + # type: () -> List[nodes.Node] """ Main directive entry function, called by docutils upon encountering the directive. @@ -120,7 +135,7 @@ class ObjectDescription(Directive): self.domain, self.objtype = self.name.split(':', 1) else: self.domain, self.objtype = '', self.name - self.env = self.state.document.settings.env + self.env = self.state.document.settings.env # type: BuildEnvironment self.indexnode = addnodes.index(entries=[]) node = addnodes.desc() @@ -130,7 +145,7 @@ class ObjectDescription(Directive): node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) - self.names = [] + self.names = [] # type: List[unicode] signatures = self.get_signatures() for i, sig in enumerate(signatures): # add a signature node for each signature in the current unit @@ -181,6 +196,7 @@ class DefaultRole(Directive): final_argument_whitespace = False def run(self): + # type: () -> List[nodes.Node] if not self.arguments: if '' in roles._roles: # restore the "default" default role @@ -209,9 +225,10 @@ class DefaultDomain(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] env = self.state.document.settings.env domain_name = self.arguments[0].lower() # if domain_name not in env.domains: @@ -225,6 +242,7 @@ class DefaultDomain(Directive): def setup(app): + # type: (Sphinx) -> None directives.register_directive('default-role', DefaultRole) directives.register_directive('default-domain', DefaultDomain) directives.register_directive('describe', ObjectDescription) diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 5bef8c386..e401a50de 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -11,17 +11,22 @@ import sys import codecs from difflib import unified_diff +from six import string_types + from docutils import nodes from docutils.parsers.rst import Directive, directives from docutils.statemachine import ViewList -from six import string_types - from sphinx import addnodes from sphinx.locale import _ from sphinx.util import parselinenos from sphinx.util.nodes import set_source_info +if False: + # For type annotation + from typing import Any # NOQA + from sphinx.application import Sphinx # NOQA + class Highlight(Directive): """ @@ -38,6 +43,7 @@ class Highlight(Directive): } def run(self): + # type: () -> List[nodes.Node] if 'linenothreshold' in self.options: try: linenothreshold = int(self.options['linenothreshold']) @@ -50,6 +56,7 @@ class Highlight(Directive): def dedent_lines(lines, dedent): + # type: (List[unicode], int) -> List[unicode] if not dedent: return lines @@ -64,6 +71,7 @@ def dedent_lines(lines, dedent): def container_wrapper(directive, literal_node, caption): + # type: (Directive, nodes.Node, unicode) -> nodes.container container_node = nodes.container('', literal_block=True, classes=['literal-block-wrapper']) parsed = nodes.Element() @@ -101,6 +109,7 @@ class CodeBlock(Directive): } def run(self): + # type: () -> List[nodes.Node] code = u'\n'.join(self.content) linespec = self.options.get('emphasize-lines') @@ -137,7 +146,7 @@ class CodeBlock(Directive): literal = container_wrapper(self, literal, caption) except ValueError as exc: document = self.state.document - errmsg = _('Invalid caption: %s' % exc[0][0].astext()) + errmsg = _('Invalid caption: %s' % exc[0][0].astext()) # type: ignore return [document.reporter.warning(errmsg, line=self.lineno)] # literal will be note_implicit_target that is linked from caption and numref. @@ -182,11 +191,12 @@ class LiteralInclude(Directive): } def read_with_encoding(self, filename, document, codec_info, encoding): + # type: (unicode, nodes.Node, Any, unicode) -> List try: with codecs.StreamReaderWriter(open(filename, 'rb'), codec_info[2], codec_info[3], 'strict') as f: lines = f.readlines() - lines = dedent_lines(lines, self.options.get('dedent')) + lines = dedent_lines(lines, self.options.get('dedent')) # type: ignore return lines except (IOError, OSError): return [document.reporter.warning( @@ -199,6 +209,7 @@ class LiteralInclude(Directive): (encoding, filename))] def run(self): + # type: () -> List[nodes.Node] document = self.state.document if not document.settings.file_insertion_enabled: return [document.reporter.warning('File insertion disabled', @@ -367,7 +378,7 @@ class LiteralInclude(Directive): retnode = container_wrapper(self, retnode, caption) except ValueError as exc: document = self.state.document - errmsg = _('Invalid caption: %s' % exc[0][0].astext()) + errmsg = _('Invalid caption: %s' % exc[0][0].astext()) # type: ignore return [document.reporter.warning(errmsg, line=self.lineno)] # retnode will be note_implicit_target that is linked from caption and numref. @@ -378,6 +389,7 @@ class LiteralInclude(Directive): def setup(app): + # type: (Sphinx) -> None directives.register_directive('highlight', Highlight) directives.register_directive('highlightlang', Highlight) # old directives.register_directive('code-block', CodeBlock) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index e071b327e..15944668e 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -8,6 +8,7 @@ """ from six.moves import range + from docutils import nodes from docutils.parsers.rst import Directive, directives from docutils.parsers.rst.directives.admonitions import BaseAdmonition @@ -21,8 +22,14 @@ from sphinx.util.nodes import explicit_title_re, set_source_info, \ process_index_entry from sphinx.util.matching import patfilter +if False: + # For type annotation + from typing import Tuple # NOQA + from sphinx.application import Sphinx # NOQA + def int_or_nothing(argument): + # type: (unicode) -> int if not argument: return 999 return int(argument) @@ -50,6 +57,7 @@ class TocTree(Directive): } def run(self): + # type: () -> List[nodes.Node] env = self.state.document.settings.env suffixes = env.config.source_suffix glob = 'glob' in self.options @@ -57,7 +65,7 @@ class TocTree(Directive): ret = [] # (title, ref) pairs, where ref may be a document, or an external link, # and title may be None if the document's title is to be used - entries = [] + entries = [] # type: List[Tuple[unicode, unicode]] includefiles = [] all_docnames = env.found_docs.copy() # don't add the currently visited file in catch-all patterns @@ -136,9 +144,10 @@ class Author(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] env = self.state.document.settings.env if not env.config.show_authors: return [] @@ -168,20 +177,21 @@ class Index(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] arguments = self.arguments[0].split('\n') env = self.state.document.settings.env targetid = 'index-%s' % env.new_serialno('index') targetnode = nodes.target('', '', ids=[targetid]) self.state.document.note_explicit_target(targetnode) indexnode = addnodes.index() - indexnode['entries'] = ne = [] + indexnode['entries'] = [] indexnode['inline'] = False set_source_info(self, indexnode) for entry in arguments: - ne.extend(process_index_entry(entry, targetid)) + indexnode['entries'].extend(process_index_entry(entry, targetid)) return [indexnode, targetnode] @@ -193,9 +203,10 @@ class VersionChange(Directive): required_arguments = 1 optional_arguments = 1 final_argument_whitespace = True - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] node = addnodes.versionmodified() node.document = self.state.document set_source_info(self, node) @@ -248,9 +259,10 @@ class TabularColumns(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] node = addnodes.tabular_col_spec() node['spec'] = self.arguments[0] set_source_info(self, node) @@ -265,9 +277,10 @@ class Centered(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] if not self.arguments: return [] subnode = addnodes.centered() @@ -285,9 +298,10 @@ class Acks(Directive): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] node = addnodes.acks() node.document = self.state.document self.state.nested_parse(self.content, self.content_offset, node) @@ -311,6 +325,7 @@ class HList(Directive): } def run(self): + # type: () -> List[nodes.Node] ncolumns = self.options.get('columns', 2) node = nodes.paragraph() node.document = self.state.document @@ -342,9 +357,10 @@ class Only(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = True - option_spec = {} + option_spec = {} # type: Dict def run(self): + # type: () -> List[nodes.Node] node = addnodes.only() node.document = self.state.document set_source_info(self, node) @@ -398,6 +414,7 @@ class Include(BaseInclude): """ def run(self): + # type: () -> List[nodes.Node] env = self.state.document.settings.env if self.arguments[0].startswith('<') and \ self.arguments[0].endswith('>'): @@ -410,6 +427,7 @@ class Include(BaseInclude): def setup(app): + # type: (Sphinx) -> None directives.register_directive('toctree', TocTree) directives.register_directive('sectionauthor', Author) directives.register_directive('moduleauthor', Author) diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index 9594b5336..543e1485a 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -16,6 +16,7 @@ from sphinx.util.texescape import tex_hl_escape_map_new from sphinx.ext import doctest from pygments import highlight +from pygments.lexer import Lexer # NOQA from pygments.lexers import PythonLexer, Python3Lexer, PythonConsoleLexer, \ CLexer, TextLexer, RstLexer from pygments.lexers import get_lexer_by_name, guess_lexer @@ -33,7 +34,7 @@ lexers = dict( pycon3 = PythonConsoleLexer(python3=True, stripnl=False), rest = RstLexer(stripnl=False), c = CLexer(stripnl=False), -) +) # type: Dict[unicode, Lexer] for _lexer in lexers.values(): _lexer.add_filter('raiseonerror') diff --git a/sphinx/io.py b/sphinx/io.py index f1386c9a8..c6fea570e 100644 --- a/sphinx/io.py +++ b/sphinx/io.py @@ -12,6 +12,7 @@ from docutils.io import FileInput from docutils.readers import standalone from docutils.writers import UnfilteredWriter from six import string_types, text_type +from typing import Any, Union # NOQA from sphinx.transforms import ( ApplySourceWorkaround, ExtraTranslatableNodes, CitationReferences, @@ -24,23 +25,36 @@ from sphinx.transforms.i18n import ( ) from sphinx.util import import_object, split_docinfo +if False: + # For type annotation + from typing import Any, Union # NOQA + from docutils import nodes # NOQA + from docutils.io import Input # NOQA + from docutils.parsers import Parser # NOQA + from docutils.transforms import Transform # NOQA + from sphinx.application import Sphinx # NOQA + from sphinx.builders import Builder # NOQA + from sphinx.environment import BuildEnvironment # NOQA + class SphinxBaseReader(standalone.Reader): """ Add our source parsers """ def __init__(self, app, parsers={}, *args, **kwargs): + # type: (Sphinx, Dict[unicode, Parser], Any, Any) -> None standalone.Reader.__init__(self, *args, **kwargs) - self.parser_map = {} + self.parser_map = {} # type: Dict[unicode, Parser] for suffix, parser_class in parsers.items(): if isinstance(parser_class, string_types): - parser_class = import_object(parser_class, 'source parser') + parser_class = import_object(parser_class, 'source parser') # type: ignore parser = parser_class() if hasattr(parser, 'set_application'): parser.set_application(app) self.parser_map[suffix] = parser def read(self, source, parser, settings): + # type: (Input, Parser, Dict) -> nodes.document self.source = source for suffix in self.parser_map: @@ -56,6 +70,7 @@ class SphinxBaseReader(standalone.Reader): return self.document def get_transforms(self): + # type: () -> List[Transform] return standalone.Reader.get_transforms(self) + self.transforms @@ -84,13 +99,16 @@ class SphinxI18nReader(SphinxBaseReader): FilterSystemMessages, RefOnlyBulletListTransform] def __init__(self, *args, **kwargs): + # type: (Any, Any) -> None SphinxBaseReader.__init__(self, *args, **kwargs) - self.lineno = None + self.lineno = None # type: int def set_lineno_for_reporter(self, lineno): + # type: (int) -> None self.lineno = lineno def new_document(self): + # type: () -> nodes.document document = SphinxBaseReader.new_document(self) reporter = document.reporter @@ -105,28 +123,32 @@ class SphinxDummyWriter(UnfilteredWriter): supported = ('html',) # needed to keep "meta" nodes def translate(self): + # type: () -> None pass class SphinxFileInput(FileInput): def __init__(self, app, env, *args, **kwds): + # type: (Sphinx, BuildEnvironment, Any, Any) -> None self.app = app self.env = env kwds['error_handler'] = 'sphinx' # py3: handle error on open. FileInput.__init__(self, *args, **kwds) def decode(self, data): + # type: (Union[unicode, bytes]) -> unicode if isinstance(data, text_type): # py3: `data` already decoded. return data return data.decode(self.encoding, 'sphinx') # py2: decoding def read(self): + # type: () -> unicode def get_parser_type(source_path): for suffix in self.env.config.source_parsers: if source_path.endswith(suffix): parser_class = self.env.config.source_parsers[suffix] if isinstance(parser_class, string_types): - parser_class = import_object(parser_class, 'source parser') + parser_class = import_object(parser_class, 'source parser') # type: ignore # NOQA return parser_class.supported else: return ('restructuredtext',) diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index 6e2ef7186..c1bd04765 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -17,18 +17,28 @@ from jinja2 import FileSystemLoader, BaseLoader, TemplateNotFound, \ contextfunction from jinja2.utils import open_if_exists from jinja2.sandbox import SandboxedEnvironment +from typing import Any, Callable, Iterator, Tuple # NOQA from sphinx.application import TemplateBridge from sphinx.util.osutil import mtimes_of_files +if False: + # For type annotation + from typing import Any, Callable, Iterator, Tuple # NOQA + from sphinx.builders import Builder # NOQA + from sphinx.environment import BuildEnvironment # NOQA + from sphinx.themes import Theme # NOQA + def _tobool(val): + # type: (unicode) -> bool if isinstance(val, string_types): - return val.lower() in ('true', '1', 'yes', 'on') + return val.lower() in ('true', '1', 'yes', 'on') # type: ignore return bool(val) def _toint(val): + # type: (unicode) -> int try: return int(val) except ValueError: @@ -36,6 +46,7 @@ def _toint(val): def _slice_index(values, slices): + # type: (List, int) -> Iterator[List] seq = list(values) length = 0 for value in values: @@ -57,6 +68,7 @@ def _slice_index(values, slices): def accesskey(context, key): + # type: (Any, unicode) -> unicode """Helper to output each access key only once.""" if '_accesskeys' not in context: context.vars['_accesskeys'] = {} @@ -68,12 +80,15 @@ def accesskey(context, key): class idgen(object): def __init__(self): + # type: () -> None self.id = 0 def current(self): + # type: () -> int return self.id def __next__(self): + # type: () -> int self.id += 1 return self.id next = __next__ # Python 2/Jinja compatibility @@ -86,6 +101,7 @@ class SphinxFileSystemLoader(FileSystemLoader): """ def get_source(self, environment, template): + # type: (BuildEnvironment, unicode) -> Tuple[unicode, unicode, Callable] for searchpath in self.searchpath: filename = path.join(searchpath, template) f = open_if_exists(filename) @@ -113,6 +129,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): # TemplateBridge interface def init(self, builder, theme=None, dirs=None): + # type: (Builder, Theme, List[unicode]) -> None # create a chain of paths to search if theme: # the theme's own dir and its bases' dirs @@ -155,17 +172,21 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): builder.app.translator) def render(self, template, context): + # type: (unicode, Dict) -> None return self.environment.get_template(template).render(context) def render_string(self, source, context): + # type: (unicode, Dict) -> unicode return self.environment.from_string(source).render(context) def newest_template_mtime(self): + # type: () -> float return max(mtimes_of_files(self.pathchain, '.html')) # Loader interface def get_source(self, environment, template): + # type: (BuildEnvironment, unicode) -> Tuple[unicode, unicode, Callable] loaders = self.loaders # exclamation mark starts search from theme if template.startswith('!'): diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index d6ce7329b..44ad64304 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -14,6 +14,10 @@ import gettext from six import PY3, text_type from six.moves import UserString +if False: + # For type annotation + from typing import Any, Tuple # NOQA + class _TranslationProxy(UserString, object): """ @@ -140,6 +144,7 @@ class _TranslationProxy(UserString, object): def mygettext(string): + # type: (unicode) -> unicode """Used instead of _ when creating TranslationProxies, because _ is not bound yet at that time. """ @@ -147,10 +152,11 @@ def mygettext(string): def lazy_gettext(string): + # type: (unicode) -> unicode """A lazy version of `gettext`.""" # if isinstance(string, _TranslationProxy): # return string - return _TranslationProxy(mygettext, string) + return _TranslationProxy(mygettext, string) # type: ignore l_ = lazy_gettext @@ -184,19 +190,22 @@ pairindextypes = { 'exception': l_('exception'), 'statement': l_('statement'), 'builtin': l_('built-in function'), -} +} # Dict[unicode, _TranslationProxy] -translators = {} +translators = {} # type: Dict[unicode, Any] if PY3: def _(message): + # type: (unicode) -> unicode return translators['sphinx'].gettext(message) else: def _(message): + # type: (unicode) -> unicode return translators['sphinx'].ugettext(message) def init(locale_dirs, language, catalog='sphinx'): + # type: (List, unicode, unicode) -> Tuple[Any, bool] """Look for message catalogs in `locale_dirs` and *ensure* that there is at least a NullTranslations catalog set in `translators`. If called multiple times or if several ``.mo`` files are found, their contents are merged @@ -213,12 +222,12 @@ def init(locale_dirs, language, catalog='sphinx'): # loading for dir_ in locale_dirs: try: - trans = gettext.translation(catalog, localedir=dir_, - languages=[language]) + trans = gettext.translation(catalog, localedir=dir_, # type: ignore + languages=[language]) # type: ignore if translator is None: translator = trans else: - translator._catalog.update(trans._catalog) + translator._catalog.update(trans._catalog) # type: ignore except Exception: # Language couldn't be found in the specified path pass diff --git a/sphinx/make_mode.py b/sphinx/make_mode.py index 28316458e..87333301c 100644 --- a/sphinx/make_mode.py +++ b/sphinx/make_mode.py @@ -22,7 +22,7 @@ from os import path from subprocess import call import sphinx -from sphinx.util.console import bold, blue +from sphinx.util.console import bold, blue # type: ignore from sphinx.util.osutil import cd, rmtree proj_name = os.getenv('SPHINXPROJ', '<project>') @@ -59,14 +59,17 @@ BUILDERS = [ class Make(object): def __init__(self, srcdir, builddir, opts): + # type: (unicode, unicode, List[unicode]) -> None self.srcdir = srcdir self.builddir = builddir self.opts = opts def builddir_join(self, *comps): + # type: (unicode) -> unicode return path.join(self.builddir, *comps) def build_clean(self): + # type: () -> int if not path.exists(self.builddir): return elif not path.isdir(self.builddir): @@ -77,19 +80,22 @@ class Make(object): rmtree(self.builddir_join(item)) def build_help(self): + # type: () -> None print(bold("Sphinx v%s" % sphinx.__display_version__)) - print("Please use `make %s' where %s is one of" % ((blue('target'),)*2)) + print("Please use `make %s' where %s is one of" % ((blue('target'),)*2)) # type: ignore # NOQA for osname, bname, description in BUILDERS: if not osname or os.name == osname: print(' %s %s' % (blue(bname.ljust(10)), description)) def build_html(self): + # type: () -> int if self.run_generic_build('html') > 0: return 1 print() print('Build finished. The HTML pages are in %s.' % self.builddir_join('html')) def build_dirhtml(self): + # type: () -> int if self.run_generic_build('dirhtml') > 0: return 1 print() @@ -97,6 +103,7 @@ class Make(object): self.builddir_join('dirhtml')) def build_singlehtml(self): + # type: () -> int if self.run_generic_build('singlehtml') > 0: return 1 print() @@ -104,18 +111,21 @@ class Make(object): self.builddir_join('singlehtml')) def build_pickle(self): + # type: () -> int if self.run_generic_build('pickle') > 0: return 1 print() print('Build finished; now you can process the pickle files.') def build_json(self): + # type: () -> int if self.run_generic_build('json') > 0: return 1 print() print('Build finished; now you can process the JSON files.') def build_htmlhelp(self): + # type: () -> int if self.run_generic_build('htmlhelp') > 0: return 1 print() @@ -123,6 +133,7 @@ class Make(object): '.hhp project file in %s.' % self.builddir_join('htmlhelp')) def build_qthelp(self): + # type: () -> int if self.run_generic_build('qthelp') > 0: return 1 print() @@ -134,6 +145,7 @@ class Make(object): self.builddir_join('qthelp', proj_name)) def build_devhelp(self): + # type: () -> int if self.run_generic_build('devhelp') > 0: return 1 print() @@ -145,12 +157,14 @@ class Make(object): print("$ devhelp") def build_epub(self): + # type: () -> int if self.run_generic_build('epub') > 0: return 1 print() print('Build finished. The ePub file is in %s.' % self.builddir_join('epub')) def build_latex(self): + # type: () -> int if self.run_generic_build('latex') > 0: return 1 print("Build finished; the LaTeX files are in %s." % self.builddir_join('latex')) @@ -159,24 +173,28 @@ class Make(object): print("(use `make latexpdf' here to do that automatically).") def build_latexpdf(self): + # type: () -> int if self.run_generic_build('latex') > 0: return 1 with cd(self.builddir_join('latex')): os.system('make all-pdf') def build_latexpdfja(self): + # type: () -> int if self.run_generic_build('latex') > 0: return 1 with cd(self.builddir_join('latex')): os.system('make all-pdf-ja') def build_text(self): + # type: () -> int if self.run_generic_build('text') > 0: return 1 print() print('Build finished. The text files are in %s.' % self.builddir_join('text')) def build_texinfo(self): + # type: () -> int if self.run_generic_build('texinfo') > 0: return 1 print("Build finished; the Texinfo files are in %s." % @@ -186,12 +204,14 @@ class Make(object): print("(use `make info' here to do that automatically).") def build_info(self): + # type: () -> int if self.run_generic_build('texinfo') > 0: return 1 with cd(self.builddir_join('texinfo')): os.system('make info') def build_gettext(self): + # type: () -> int dtdir = self.builddir_join('gettext', '.doctrees') if self.run_generic_build('gettext', doctreedir=dtdir) > 0: return 1 @@ -200,6 +220,7 @@ class Make(object): self.builddir_join('gettext')) def build_changes(self): + # type: () -> int if self.run_generic_build('changes') > 0: return 1 print() @@ -207,6 +228,7 @@ class Make(object): self.builddir_join('changes')) def build_linkcheck(self): + # type: () -> int res = self.run_generic_build('linkcheck') print() print('Link check complete; look for any errors in the above output ' @@ -214,12 +236,14 @@ class Make(object): return res def build_doctest(self): + # type: () -> int res = self.run_generic_build('doctest') print("Testing of doctests in the sources finished, look at the " "results in %s." % self.builddir_join('doctest', 'output.txt')) return res def build_coverage(self): + # type: () -> int if self.run_generic_build('coverage') > 0: print("Has the coverage extension been enabled?") return 1 @@ -228,12 +252,14 @@ class Make(object): "results in %s." % self.builddir_join('coverage')) def build_xml(self): + # type: () -> int if self.run_generic_build('xml') > 0: return 1 print() print('Build finished. The XML files are in %s.' % self.builddir_join('xml')) def build_pseudoxml(self): + # type: () -> int if self.run_generic_build('pseudoxml') > 0: return 1 print() @@ -241,6 +267,7 @@ class Make(object): self.builddir_join('pseudoxml')) def run_generic_build(self, builder, doctreedir=None): + # type: (unicode, unicode) -> int # compatibility with old Makefile papersize = os.getenv('PAPER', '') opts = self.opts @@ -261,11 +288,12 @@ class Make(object): # linux, mac: 'sphinx-build' or 'sphinx-build.py' cmd = [sys.executable, orig_cmd] - return call(cmd + ['-b', builder] + opts + - ['-d', doctreedir, self.srcdir, self.builddir_join(builder)]) + return call(cmd + ['-b', builder] + opts + # type: ignore + ['-d', doctreedir, self.srcdir, self.builddir_join(builder)]) # type: ignore # NOQA def run_make_mode(args): + # type: (List[unicode]) -> int if len(args) < 3: print('Error: at least 3 arguments (builder, source ' 'dir, build dir) are required.', file=sys.stderr) diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index baf5c0068..2c898560b 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -24,6 +24,10 @@ from sphinx.util import get_module_source, detect_encoding from sphinx.util.pycompat import TextIOWrapper from sphinx.util.docstrings import prepare_docstring, prepare_commentdoc +if False: + # For type annotation + from typing import Any, Tuple # NOQA + # load the Python grammar _grammarfile = path.join(package_dir, 'pycode', @@ -63,10 +67,10 @@ class AttrDocVisitor(nodes.NodeVisitor): self.scope = scope self.in_init = 0 self.encoding = encoding - self.namespace = [] - self.collected = {} + self.namespace = [] # type: List[unicode] + self.collected = {} # type: Dict[Tuple[unicode, unicode], unicode] self.tagnumber = 0 - self.tagorder = {} + self.tagorder = {} # type: Dict[unicode, int] def add_tag(self, name): name = '.'.join(self.namespace + [name]) @@ -102,10 +106,10 @@ class AttrDocVisitor(nodes.NodeVisitor): parent = node.parent idx = parent.children.index(node) + 1 while idx < len(parent): - if parent[idx].type == sym.SEMI: + if parent[idx].type == sym.SEMI: # type: ignore idx += 1 continue # skip over semicolon - if parent[idx].type == sym.NEWLINE: + if parent[idx].type == sym.NEWLINE: # type: ignore prefix = parent[idx].get_prefix() if not isinstance(prefix, text_type): prefix = prefix.decode(self.encoding) @@ -138,8 +142,8 @@ class AttrDocVisitor(nodes.NodeVisitor): prev = node.get_prev_sibling() if not prev: return - if prev.type == sym.simple_stmt and \ - prev[0].type == sym.expr_stmt and _eq in prev[0].children: + if (prev.type == sym.simple_stmt and # type: ignore + prev[0].type == sym.expr_stmt and _eq in prev[0].children): # type: ignore # need to "eval" the string because it's returned in its # original form docstring = literals.evalString(node[0].value, self.encoding) @@ -178,7 +182,7 @@ class AttrDocVisitor(nodes.NodeVisitor): class ModuleAnalyzer(object): # cache for analyzer objects -- caches both by module and file name - cache = {} + cache = {} # type: Dict[Tuple[unicode, unicode], Any] @classmethod def for_string(cls, string, modname, srcname='<string>'): @@ -240,14 +244,14 @@ class ModuleAnalyzer(object): self.source.seek(pos) # will be filled by tokenize() - self.tokens = None + self.tokens = None # type: List[unicode] # will be filled by parse() - self.parsetree = None + self.parsetree = None # type: Any # will be filled by find_attr_docs() - self.attr_docs = None - self.tagorder = None + self.attr_docs = None # type: List[unicode] + self.tagorder = None # type: Dict[unicode, int] # will be filled by find_tags() - self.tags = None + self.tags = None # type: List[unicode] def tokenize(self): """Generate tokens from the source.""" @@ -289,8 +293,8 @@ class ModuleAnalyzer(object): return self.tags self.tokenize() result = {} - namespace = [] - stack = [] + namespace = [] # type: List[unicode] + stack = [] # type: List[Tuple[unicode, unicode, unicode, int]] indent = 0 defline = False expect_indent = False @@ -301,7 +305,7 @@ class ModuleAnalyzer(object): if tokentup[0] not in ignore: yield tokentup tokeniter = tokeniter() - for type, tok, spos, epos, line in tokeniter: + for type, tok, spos, epos, line in tokeniter: # type: ignore if expect_indent and type != token.NL: if type != token.INDENT: # no suite -- one-line definition @@ -312,7 +316,7 @@ class ModuleAnalyzer(object): result[fullname] = (dtype, startline, endline - emptylines) expect_indent = False if tok in ('def', 'class'): - name = next(tokeniter)[1] + name = next(tokeniter)[1] # type: ignore namespace.append(name) fullname = '.'.join(namespace) stack.append((tok, fullname, spos[0], indent)) diff --git a/sphinx/pycode/nodes.py b/sphinx/pycode/nodes.py index ee40f3c0d..b6b3355c0 100644 --- a/sphinx/pycode/nodes.py +++ b/sphinx/pycode/nodes.py @@ -14,7 +14,7 @@ class BaseNode(object): """ Node superclass for both terminal and nonterminal nodes. """ - parent = None + parent = None # type: BaseNode def _eq(self, other): raise NotImplementedError @@ -29,7 +29,7 @@ class BaseNode(object): return NotImplemented return not self._eq(other) - __hash__ = None + __hash__ = None # type: str def get_prev_sibling(self): """Return previous child in parent's children, or None.""" @@ -204,5 +204,5 @@ class NodeVisitor(object): def generic_visit(self, node): """Called if no explicit visitor function exists for a node.""" if isinstance(node, Node): - for child in node: + for child in node: # type: ignore self.visit(child) diff --git a/sphinx/pycode/pgen2/grammar.py b/sphinx/pycode/pgen2/grammar.py index 42e6d72ee..cd6a435d5 100644 --- a/sphinx/pycode/pgen2/grammar.py +++ b/sphinx/pycode/pgen2/grammar.py @@ -19,6 +19,10 @@ import pickle # Local imports from sphinx.pycode.pgen2 import token +if False: + # For type annotation + from typing import Tuple # NOQA + class Grammar(object): """Pgen parsing tables tables conversion class. @@ -75,14 +79,14 @@ class Grammar(object): """ def __init__(self): - self.symbol2number = {} - self.number2symbol = {} - self.states = [] - self.dfas = {} + self.symbol2number = {} # type: Dict[unicode, int] + self.number2symbol = {} # type: Dict[int, unicode] + self.states = [] # type: List[List[List[Tuple[int, int]]]] + self.dfas = {} # type: Dict[int, Tuple[List[List[Tuple[int, int]]], unicode]] self.labels = [(0, "EMPTY")] - self.keywords = {} - self.tokens = {} - self.symbol2label = {} + self.keywords = {} # type: Dict[unicode, unicode] + self.tokens = {} # type: Dict[unicode, unicode] + self.symbol2label = {} # type: Dict[unicode, unicode] self.start = 256 def dump(self, filename): diff --git a/sphinx/pycode/pgen2/parse.py b/sphinx/pycode/pgen2/parse.py index 60eec05ea..43b88b519 100644 --- a/sphinx/pycode/pgen2/parse.py +++ b/sphinx/pycode/pgen2/parse.py @@ -13,6 +13,10 @@ how this parsing engine works. # Local imports from sphinx.pycode.pgen2 import token +if False: + # For type annotation + from typing import Any, Tuple # NOQA + class ParseError(Exception): """Exception to signal the parser is stuck.""" @@ -104,11 +108,12 @@ class Parser(object): # Each stack entry is a tuple: (dfa, state, node). # A node is a tuple: (type, value, context, children), # where children is a list of nodes or None, and context may be None. - newnode = (start, None, None, []) + newnode = (start, None, None, []) # type: Tuple[unicode, unicode, unicode, List] stackentry = (self.grammar.dfas[start], 0, newnode) self.stack = [stackentry] - self.rootnode = None - self.used_names = set() # Aliased to self.rootnode.used_names in pop() + self.rootnode = None # type: Any + self.used_names = set() # type: Set[unicode] + # Aliased to self.rootnode.used_names in pop() def addtoken(self, type, value, context): """Add a token; return True iff this is the end of the program.""" @@ -175,7 +180,7 @@ class Parser(object): def shift(self, type, value, newstate, context): """Shift a token. (Internal)""" dfa, state, node = self.stack[-1] - newnode = (type, value, context, None) + newnode = (type, value, context, None) # type: Tuple[unicode, unicode, unicode, List] newnode = self.convert(self.grammar, newnode) if newnode is not None: node[-1].append(newnode) @@ -184,7 +189,7 @@ class Parser(object): def push(self, type, newdfa, newstate, context): """Push a nonterminal. (Internal)""" dfa, state, node = self.stack[-1] - newnode = (type, None, context, []) + newnode = (type, None, context, []) # type: Tuple[unicode, unicode, unicode, List] self.stack[-1] = (dfa, newstate, node) self.stack.append((newdfa, 0, newnode)) diff --git a/sphinx/pycode/pgen2/pgen.py b/sphinx/pycode/pgen2/pgen.py index 7598e6abc..3fe91e57e 100644 --- a/sphinx/pycode/pgen2/pgen.py +++ b/sphinx/pycode/pgen2/pgen.py @@ -7,9 +7,13 @@ from six import iteritems from collections import OrderedDict # Pgen imports - from sphinx.pycode.pgen2 import grammar, token, tokenize +if False: + # For type annotation + from typing import Any, Tuple # NOQA + + class PgenGrammar(grammar.Grammar): pass @@ -27,7 +31,8 @@ class ParserGenerator(object): self.dfas, self.startsymbol = self.parse() if close_stream is not None: close_stream() - self.first = {} # map from symbol name to set of tokens + self.first = {} # type: Dict[unicode, List[unicode]] + # map from symbol name to set of tokens self.addfirstsets() def make_grammar(self): @@ -42,7 +47,7 @@ class ParserGenerator(object): c.number2symbol[i] = name for name in names: dfa = self.dfas[name] - states = [] + states = [] # type: List[List[Tuple[int, int]]] for state in dfa: arcs = [] for label, next in iteritems(state.arcs): @@ -122,7 +127,7 @@ class ParserGenerator(object): dfa = self.dfas[name] self.first[name] = None # dummy to detect left recursion state = dfa[0] - totalset = {} + totalset = {} # type: Dict[unicode, int] overlapcheck = {} for label, next in iteritems(state.arcs): if label in self.dfas: @@ -138,7 +143,7 @@ class ParserGenerator(object): else: totalset[label] = 1 overlapcheck[label] = {label: 1} - inverse = {} + inverse = {} # type: Dict[unicode, unicode] for label, itsfirst in sorted(overlapcheck.items()): for symbol in sorted(itsfirst): if symbol in inverse: @@ -180,7 +185,7 @@ class ParserGenerator(object): assert isinstance(start, NFAState) assert isinstance(finish, NFAState) def closure(state): - base = {} + base = {} # type: Dict addclosure(state, base) return base def addclosure(state, base): @@ -188,12 +193,12 @@ class ParserGenerator(object): if state in base: return base[state] = 1 - for label, next in state.arcs: + for label, next in state.arcs: # type: ignore if label is None: addclosure(next, base) states = [DFAState(closure(start), finish)] for state in states: # NB states grows while we're iterating - arcs = {} + arcs = {} # type: Dict[unicode, Dict] for nfastate in state.nfaset: for label, next in nfastate.arcs: if label is not None: @@ -343,7 +348,8 @@ class ParserGenerator(object): class NFAState(object): def __init__(self): - self.arcs = [] # list of (label, NFAState) pairs + self.arcs = [] # type: List[Tuple[unicode, Any]] + # list of (label, NFAState) pairs def addarc(self, next, label=None): assert label is None or isinstance(label, str) @@ -361,7 +367,8 @@ class DFAState(object): assert isinstance(final, NFAState) self.nfaset = nfaset self.isfinal = final in nfaset - self.arcs = OrderedDict() # map from label to DFAState + self.arcs = OrderedDict() # type: OrderedDict + # map from label to DFAState def __hash__(self): return hash(tuple(self.arcs)) diff --git a/sphinx/pycode/pgen2/tokenize.py b/sphinx/pycode/pgen2/tokenize.py index c7013bf91..a096795f8 100644 --- a/sphinx/pycode/pgen2/tokenize.py +++ b/sphinx/pycode/pgen2/tokenize.py @@ -183,7 +183,7 @@ def tokenize_loop(readline, tokeneater): class Untokenizer: def __init__(self): - self.tokens = [] + self.tokens = [] # type: List[unicode] self.prev_row = 1 self.prev_col = 0 @@ -294,17 +294,17 @@ def generate_tokens(readline): if contstr: # continued string if not line: - raise TokenError("EOF in multi-line string", strstart) - endmatch = endprog.match(line) + raise TokenError("EOF in multi-line string", strstart) # type: ignore + endmatch = endprog.match(line) # type: ignore if endmatch: pos = end = endmatch.end(0) - yield (STRING, contstr + line[:end], - strstart, (lnum, end), contline + line) + yield (STRING, contstr + line[:end], # type: ignore + strstart, (lnum, end), contline + line) # type: ignore contstr, needcont = '', 0 contline = None elif needcont and line[-2:] != '\\\n' and line[-3:] != '\\\r\n': - yield (ERRORTOKEN, contstr + line, - strstart, (lnum, len(line)), contline) + yield (ERRORTOKEN, contstr + line, # type: ignore + strstart, (lnum, len(line)), contline) # type: ignore contstr = '' contline = None continue @@ -333,7 +333,7 @@ def generate_tokens(readline): yield (NL, line[nl_pos:], (lnum, nl_pos), (lnum, len(line)), line) else: - yield ((NL, COMMENT)[line[pos] == '#'], line[pos:], + yield ((NL, COMMENT)[line[pos] == '#'], line[pos:], # type: ignore (lnum, pos), (lnum, len(line)), line) continue diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 3c7ab3d97..eb5349ede 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -36,8 +36,9 @@ from docutils.utils import column_width from sphinx import __display_version__, package_dir from sphinx.util.osutil import make_filename -from sphinx.util.console import purple, bold, red, turquoise, \ - nocolor, color_terminal +from sphinx.util.console import ( # type: ignore + purple, bold, red, turquoise, nocolor, color_terminal +) from sphinx.util.template import SphinxRenderer from sphinx.util import texescape diff --git a/sphinx/roles.py b/sphinx/roles.py index 6e8de3b4a..01e34fa71 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -175,7 +175,7 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner, typ = env.config.default_role else: typ = typ.lower() - has_explicit_title, title, target = split_explicit_title(text) + has_explicit_title, title, target = split_explicit_title(text) # type: bool, unicode, unicode # NOQA title = utils.unescape(title) target = utils.unescape(target) targetid = 'index-%s' % env.new_serialno('index') @@ -186,7 +186,7 @@ def indexmarkup_role(typ, rawtext, text, lineno, inliner, indexnode['entries'] = [ ('single', _('Python Enhancement Proposals; PEP %s') % target, targetid, '', None)] - anchor = '' + anchor = '' # type: unicode anchorindex = target.find('#') if anchorindex > 0: target, anchor = target[:anchorindex], target[anchorindex:] diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index d3c6c0eba..959e335c3 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -9,16 +9,23 @@ :license: BSD, see LICENSE for details. """ import re +from os import path from six import iteritems, itervalues, text_type, string_types from six.moves import cPickle as pickle + from docutils.nodes import raw, comment, title, Text, NodeVisitor, SkipNode -from os import path import sphinx from sphinx.util import jsdump, rpartition from sphinx.util.pycompat import htmlescape +if False: + # For type annotation + from typing import Any, IO, Iterable, Tuple, Type # NOQA + from docutils import nodes # NOQA + from sphinx.environment import BuildEnvironment # NOQA + class SearchLanguage(object): """ @@ -42,10 +49,10 @@ class SearchLanguage(object): This class is used to preprocess search word which Sphinx HTML readers type, before searching index. Default implementation does nothing. """ - lang = None - language_name = None - stopwords = set() - js_stemmer_rawcode = None + lang = None # type: unicode + language_name = None # type: unicode + stopwords = set() # type: Set[unicode] + js_stemmer_rawcode = None # type: unicode js_stemmer_code = """ /** * Dummy stemmer for languages without stemming rules. @@ -60,23 +67,27 @@ var Stemmer = function() { _word_re = re.compile(r'\w+(?u)') def __init__(self, options): + # type: (Dict) -> None self.options = options self.init(options) def init(self, options): + # type: (Dict) -> None """ Initialize the class with the options the user has given. """ def split(self, input): + # type: (unicode) -> List[unicode] """ This method splits a sentence into words. Default splitter splits input at white spaces, which should be enough for most languages except CJK languages. """ - return self._word_re.findall(input) + return self._word_re.findall(input) # type: ignore def stem(self, word): + # type: (unicode) -> unicode """ This method implements stemming algorithm of the Python version. @@ -90,6 +101,7 @@ var Stemmer = function() { return word def word_filter(self, word): + # type: (unicode) -> bool """ Return true if the target word should be registered in the search index. This method is called after stemming. @@ -107,6 +119,7 @@ from sphinx.search.en import SearchEnglish def parse_stop_word(source): + # type: (unicode) -> Set[unicode] """ parse snowball style word list like this: @@ -138,7 +151,7 @@ languages = { 'sv': 'sphinx.search.sv.SearchSwedish', 'tr': 'sphinx.search.tr.SearchTurkish', 'zh': 'sphinx.search.zh.SearchChinese', -} +} # type: Dict[unicode, Any] class _JavaScriptIndex(object): @@ -151,9 +164,11 @@ class _JavaScriptIndex(object): SUFFIX = ')' def dumps(self, data): + # type: (Any) -> unicode return self.PREFIX + jsdump.dumps(data) + self.SUFFIX def loads(self, s): + # type: (str) -> Any data = s[len(self.PREFIX):-len(self.SUFFIX)] if not data or not s.startswith(self.PREFIX) or not \ s.endswith(self.SUFFIX): @@ -161,9 +176,11 @@ class _JavaScriptIndex(object): return jsdump.loads(data) def dump(self, data, f): + # type: (Any, IO) -> None f.write(self.dumps(data)) def load(self, f): + # type: (IO) -> Any return self.loads(f.read()) @@ -176,12 +193,14 @@ class WordCollector(NodeVisitor): """ def __init__(self, document, lang): + # type: (nodes.Node, SearchLanguage) -> None NodeVisitor.__init__(self, document) - self.found_words = [] - self.found_title_words = [] + self.found_words = [] # type: List[unicode] + self.found_title_words = [] # type: List[unicode] self.lang = lang def is_meta_keywords(self, node, nodetype): + # type: (nodes.Node, Type) -> bool if isinstance(node, sphinx.addnodes.meta) and node.get('name') == 'keywords': meta_lang = node.get('lang') if meta_lang is None: # lang not specified @@ -192,6 +211,7 @@ class WordCollector(NodeVisitor): return False def dispatch_visit(self, node): + # type: (nodes.Node) -> None nodetype = type(node) if issubclass(nodetype, comment): raise SkipNode @@ -223,28 +243,29 @@ class IndexBuilder(object): formats = { 'jsdump': jsdump, 'pickle': pickle - } + } # type: Dict[unicode, Any] def __init__(self, env, lang, options, scoring): + # type: (BuildEnvironment, unicode, Dict, unicode) -> None self.env = env - # docname -> title - self._titles = {} - # docname -> filename - self._filenames = {} - # stemmed word -> set(docname) - self._mapping = {} - # stemmed words in titles -> set(docname) - self._title_mapping = {} - # word -> stemmed word - self._stem_cache = {} - # objtype -> index - self._objtypes = {} - # objtype index -> (domain, type, objname (localized)) - self._objnames = {} - # add language-specific SearchLanguage instance - lang_class = languages.get(lang) + self._titles = {} # type: Dict[unicode, unicode] + # docname -> title + self._filenames = {} # type: Dict[unicode, unicode] + # docname -> filename + self._mapping = {} # type: Dict[unicode, Set[unicode]] + # stemmed word -> set(docname) + self._title_mapping = {} # type: Dict[unicode, Set[unicode]] + # stemmed words in titles -> set(docname) + self._stem_cache = {} # type: Dict[unicode, unicode] + # word -> stemmed word + self._objtypes = {} # type: Dict[Tuple[unicode, unicode], int] + # objtype -> index + self._objnames = {} # type: Dict[int, Tuple[unicode, unicode, unicode]] + # objtype index -> (domain, type, objname (localized)) + lang_class = languages.get(lang) # type: Type[SearchLanguage] + # add language-specific SearchLanguage instance if lang_class is None: - self.lang = SearchEnglish(options) + self.lang = SearchEnglish(options) # type: SearchLanguage elif isinstance(lang_class, str): module, classname = lang_class.rsplit('.', 1) lang_class = getattr(__import__(module, None, None, [classname]), @@ -261,6 +282,7 @@ class IndexBuilder(object): self.js_scorer_code = u'' def load(self, stream, format): + # type: (IO, Any) -> None """Reconstruct from frozen data.""" if isinstance(format, string_types): format = self.formats[format] @@ -273,6 +295,7 @@ class IndexBuilder(object): self._titles = dict(zip(index2fn, frozen['titles'])) def load_terms(mapping): + # type: (Dict[unicode, Any]) -> Dict[unicode, Set[unicode]] rv = {} for k, v in iteritems(mapping): if isinstance(v, int): @@ -286,13 +309,15 @@ class IndexBuilder(object): # no need to load keywords/objtypes def dump(self, stream, format): + # type: (IO, Any) -> None """Dump the frozen index to a stream.""" if isinstance(format, string_types): format = self.formats[format] - format.dump(self.freeze(), stream) + format.dump(self.freeze(), stream) # type: ignore def get_objects(self, fn2index): - rv = {} + # type: (Dict[unicode, int]) -> Dict[unicode, Dict[unicode, Tuple[int, int, int, unicode]]] # NOQA + rv = {} # type: Dict[unicode, Dict[unicode, Tuple[int, int, int, unicode]]] otypes = self._objtypes onames = self._objnames for domainname, domain in sorted(iteritems(self.env.domains)): @@ -319,7 +344,7 @@ class IndexBuilder(object): else: onames[typeindex] = (domainname, type, type) if anchor == fullname: - shortanchor = '' + shortanchor = '' # type: unicode elif anchor == type + '-' + fullname: shortanchor = '-' else: @@ -328,7 +353,8 @@ class IndexBuilder(object): return rv def get_terms(self, fn2index): - rvs = {}, {} + # type: (Dict) -> Tuple[Dict[unicode, List[unicode]], Dict[unicode, List[unicode]]] + rvs = {}, {} # type: Tuple[Dict[unicode, List[unicode]], Dict[unicode, List[unicode]]] for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)): for k, v in iteritems(mapping): if len(v) == 1: @@ -340,6 +366,7 @@ class IndexBuilder(object): return rvs def freeze(self): + # type: () -> Dict[unicode, Any] """Create a usable data structure for serializing.""" docnames, titles = zip(*sorted(self._titles.items())) filenames = [self._filenames.get(docname) for docname in docnames] @@ -355,9 +382,11 @@ class IndexBuilder(object): titleterms=title_terms, envversion=self.env.version) def label(self): + # type: () -> unicode return "%s (code: %s)" % (self.lang.language_name, self.lang.lang) def prune(self, filenames): + # type: (Iterable[unicode]) -> None """Remove data for all filenames not in the list.""" new_titles = {} for filename in filenames: @@ -370,6 +399,7 @@ class IndexBuilder(object): wordnames.intersection_update(filenames) def feed(self, docname, filename, title, doctree): + # type: (unicode, unicode, unicode, nodes.Node) -> None """Feed a doctree to the index.""" self._titles[docname] = title self._filenames[docname] = filename @@ -379,6 +409,7 @@ class IndexBuilder(object): # memoize self.lang.stem def stem(word): + # type: (unicode) -> unicode try: return self._stem_cache[word] except KeyError: @@ -403,6 +434,7 @@ class IndexBuilder(object): self._mapping.setdefault(stemmed_word, set()).add(docname) def context_for_searchtool(self): + # type: () -> Dict[unicode, Any] return dict( search_language_stemming_code = self.lang.js_stemmer_code, search_language_stop_words = jsdump.dumps(sorted(self.lang.stopwords)), @@ -410,6 +442,7 @@ class IndexBuilder(object): ) def get_js_stemmer_rawcode(self): + # type: () -> unicode if self.lang.js_stemmer_rawcode: return path.join( path.dirname(path.abspath(__file__)), diff --git a/sphinx/search/en.py b/sphinx/search/en.py index d5259bed7..22d4e5acb 100644 --- a/sphinx/search/en.py +++ b/sphinx/search/en.py @@ -224,12 +224,15 @@ class SearchEnglish(SearchLanguage): stopwords = english_stopwords def init(self, options): + # type: (Dict) -> None if PYSTEMMER: class Stemmer(object): def __init__(self): + # type: () -> None self.stemmer = PyStemmer('porter') def stem(self, word): + # type: (unicode) -> unicode return self.stemmer.stemWord(word) else: class Stemmer(PorterStemmer): @@ -237,9 +240,11 @@ class SearchEnglish(SearchLanguage): make at least the stem method nicer. """ def stem(self, word): + # type: (unicode) -> unicode return PorterStemmer.stem(self, word, 0, len(word) - 1) self.stemmer = Stemmer() def stem(self, word): + # type: (unicode) -> unicode return self.stemmer.stem(word.lower()) diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index 0d4d01b9c..cf3b67c00 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -43,9 +43,11 @@ from sphinx.util import import_object class BaseSplitter(object): def __init__(self, options): + # type: (Dict) -> None self.options = options def split(self, input): + # type: (unicode) -> List[unicode] """ :param str input: @@ -57,9 +59,10 @@ class BaseSplitter(object): class MecabSplitter(BaseSplitter): def __init__(self, options): + # type: (Dict) -> None super(MecabSplitter, self).__init__(options) - self.ctypes_libmecab = None - self.ctypes_mecab = None + self.ctypes_libmecab = None # type: ignore + self.ctypes_mecab = None # type: ignore if not native_module: self.init_ctypes(options) else: @@ -67,6 +70,7 @@ class MecabSplitter(BaseSplitter): self.dict_encode = options.get('dic_enc', 'utf-8') def split(self, input): + # type: (unicode) -> List[unicode] input2 = input if PY3 else input.encode(self.dict_encode) if native_module: result = self.native.parse(input2) @@ -79,6 +83,7 @@ class MecabSplitter(BaseSplitter): return result.decode(self.dict_encode).split(' ') def init_native(self, options): + # type: (Dict) -> None param = '-Owakati' dict = options.get('dict') if dict: @@ -86,6 +91,7 @@ class MecabSplitter(BaseSplitter): self.native = MeCab.Tagger(param) def init_ctypes(self, options): + # type: (Dict) -> None import ctypes.util lib = options.get('lib') @@ -122,6 +128,7 @@ class MecabSplitter(BaseSplitter): raise SphinxError('mecab initialization failed') def __del__(self): + # type: () -> None if self.ctypes_libmecab: self.ctypes_libmecab.mecab_destroy(self.ctypes_mecab) @@ -130,17 +137,20 @@ MeCabBinder = MecabSplitter # keep backward compatibility until Sphinx-1.6 class JanomeSplitter(BaseSplitter): def __init__(self, options): + # type: (Dict) -> None super(JanomeSplitter, self).__init__(options) self.user_dict = options.get('user_dic') self.user_dict_enc = options.get('user_dic_enc', 'utf8') self.init_tokenizer() def init_tokenizer(self): + # type: () -> None if not janome_module: raise RuntimeError('Janome is not available') self.tokenizer = janome.tokenizer.Tokenizer(udic=self.user_dict, udic_enc=self.user_dict_enc) def split(self, input): + # type: (unicode) -> List[unicode] result = u' '.join(token.surface for token in self.tokenizer.tokenize(input)) return result.split(u' ') @@ -417,6 +427,7 @@ class DefaultSplitter(BaseSplitter): # ctype_ def ctype_(self, char): + # type: (unicode) -> unicode for pattern, value in iteritems(self.patterns_): if pattern.match(char): return value @@ -424,12 +435,14 @@ class DefaultSplitter(BaseSplitter): # ts_ def ts_(self, dict, key): + # type: (Dict[unicode, int], unicode) -> int if key in dict: return dict[key] return 0 # segment def split(self, input): + # type: (unicode) -> List[unicode] if not input: return [] @@ -538,6 +551,7 @@ class SearchJapanese(SearchLanguage): } def init(self, options): + # type: (Dict) -> None type = options.get('type', 'default') if type in self.splitters: dotted_path = self.splitters[type] @@ -550,10 +564,13 @@ class SearchJapanese(SearchLanguage): dotted_path) def split(self, input): + # type: (unicode) -> List[unicode] return self.splitter.split(input) def word_filter(self, stemmed_word): + # type: (unicode) -> bool return len(stemmed_word) > 1 def stem(self, word): + # type: (unicode) -> unicode return word diff --git a/sphinx/search/ro.py b/sphinx/search/ro.py index 78ae01851..f44f38e34 100644 --- a/sphinx/search/ro.py +++ b/sphinx/search/ro.py @@ -24,10 +24,12 @@ class SearchRomanian(SearchLanguage): language_name = 'Romanian' js_stemmer_rawcode = 'romanian-stemmer.js' js_stemmer_code = js_stemmer - stopwords = [] + stopwords = [] # type: List[unicode] def init(self, options): + # type: (Dict) -> None self.stemmer = snowballstemmer.stemmer('romanian') def stem(self, word): + # type: (unicode) -> unicode return self.stemmer.stemWord(word) diff --git a/sphinx/search/tr.py b/sphinx/search/tr.py index 33c5c5192..14cc710f8 100644 --- a/sphinx/search/tr.py +++ b/sphinx/search/tr.py @@ -24,10 +24,12 @@ class SearchTurkish(SearchLanguage): language_name = 'Turkish' js_stemmer_rawcode = 'turkish-stemmer.js' js_stemmer_code = js_stemmer - stopwords = [] + stopwords = [] # type: List[unicode] def init(self, options): + # type: (Dict) -> None self.stemmer = snowballstemmer.stemmer('turkish') def stem(self, word): + # type: (unicode) -> unicode return self.stemmer.stemWord(word) diff --git a/sphinx/search/zh.py b/sphinx/search/zh.py index c1fecefc6..bd4787506 100644 --- a/sphinx/search/zh.py +++ b/sphinx/search/zh.py @@ -238,6 +238,7 @@ class SearchChinese(SearchLanguage): latin1_letters = re.compile(r'\w+(?u)[\u0000-\u00ff]') def init(self, options): + # type: (Dict) -> None if JIEBA: dict_path = options.get('dict') if dict_path and os.path.isfile(dict_path): @@ -246,9 +247,11 @@ class SearchChinese(SearchLanguage): if PYSTEMMER: class Stemmer(object): def __init__(self): + # type: () -> None self.stemmer = PyStemmer('porter') def stem(self, word): + # type: (unicode) -> unicode return self.stemmer.stemWord(word) else: class Stemmer(PorterStemmer): @@ -256,20 +259,24 @@ class SearchChinese(SearchLanguage): make at least the stem method nicer. """ def stem(self, word): + # type: (unicode) -> unicode return PorterStemmer.stem(self, word, 0, len(word) - 1) self.stemmer = Stemmer() def split(self, input): - chinese = [] + # type: (unicode) -> List[unicode] + chinese = [] # type: List[unicode] if JIEBA: chinese = list(jieba.cut_for_search(input)) - latin1 = self.latin1_letters.findall(input) + latin1 = self.latin1_letters.findall(input) # type: ignore return chinese + latin1 def word_filter(self, stemmed_word): + # type: (unicode) -> bool return len(stemmed_word) > 1 def stem(self, word): + # type: (unicode) -> unicode return self.stemmer.stem(word) diff --git a/sphinx/setup_command.py b/sphinx/setup_command.py index c23f22228..f263f8df1 100644 --- a/sphinx/setup_command.py +++ b/sphinx/setup_command.py @@ -18,7 +18,7 @@ import os from six import StringIO, string_types from distutils.cmd import Command -from distutils.errors import DistutilsOptionError, DistutilsExecError +from distutils.errors import DistutilsOptionError, DistutilsExecError # type: ignore from sphinx.application import Sphinx from sphinx.cmdline import handle_exception @@ -26,6 +26,10 @@ from sphinx.util.console import nocolor, color_terminal from sphinx.util.docutils import docutils_namespace from sphinx.util.osutil import abspath +if False: + # For type annotation + from typing import Any # NOQA + class BuildDoc(Command): """ @@ -87,22 +91,24 @@ class BuildDoc(Command): 'link-index'] def initialize_options(self): + # type: () -> None self.fresh_env = self.all_files = False self.pdb = False - self.source_dir = self.build_dir = None + self.source_dir = self.build_dir = None # type: unicode self.builder = 'html' self.warning_is_error = False self.project = '' self.version = '' self.release = '' self.today = '' - self.config_dir = None + self.config_dir = None # type: unicode self.link_index = False self.copyright = '' self.verbosity = 0 self.traceback = False def _guess_source_dir(self): + # type: () -> unicode for guess in ('doc', 'docs'): if not os.path.isdir(guess): continue @@ -115,6 +121,7 @@ class BuildDoc(Command): # unicode, causing finalize_options to fail if invoked again. Workaround # for http://bugs.python.org/issue19570 def _ensure_stringlike(self, option, what, default=None): + # type: (unicode, unicode, Any) -> Any val = getattr(self, option) if val is None: setattr(self, option, default) @@ -125,10 +132,11 @@ class BuildDoc(Command): return val def finalize_options(self): + # type: () -> None 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') + self.announce('Using source directory %s' % self.source_dir) # type: ignore + self.ensure_dirname('source_dir') # type: ignore if self.source_dir is None: self.source_dir = os.curdir self.source_dir = abspath(self.source_dir) @@ -137,22 +145,23 @@ class BuildDoc(Command): self.config_dir = abspath(self.config_dir) if self.build_dir is None: - build = self.get_finalized_command('build') + build = self.get_finalized_command('build') # type: ignore self.build_dir = os.path.join(abspath(build.build_base), 'sphinx') - self.mkpath(self.build_dir) + 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) + self.mkpath(self.doctree_dir) # type: ignore self.builder_target_dir = os.path.join(self.build_dir, self.builder) - self.mkpath(self.builder_target_dir) + self.mkpath(self.builder_target_dir) # type: ignore def run(self): + # type: () -> None if not color_terminal(): nocolor() - if not self.verbose: + if not self.verbose: # type: ignore status_stream = StringIO() else: - status_stream = sys.stdout + status_stream = sys.stdout # type: ignore confoverrides = {} if self.project: confoverrides['project'] = self.project @@ -182,6 +191,6 @@ class BuildDoc(Command): raise SystemExit(1) if self.link_index: - src = app.config.master_doc + app.builder.out_suffix - dst = app.builder.get_outfilename('index') + src = app.config.master_doc + app.builder.out_suffix # type: ignore + dst = app.builder.get_outfilename('index') # type: ignore os.symlink(src, dst) diff --git a/sphinx/theming.py b/sphinx/theming.py index 42e4448db..4e05652cd 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -16,7 +16,8 @@ import tempfile from os import path from six import string_types, iteritems -from six.moves import configparser +from six.moves import configparser # type: ignore +from typing import Any, Callable, Tuple # NOQA try: import pkg_resources @@ -26,6 +27,10 @@ except ImportError: from sphinx import package_dir from sphinx.errors import ThemeError +if False: + # For type annotation + from typing import Any, Callable, Tuple # NOQA + NODEFAULT = object() THEMECONF = 'theme.conf' @@ -34,10 +39,12 @@ class Theme(object): """ Represents the theme chosen in the configuration. """ - themes = {} + themes = {} # type: Dict[unicode, Tuple[unicode, zipfile.ZipFile]] + themepath = [] # type: List[unicode] @classmethod def init_themes(cls, confdir, theme_path, warn=None): + # type: (unicode, unicode, Callable) -> None """Search all theme paths for available themes.""" cls.themepath = list(theme_path) cls.themepath.append(path.join(package_dir, 'themes')) @@ -49,7 +56,7 @@ class Theme(object): for theme in os.listdir(themedir): if theme.lower().endswith('.zip'): try: - zfile = zipfile.ZipFile(path.join(themedir, theme)) + zfile = zipfile.ZipFile(path.join(themedir, theme)) # type: ignore if THEMECONF not in zfile.namelist(): continue tname = theme[:-4] @@ -68,6 +75,7 @@ class Theme(object): @classmethod def load_extra_theme(cls, name): + # type: (unicode) -> None themes = ['alabaster'] try: import sphinx_rtd_theme @@ -98,6 +106,7 @@ class Theme(object): return def __init__(self, name, warn=None): + # type: (unicode, Callable) -> None if name not in self.themes: self.load_extra_theme(name) if name not in self.themes: @@ -156,6 +165,7 @@ class Theme(object): self.base = Theme(inherit, warn=warn) def get_confstr(self, section, name, default=NODEFAULT): + # type: (unicode, unicode, Any) -> Any """Return the value for a theme configuration setting, searching the base theme chain. """ @@ -171,13 +181,14 @@ class Theme(object): return default def get_options(self, overrides): + # type: (Dict) -> Any """Return a dictionary of theme options and their values.""" chain = [self.themeconf] base = self.base while base is not None: chain.append(base.themeconf) base = base.base - options = {} + options = {} # type: Dict[unicode, Any] for conf in reversed(chain): try: options.update(conf.items('options')) @@ -190,6 +201,7 @@ class Theme(object): return options def get_dirchain(self): + # type: () -> List[unicode] """Return a list of theme directories, beginning with this theme's, then the base theme's, then that one's base theme's, etc. """ @@ -201,6 +213,7 @@ class Theme(object): return chain def cleanup(self): + # type: () -> None """Remove temporary directories.""" if self.themedir_created: try: @@ -212,6 +225,7 @@ class Theme(object): def load_theme_plugins(): + # type: () -> List[unicode] """load plugins by using``sphinx_themes`` section in setuptools entry_points. This API will return list of directory that contain some theme directory. """ @@ -219,7 +233,7 @@ def load_theme_plugins(): if not pkg_resources: return [] - theme_paths = [] + theme_paths = [] # type: List[unicode] for plugin in pkg_resources.iter_entry_points('sphinx_themes'): func_or_path = plugin.load() @@ -229,7 +243,7 @@ def load_theme_plugins(): path = func_or_path if isinstance(path, string_types): - theme_paths.append(path) + theme_paths.append(path) # type: ignore else: raise ThemeError('Plugin %r does not response correctly.' % plugin.module_name) diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 79ac99c9f..68e45d62d 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -33,6 +33,7 @@ class DefaultSubstitutions(Transform): default_priority = 210 def apply(self): + # type: () -> None env = self.document.settings.env config = self.document.settings.env.config # only handle those not otherwise defined in the document @@ -58,6 +59,7 @@ class MoveModuleTargets(Transform): default_priority = 210 def apply(self): + # type: () -> None for node in self.document.traverse(nodes.target): if not node['ids']: continue @@ -76,6 +78,7 @@ class HandleCodeBlocks(Transform): default_priority = 210 def apply(self): + # type: () -> None # move doctest blocks out of blockquotes for node in self.document.traverse(nodes.block_quote): if all(isinstance(child, nodes.doctest_block) for child @@ -100,6 +103,7 @@ class AutoNumbering(Transform): default_priority = 210 def apply(self): + # type: () -> None domain = self.document.settings.env.domains['std'] for node in self.document.traverse(nodes.Element): @@ -114,6 +118,7 @@ class SortIds(Transform): default_priority = 261 def apply(self): + # type: () -> None for node in self.document.traverse(nodes.section): if len(node['ids']) > 1 and node['ids'][0].startswith('id'): node['ids'] = node['ids'][1:] + [node['ids'][0]] @@ -127,6 +132,7 @@ class CitationReferences(Transform): default_priority = 619 def apply(self): + # type: () -> None for citnode in self.document.traverse(nodes.citation_reference): cittext = citnode.astext() refnode = addnodes.pending_xref(cittext, refdomain='std', reftype='citation', @@ -154,6 +160,7 @@ class ApplySourceWorkaround(Transform): default_priority = 10 def apply(self): + # type: () -> None for n in self.document.traverse(): if isinstance(n, nodes.TextElement): apply_source_workaround(n) @@ -166,6 +173,7 @@ class AutoIndexUpgrader(Transform): default_priority = 210 def apply(self): + # type: () -> None env = self.document.settings.env for node in self.document.traverse(addnodes.index): if 'entries' in node and any(len(entry) == 4 for entry in node['entries']): @@ -184,12 +192,14 @@ class ExtraTranslatableNodes(Transform): default_priority = 10 def apply(self): + # type: () -> None targets = self.document.settings.env.config.gettext_additional_targets target_nodes = [v for k, v in TRANSLATABLE_NODES.items() if k in targets] if not target_nodes: return def is_translatable_node(node): + # type: (nodes.Node) -> bool return isinstance(node, tuple(target_nodes)) for node in self.document.traverse(is_translatable_node): @@ -201,6 +211,7 @@ class FilterSystemMessages(Transform): default_priority = 999 def apply(self): + # type: () -> None env = self.document.settings.env filterlevel = env.config.keep_warnings and 2 or 5 for node in self.document.traverse(nodes.system_message): @@ -215,9 +226,11 @@ class SphinxContentsFilter(ContentsFilter): within table-of-contents link nodes. """ def visit_pending_xref(self, node): + # type: (nodes.Node) -> None text = node.astext() self.parent.append(nodes.literal(text, text)) raise nodes.SkipNode def visit_image(self, node): + # type: (nodes.Node) -> None raise nodes.SkipNode diff --git a/sphinx/transforms/compact_bullet_list.py b/sphinx/transforms/compact_bullet_list.py index 61b23f382..0fe2e8b83 100644 --- a/sphinx/transforms/compact_bullet_list.py +++ b/sphinx/transforms/compact_bullet_list.py @@ -23,12 +23,15 @@ class RefOnlyListChecker(nodes.GenericNodeVisitor): """ def default_visit(self, node): + # type: (nodes.Node) -> None raise nodes.NodeFound def visit_bullet_list(self, node): + # type: (nodes.Node) -> None pass def visit_list_item(self, node): + # type: (nodes.Node) -> None children = [] for child in node.children: if not isinstance(child, nodes.Invisible): @@ -45,6 +48,7 @@ class RefOnlyListChecker(nodes.GenericNodeVisitor): raise nodes.SkipChildren def invisible_visit(self, node): + # type: (nodes.Node) -> None """Invisible nodes should be ignored.""" pass @@ -58,11 +62,13 @@ class RefOnlyBulletListTransform(Transform): default_priority = 100 def apply(self): + # type: () -> None env = self.document.settings.env if env.config.html_compact_lists: return def check_refonly_list(node): + # type: (nodes.Node) -> bool """Check for list with only references in it.""" visitor = RefOnlyListChecker(self.document) try: diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 38c5aef25..693ae663e 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -27,8 +27,15 @@ from sphinx.util.pycompat import indent from sphinx.locale import init as init_locale from sphinx.domains.std import make_glossary_term, split_term_classifiers +if False: + # For type annotation + from typing import Any, Tuple # NOQA + from sphinx.application import Sphinx # NOQA + from sphinx.config import Config # NOQA + def publish_msgstr(app, source, source_path, source_line, config, settings): + # type: (Sphinx, unicode, unicode, int, Config, Dict) -> nodes.document """Publish msgstr (single line) into docutils document :param sphinx.application.Sphinx app: sphinx application @@ -66,6 +73,7 @@ class PreserveTranslatableMessages(Transform): default_priority = 10 # this MUST be invoked before Locale transform def apply(self): + # type: () -> None for node in self.document.traverse(addnodes.translatable): node.preserve_original_messages() @@ -77,6 +85,7 @@ class Locale(Transform): default_priority = 20 def apply(self): + # type: () -> None env = self.document.settings.env settings, source = self.document.settings, self.document['source'] # XXX check if this is reliable @@ -176,6 +185,7 @@ class Locale(Transform): # replace target's refname to new target name def is_named_target(node): + # type: (nodes.Node) -> bool return isinstance(node, nodes.target) and \ node.get('refname') == old_name for old_target in self.document.traverse(is_named_target): @@ -249,10 +259,12 @@ class Locale(Transform): # auto-numbered foot note reference should use original 'ids'. def is_autonumber_footnote_ref(node): + # type: (nodes.Node) -> bool return isinstance(node, nodes.footnote_reference) and \ node.get('auto') == 1 def list_replace_or_append(lst, old, new): + # type: (List, Any, Any) -> None if old in lst: lst[lst.index(old)] = new else: @@ -262,7 +274,7 @@ class Locale(Transform): if len(old_foot_refs) != len(new_foot_refs): env.warn_node('inconsistent footnote references in ' 'translated message', node) - old_foot_namerefs = {} + old_foot_namerefs = {} # type: Dict[unicode, List[nodes.footnote_reference]] for r in old_foot_refs: old_foot_namerefs.setdefault(r.get('refname'), []).append(r) for new in new_foot_refs: @@ -315,6 +327,7 @@ class Locale(Transform): # refnamed footnote and citation should use original 'ids'. def is_refnamed_footnote_ref(node): + # type: (nodes.Node) -> bool footnote_ref_classes = (nodes.footnote_reference, nodes.citation_reference) return isinstance(node, footnote_ref_classes) and \ @@ -343,6 +356,7 @@ class Locale(Transform): 'translated message', node) def get_ref_key(node): + # type: (nodes.Node) -> Tuple[unicode, unicode, unicode] case = node["refdomain"], node["reftype"] if case == ('std', 'term'): return None @@ -384,7 +398,7 @@ class Locale(Transform): if 'index' in env.config.gettext_additional_targets: # Extract and translate messages for index entries. for node, entries in traverse_translatable_index(self.document): - new_entries = [] + new_entries = [] # type: List[Tuple[unicode, unicode, unicode, unicode, unicode]] # NOQA for type, msg, tid, main, key_ in entries: msg_parts = split_index_msg(type, msg) msgstr_parts = [] @@ -407,6 +421,7 @@ class RemoveTranslatableInline(Transform): default_priority = 999 def apply(self): + # type: () -> None from sphinx.builders.gettext import MessageCatalogBuilder env = self.document.settings.env builder = env.app.builder diff --git a/sphinx/versioning.py b/sphinx/versioning.py index f6c446b4f..0f862ac67 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -16,6 +16,11 @@ from itertools import product from six import iteritems from six.moves import range, zip_longest +if False: + # For type annotation + from typing import Any, Iterator # NOQA + from docutils import nodes # NOQA + try: import Levenshtein IS_SPEEDUP = True @@ -27,6 +32,7 @@ VERSIONING_RATIO = 65 def add_uids(doctree, condition): + # type: (nodes.Node, Any) -> Iterator[nodes.Node] """Add a unique id to every node in the `doctree` which matches the condition and yield the nodes. @@ -42,6 +48,7 @@ def add_uids(doctree, condition): def merge_doctrees(old, new, condition): + # type: (nodes.Node, nodes.Node, Any) -> Iterator[nodes.Node] """Merge the `old` doctree with the `new` one while looking at nodes matching the `condition`. @@ -90,7 +97,7 @@ def merge_doctrees(old, new, condition): # choose the old node with the best ratio for each new node and set the uid # as long as the ratio is under a certain value, in which case we consider # them not changed but different - ratios = sorted(iteritems(ratios), key=itemgetter(1)) + ratios = sorted(iteritems(ratios), key=itemgetter(1)) # type: ignore for (old_node, new_node), ratio in ratios: if new_node in seen: continue @@ -109,6 +116,7 @@ def merge_doctrees(old, new, condition): def get_ratio(old, new): + # type: (unicode, unicode) -> float """Return a "similiarity ratio" (in percent) representing the similarity between the two strings where 0 is equal and anything above less than equal. """ @@ -122,6 +130,7 @@ def get_ratio(old, new): def levenshtein_distance(a, b): + # type: (unicode, unicode) -> int """Return the Levenshtein edit distance between two strings *a* and *b*.""" if a == b: return 0 @@ -137,5 +146,5 @@ def levenshtein_distance(a, b): deletions = current_row[j] + 1 substitutions = previous_row[j] + (column1 != column2) current_row.append(min(insertions, deletions, substitutions)) - previous_row = current_row + previous_row = current_row # type: ignore return previous_row[-1] diff --git a/sphinx/websupport/__init__.py b/sphinx/websupport/__init__.py index 69914da95..f7b215f83 100644 --- a/sphinx/websupport/__init__.py +++ b/sphinx/websupport/__init__.py @@ -66,7 +66,7 @@ class WebSupport(object): self._init_search(search) self._init_storage(storage) - self._globalcontext = None + self._globalcontext = None # type: ignore self._make_base_comment_options() @@ -119,7 +119,7 @@ class WebSupport(object): raise RuntimeError('No srcdir associated with WebSupport object') app = Sphinx(self.srcdir, self.srcdir, self.outdir, self.doctreedir, 'websupport', status=self.status, warning=self.warning) - app.builder.set_webinfo(self.staticdir, self.staticroot, + app.builder.set_webinfo(self.staticdir, self.staticroot, # type: ignore self.search, self.storage) self.storage.pre_build() @@ -384,7 +384,7 @@ class WebSupport(object): that remains the same throughout the lifetime of the :class:`~sphinx.websupport.WebSupport` object. """ - self.base_comment_opts = {} + self.base_comment_opts = {} # type: Dict[unicode, unicode] if self.docroot != '': comment_urls = [ diff --git a/sphinx/websupport/storage/sqlalchemy_db.py b/sphinx/websupport/storage/sqlalchemy_db.py index b412ad488..16418ec8f 100644 --- a/sphinx/websupport/storage/sqlalchemy_db.py +++ b/sphinx/websupport/storage/sqlalchemy_db.py @@ -14,7 +14,7 @@ from datetime import datetime from sqlalchemy import Column, Integer, Text, String, Boolean, \ ForeignKey, DateTime -from sqlalchemy.orm import relation, sessionmaker, aliased +from sqlalchemy.orm import relation, sessionmaker, aliased # type: ignore from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() @@ -23,7 +23,7 @@ Session = sessionmaker() db_prefix = 'sphinx_' -class Node(Base): +class Node(Base): # type: ignore """Data about a Node in a doctree.""" __tablename__ = db_prefix + 'nodes' @@ -74,7 +74,7 @@ class Node(Base): :param results: the flat list of comments :param username: the name of the user requesting the comments. """ - comments = [] + comments = [] # type: List list_stack = [comments] for r in results: if username: @@ -101,7 +101,7 @@ class Node(Base): self.source = source -class CommentVote(Base): +class CommentVote(Base): # type: ignore """A vote a user has made on a Comment.""" __tablename__ = db_prefix + 'commentvote' @@ -117,7 +117,7 @@ class CommentVote(Base): self.value = value -class Comment(Base): +class Comment(Base): # type: ignore """An individual Comment being stored.""" __tablename__ = db_prefix + 'comments' diff --git a/sphinx/websupport/storage/sqlalchemystorage.py b/sphinx/websupport/storage/sqlalchemystorage.py index c8794f75c..8b7d76714 100644 --- a/sphinx/websupport/storage/sqlalchemystorage.py +++ b/sphinx/websupport/storage/sqlalchemystorage.py @@ -12,7 +12,7 @@ from datetime import datetime import sqlalchemy -from sqlalchemy.orm import aliased +from sqlalchemy.orm import aliased # type: ignore from sqlalchemy.sql import func from sphinx.websupport.errors import CommentNotAllowedError, \ @@ -22,7 +22,7 @@ from sphinx.websupport.storage.sqlalchemy_db import Base, Node, \ Comment, CommentVote, Session from sphinx.websupport.storage.differ import CombinedHtmlDiff -if sqlalchemy.__version__[:3] < '0.5': +if sqlalchemy.__version__[:3] < '0.5': # type: ignore raise ImportError('SQLAlchemy version 0.5 or greater is required for this ' 'storage backend; you have version %s' % sqlalchemy.__version__) |
