From b0f5cf3b75a9031f9ffd92054812bebf6a28613f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Dec 2019 00:11:36 +0900 Subject: [PATCH 01/20] refactor: latex: Use join() to generate sphinxpkgoptions --- sphinx/writers/latex.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 56358e7f465..cce0eb13c05 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -492,6 +492,8 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: self.compact_list = 0 self.first_param = 0 + sphinxpkgoptions = [] + # sort out some elements self.elements = self.builder.context.copy() @@ -535,13 +537,13 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: self.numfig_secnum_depth = min(self.numfig_secnum_depth, len(LATEXSECTIONNAMES) - 1) # if passed key value is < 1 LaTeX will act as if 0; see sphinx.sty - self.elements['sphinxpkgoptions'] += \ - (',numfigreset=%s' % self.numfig_secnum_depth) + sphinxpkgoptions.append('numfigreset=%s' % self.numfig_secnum_depth) else: - self.elements['sphinxpkgoptions'] += ',nonumfigreset' + sphinxpkgoptions.append('nonumfigreset') + try: if self.config.math_numfig: - self.elements['sphinxpkgoptions'] += ',mathnumfig' + sphinxpkgoptions.append('mathnumfig') except AttributeError: pass @@ -630,11 +632,9 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: contentsname) if self.elements['maxlistdepth']: - self.elements['sphinxpkgoptions'] += (',maxlistdepth=%s' % - self.elements['maxlistdepth']) - if self.elements['sphinxpkgoptions']: - self.elements['sphinxpkgoptions'] = ('[%s]' % - self.elements['sphinxpkgoptions']) + sphinxpkgoptions.append('maxlistdepth=%s' % self.elements['maxlistdepth']) + if sphinxpkgoptions: + self.elements['sphinxpkgoptions'] = '[,%s]' % ','.join(sphinxpkgoptions) if self.elements['sphinxsetup']: self.elements['sphinxsetup'] = ('\\sphinxsetup{%s}' % self.elements['sphinxsetup']) From d71d9482ed1a56757739c61d1fa7d964396bb660 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Dec 2019 00:16:12 +0900 Subject: [PATCH 02/20] refactor: latex: Simplify condition for math_numfig --- sphinx/writers/latex.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index cce0eb13c05..1240463adf3 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -541,11 +541,8 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: else: sphinxpkgoptions.append('nonumfigreset') - try: - if self.config.math_numfig: - sphinxpkgoptions.append('mathnumfig') - except AttributeError: - pass + if self.config.numfig and self.config.math_numfig: + sphinxpkgoptions.append('mathnumfig') if (self.config.language not in {None, 'en', 'ja'} and 'fncychap' not in self.config.latex_elements): From fe11e2bce4b76c4b770a9e49c5192f934efb3e81 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Dec 2019 01:20:05 +0900 Subject: [PATCH 03/20] refactor: latex: Move constants to sphinx.builders.latex.constants --- sphinx/builders/latex/__init__.py | 5 +- sphinx/builders/latex/constants.py | 192 +++++++++++++++++++++++++++++ sphinx/writers/latex.py | 183 ++------------------------- 3 files changed, 201 insertions(+), 179 deletions(-) create mode 100644 sphinx/builders/latex/constants.py diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 8111044d506..77f703ffe33 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -20,6 +20,7 @@ from sphinx import package_dir, addnodes, highlighting from sphinx.application import Sphinx from sphinx.builders import Builder +from sphinx.builders.latex.constants import ADDITIONAL_SETTINGS, DEFAULT_SETTINGS from sphinx.builders.latex.util import ExtBabel from sphinx.config import Config, ENUM from sphinx.deprecation import RemovedInSphinx40Warning @@ -34,9 +35,7 @@ from sphinx.util.nodes import inline_all_toctrees from sphinx.util.osutil import SEP, make_filename_from_project from sphinx.util.template import LaTeXRenderer -from sphinx.writers.latex import ( - ADDITIONAL_SETTINGS, DEFAULT_SETTINGS, LaTeXWriter, LaTeXTranslator -) +from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator # load docutils.nodes after loading sphinx.builders.latex.nodes from docutils import nodes # NOQA diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py new file mode 100644 index 00000000000..9cc5dda810a --- /dev/null +++ b/sphinx/builders/latex/constants.py @@ -0,0 +1,192 @@ +""" + sphinx.builders.latex.constants + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + consntants for LaTeX builder. + + :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from typing import Any, Dict + + +PDFLATEX_DEFAULT_FONTPKG = r''' +\usepackage{times} +\expandafter\ifx\csname T@LGR\endcsname\relax +\else +% LGR was declared as font encoding + \substitutefont{LGR}{\rmdefault}{cmr} + \substitutefont{LGR}{\sfdefault}{cmss} + \substitutefont{LGR}{\ttdefault}{cmtt} +\fi +\expandafter\ifx\csname T@X2\endcsname\relax + \expandafter\ifx\csname T@T2A\endcsname\relax + \else + % T2A was declared as font encoding + \substitutefont{T2A}{\rmdefault}{cmr} + \substitutefont{T2A}{\sfdefault}{cmss} + \substitutefont{T2A}{\ttdefault}{cmtt} + \fi +\else +% X2 was declared as font encoding + \substitutefont{X2}{\rmdefault}{cmr} + \substitutefont{X2}{\sfdefault}{cmss} + \substitutefont{X2}{\ttdefault}{cmtt} +\fi +''' + +XELATEX_DEFAULT_FONTPKG = r''' +\setmainfont{FreeSerif}[ + Extension = .otf, + UprightFont = *, + ItalicFont = *Italic, + BoldFont = *Bold, + BoldItalicFont = *BoldItalic +] +\setsansfont{FreeSans}[ + Extension = .otf, + UprightFont = *, + ItalicFont = *Oblique, + BoldFont = *Bold, + BoldItalicFont = *BoldOblique, +] +\setmonofont{FreeMono}[ + Extension = .otf, + UprightFont = *, + ItalicFont = *Oblique, + BoldFont = *Bold, + BoldItalicFont = *BoldOblique, +] +''' + +XELATEX_GREEK_DEFAULT_FONTPKG = (XELATEX_DEFAULT_FONTPKG + + '\n\\newfontfamily\\greekfont{FreeSerif}' + + '\n\\newfontfamily\\greekfontsf{FreeSans}' + + '\n\\newfontfamily\\greekfonttt{FreeMono}') + +LUALATEX_DEFAULT_FONTPKG = XELATEX_DEFAULT_FONTPKG + +DEFAULT_SETTINGS = { + 'latex_engine': 'pdflatex', + 'papersize': 'letterpaper', + 'pointsize': '10pt', + 'pxunit': '.75bp', + 'classoptions': '', + 'extraclassoptions': '', + 'maxlistdepth': '', + 'sphinxpkgoptions': '', + 'sphinxsetup': '', + 'fvset': '\\fvset{fontsize=\\small}', + 'passoptionstopackages': '', + 'geometry': '\\usepackage{geometry}', + 'inputenc': '', + 'utf8extra': '', + 'cmappkg': '\\usepackage{cmap}', + 'fontenc': '\\usepackage[T1]{fontenc}', + 'amsmath': '\\usepackage{amsmath,amssymb,amstext}', + 'multilingual': '', + 'babel': '\\usepackage{babel}', + 'polyglossia': '', + 'fontpkg': PDFLATEX_DEFAULT_FONTPKG, + 'substitutefont': '', + 'textcyrillic': '', + 'textgreek': '\\usepackage{textalpha}', + 'fncychap': '\\usepackage[Bjarne]{fncychap}', + 'hyperref': ('% Include hyperref last.\n' + '\\usepackage{hyperref}\n' + '% Fix anchor placement for figures with captions.\n' + '\\usepackage{hypcap}% it must be loaded after hyperref.\n' + '% Set up styles of URL: it should be placed after hyperref.\n' + '\\urlstyle{same}'), + 'contentsname': '', + 'extrapackages': '', + 'preamble': '', + 'title': '', + 'release': '', + 'author': '', + 'releasename': '', + 'makeindex': '\\makeindex', + 'shorthandoff': '', + 'maketitle': '\\sphinxmaketitle', + 'tableofcontents': '\\sphinxtableofcontents', + 'atendofbody': '', + 'printindex': '\\printindex', + 'transition': '\n\n\\bigskip\\hrule\\bigskip\n\n', + 'figure_align': 'htbp', + 'tocdepth': '', + 'secnumdepth': '', +} # type: Dict[str, Any] + +ADDITIONAL_SETTINGS = { + 'pdflatex': { + 'inputenc': '\\usepackage[utf8]{inputenc}', + 'utf8extra': ('\\ifdefined\\DeclareUnicodeCharacter\n' + '% support both utf8 and utf8x syntaxes\n' + ' \\ifdefined\\DeclareUnicodeCharacterAsOptional\n' + ' \\def\\sphinxDUC#1{\\DeclareUnicodeCharacter{"#1}}\n' + ' \\else\n' + ' \\let\\sphinxDUC\\DeclareUnicodeCharacter\n' + ' \\fi\n' + ' \\sphinxDUC{00A0}{\\nobreakspace}\n' + ' \\sphinxDUC{2500}{\\sphinxunichar{2500}}\n' + ' \\sphinxDUC{2502}{\\sphinxunichar{2502}}\n' + ' \\sphinxDUC{2514}{\\sphinxunichar{2514}}\n' + ' \\sphinxDUC{251C}{\\sphinxunichar{251C}}\n' + ' \\sphinxDUC{2572}{\\textbackslash}\n' + '\\fi'), + }, + 'xelatex': { + 'latex_engine': 'xelatex', + 'polyglossia': '\\usepackage{polyglossia}', + 'babel': '', + 'fontenc': ('\\usepackage{fontspec}\n' + '\\defaultfontfeatures[\\rmfamily,\\sffamily,\\ttfamily]{}'), + 'fontpkg': XELATEX_DEFAULT_FONTPKG, + 'textgreek': '', + 'utf8extra': ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0' + '{\\leavevmode\\nobreak\\ }'), + }, + 'lualatex': { + 'latex_engine': 'lualatex', + 'polyglossia': '\\usepackage{polyglossia}', + 'babel': '', + 'fontenc': ('\\usepackage{fontspec}\n' + '\\defaultfontfeatures[\\rmfamily,\\sffamily,\\ttfamily]{}'), + 'fontpkg': LUALATEX_DEFAULT_FONTPKG, + 'textgreek': '', + 'utf8extra': ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0' + '{\\leavevmode\\nobreak\\ }'), + }, + 'platex': { + 'latex_engine': 'platex', + 'babel': '', + 'classoptions': ',dvipdfmx', + 'fontpkg': '\\usepackage{times}', + 'textgreek': '', + 'fncychap': '', + 'geometry': '\\usepackage[dvipdfm]{geometry}', + }, + 'uplatex': { + 'latex_engine': 'uplatex', + 'babel': '', + 'classoptions': ',dvipdfmx', + 'fontpkg': '\\usepackage{times}', + 'textgreek': '', + 'fncychap': '', + 'geometry': '\\usepackage[dvipdfm]{geometry}', + }, + + # special settings for latex_engine + language_code + ('xelatex', 'fr'): { + # use babel instead of polyglossia by default + 'polyglossia': '', + 'babel': '\\usepackage{babel}', + }, + ('xelatex', 'zh'): { + 'fontenc': '\\usepackage{xeCJK}', + }, + ('xelatex', 'el'): { + 'fontpkg': XELATEX_GREEK_DEFAULT_FONTPKG, + }, +} # type: Dict[Any, Dict[str, Any]] diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 1240463adf3..3eee3eb022e 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -68,182 +68,6 @@ 'lowerroman': r'\roman', 'upperroman': r'\Roman', }) -PDFLATEX_DEFAULT_FONTPKG = r''' -\usepackage{times} -\expandafter\ifx\csname T@LGR\endcsname\relax -\else -% LGR was declared as font encoding - \substitutefont{LGR}{\rmdefault}{cmr} - \substitutefont{LGR}{\sfdefault}{cmss} - \substitutefont{LGR}{\ttdefault}{cmtt} -\fi -\expandafter\ifx\csname T@X2\endcsname\relax - \expandafter\ifx\csname T@T2A\endcsname\relax - \else - % T2A was declared as font encoding - \substitutefont{T2A}{\rmdefault}{cmr} - \substitutefont{T2A}{\sfdefault}{cmss} - \substitutefont{T2A}{\ttdefault}{cmtt} - \fi -\else -% X2 was declared as font encoding - \substitutefont{X2}{\rmdefault}{cmr} - \substitutefont{X2}{\sfdefault}{cmss} - \substitutefont{X2}{\ttdefault}{cmtt} -\fi -''' -XELATEX_DEFAULT_FONTPKG = r''' -\setmainfont{FreeSerif}[ - Extension = .otf, - UprightFont = *, - ItalicFont = *Italic, - BoldFont = *Bold, - BoldItalicFont = *BoldItalic -] -\setsansfont{FreeSans}[ - Extension = .otf, - UprightFont = *, - ItalicFont = *Oblique, - BoldFont = *Bold, - BoldItalicFont = *BoldOblique, -] -\setmonofont{FreeMono}[ - Extension = .otf, - UprightFont = *, - ItalicFont = *Oblique, - BoldFont = *Bold, - BoldItalicFont = *BoldOblique, -] -''' -XELATEX_GREEK_DEFAULT_FONTPKG = (XELATEX_DEFAULT_FONTPKG + - '\n\\newfontfamily\\greekfont{FreeSerif}' + - '\n\\newfontfamily\\greekfontsf{FreeSans}' + - '\n\\newfontfamily\\greekfonttt{FreeMono}') -LUALATEX_DEFAULT_FONTPKG = XELATEX_DEFAULT_FONTPKG - -DEFAULT_SETTINGS = { - 'latex_engine': 'pdflatex', - 'papersize': 'letterpaper', - 'pointsize': '10pt', - 'pxunit': '.75bp', - 'classoptions': '', - 'extraclassoptions': '', - 'maxlistdepth': '', - 'sphinxpkgoptions': '', - 'sphinxsetup': '', - 'fvset': '\\fvset{fontsize=\\small}', - 'passoptionstopackages': '', - 'geometry': '\\usepackage{geometry}', - 'inputenc': '', - 'utf8extra': '', - 'cmappkg': '\\usepackage{cmap}', - 'fontenc': '\\usepackage[T1]{fontenc}', - 'amsmath': '\\usepackage{amsmath,amssymb,amstext}', - 'multilingual': '', - 'babel': '\\usepackage{babel}', - 'polyglossia': '', - 'fontpkg': PDFLATEX_DEFAULT_FONTPKG, - 'substitutefont': '', - 'textcyrillic': '', - 'textgreek': '\\usepackage{textalpha}', - 'fncychap': '\\usepackage[Bjarne]{fncychap}', - 'hyperref': ('% Include hyperref last.\n' - '\\usepackage{hyperref}\n' - '% Fix anchor placement for figures with captions.\n' - '\\usepackage{hypcap}% it must be loaded after hyperref.\n' - '% Set up styles of URL: it should be placed after hyperref.\n' - '\\urlstyle{same}'), - 'contentsname': '', - 'extrapackages': '', - 'preamble': '', - 'title': '', - 'release': '', - 'author': '', - 'releasename': '', - 'makeindex': '\\makeindex', - 'shorthandoff': '', - 'maketitle': '\\sphinxmaketitle', - 'tableofcontents': '\\sphinxtableofcontents', - 'atendofbody': '', - 'printindex': '\\printindex', - 'transition': '\n\n\\bigskip\\hrule\\bigskip\n\n', - 'figure_align': 'htbp', - 'tocdepth': '', - 'secnumdepth': '', -} # type: Dict[str, Any] - -ADDITIONAL_SETTINGS = { - 'pdflatex': { - 'inputenc': '\\usepackage[utf8]{inputenc}', - 'utf8extra': ('\\ifdefined\\DeclareUnicodeCharacter\n' - '% support both utf8 and utf8x syntaxes\n' - ' \\ifdefined\\DeclareUnicodeCharacterAsOptional\n' - ' \\def\\sphinxDUC#1{\\DeclareUnicodeCharacter{"#1}}\n' - ' \\else\n' - ' \\let\\sphinxDUC\\DeclareUnicodeCharacter\n' - ' \\fi\n' - ' \\sphinxDUC{00A0}{\\nobreakspace}\n' - ' \\sphinxDUC{2500}{\\sphinxunichar{2500}}\n' - ' \\sphinxDUC{2502}{\\sphinxunichar{2502}}\n' - ' \\sphinxDUC{2514}{\\sphinxunichar{2514}}\n' - ' \\sphinxDUC{251C}{\\sphinxunichar{251C}}\n' - ' \\sphinxDUC{2572}{\\textbackslash}\n' - '\\fi'), - }, - 'xelatex': { - 'latex_engine': 'xelatex', - 'polyglossia': '\\usepackage{polyglossia}', - 'babel': '', - 'fontenc': ('\\usepackage{fontspec}\n' - '\\defaultfontfeatures[\\rmfamily,\\sffamily,\\ttfamily]{}'), - 'fontpkg': XELATEX_DEFAULT_FONTPKG, - 'textgreek': '', - 'utf8extra': ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0' - '{\\leavevmode\\nobreak\\ }'), - }, - 'lualatex': { - 'latex_engine': 'lualatex', - 'polyglossia': '\\usepackage{polyglossia}', - 'babel': '', - 'fontenc': ('\\usepackage{fontspec}\n' - '\\defaultfontfeatures[\\rmfamily,\\sffamily,\\ttfamily]{}'), - 'fontpkg': LUALATEX_DEFAULT_FONTPKG, - 'textgreek': '', - 'utf8extra': ('\\catcode`^^^^00a0\\active\\protected\\def^^^^00a0' - '{\\leavevmode\\nobreak\\ }'), - }, - 'platex': { - 'latex_engine': 'platex', - 'babel': '', - 'classoptions': ',dvipdfmx', - 'fontpkg': '\\usepackage{times}', - 'textgreek': '', - 'fncychap': '', - 'geometry': '\\usepackage[dvipdfm]{geometry}', - }, - 'uplatex': { - 'latex_engine': 'uplatex', - 'babel': '', - 'classoptions': ',dvipdfmx', - 'fontpkg': '\\usepackage{times}', - 'textgreek': '', - 'fncychap': '', - 'geometry': '\\usepackage[dvipdfm]{geometry}', - }, - - # special settings for latex_engine + language_code - ('xelatex', 'fr'): { - # use babel instead of polyglossia by default - 'polyglossia': '', - 'babel': '\\usepackage{babel}', - }, - ('xelatex', 'zh'): { - 'fontenc': '\\usepackage{xeCJK}', - }, - ('xelatex', 'el'): { - 'fontpkg': XELATEX_GREEK_DEFAULT_FONTPKG, - }, -} # type: Dict[Any, Dict[str, Any]] EXTRA_RE = re.compile(r'^(.*\S)\s+\(([^()]*)\)\s*$') @@ -2412,6 +2236,7 @@ def generate_numfig_format(self, builder: "LaTeXBuilder") -> str: # Import old modules here for compatibility +from sphinx.builders.latex import constants # NOQA from sphinx.builders.latex.transforms import URI_SCHEMES, ShowUrlsTransform # NOQA from sphinx.builders.latex.util import ExtBabel # NOQA @@ -2424,6 +2249,12 @@ def generate_numfig_format(self, builder: "LaTeXBuilder") -> str: RemovedInSphinx30Warning) deprecated_alias('sphinx.writers.latex', { + 'ADDITIONAL_SETTINGS': constants.ADDITIONAL_SETTINGS, + 'DEFAULT_SETTINGS': constants.DEFAULT_SETTINGS, + 'LUALATEX_DEFAULT_FONTPKG': constants.LUALATEX_DEFAULT_FONTPKG, + 'PDFLATEX_DEFAULT_FONTPKG': constants.PDFLATEX_DEFAULT_FONTPKG, + 'XELATEX_DEFAULT_FONTPKG': constants.XELATEX_DEFAULT_FONTPKG, + 'XELATEX_GREEK_DEFAULT_FONTPKG': constants.XELATEX_GREEK_DEFAULT_FONTPKG, 'ExtBabel': ExtBabel, }, RemovedInSphinx40Warning) From 3ddbd73f495014a72d7afdd25714801d08239de5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 26 Dec 2019 02:04:13 +0900 Subject: [PATCH 04/20] refactor: latex: Deprecate settings.* attributes based on latex_documents --- CHANGES | 5 +++ doc/extdev/deprecated.rst | 25 +++++++++++++++ sphinx/builders/latex/__init__.py | 51 ++++++++++++++++++++++++++++--- sphinx/writers/latex.py | 8 ++--- 4 files changed, 80 insertions(+), 9 deletions(-) diff --git a/CHANGES b/CHANGES index 11227774287..5f962d2d7f9 100644 --- a/CHANGES +++ b/CHANGES @@ -18,6 +18,11 @@ Deprecated * ``sphinx.pycode.ModuleAnalyzer.encoding`` * ``sphinx.util.detect_encoding()`` * ``sphinx.util.get_module_source()`` +* ``sphinx.writers.latex.LaTeXTranslator.settings.author`` +* ``sphinx.writers.latex.LaTeXTranslator.settings.contentsname`` +* ``sphinx.writers.latex.LaTeXTranslator.settings.docclass`` +* ``sphinx.writers.latex.LaTeXTranslator.settings.docname`` +* ``sphinx.writers.latex.LaTeXTranslator.settings.title`` Features added -------------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 58638f999a7..e2f8b68c615 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -66,6 +66,31 @@ The following is a list of deprecated interfaces. - 4.0 - ``tokenize.detect_encoding()`` + * - ``sphinx.writers.latex.LaTeXTranslator.settings.author`` + - 2.4 + - 4.0 + - N/A + + * - ``sphinx.writers.latex.LaTeXTranslator.settings.contentsname`` + - 2.4 + - 4.0 + - ``document['contentsname']`` + + * - ``sphinx.writers.latex.LaTeXTranslator.settings.docclass`` + - 2.4 + - 4.0 + - ``document['docclass']`` + + * - ``sphinx.writers.latex.LaTeXTranslator.settings.docname`` + - 2.4 + - 4.0 + - N/A + + * - ``sphinx.writers.latex.LaTeXTranslator.settings.title`` + - 2.4 + - 4.0 + - N/A + * - ``sphinx.builders.gettext.POHEADER`` - 2.3 - 4.0 diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 77f703ffe33..5fe3cb9af03 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -221,6 +221,7 @@ def write(self, *ignored) -> None: defaults=self.env.settings, components=(docwriter,), read_config_files=True).get_default_values() # type: Any + patch_settings(docsettings) self.init_document_data() self.write_stylesheet() @@ -243,16 +244,18 @@ def write(self, *ignored) -> None: doctree = self.assemble_doctree( docname, toctree_only, appendices=(self.config.latex_appendices if docclass != 'howto' else [])) + doctree['docclass'] = docclass + doctree['contentsname'] = self.get_contentsname(docname) doctree['tocdepth'] = tocdepth self.post_process_images(doctree) self.update_doc_context(title, author) with progress_message(__("writing")): - docsettings.author = author - docsettings.title = title - docsettings.contentsname = self.get_contentsname(docname) - docsettings.docname = docname - docsettings.docclass = docclass + docsettings._author = author + docsettings._title = title + docsettings._contentsname = doctree['contentsname'] + docsettings._docname = docname + docsettings._docclass = docclass doctree.settings = docsettings docwriter.write(doctree, destination) @@ -400,6 +403,44 @@ def write_message_catalog(self) -> None: copy_asset_file(filename, self.outdir, context=context, renderer=LaTeXRenderer()) +def patch_settings(settings: Any): + """Make settings object to show deprecation messages.""" + + class Values(type(settings)): # type: ignore + @property + def author(self): + warnings.warn('settings.author is deprecated', + RemovedInSphinx40Warning, stacklevel=2) + return self._author + + @property + def title(self): + warnings.warn('settings.title is deprecated', + RemovedInSphinx40Warning, stacklevel=2) + return self._title + + @property + def contentsname(self): + warnings.warn('settings.contentsname is deprecated', + RemovedInSphinx40Warning, stacklevel=2) + return self._contentsname + + @property + def docname(self): + warnings.warn('settings.docname is deprecated', + RemovedInSphinx40Warning, stacklevel=2) + return self._docname + + @property + def docclass(self): + warnings.warn('settings.docclass is deprecated', + RemovedInSphinx40Warning, stacklevel=2) + return self._docclass + + # dynamic subclassing + settings.__class__ = Values + + def validate_config_values(app: Sphinx, config: Config) -> None: for key in list(config.latex_elements): if key not in DEFAULT_SETTINGS: diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 3eee3eb022e..89ee63e8fef 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -322,12 +322,12 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: self.elements = self.builder.context.copy() # but some have other interface in config file - self.elements['wrapperclass'] = self.format_docclass(self.settings.docclass) + self.elements['wrapperclass'] = self.format_docclass(document.get('docclass')) # we assume LaTeX class provides \chapter command except in case # of non-Japanese 'howto' case self.sectionnames = LATEXSECTIONNAMES[:] - if self.settings.docclass == 'howto': + if document.get('docclass') == 'howto': docclass = self.config.latex_docclass.get('howto', 'article') if docclass[0] == 'j': # Japanese class... pass @@ -429,7 +429,7 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: # tocdepth = 1: show parts, chapters and sections # tocdepth = 2: show parts, chapters, sections and subsections # ... - tocdepth = self.document['tocdepth'] + self.top_sectionlevel - 2 + tocdepth = self.document.get('tocdepth', 999) + self.top_sectionlevel - 2 if len(self.sectionnames) < len(LATEXSECTIONNAMES) and \ self.top_sectionlevel > 0: tocdepth += 1 # because top_sectionlevel is shifted by -1 @@ -447,7 +447,7 @@ def __init__(self, document: nodes.document, builder: "LaTeXBuilder") -> None: self.elements['secnumdepth'] = '\\setcounter{secnumdepth}{%d}' %\ minsecnumdepth - contentsname = self.settings.contentsname + contentsname = document.get('contentsname') if contentsname: self.elements['contentsname'] = self.babel_renewcommand('\\contentsname', contentsname) From 041435024faa09112a397d578d22d002fd217e76 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 30 Jan 2020 23:08:00 +0900 Subject: [PATCH 05/20] Fix #7055: linkcheck: redirect is treated as an error --- CHANGES | 1 + sphinx/builders/linkcheck.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 1466548e4e3..eb9094ceac0 100644 --- a/CHANGES +++ b/CHANGES @@ -55,6 +55,7 @@ Bugs fixed * #7023: autodoc: nested partial functions are not listed * #7023: autodoc: partial functions imported from other modules are listed as module members without :impoprted-members: option +* #7055: linkcheck: redirect is treated as an error Testing -------- diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 479b62a0758..1b0dd401106 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -26,7 +26,7 @@ from sphinx.locale import __ from sphinx.util import encode_uri, requests, logging from sphinx.util.console import ( # type: ignore - purple, red, darkgreen, darkgray, darkred, turquoise + purple, red, darkgreen, darkgray, turquoise ) from sphinx.util.nodes import get_node_line from sphinx.util.requests import is_ssl_error @@ -251,11 +251,11 @@ def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None: elif status == 'redirected': try: text, color = { - 301: ('permanently', darkred), + 301: ('permanently', purple), 302: ('with Found', purple), 303: ('with See Other', purple), 307: ('temporarily', turquoise), - 308: ('permanently', darkred), + 308: ('permanently', purple), }[code] except KeyError: text, color = ('with unknown code', purple) From 09cf37eebec88ed3c81e78b25138a870eae85d31 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 26 Jan 2020 02:13:21 +0900 Subject: [PATCH 06/20] Fix #6899: apidoc: private members are not shown even if --private given --- CHANGES | 1 + sphinx/ext/apidoc.py | 12 ++++++++++-- tests/test_ext_apidoc.py | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 2f568045018..a654530488d 100644 --- a/CHANGES +++ b/CHANGES @@ -52,6 +52,7 @@ Bugs fixed * #6961: latex: warning for babel shown twice * #6559: Wrong node-ids are generated in glossary directive * #6986: apidoc: misdetects module name for .so file inside module +* #6899: apidoc: private members are not shown even if ``--private`` given * #6999: napoleon: fails to parse tilde in :exc: role * #7019: gettext: Absolute path used in message catalogs * #7023: autodoc: nested partial functions are not listed diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index 1d12ac6a609..0c70b4ec88b 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -20,6 +20,7 @@ import os import sys import warnings +from copy import copy from fnmatch import fnmatch from importlib.machinery import EXTENSION_SUFFIXES from os import path @@ -107,12 +108,16 @@ def format_directive(module: str, package: str = None) -> str: def create_module_file(package: str, basename: str, opts: Any, user_template_dir: str = None) -> None: """Build the text of the file and write the file.""" + options = copy(OPTIONS) + if opts.includeprivate and 'private-members' not in options: + options.append('private-members') + qualname = module_join(package, basename) context = { 'show_headings': not opts.noheadings, 'basename': basename, 'qualname': qualname, - 'automodule_options': OPTIONS, + 'automodule_options': options, } text = ReSTRenderer([user_template_dir, template_dir]).render('module.rst_t', context) write_file(qualname, text, opts) @@ -133,6 +138,9 @@ def create_package_file(root: str, master_package: str, subroot: str, py_files: sub != INITPY] submodules = [module_join(master_package, subroot, modname) for modname in submodules] + options = copy(OPTIONS) + if opts.includeprivate and 'private-members' not in options: + options.append('private-members') pkgname = module_join(master_package, subroot) context = { @@ -142,7 +150,7 @@ def create_package_file(root: str, master_package: str, subroot: str, py_files: 'is_namespace': is_namespace, 'modulefirst': opts.modulefirst, 'separatemodules': opts.separatemodules, - 'automodule_options': OPTIONS, + 'automodule_options': options, 'show_headings': not opts.noheadings, } text = ReSTRenderer([user_template_dir, template_dir]).render('package.rst_t', context) diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py index 767aa047e29..3033cb4504b 100644 --- a/tests/test_ext_apidoc.py +++ b/tests/test_ext_apidoc.py @@ -408,11 +408,13 @@ def test_private(tempdir): # without --private option apidoc_main(['-o', tempdir, tempdir]) assert (tempdir / 'hello.rst').exists() + assert ':private-members:' not in (tempdir / 'hello.rst').text() assert not (tempdir / '_world.rst').exists() # with --private option - apidoc_main(['--private', '-o', tempdir, tempdir]) + apidoc_main(['--private', '-f', '-o', tempdir, tempdir]) assert (tempdir / 'hello.rst').exists() + assert ':private-members:' in (tempdir / 'hello.rst').text() assert (tempdir / '_world.rst').exists() From 377c29db78974a41284e312dc851be40a1316e0e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 30 Jan 2020 23:45:22 +0900 Subject: [PATCH 07/20] typehints: Fix wrong order of info-field-list --- sphinx/ext/autodoc/typehints.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py index 3eb84568531..acdf6479c14 100644 --- a/sphinx/ext/autodoc/typehints.py +++ b/sphinx/ext/autodoc/typehints.py @@ -9,6 +9,7 @@ """ import re +from collections import OrderedDict from typing import Any, Dict, Iterable from typing import cast @@ -37,13 +38,14 @@ def record_typehints(app: Sphinx, objtype: str, name: str, obj: Any, """Record type hints to env object.""" try: if callable(obj): - annotations = app.env.temp_data.setdefault('annotations', {}).setdefault(name, {}) + annotations = app.env.temp_data.setdefault('annotations', {}) + annotation = annotations.setdefault(name, OrderedDict()) sig = inspect.signature(obj) for param in sig.parameters.values(): if param.annotation is not param.empty: - annotations[param.name] = typing.stringify(param.annotation) + annotation[param.name] = typing.stringify(param.annotation) if sig.return_annotation is not sig.empty: - annotations['return'] = typing.stringify(sig.return_annotation) + annotation['return'] = typing.stringify(sig.return_annotation) except TypeError: pass From e4bc1a48ac2a3aaf3ea147f0a231a3d0b60886ea Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 31 Jan 2020 00:42:26 +0900 Subject: [PATCH 08/20] Fix #6889: autodoc: Trailing comma in :members:: option causes cryptic warning --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index a5dc6da1516..4c33180a388 100644 --- a/CHANGES +++ b/CHANGES @@ -63,6 +63,7 @@ Bugs fixed * #7023: autodoc: nested partial functions are not listed * #7023: autodoc: partial functions imported from other modules are listed as module members without :impoprted-members: option +* #6889: autodoc: Trailing comma in ``:members::`` option causes cryptic warning Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index c9eb5207f92..e421696845b 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -72,14 +72,14 @@ def members_option(arg: Any) -> Union[object, List[str]]: """Used to convert the :members: option to auto directives.""" if arg is None or arg is True: return ALL - return [x.strip() for x in arg.split(',')] + return [x.strip() for x in arg.split(',') if x.strip()] def members_set_option(arg: Any) -> Union[object, Set[str]]: """Used to convert the :members: option to auto directives.""" if arg is None: return ALL - return {x.strip() for x in arg.split(',')} + return {x.strip() for x in arg.split(',') if x.strip()} SUPPRESS = object() From e7db75dbb16a15f03c74629e8b0f7c6ef3eed2f0 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 31 Jan 2020 01:19:35 +0900 Subject: [PATCH 09/20] Update copyright year --- sphinx/builders/latex/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/latex/constants.py b/sphinx/builders/latex/constants.py index 9cc5dda810a..39fbe0195e5 100644 --- a/sphinx/builders/latex/constants.py +++ b/sphinx/builders/latex/constants.py @@ -4,7 +4,7 @@ consntants for LaTeX builder. - :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ From 2dc89023ba717f15efdbe94b66bc188f8005620c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 31 Jan 2020 01:31:16 +0900 Subject: [PATCH 10/20] Fix mypy violation --- sphinx/builders/latex/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index af264de4ba3..6712ffc2552 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -403,7 +403,7 @@ def write_message_catalog(self) -> None: copy_asset_file(filename, self.outdir, context=context, renderer=LaTeXRenderer()) -def patch_settings(settings: Any): +def patch_settings(settings: Any) -> Any: """Make settings object to show deprecation messages.""" class Values(type(settings)): # type: ignore From b73cc5652aee6e67316799c901116889a3c6736e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 31 Jan 2020 01:56:05 +0900 Subject: [PATCH 11/20] Update deprecation list --- CHANGES | 6 ++++++ doc/extdev/deprecated.rst | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/CHANGES b/CHANGES index 09630825de3..663dd160beb 100644 --- a/CHANGES +++ b/CHANGES @@ -28,6 +28,12 @@ Deprecated * ``sphinx.writers.latex.LaTeXTranslator.settings.docclass`` * ``sphinx.writers.latex.LaTeXTranslator.settings.docname`` * ``sphinx.writers.latex.LaTeXTranslator.settings.title`` +* ``sphinx.writers.latex.ADDITIONAL_SETTINGS`` +* ``sphinx.writers.latex.DEFAULT_SETTINGS`` +* ``sphinx.writers.latex.LUALATEX_DEFAULT_FONTPKG`` +* ``sphinx.writers.latex.PDFLATEX_DEFAULT_FONTPKG`` +* ``sphinx.writers.latex.XELATEX_DEFAULT_FONTPKG`` +* ``sphinx.writers.latex.XELATEX_GREEK_DEFAULT_FONTPKG`` Features added -------------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index ac8ff8c464a..8a084be4de1 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -117,6 +117,36 @@ The following is a list of deprecated interfaces. - 4.0 - N/A + * - ``sphinx.writers.latex.ADDITIONAL_SETTINGS`` + - 2.4 + - 4.0 + - ``sphinx.builders.latex.constants.ADDITIONAL_SETTINGS`` + + * - ``sphinx.writers.latex.DEFAULT_SETTINGS`` + - 2.4 + - 4.0 + - ``sphinx.builders.latex.constants.DEFAULT_SETTINGS`` + + * - ``sphinx.writers.latex.LUALATEX_DEFAULT_FONTPKG`` + - 2.4 + - 4.0 + - ``sphinx.builders.latex.constants.LUALATEX_DEFAULT_FONTPKG`` + + * - ``sphinx.writers.latex.PDFLATEX_DEFAULT_FONTPKG`` + - 2.4 + - 4.0 + - ``sphinx.builders.latex.constants.PDFLATEX_DEFAULT_FONTPKG`` + + * - ``sphinx.writers.latex.XELATEX_DEFAULT_FONTPKG`` + - 2.4 + - 4.0 + - ``sphinx.builders.latex.constants.XELATEX_DEFAULT_FONTPKG`` + + * - ``sphinx.writers.latex.XELATEX_GREEK_DEFAULT_FONTPKG`` + - 2.4 + - 4.0 + - ``sphinx.builders.latex.constants.XELATEX_GREEK_DEFAULT_FONTPKG`` + * - ``sphinx.builders.gettext.POHEADER`` - 2.3 - 4.0 From aced2be1fb228f99284b5a0bca778e8769d3c7e5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Feb 2020 00:30:09 +0900 Subject: [PATCH 12/20] apidoc: Add ``-q`` option for quiet mode (refs: #6772) --- CHANGES | 1 + doc/man/sphinx-apidoc.rst | 5 +++++ sphinx/ext/apidoc.py | 15 ++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 663dd160beb..74032a38c1e 100644 --- a/CHANGES +++ b/CHANGES @@ -59,6 +59,7 @@ Features added * SphinxTranslator now calls visitor/departure method for super node class if visitor/departure method for original node class not found * #6418: Add new event: :event:`object-description-transform` +* #6772: apidoc: Add ``-q`` option for quiet mode Bugs fixed ---------- diff --git a/doc/man/sphinx-apidoc.rst b/doc/man/sphinx-apidoc.rst index 78c0735cb9b..30bfde0bbe1 100644 --- a/doc/man/sphinx-apidoc.rst +++ b/doc/man/sphinx-apidoc.rst @@ -39,6 +39,11 @@ Options Directory to place the output files. If it does not exist, it is created. +.. option:: -q + + Do not output anything on standard output, only write warnings and errors to + standard error. + .. option:: -f, --force Force overwriting of any existing generated files. diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index 0c70b4ec88b..99cf6701672 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -73,14 +73,19 @@ def module_join(*modnames: str) -> str: def write_file(name: str, text: str, opts: Any) -> None: """Write the output file for module/package .""" + quiet = getattr(opts, 'quiet', None) + fname = path.join(opts.destdir, '%s.%s' % (name, opts.suffix)) if opts.dryrun: - print(__('Would create file %s.') % fname) + if not quiet: + print(__('Would create file %s.') % fname) return if not opts.force and path.isfile(fname): - print(__('File %s already exists, skipping.') % fname) + if not quiet: + print(__('File %s already exists, skipping.') % fname) else: - print(__('Creating file %s.') % fname) + if not quiet: + print(__('Creating file %s.') % fname) with FileAvoidWrite(fname) as f: f.write(text) @@ -324,6 +329,8 @@ def get_parser() -> argparse.ArgumentParser: parser.add_argument('-o', '--output-dir', action='store', dest='destdir', required=True, help=__('directory to place all output')) + parser.add_argument('-q', action='store_true', dest='quiet', + help=__('no output on stdout, just warnings on stderr')) parser.add_argument('-d', '--maxdepth', action='store', dest='maxdepth', type=int, default=4, help=__('maximum depth of submodules to show in the TOC ' @@ -451,6 +458,8 @@ def main(argv: List[str] = sys.argv[1:]) -> int: } if args.extensions: d['extensions'].extend(args.extensions) + if args.quiet: + d['quiet'] = True for ext in d['extensions'][:]: if ',' in ext: From 768275466ab3a24c876b441e0e91291444b85786 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Feb 2020 10:47:08 +0900 Subject: [PATCH 13/20] autodoc: Fix crashed for objects having no module --- sphinx/ext/autodoc/typehints.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autodoc/typehints.py b/sphinx/ext/autodoc/typehints.py index acdf6479c14..d7b4ee96b90 100644 --- a/sphinx/ext/autodoc/typehints.py +++ b/sphinx/ext/autodoc/typehints.py @@ -57,7 +57,10 @@ def merge_typehints(app: Sphinx, domain: str, objtype: str, contentnode: Element return signature = cast(addnodes.desc_signature, contentnode.parent[0]) - fullname = '.'.join([signature['module'], signature['fullname']]) + if signature['module']: + fullname = '.'.join([signature['module'], signature['fullname']]) + else: + fullname = signature['fullname'] annotations = app.env.temp_data.get('annotations', {}) if annotations.get(fullname, {}): field_lists = [n for n in contentnode if isinstance(n, nodes.field_list)] From 179a1f9cc2832f2abc24c0e574f86f4aa6e7e632 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Thu, 30 Jan 2020 13:09:10 +0900 Subject: [PATCH 14/20] py domain: Support type annotations for variables This adds ``:type:`` and ``:value:`` options to both ``py:data`` and ``py:attribute`` directives. It allows to describe its annotation in detail. --- CHANGES | 2 ++ doc/usage/restructuredtext/domains.rst | 24 ++++++++++++++++ sphinx/domains/python.py | 38 ++++++++++++++++++++++++++ tests/test_domain_py.py | 22 +++++++++++++-- 4 files changed, 84 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 663dd160beb..e07015afdea 100644 --- a/CHANGES +++ b/CHANGES @@ -59,6 +59,8 @@ Features added * SphinxTranslator now calls visitor/departure method for super node class if visitor/departure method for original node class not found * #6418: Add new event: :event:`object-description-transform` +* py domain: :rst:dir:`py:data` and :rst:dir:`py:attribute` take new options + named ``:type:`` and ``:value:`` to describe its type and initial value Bugs fixed ---------- diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index c0ee3f230ab..870f1be6335 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -195,6 +195,18 @@ The following directives are provided for module and class contents: as "defined constants." Class and object attributes are not documented using this environment. + .. rubric:: options + + .. rst:directive:option:: type: type of the variable + :type: text + + .. versionadded:: 2.4 + + .. rst:directive:option:: value: initial value of the variable + :type: text + + .. versionadded:: 2.4 + .. rst:directive:: .. py:exception:: name Describes an exception class. The signature can, but need not include @@ -229,6 +241,18 @@ The following directives are provided for module and class contents: information about the type of the data to be expected and whether it may be changed directly. + .. rubric:: options + + .. rst:directive:option:: type: type of the attribute + :type: text + + .. versionadded:: 2.4 + + .. rst:directive:option:: value: initial value of the attribute + :type: text + + .. versionadded:: 2.4 + .. rst:directive:: .. py:method:: name(parameters) Describes an object method. The parameters should not include the ``self`` diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 777865b4bff..b5ce9bdbf41 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -446,6 +446,25 @@ def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: class PyVariable(PyObject): """Description of a variable.""" + option_spec = PyObject.option_spec.copy() + option_spec.update({ + 'type': directives.unchanged, + 'value': directives.unchanged, + }) + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + fullname, prefix = super().handle_signature(sig, signode) + + typ = self.options.get('type') + if typ: + signode += addnodes.desc_annotation(typ, ': ' + typ) + + value = self.options.get('value') + if value: + signode += addnodes.desc_annotation(value, ' = ' + value) + + return fullname, prefix + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls if modname: @@ -638,6 +657,25 @@ def run(self) -> List[Node]: class PyAttribute(PyObject): """Description of an attribute.""" + option_spec = PyObject.option_spec.copy() + option_spec.update({ + 'type': directives.unchanged, + 'value': directives.unchanged, + }) + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + fullname, prefix = super().handle_signature(sig, signode) + + typ = self.options.get('type') + if typ: + signode += addnodes.desc_annotation(typ, ': ' + typ) + + value = self.options.get('value') + if value: + signode += addnodes.desc_annotation(value, ' = ' + value) + + return fullname, prefix + def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str: name, cls = name_cls try: diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 3ff29cbb7a6..ec7b56f5729 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -267,6 +267,20 @@ def test_exceptions_module_is_ignored(app): def test_pydata_signature(app): + text = (".. py:data:: version\n" + " :type: int\n" + " :value: 1\n") + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_name, "version"], + [desc_annotation, ": int"], + [desc_annotation, " = 1"])], + desc_content)])) + assert_node(doctree[1], addnodes.desc, desctype="data", + domain="py", objtype="data", noindex=False) + + +def test_pydata_signature_old(app): text = (".. py:data:: version\n" " :annotation: = 1\n") doctree = restructuredtext.parse(app, text) @@ -463,7 +477,9 @@ def test_pystaticmethod(app): def test_pyattribute(app): text = (".. py:class:: Class\n" "\n" - " .. py:attribute:: attr\n") + " .. py:attribute:: attr\n" + " :type: str\n" + " :value: ''\n") domain = app.env.get_domain('py') doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, @@ -473,7 +489,9 @@ def test_pyattribute(app): desc)])])) assert_node(doctree[1][1][0], addnodes.index, entries=[('single', 'attr (Class attribute)', 'Class.attr', '', None)]) - assert_node(doctree[1][1][1], ([desc_signature, desc_name, "attr"], + assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "attr"], + [desc_annotation, ": str"], + [desc_annotation, " = ''"])], [desc_content, ()])) assert 'Class.attr' in domain.objects assert domain.objects['Class.attr'] == ('index', 'attribute') From b1bde4f21e4522c0f0080dc58c7c67343d3ca652 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Feb 2020 22:51:23 +0900 Subject: [PATCH 15/20] Fix #7090: std domain: Can't assign numfig-numbers for custom container nodes --- CHANGES | 1 + sphinx/domains/std.py | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index e07015afdea..d4ce18856b4 100644 --- a/CHANGES +++ b/CHANGES @@ -78,6 +78,7 @@ Bugs fixed module members without :impoprted-members: option * #6889: autodoc: Trailing comma in ``:members::`` option causes cryptic warning * #7055: linkcheck: redirect is treated as an error +* #7090: std domain: Can't assign numfig-numbers for custom container nodes Testing -------- diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index c109b763bf6..4e46c155196 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -925,11 +925,11 @@ def has_child(node: Element, cls: "Type") -> bool: if isinstance(node, nodes.section): return 'section' - elif isinstance(node, nodes.container): - if node.get('literal_block') and has_child(node, nodes.literal_block): - return 'code-block' - else: - return None + elif (isinstance(node, nodes.container) and + 'literal_block' in node and + has_child(node, nodes.literal_block)): + # given node is a code-block having caption + return 'code-block' else: figtype, _ = self.enumerable_nodes.get(node.__class__, (None, None)) return figtype From 5e4e44c19598aaeeda15422422cf5eec8136a9ea Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Feb 2020 13:38:26 +0900 Subject: [PATCH 16/20] autodoc: Support type annotations for variables --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 30 ++++++++++++++++++--- tests/test_autodoc.py | 48 ++++++++++++++++++---------------- 3 files changed, 52 insertions(+), 27 deletions(-) diff --git a/CHANGES b/CHANGES index a761731b8d8..b8b42e51cd8 100644 --- a/CHANGES +++ b/CHANGES @@ -47,6 +47,7 @@ Features added images (imagesize-1.2.0 or above is required) * #6994: imgconverter: Support illustrator file (.ai) to .png conversion * autodoc: Support Positional-Only Argument separator (PEP-570 compliant) +* autodoc: Support type annotations for variables * #2755: autodoc: Add new event: :event:`autodoc-before-process-signature` * #2755: autodoc: Support type_comment style (ex. ``# type: (str) -> str``) annotation (python3.8+ or `typed_ast `_ diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index e421696845b..682fa1cdd36 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -10,6 +10,7 @@ :license: BSD, see LICENSE for details. """ +import importlib import re import warnings from types import ModuleType @@ -33,6 +34,7 @@ from sphinx.util import rpartition from sphinx.util.docstrings import prepare_docstring from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature +from sphinx.util.typing import stringify as stringify_typehint if False: # For type annotation @@ -1232,12 +1234,19 @@ def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: + try: + annotations = getattr(self.parent, '__annotations__', {}) + if self.objpath[-1] in annotations: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) + except ValueError: + pass + try: objrepr = object_description(self.object) + self.add_line(' :value: ' + objrepr, sourcename) except ValueError: pass - else: - self.add_line(' :annotation: = ' + objrepr, sourcename) elif self.options.annotation is SUPPRESS: pass else: @@ -1276,6 +1285,12 @@ def import_object(self) -> bool: """Never import anything.""" # disguise as a data self.objtype = 'data' + try: + # import module to obtain type annotation + self.parent = importlib.import_module(self.modname) + except ImportError: + pass + return True def add_content(self, more_content: Any, no_docstring: bool = False) -> None: @@ -1404,12 +1419,19 @@ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() if not self.options.annotation: if not self._datadescriptor: + try: + annotations = getattr(self.parent, '__annotations__', {}) + if self.objpath[-1] in annotations: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) + except ValueError: + pass + try: objrepr = object_description(self.object) + self.add_line(' :value: ' + objrepr, sourcename) except ValueError: pass - else: - self.add_line(' :annotation: = ' + objrepr, sourcename) elif self.options.annotation is SUPPRESS: pass else: diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index b7c645be85f..c8ab55479fd 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -906,7 +906,7 @@ def test_autodoc_module_scope(app): '', '.. py:attribute:: Class.mdocattr', ' :module: target', - ' :annotation: = <_io.StringIO object>', + ' :value: <_io.StringIO object>', '', ' should be documented as well - süß', ' ' @@ -922,7 +922,7 @@ def test_autodoc_class_scope(app): '', '.. py:attribute:: Class.mdocattr', ' :module: target', - ' :annotation: = <_io.StringIO object>', + ' :value: <_io.StringIO object>', '', ' should be documented as well - süß', ' ' @@ -942,12 +942,12 @@ def test_class_attributes(app): ' ', ' .. py:attribute:: AttCls.a1', ' :module: target', - ' :annotation: = hello world', + ' :value: hello world', ' ', ' ', ' .. py:attribute:: AttCls.a2', ' :module: target', - ' :annotation: = None', + ' :value: None', ' ' ] @@ -966,7 +966,7 @@ def test_instance_attributes(app): ' ', ' .. py:attribute:: InstAttCls.ca1', ' :module: target', - " :annotation: = 'a'", + " :value: 'a'", ' ', ' Doc comment for class attribute InstAttCls.ca1.', ' It can have multiple lines.', @@ -974,28 +974,28 @@ def test_instance_attributes(app): ' ', ' .. py:attribute:: InstAttCls.ca2', ' :module: target', - " :annotation: = 'b'", + " :value: 'b'", ' ', ' Doc comment for InstAttCls.ca2. One line only.', ' ', ' ', ' .. py:attribute:: InstAttCls.ca3', ' :module: target', - " :annotation: = 'c'", + " :value: 'c'", ' ', ' Docstring for class attribute InstAttCls.ca3.', ' ', ' ', ' .. py:attribute:: InstAttCls.ia1', ' :module: target', - ' :annotation: = None', + ' :value: None', ' ', ' Doc comment for instance attribute InstAttCls.ia1', ' ', ' ', ' .. py:attribute:: InstAttCls.ia2', ' :module: target', - ' :annotation: = None', + ' :value: None', ' ', ' Docstring for instance attribute InstAttCls.ia2.', ' ' @@ -1014,7 +1014,7 @@ def test_instance_attributes(app): ' ', ' .. py:attribute:: InstAttCls.ca1', ' :module: target', - " :annotation: = 'a'", + " :value: 'a'", ' ', ' Doc comment for class attribute InstAttCls.ca1.', ' It can have multiple lines.', @@ -1022,7 +1022,7 @@ def test_instance_attributes(app): ' ', ' .. py:attribute:: InstAttCls.ia1', ' :module: target', - ' :annotation: = None', + ' :value: None', ' ', ' Doc comment for instance attribute InstAttCls.ia1', ' ' @@ -1090,28 +1090,28 @@ def test_enum_class(app): ' ', ' .. py:attribute:: EnumCls.val1', ' :module: target.enum', - ' :annotation: = 12', + ' :value: 12', ' ', ' doc for val1', ' ', ' ', ' .. py:attribute:: EnumCls.val2', ' :module: target.enum', - ' :annotation: = 23', + ' :value: 23', ' ', ' doc for val2', ' ', ' ', ' .. py:attribute:: EnumCls.val3', ' :module: target.enum', - ' :annotation: = 34', + ' :value: 34', ' ', ' doc for val3', ' ', ' ', ' .. py:attribute:: EnumCls.val4', ' :module: target.enum', - ' :annotation: = 34', + ' :value: 34', ' ' ] @@ -1121,7 +1121,7 @@ def test_enum_class(app): '', '.. py:attribute:: EnumCls.val1', ' :module: target.enum', - ' :annotation: = 12', + ' :value: 12', '', ' doc for val1', ' ' @@ -1405,38 +1405,40 @@ def test_autodoc_typed_instance_variables(app): ' ', ' .. py:attribute:: Class.attr1', ' :module: target.typed_vars', - ' :annotation: = 0', + ' :type: int', + ' :value: 0', ' ', ' ', ' .. py:attribute:: Class.attr2', ' :module: target.typed_vars', - ' :annotation: = None', + ' :value: None', ' ', ' ', ' .. py:attribute:: Class.attr3', ' :module: target.typed_vars', - ' :annotation: = None', + ' :value: None', ' ', ' attr3', ' ', ' ', ' .. py:attribute:: Class.attr4', ' :module: target.typed_vars', - ' :annotation: = None', + ' :value: None', ' ', ' attr4', ' ', '', '.. py:data:: attr1', ' :module: target.typed_vars', - " :annotation: = ''", + ' :type: str', + " :value: ''", '', ' attr1', ' ', '', '.. py:data:: attr2', ' :module: target.typed_vars', - " :annotation: = None", + " :value: None", '', ' attr2', ' ' @@ -1455,7 +1457,7 @@ def test_autodoc_for_egged_code(app): '', '.. py:data:: CONSTANT', ' :module: sample', - ' :annotation: = 1', + ' :value: 1', '', ' constant on sample.py', ' ', From 2ed26b437744da564e456ed2adc8d50eee3b5166 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Feb 2020 13:38:31 +0900 Subject: [PATCH 17/20] pycode: Support type annotations for variables --- sphinx/pycode/__init__.py | 8 +++++--- sphinx/pycode/parser.py | 27 +++++++++++++++++++++++++-- tests/test_pycode_parser.py | 3 +++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 12bd8d9ef8b..55d5d2c1d05 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -142,9 +142,10 @@ def __init__(self, source: IO, modname: str, srcname: str, decoded: bool = False self.code = source.read() # will be filled by parse() - self.attr_docs = None # type: Dict[Tuple[str, str], List[str]] - self.tagorder = None # type: Dict[str, int] - self.tags = None # type: Dict[str, Tuple[str, int, int]] + self.annotations = None # type: Dict[Tuple[str, str], str] + self.attr_docs = None # type: Dict[Tuple[str, str], List[str]] + self.tagorder = None # type: Dict[str, int] + self.tags = None # type: Dict[str, Tuple[str, int, int]] def parse(self) -> None: """Parse the source code.""" @@ -159,6 +160,7 @@ def parse(self) -> None: else: self.attr_docs[scope] = [''] + self.annotations = parser.annotations self.tags = parser.definitions self.tagorder = parser.deforders except Exception as exc: diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 855167ba759..4ec1a844da0 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -7,7 +7,6 @@ :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -import ast import inspect import itertools import re @@ -17,6 +16,9 @@ from tokenize import COMMENT, NL from typing import Any, Dict, List, Tuple +from sphinx.pycode.ast import ast # for py37 or older +from sphinx.pycode.ast import parse, unparse + comment_re = re.compile('^\\s*#: ?(.*)\r?\n?$') indent_re = re.compile('^\\s*$') @@ -226,6 +228,7 @@ def __init__(self, buffers: List[str], encoding: str) -> None: self.current_classes = [] # type: List[str] self.current_function = None # type: ast.FunctionDef self.comments = {} # type: Dict[Tuple[str, str], str] + self.annotations = {} # type: Dict[Tuple[str, str], str] self.previous = None # type: ast.AST self.deforders = {} # type: Dict[str, int] super().__init__() @@ -254,6 +257,18 @@ def add_variable_comment(self, name: str, comment: str) -> None: self.comments[(context, name)] = comment + def add_variable_annotation(self, name: str, annotation: ast.AST) -> None: + if self.current_function: + if self.current_classes and self.context[-1] == "__init__": + # store variable comments inside __init__ method of classes + context = ".".join(self.context[:-1]) + else: + return + else: + context = ".".join(self.context) + + self.annotations[(context, name)] = unparse(annotation) + def get_self(self) -> ast.arg: """Returns the name of first argument if in function.""" if self.current_function and self.current_function.args.args: @@ -295,6 +310,12 @@ def visit_Assign(self, node: ast.Assign) -> None: except TypeError: return # this assignment is not new definition! + # record annotation + annotation = getattr(node, 'annotation', None) + if annotation: + for varname in varnames: + self.add_variable_annotation(varname, annotation) + # check comments after assignment parser = AfterCommentParser([current_line[node.col_offset:]] + self.buffers[node.lineno:]) @@ -468,6 +489,7 @@ class Parser: def __init__(self, code: str, encoding: str = 'utf-8') -> None: self.code = filter_whitespace(code) self.encoding = encoding + self.annotations = {} # type: Dict[Tuple[str, str], str] self.comments = {} # type: Dict[Tuple[str, str], str] self.deforders = {} # type: Dict[str, int] self.definitions = {} # type: Dict[str, Tuple[str, int, int]] @@ -479,9 +501,10 @@ def parse(self) -> None: def parse_comments(self) -> None: """Parse the code and pick up comments.""" - tree = ast.parse(self.code) + tree = parse(self.code) picker = VariableCommentPicker(self.code.splitlines(True), self.encoding) picker.visit(tree) + self.annotations = picker.annotations self.comments = picker.comments self.deforders = picker.deforders diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py index b8bece84e25..6cc18bcb6a7 100644 --- a/tests/test_pycode_parser.py +++ b/tests/test_pycode_parser.py @@ -105,6 +105,9 @@ def test_annotated_assignment_py36(): assert parser.comments == {('', 'a'): 'comment', ('', 'b'): 'string on next line', ('', 'c'): 'comment'} + assert parser.annotations == {('', 'a'): 'str', + ('', 'b'): 'int', + ('', 'c'): 'int'} assert parser.definitions == {} From 20126433d6598a53eee0067bfb0ae641128b864f Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Feb 2020 13:38:32 +0900 Subject: [PATCH 18/20] autodoc: Show type annotation for instance variables --- sphinx/ext/autodoc/__init__.py | 29 +++++++++++++++-------------- tests/test_autodoc.py | 4 ++++ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 682fa1cdd36..3fcbf9a2ebf 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1234,13 +1234,11 @@ def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: - try: - annotations = getattr(self.parent, '__annotations__', {}) - if self.objpath[-1] in annotations: - objrepr = stringify_typehint(annotations.get(self.objpath[-1])) - self.add_line(' :type: ' + objrepr, sourcename) - except ValueError: - pass + # obtain annotation for this data + annotations = getattr(self.parent, '__annotations__', {}) + if self.objpath[-1] in annotations: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) try: objrepr = object_description(self.object) @@ -1419,13 +1417,16 @@ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() if not self.options.annotation: if not self._datadescriptor: - try: - annotations = getattr(self.parent, '__annotations__', {}) - if self.objpath[-1] in annotations: - objrepr = stringify_typehint(annotations.get(self.objpath[-1])) - self.add_line(' :type: ' + objrepr, sourcename) - except ValueError: - pass + # obtain annotation for this attribute + annotations = getattr(self.parent, '__annotations__', {}) + if self.objpath[-1] in annotations: + objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + self.add_line(' :type: ' + objrepr, sourcename) + else: + key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) + if self.analyzer and key in self.analyzer.annotations: + self.add_line(' :type: ' + self.analyzer.annotations[key], + sourcename) try: objrepr = object_description(self.object) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c8ab55479fd..04768b638f4 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1411,11 +1411,13 @@ def test_autodoc_typed_instance_variables(app): ' ', ' .. py:attribute:: Class.attr2', ' :module: target.typed_vars', + ' :type: int', ' :value: None', ' ', ' ', ' .. py:attribute:: Class.attr3', ' :module: target.typed_vars', + ' :type: int', ' :value: None', ' ', ' attr3', @@ -1423,6 +1425,7 @@ def test_autodoc_typed_instance_variables(app): ' ', ' .. py:attribute:: Class.attr4', ' :module: target.typed_vars', + ' :type: int', ' :value: None', ' ', ' attr4', @@ -1438,6 +1441,7 @@ def test_autodoc_typed_instance_variables(app): '', '.. py:data:: attr2', ' :module: target.typed_vars', + ' :type: str', " :value: None", '', ' attr2', From 92cb828f14afb61645ea807f456d9ba9f0f8d0e0 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 1 Feb 2020 13:38:34 +0900 Subject: [PATCH 19/20] autodoc: Support type_comment styled type annotation for variables --- sphinx/ext/autodoc/__init__.py | 5 ++++ sphinx/pycode/ast.py | 2 ++ sphinx/pycode/parser.py | 8 +++-- .../test-ext-autodoc/target/typed_vars.py | 9 ++++-- tests/test_autodoc.py | 30 ++++++++++++++++--- tests/test_pycode_parser.py | 10 +++++-- 6 files changed, 52 insertions(+), 12 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 3fcbf9a2ebf..f433ea88aab 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1239,6 +1239,11 @@ def add_directive_header(self, sig: str) -> None: if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) self.add_line(' :type: ' + objrepr, sourcename) + else: + key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) + if self.analyzer and key in self.analyzer.annotations: + self.add_line(' :type: ' + self.analyzer.annotations[key], + sourcename) try: objrepr = object_description(self.object) diff --git a/sphinx/pycode/ast.py b/sphinx/pycode/ast.py index 155ae86d53a..22207b71577 100644 --- a/sphinx/pycode/ast.py +++ b/sphinx/pycode/ast.py @@ -38,6 +38,8 @@ def unparse(node: ast.AST) -> str: """Unparse an AST to string.""" if node is None: return None + elif isinstance(node, str): + return node elif isinstance(node, ast.Attribute): return "%s.%s" % (unparse(node.value), node.attr) elif isinstance(node, ast.Bytes): diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py index 4ec1a844da0..1f803e950de 100644 --- a/sphinx/pycode/parser.py +++ b/sphinx/pycode/parser.py @@ -311,10 +311,12 @@ def visit_Assign(self, node: ast.Assign) -> None: return # this assignment is not new definition! # record annotation - annotation = getattr(node, 'annotation', None) - if annotation: + if hasattr(node, 'annotation') and node.annotation: # type: ignore for varname in varnames: - self.add_variable_annotation(varname, annotation) + self.add_variable_annotation(varname, node.annotation) # type: ignore + elif hasattr(node, 'type_comment') and node.type_comment: + for varname in varnames: + self.add_variable_annotation(varname, node.type_comment) # type: ignore # check comments after assignment parser = AfterCommentParser([current_line[node.col_offset:]] + diff --git a/tests/roots/test-ext-autodoc/target/typed_vars.py b/tests/roots/test-ext-autodoc/target/typed_vars.py index 4a9a6f7b515..b0782787e8d 100644 --- a/tests/roots/test-ext-autodoc/target/typed_vars.py +++ b/tests/roots/test-ext-autodoc/target/typed_vars.py @@ -2,12 +2,17 @@ attr1: str = '' #: attr2 attr2: str +#: attr3 +attr3 = '' # type: str class Class: attr1: int = 0 attr2: int + attr3 = 0 # type: int def __init__(self): - self.attr3: int = 0 #: attr3 - self.attr4: int #: attr4 + self.attr4: int = 0 #: attr4 + self.attr5: int #: attr5 + self.attr6 = 0 # type: int + """attr6""" diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 04768b638f4..2e8ff0414cc 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1418,10 +1418,8 @@ def test_autodoc_typed_instance_variables(app): ' .. py:attribute:: Class.attr3', ' :module: target.typed_vars', ' :type: int', - ' :value: None', + ' :value: 0', ' ', - ' attr3', - ' ', ' ', ' .. py:attribute:: Class.attr4', ' :module: target.typed_vars', @@ -1430,6 +1428,22 @@ def test_autodoc_typed_instance_variables(app): ' ', ' attr4', ' ', + ' ', + ' .. py:attribute:: Class.attr5', + ' :module: target.typed_vars', + ' :type: int', + ' :value: None', + ' ', + ' attr5', + ' ', + ' ', + ' .. py:attribute:: Class.attr6', + ' :module: target.typed_vars', + ' :type: int', + ' :value: None', + ' ', + ' attr6', + ' ', '', '.. py:data:: attr1', ' :module: target.typed_vars', @@ -1442,9 +1456,17 @@ def test_autodoc_typed_instance_variables(app): '.. py:data:: attr2', ' :module: target.typed_vars', ' :type: str', - " :value: None", + ' :value: None', '', ' attr2', + ' ', + '', + '.. py:data:: attr3', + ' :module: target.typed_vars', + ' :type: str', + " :value: ''", + '', + ' attr3', ' ' ] diff --git a/tests/test_pycode_parser.py b/tests/test_pycode_parser.py index 6cc18bcb6a7..0bf505a33ef 100644 --- a/tests/test_pycode_parser.py +++ b/tests/test_pycode_parser.py @@ -99,15 +99,19 @@ def test_annotated_assignment_py36(): source = ('a: str = "Sphinx" #: comment\n' 'b: int = 1\n' '"""string on next line"""\n' - 'c: int #: comment') + 'c: int #: comment\n' + 'd = 1 # type: int\n' + '"""string on next line"""\n') parser = Parser(source) parser.parse() assert parser.comments == {('', 'a'): 'comment', ('', 'b'): 'string on next line', - ('', 'c'): 'comment'} + ('', 'c'): 'comment', + ('', 'd'): 'string on next line'} assert parser.annotations == {('', 'a'): 'str', ('', 'b'): 'int', - ('', 'c'): 'int'} + ('', 'c'): 'int', + ('', 'd'): 'int'} assert parser.definitions == {} From 633487ffc8a6778d905bb969c7e18d48a55b34f2 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Mon, 3 Feb 2020 01:40:06 +0900 Subject: [PATCH 20/20] Fix #7059: latex: LaTeX compilation fails into infinite loop --- CHANGES | 1 + sphinx/writers/latex.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index a761731b8d8..0faf6972f90 100644 --- a/CHANGES +++ b/CHANGES @@ -69,6 +69,7 @@ Bugs fixed * #6925: html: Remove redundant type="text/javascript" from