diff --git a/.codecov.yml b/.codecov.yml index 0e6fdf0c3..2ce4fda70 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,3 +4,7 @@ coverage: default: # allowed to drop X% and still result in a "success" commit status threshold: 0.05 + patch: + default: + # allowed to drop X% and still result in a "success" commit status + threshold: 0.05 diff --git a/CHANGES b/CHANGES index 2af5732d5..f69050d6a 100644 --- a/CHANGES +++ b/CHANGES @@ -45,7 +45,7 @@ Bugs fixed Testing -------- -Release 1.7.1 (in development) +Release 1.7.2 (in development) ============================== Dependencies @@ -57,13 +57,27 @@ Incompatible changes Deprecated ---------- +Features added +-------------- + +Bugs fixed +---------- + +Testing +-------- + +Release 1.7.1 (released Feb 23, 2018) +===================================== + +Deprecated +---------- + * #4623: ``sphinx.build_main()`` is deprecated. Use ``sphinx.cmd.build.build_main()`` instead. * autosummary: The interface of ``sphinx.ext.autosummary.get_documenter()`` has been changed (Since 1.7.0) - -Features added --------------- +* #4664: ``sphinx.ext.intersphinx.debug()`` is deprecated. Use + ``sphinx.ext.intersphinx.inspect_main()`` instead. Bugs fixed ---------- @@ -81,9 +95,14 @@ Bugs fixed * #4630: Have order on msgids in sphinx.pot deterministic * #4563: autosummary: Incorrect end of line punctuation detection * #4577: Enumerated sublists with explicit start with wrong number - -Testing --------- +* #4641: A external link in TOC cannot contain "?" with ``:glob:`` option +* C++, add missing parsing of explicit casts and typeid in expression parsing. +* C++, add missing parsing of ``this`` in expression parsing. +* #4655: Fix incomplete localization strings in Polish +* #4653: Fix error reporting for parameterless ImportErrors +* #4664: Reading objects.inv fails again +* #4662: ``any`` refs with ``term`` targets crash when an ambiguity is + encountered Release 1.7.0 (released Feb 12, 2018) ===================================== diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index 715c6e6c2..53deb0ee5 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -134,6 +134,7 @@ class Epub3Builder(_epub_base.EpubBuilder): metadata['ibook_scroll_axis'] = IBOOK_SCROLL_AXIS.get(writing_mode) metadata['date'] = self.esc(format_date("%Y-%m-%dT%H:%M:%SZ")) metadata['version'] = self.esc(self.config.version) + metadata['epub_version'] = self.config.epub_version return metadata def prepare_writing(self, docnames): @@ -230,6 +231,7 @@ def setup(app): # config values app.add_config_value('epub_basename', lambda self: make_filename(self.project), None) + app.add_config_value('epub_version', 3.0, 'epub') # experimental app.add_config_value('epub_theme', 'epub', 'epub') app.add_config_value('epub_theme_options', {}, 'epub') app.add_config_value('epub_title', lambda self: self.html_title, 'epub') diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 6c1a4cd86..f04e429c2 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -73,7 +73,9 @@ class TocTree(Directive): for entry in self.content: if not entry: continue - if glob and ('*' in entry or '?' in entry or '[' in entry): + # look for explicit titles ("Some Title ") + explicit = explicit_title_re.match(entry) + if glob and ('*' in entry or '?' in entry or '[' in entry) and not explicit: patname = docname_join(env.docname, entry) docnames = sorted(patfilter(all_docnames, patname)) for docname in docnames: @@ -85,11 +87,9 @@ class TocTree(Directive): 'toctree glob pattern %r didn\'t match any documents' % entry, line=self.lineno)) else: - # look for explicit titles ("Some Title ") - m = explicit_title_re.match(entry) - if m: - ref = m.group(2) - title = m.group(1) + if explicit: + ref = explicit.group(2) + title = explicit.group(1) docname = ref else: ref = docname = entry diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7d081ff86..0bd883a30 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -530,6 +530,12 @@ _expression_bin_ops = [ _expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "~"] _expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=", ">>=", "<<=", "&=", "^=", "|="] +_id_explicit_cast = { + 'dynamic_cast': 'dc', + 'static_cast': 'sc', + 'const_cast': 'cc', + 'reinterpret_cast': 'rc' +} class NoOldIdError(UnicodeMixin, Exception): @@ -779,6 +785,17 @@ class ASTStringLiteral(ASTBase): signode.append(nodes.Text(txt, txt)) +class ASTThisLiteral(ASTBase): + def __unicode__(self): + return "this" + + def get_id(self, version): + return "fpT" + + def describe_signature(self, signode, mode, env, symbol): + signode.append(nodes.Text("this")) + + class ASTParenExpr(ASTBase): def __init__(self, expr): self.expr = expr @@ -1028,6 +1045,56 @@ class ASTNoexceptExpr(ASTBase): signode.append(nodes.Text(')')) +class ASTExplicitCast(ASTBase): + def __init__(self, cast, typ, expr): + assert cast in _id_explicit_cast + self.cast = cast + self.typ = typ + self.expr = expr + + def __unicode__(self): + res = [self.cast] + res.append('<') + res.append(text_type(self.typ)) + res.append('>(') + res.append(text_type(self.expr)) + res.append(')') + return u''.join(res) + + def get_id(self, version): + return (_id_explicit_cast[self.cast] + + self.typ.get_id(version) + + self.expr.get_id(version)) + + def describe_signature(self, signode, mode, env, symbol): + signode.append(nodes.Text(self.cast)) + signode.append(nodes.Text('<')) + self.typ.describe_signature(signode, mode, env, symbol) + signode.append(nodes.Text('>')) + signode.append(nodes.Text('(')) + self.expr.describe_signature(signode, mode, env, symbol) + signode.append(nodes.Text(')')) + + +class ASTTypeId(ASTBase): + def __init__(self, typeOrExpr, isType): + self.typeOrExpr = typeOrExpr + self.isType = isType + + def __unicode__(self): + return 'typeid(' + text_type(self.typeOrExpr) + ')' + + def get_id(self, version): + prefix = 'ti' if self.isType else 'te' + return prefix + self.typeOrExpr.get_id(version) + + def describe_signature(self, signode, mode, env, symbol): + signode.append(nodes.Text('typeid')) + signode.append(nodes.Text('(')) + self.typeOrExpr.describe_signature(signode, mode, env, symbol) + signode.append(nodes.Text(')')) + + class ASTPostfixCallExpr(ASTBase): def __init__(self, exprs): self.exprs = exprs @@ -4069,7 +4136,10 @@ class DefinitionParser(object): res = self._parse_literal() if res is not None: return res - # TODO: try 'this' and lambda expression + self.skip_ws() + if self.skip_word("this"): + return ASTThisLiteral() + # TODO: try lambda expression res = self._parse_fold_or_paren_expression() if res is not None: return res @@ -4097,39 +4167,94 @@ class DefinitionParser(object): # | "typeid" "(" expression ")" # | "typeid" "(" type-id ")" - # TODO: try the productions with prefixes: - # dynamic_cast, static_cast, reinterpret_cast, const_cast, typeid prefixType = None - pos = self.pos - try: - prefix = self._parse_primary_expression() - prefixType = 'expr' - except DefinitionError as eOuter: - self.pos = pos + prefix = None # type: Any + self.skip_ws() + + cast = None + for c in _id_explicit_cast: + if self.skip_word_and_ws(c): + cast = c + break + if cast is not None: + prefixType = "cast" + if not self.skip_string("<"): + self.fail("Expected '<' afer '%s'." % cast) + typ = self._parse_type(False) + self.skip_ws() + if not self.skip_string_and_ws(">"): + self.fail("Expected '>' after type in '%s'." % cast) + if not self.skip_string("("): + self.fail("Expected '(' in '%s'." % cast) + + def parser(): + return self._parse_expression(inTemplate=False) + expr = self._parse_expression_fallback([')'], parser) + self.skip_ws() + if not self.skip_string(")"): + self.fail("Expected ')' to end '%s'." % cast) + prefix = ASTExplicitCast(cast, typ, expr) + elif self.skip_word_and_ws("typeid"): + prefixType = "typeid" + if not self.skip_string_and_ws('('): + self.fail("Expected '(' after 'typeid'.") + pos = self.pos try: - # we are potentially casting, so save parens for us - # TODO: hmm, would we need to try both with operatorCast and with None? - prefix = self._parse_type(False, 'operatorCast') - prefixType = 'typeOperatorCast' - # | simple-type-specifier "(" expression-list [opt] ")" - # | simple-type-specifier braced-init-list - # | typename-specifier "(" expression-list [opt] ")" - # | typename-specifier braced-init-list - self.skip_ws() - if self.current_char != '(' and self.current_char != '{': - self.fail("Expecting '(' or '{' after type in cast expression.") - except DefinitionError as eInner: + typ = self._parse_type(False) + prefix = ASTTypeId(typ, isType=True) + if not self.skip_string(')'): + self.fail("Expected ')' to end 'typeid' of type.") + except DefinitionError as eType: self.pos = pos - header = "Error in postfix expression, expected primary expression or type." - errors = [] - errors.append((eOuter, "If primary expression")) - errors.append((eInner, "If type")) - raise self._make_multi_error(errors, header) + try: + + def parser(): + return self._parse_expression(inTemplate=False) + expr = self._parse_expression_fallback([')'], parser) + prefix = ASTTypeId(expr, isType=False) + if not self.skip_string(')'): + self.fail("Expected ')' to end 'typeid' of expression.") + except DefinitionError as eExpr: + self.pos = pos + header = "Error in 'typeid(...)'." + header += " Expected type or expression." + errors = [] + errors.append((eType, "If type")) + errors.append((eExpr, "If expression")) + raise self._make_multi_error(errors, header) + else: # a primary expression or a type + pos = self.pos + try: + prefix = self._parse_primary_expression() + prefixType = 'expr' + except DefinitionError as eOuter: + self.pos = pos + try: + # we are potentially casting, so save parens for us + # TODO: hmm, would we need to try both with operatorCast and with None? + prefix = self._parse_type(False, 'operatorCast') + prefixType = 'typeOperatorCast' + # | simple-type-specifier "(" expression-list [opt] ")" + # | simple-type-specifier braced-init-list + # | typename-specifier "(" expression-list [opt] ")" + # | typename-specifier braced-init-list + self.skip_ws() + if self.current_char != '(' and self.current_char != '{': + self.fail("Expecting '(' or '{' after type in cast expression.") + except DefinitionError as eInner: + self.pos = pos + header = "Error in postfix expression," + header += " expected primary expression or type." + errors = [] + errors.append((eOuter, "If primary expression")) + errors.append((eInner, "If type")) + raise self._make_multi_error(errors, header) + # and now parse postfixes postFixes = [] while True: self.skip_ws() - if prefixType == 'expr': + if prefixType in ['expr', 'cast', 'typeid']: if self.skip_string_and_ws('['): expr = self._parse_expression(inTemplate=False) self.skip_ws() diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index a83b6b138..fec8c1936 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -181,7 +181,7 @@ def import_object(modname, objpath, objtype='', attrgetter=safe_getattr, warning if isinstance(real_exc, SystemExit): errmsg += ('; the module executes module level statement ' 'and it might call sys.exit().') - elif isinstance(real_exc, ImportError): + elif isinstance(real_exc, ImportError) and real_exc.args: errmsg += '; the following exception was raised:\n%s' % real_exc.args[0] else: errmsg += '; the following exception was raised:\n%s' % traceback_msg diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 138c2044c..d8362a48d 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -30,6 +30,7 @@ import functools import posixpath import sys import time +import warnings from os import path from typing import TYPE_CHECKING @@ -40,6 +41,7 @@ from six.moves.urllib.parse import urlsplit, urlunsplit import sphinx from sphinx.builders.html import INVENTORY_FILENAME +from sphinx.deprecation import RemovedInSphinx20Warning from sphinx.locale import _ from sphinx.util import requests, logging from sphinx.util.inventory import InventoryFile @@ -381,7 +383,16 @@ def setup(app): def debug(argv): # type: (List[unicode]) -> None """Debug functionality to print out an inventory""" - if len(argv) < 2: + warnings.warn('sphinx.ext.intersphinx.debug() is deprecated. ' + 'Please use inspect_main() instead', + RemovedInSphinx20Warning) + inspect_main(argv[1:]) + + +def inspect_main(argv): + # type: (List[unicode]) -> None + """Debug functionality to print out an inventory""" + if len(argv) < 1: print("Print out an inventory file.\n" "Error: must specify local path or URL to an inventory file.", file=sys.stderr) @@ -399,7 +410,7 @@ def debug(argv): # type: (unicode) -> None print(msg, file=sys.stderr) - filename = argv[1] + filename = argv[0] invdata = fetch_inventory(MockApp(), '', filename) # type: ignore for key in sorted(invdata or {}): print(key) @@ -413,4 +424,4 @@ if __name__ == '__main__': import logging # type: ignore logging.basicConfig() - debug(argv=sys.argv[1:]) # type: ignore + inspect_main(argv=sys.argv[1:]) # type: ignore diff --git a/sphinx/locale/pl/LC_MESSAGES/sphinx.po b/sphinx/locale/pl/LC_MESSAGES/sphinx.po index 293b6f5c8..6d0d655fb 100644 --- a/sphinx/locale/pl/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/pl/LC_MESSAGES/sphinx.po @@ -1298,12 +1298,12 @@ msgstr "znaleziono więcej niż jeden cel dla cross-referencji „any” %r: mo #: sphinx/transforms/post_transforms/__init__.py:169 #, python-format msgid "%s:%s reference target not found: %%(target)s" -msgstr "nie znaleziono celu referencji %s:%s: %%(cel)e" +msgstr "nie znaleziono celu referencji %s:%s: %%(target)s" #: sphinx/transforms/post_transforms/__init__.py:172 #, python-format msgid "%r reference target not found: %%(target)s" -msgstr "nie znaleziono celu referencji %r: %%(cel)e" +msgstr "nie znaleziono celu referencji %r: %%(target)s" #: sphinx/util/docutils.py:202 msgid "when adding directive classes, no additional arguments may be given" diff --git a/sphinx/templates/epub3/content.opf_t b/sphinx/templates/epub3/content.opf_t index c201e49d6..417888c7e 100644 --- a/sphinx/templates/epub3/content.opf_t +++ b/sphinx/templates/epub3/content.opf_t @@ -1,5 +1,5 @@ - {{ contributor }} {{ publisher }} {{ copyright }} + {%- if epub_version == 3.1 %} {{ id }} + {%- else %} + {{ id }} + {%- endif %} {{ date }} {{ date }} {{ version }} diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index e1f8260a3..952b972f7 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -135,10 +135,12 @@ class ReferencesResolver(SphinxTransform): if not results: return None if len(results) > 1: - nice_results = ' or '.join(':%s:`%s`' % (name, role["reftitle"]) - for name, role in results) + def stringify(name, node): + reftitle = node.get('reftitle', node.astext()) + return ':%s:`%s`' % (name, reftitle) + candidates = ' or '.join(stringify(name, role) for name, role in results) logger.warning(__('more than one target found for \'any\' cross-' - 'reference %r: could be %s'), target, nice_results, + 'reference %r: could be %s'), target, candidates, location=node) res_role, newnode = results[0] # Override "any" class with the actual role type to get the styling diff --git a/tests/roots/test-toctree-glob/index.rst b/tests/roots/test-toctree-glob/index.rst index a3c198ce3..3fda0618b 100644 --- a/tests/roots/test-toctree-glob/index.rst +++ b/tests/roots/test-toctree-glob/index.rst @@ -12,6 +12,7 @@ normal order bar/* baz qux/index + hyperref reversed order ------------- diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index b6de66989..de72148a5 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -126,6 +126,7 @@ def test_expressions(): expr = '5.0' + suffix exprCheck(expr, 'L' + expr + 'E') exprCheck('"abc\\"cba"', 'LA8_KcE') # string + exprCheck('this', 'fpT') # TODO: test the rest exprCheck('(... + Ns)', '(... + Ns)') exprCheck('(5)', 'L5E') @@ -137,7 +138,12 @@ def test_expressions(): exprCheck('a->b->c', 'ptpt1a1b1c') exprCheck('i++', 'pp1i') exprCheck('i--', 'mm1i') - # TODO, those with prefixes + exprCheck('dynamic_cast(i)++', 'ppdcR1T1i') + exprCheck('static_cast(i)++', 'ppscR1T1i') + exprCheck('reinterpret_cast(i)++', 'pprcR1T1i') + exprCheck('const_cast(i)++', 'ppccR1T1i') + exprCheck('typeid(T).name', 'dtti1T4name') + exprCheck('typeid(a + b).name', 'dttepl1a1b4name') # unary exprCheck('++5', 'pp_L5E') exprCheck('--5', 'mm_L5E') diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py index 9989d7dd4..1bebbaa3e 100644 --- a/tests/test_environment_toctree.py +++ b/tests/test_environment_toctree.py @@ -113,7 +113,7 @@ def test_glob(app): maxdepth=-1, numbered=0, includefiles=includefiles, entries=[(None, 'foo'), (None, 'bar/index'), (None, 'bar/bar_1'), (None, 'bar/bar_2'), (None, 'bar/bar_3'), (None, 'baz'), - (None, 'qux/index')]) + (None, 'qux/index'), ('hyperref', 'https://sphinx-doc.org/?q=sphinx')]) assert_node(toctree[0][1][1], [list_item, ([compact_paragraph, reference, "reversed order"], [bullet_list, addnodes.toctree])]) # [0][1][1][1][0] diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index ff08ded1c..b9bb8421b 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -22,7 +22,7 @@ from test_util_inventory import inventory_v2, inventory_v2_not_having_version from sphinx import addnodes from sphinx.ext.intersphinx import ( load_mappings, missing_reference, _strip_basic_auth, - _get_safe_url, fetch_inventory, INVENTORY_FILENAME, debug + _get_safe_url, fetch_inventory, INVENTORY_FILENAME, inspect_main ) from sphinx.ext.intersphinx import setup as intersphinx_setup @@ -393,10 +393,10 @@ def test_getsafeurl_unauthed(): assert expected == actual -def test_debug_noargs(capsys): - """debug interface, without arguments""" +def test_inspect_main_noargs(capsys): + """inspect_main interface, without arguments""" with pytest.raises(SystemExit): - debug(['sphinx/ext/intersphinx.py']) + inspect_main([]) expected = ( "Print out an inventory file.\n" @@ -407,12 +407,12 @@ def test_debug_noargs(capsys): assert stderr == expected + "\n" -def test_debug_file(capsys, tempdir): - """debug interface, with file argument""" +def test_inspect_main_file(capsys, tempdir): + """inspect_main interface, with file argument""" inv_file = tempdir / 'inventory' inv_file.write_bytes(inventory_v2) - debug(['sphinx/ext/intersphinx.py', str(inv_file)]) + inspect_main([str(inv_file)]) stdout, stderr = capsys.readouterr() assert stdout.startswith("c:function\n") @@ -420,8 +420,8 @@ def test_debug_file(capsys, tempdir): @mock.patch('requests.get') -def test_debug_url(fake_get, capsys): - """debug interface, with url argument""" +def test_inspect_main_url(fake_get, capsys): + """inspect_main interface, with url argument""" raw = BytesIO(inventory_v2) real_read = raw.read @@ -436,7 +436,7 @@ def test_debug_url(fake_get, capsys): resp.raw = raw fake_get.return_value = resp - debug(['sphinx/ext/intersphinx.py', url]) + inspect_main([url]) stdout, stderr = capsys.readouterr() assert stdout.startswith("c:function\n")