diff --git a/sphinx/application.py b/sphinx/application.py index acef54ab6..584ad109f 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -233,9 +233,9 @@ class Sphinx(object): if self.config.needs_extensions: for extname, needs_ver in self.config.needs_extensions.items(): if extname not in self._extensions: - self.warn('needs_extensions config value specifies a ' - 'version requirement for extension %s, but it is ' - 'not loaded' % extname) + logger.warning('needs_extensions config value specifies a ' + 'version requirement for extension %s, but it is ' + 'not loaded', extname) continue has_ver = self._extension_metadata[extname]['version'] if has_ver == 'unknown version' or needs_ver > has_ver: @@ -246,7 +246,7 @@ class Sphinx(object): # check primary_domain if requested if self.config.primary_domain and self.config.primary_domain not in self.domains: - self.warn('primary_domain %r not found, ignored.' % self.config.primary_domain) + logger.warning('primary_domain %r not found, ignored.', self.config.primary_domain) # set up translation infrastructure self._init_i18n() diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 6230502d1..7448e2682 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -161,9 +161,8 @@ class Builder(object): if candidate: break else: - self.warn( - 'no matching candidate for image URI %r' % node['uri'], - '%s:%s' % (node.source, getattr(node, 'line', ''))) + logger.warn_node('no matching candidate for image URI %r' % node['uri'], + node) continue node['uri'] = candidate else: @@ -246,13 +245,13 @@ class Builder(object): for filename in filenames: filename = path.normpath(path.abspath(filename)) if not filename.startswith(self.srcdir): - self.warn('file %r given on command line is not under the ' - 'source directory, ignoring' % filename) + logger.warning('file %r given on command line is not under the ' + 'source directory, ignoring', filename) continue if not (path.isfile(filename) or any(path.isfile(filename + suffix) for suffix in suffixes)): - self.warn('file %r given on command line does not exist, ' - 'ignoring' % filename) + logger.warning('file %r given on command line does not exist, ' + 'ignoring', filename) continue filename = filename[dirlen:] for suffix in suffixes: @@ -297,7 +296,7 @@ class Builder(object): updated_docnames.add(docname) outdated = len(updated_docnames) - doccount if outdated: - logger.info('%d found' % outdated) + logger.info('%d found', outdated) else: logger.info('none found') @@ -330,8 +329,8 @@ class Builder(object): for extname, md in self.app._extension_metadata.items(): par_ok = md.get('parallel_write_safe', True) if not par_ok: - self.app.warn('the %s extension is not safe for parallel ' - 'writing, doing serial write' % extname) + logger.warning('the %s extension is not safe for parallel ' + 'writing, doing serial write', extname) self.parallel_ok = False break @@ -374,12 +373,10 @@ class Builder(object): self.prepare_writing(docnames) logger.info('done') - warnings = [] # type: List[Tuple[Tuple, Dict]] if self.parallel_ok: # number of subprocesses is parallel-1 because the main process # is busy loading doctrees and doing write_doc_serialized() - warnings = [] - self._write_parallel(sorted(docnames), warnings, + self._write_parallel(sorted(docnames), nproc=self.app.parallel - 1) else: self._write_serial(sorted(docnames)) @@ -393,21 +390,12 @@ class Builder(object): self.write_doc_serialized(docname, doctree) self.write_doc(docname, doctree) - def _write_parallel(self, docnames, warnings, nproc): - # type: (Iterable[unicode], List[Tuple[Tuple, Dict]], int) -> None + def _write_parallel(self, docnames, nproc): + # type: (Iterable[unicode], int) -> None def write_process(docs): - # type: (List[Tuple[unicode, nodes.Node]]) -> List[Tuple[Tuple, Dict]] - local_warnings = [] - - def warnfunc(*args, **kwargs): - local_warnings.append((args, kwargs)) - self.env.set_warnfunc(warnfunc) + # type: (List[Tuple[unicode, nodes.Node]]) -> None for docname, doctree in docs: self.write_doc(docname, doctree) - return local_warnings - - def add_warnings(docs, wlist): - warnings.extend(wlist) # warm up caches/compile templates using the first document firstname, docnames = docnames[0], docnames[1:] # type: ignore @@ -425,15 +413,12 @@ class Builder(object): doctree = self.env.get_and_resolve_doctree(docname, self) self.write_doc_serialized(docname, doctree) arg.append((docname, doctree)) - tasks.add_task(write_process, arg, add_warnings) + tasks.add_task(write_process, arg) # make sure all threads have finished logger.info(bold('waiting for workers...')) tasks.join() - for warning, kwargs in warnings: - self.warn(*warning, **kwargs) - def prepare_writing(self, docnames): # type: (Set[unicode]) -> None """A place where you can add logic before :meth:`write_doc` is run""" diff --git a/sphinx/builders/applehelp.py b/sphinx/builders/applehelp.py index b0c5c66b1..6337f96da 100644 --- a/sphinx/builders/applehelp.py +++ b/sphinx/builders/applehelp.py @@ -184,9 +184,8 @@ class AppleHelpBuilder(StandaloneHTMLBuilder): logger.info('done') except Exception as err: - self.warn('cannot copy icon file %r: %s' % - (path.join(self.srcdir, self.config.applehelp_icon), - err)) + logger.warning('cannot copy icon file %r: %s', + path.join(self.srcdir, self.config.applehelp_icon), err) del info_plist['HPDBookIconPath'] # Build the access page @@ -223,8 +222,8 @@ class AppleHelpBuilder(StandaloneHTMLBuilder): if self.config.applehelp_disable_external_tools: logger.info('skipping') - self.warn('you will need to index this help book with:\n %s' - % (' '.join([pipes.quote(arg) for arg in args]))) + logger.warning('you will need to index this help book with:\n %s', + ' '.join([pipes.quote(arg) for arg in args])) else: try: p = subprocess.Popen(args, @@ -256,9 +255,8 @@ class AppleHelpBuilder(StandaloneHTMLBuilder): if self.config.applehelp_disable_external_tools: logger.info('skipping') - - self.warn('you will need to sign this help book with:\n %s' - % (' '.join([pipes.quote(arg) for arg in args]))) + logger.warning('you will need to sign this help book with:\n %s', + ' '.join([pipes.quote(arg) for arg in args])) else: try: p = subprocess.Popen(args, diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index d1b908e3d..d6e118d97 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -136,7 +136,7 @@ class ChangesBuilder(Builder): try: lines = f.readlines() except UnicodeDecodeError: - self.warn('could not read %r for changelog creation' % docname) + logger.warning('could not read %r for changelog creation', docname) continue targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' ensuredir(path.dirname(targetfn)) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 97c736d9f..a48f94436 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -477,14 +477,14 @@ class EpubBuilder(StandaloneHTMLBuilder): img = Image.open(path.join(self.srcdir, src)) except IOError: if not self.is_vector_graphics(src): - self.warn('cannot read image file %r: copying it instead' % - (path.join(self.srcdir, src), )) + logger.warning('cannot read image file %r: copying it instead', + path.join(self.srcdir, src)) try: copyfile(path.join(self.srcdir, src), path.join(self.outdir, self.imagedir, dest)) except (IOError, OSError) as err: - self.warn('cannot copy image file %r: %s' % - (path.join(self.srcdir, src), err)) + logger.warning('cannot copy image file %r: %s', + path.join(self.srcdir, src), err) continue if self.config.epub_fix_images: if img.mode in ('P',): @@ -499,8 +499,8 @@ class EpubBuilder(StandaloneHTMLBuilder): try: img.save(path.join(self.outdir, self.imagedir, dest)) except (IOError, OSError) as err: - self.warn('cannot write image file %r: %s' % - (path.join(self.srcdir, src), err)) + logger.warning('cannot write image file %r: %s', + path.join(self.srcdir, src), err) def copy_image_files(self): # type: () -> None @@ -510,7 +510,7 @@ class EpubBuilder(StandaloneHTMLBuilder): if self.images: if self.config.epub_fix_images or self.config.epub_max_image_width: if not Image: - self.warn('PIL not found - copying image files') + logger.warning('PIL not found - copying image files') super(EpubBuilder, self).copy_image_files() else: self.copy_image_files_pil() @@ -551,14 +551,14 @@ class EpubBuilder(StandaloneHTMLBuilder): def build_mimetype(self, outdir, outname): # type: (unicode, unicode) -> None """Write the metainfo file mimetype.""" - logger.info('writing %s file...' % outname) + logger.info('writing %s file...', outname) with codecs.open(path.join(outdir, outname), 'w', 'utf-8') as f: # type: ignore f.write(self.mimetype_template) def build_container(self, outdir, outname): # type: (unicode, unicode) -> None """Write the metainfo file META-INF/cointainer.xml.""" - logger.info('writing %s file...' % outname) + logger.info('writing %s file...', outname) fn = path.join(outdir, outname) try: os.mkdir(path.dirname(fn)) @@ -593,7 +593,7 @@ class EpubBuilder(StandaloneHTMLBuilder): """Write the metainfo file content.opf It contains bibliographic data, a file list and the spine (the reading order). """ - logger.info('writing %s file...' % outname) + logger.info('writing %s file...', outname) # files if not outdir.endswith(os.sep): @@ -618,8 +618,8 @@ class EpubBuilder(StandaloneHTMLBuilder): # we always have JS and potentially OpenSearch files, don't # always warn about them if ext not in ('.js', '.xml'): - self.warn('unknown mimetype for %s, ignoring' % filename, - type='epub', subtype='unknown_project_files') + logger.warning('unknown mimetype for %s, ignoring', filename, + type='epub', subtype='unknown_project_files') continue filename = filename.replace(os.sep, '/') projectfiles.append(self.file_template % { @@ -804,7 +804,7 @@ class EpubBuilder(StandaloneHTMLBuilder): def build_toc(self, outdir, outname): # type: (unicode, unicode) -> None """Write the metainfo file toc.ncx.""" - logger.info('writing %s file...' % outname) + logger.info('writing %s file...', outname) if self.config.epub_tocscope == 'default': doctree = self.env.get_and_resolve_doctree(self.config.master_doc, @@ -828,7 +828,7 @@ class EpubBuilder(StandaloneHTMLBuilder): It is a zip file with the mimetype file stored uncompressed as the first entry. """ - logger.info('writing %s file...' % outname) + logger.info('writing %s file...', outname) projectfiles = ['META-INF/container.xml', 'content.opf', 'toc.ncx'] # type: List[unicode] # NOQA projectfiles.extend(self.files) epub = zipfile.ZipFile(path.join(outdir, outname), 'w', # type: ignore diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index 55434a499..ca4a44749 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -239,7 +239,7 @@ class Epub3Builder(EpubBuilder): def build_navigation_doc(self, outdir, outname): """Write the metainfo file nav.xhtml.""" - logger.info('writing %s file...' % outname) + logger.info('writing %s file...', outname) if self.config.epub_tocscope == 'default': doctree = self.env.get_and_resolve_doctree( @@ -262,16 +262,16 @@ class Epub3Builder(EpubBuilder): def validate_config_values(app): if app.config.epub3_description is not None: - app.warn('epub3_description is deprecated. Use epub_description instead.') + logger.warning('epub3_description is deprecated. Use epub_description instead.') app.config.epub_description = app.config.epub3_description if app.config.epub3_contributor is not None: - app.warn('epub3_contributor is deprecated. Use epub_contributor instead.') + logger.warning('epub3_contributor is deprecated. Use epub_contributor instead.') app.config.epub_contributor = app.config.epub3_contributor if app.config.epub3_page_progression_direction is not None: - app.warn('epub3_page_progression_direction option is deprecated' - ' from 1.5. Use epub_writing_mode instead.') + logger.warning('epub3_page_progression_direction option is deprecated' + ' from 1.5. Use epub_writing_mode instead.') def setup(app): diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index a38c9eca4..6993210f3 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -220,7 +220,7 @@ class MessageCatalogBuilder(I18nBuilder): # type: () -> None files = self._collect_templates() logger.info(bold('building [%s]: ' % self.name), nonl=1) - logger.info('targets for %d template files' % len(files)) + logger.info('targets for %d template files', len(files)) extract_translations = self.templates.environment.extract_translations diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index b4f4d7fd3..b979929b7 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -209,8 +209,8 @@ class StandaloneHTMLBuilder(Builder): if tag != 'tags': raise ValueError except ValueError: - self.warn('unsupported build info format in %r, building all' % - path.join(self.outdir, '.buildinfo')) + logger.warning('unsupported build info format in %r, building all', + path.join(self.outdir, '.buildinfo')) except Exception: pass if old_config_hash != self.config_hash or \ @@ -325,10 +325,10 @@ class StandaloneHTMLBuilder(Builder): favicon = self.config.html_favicon and \ path.basename(self.config.html_favicon) or '' if favicon and os.path.splitext(favicon)[1] != '.ico': - self.warn('html_favicon is not an .ico file') + logger.warning('html_favicon is not an .ico file') if not isinstance(self.config.html_use_opensearch, string_types): - self.warn('html_use_opensearch config value must now be a string') + logger.warning('html_use_opensearch config value must now be a string') self.relations = self.env.collect_relations() @@ -595,8 +595,8 @@ class StandaloneHTMLBuilder(Builder): copyfile(path.join(self.srcdir, src), path.join(self.outdir, self.imagedir, dest)) except Exception as err: - self.warn('cannot copy image file %r: %s' % - (path.join(self.srcdir, src), err)) + logger.warning('cannot copy image file %r: %s', + path.join(self.srcdir, src), err) def copy_download_files(self): # type: () -> None @@ -614,8 +614,8 @@ class StandaloneHTMLBuilder(Builder): copyfile(path.join(self.srcdir, src), path.join(self.outdir, '_downloads', dest)) except Exception as err: - self.warn('cannot copy downloadable file %r: %s' % - (path.join(self.srcdir, src), err)) + logger.warning('cannot copy downloadable file %r: %s', + path.join(self.srcdir, src), err) def copy_static_files(self): # type: () -> None @@ -655,7 +655,7 @@ class StandaloneHTMLBuilder(Builder): for static_path in self.config.html_static_path: entry = path.join(self.confdir, static_path) if not path.exists(entry): - self.warn('html_static_path entry %r does not exist' % entry) + logger.warning('html_static_path entry %r does not exist', entry) continue copy_asset(entry, path.join(self.outdir, '_static'), excluded, context=ctx, renderer=self.templates) @@ -664,7 +664,7 @@ class StandaloneHTMLBuilder(Builder): logobase = path.basename(self.config.html_logo) logotarget = path.join(self.outdir, '_static', logobase) if not path.isfile(path.join(self.confdir, self.config.html_logo)): - self.warn('logo file %r does not exist' % self.config.html_logo) + logger.warning('logo file %r does not exist', self.config.html_logo) elif not path.isfile(logotarget): copyfile(path.join(self.confdir, self.config.html_logo), logotarget) @@ -672,7 +672,7 @@ class StandaloneHTMLBuilder(Builder): iconbase = path.basename(self.config.html_favicon) icontarget = path.join(self.outdir, '_static', iconbase) if not path.isfile(path.join(self.confdir, self.config.html_favicon)): - self.warn('favicon file %r does not exist' % self.config.html_favicon) + logger.warning('favicon file %r does not exist', self.config.html_favicon) elif not path.isfile(icontarget): copyfile(path.join(self.confdir, self.config.html_favicon), icontarget) @@ -687,7 +687,7 @@ class StandaloneHTMLBuilder(Builder): for extra_path in self.config.html_extra_path: entry = path.join(self.confdir, extra_path) if not path.exists(entry): - self.warn('html_extra_path entry %r does not exist' % entry) + logger.warning('html_extra_path entry %r does not exist', entry) continue copy_asset(entry, self.outdir, excluded) @@ -748,9 +748,9 @@ class StandaloneHTMLBuilder(Builder): self.indexer.load(f, self.indexer_format) # type: ignore except (IOError, OSError, ValueError): if keep: - self.warn('search index couldn\'t be loaded, but not all ' - 'documents will be built: the index will be ' - 'incomplete.') + logger.warning('search index couldn\'t be loaded, but not all ' + 'documents will be built: the index will be ' + 'incomplete.') # delete all entries for files that will be rebuilt self.indexer.prune(keep) @@ -789,9 +789,9 @@ class StandaloneHTMLBuilder(Builder): if has_wildcard(pattern): # warn if both patterns contain wildcards if has_wildcard(matched): - self.warn('page %s matches two patterns in ' - 'html_sidebars: %r and %r' % - (pagename, matched, pattern)) + logger.warning('page %s matches two patterns in ' + 'html_sidebars: %r and %r', + pagename, matched, pattern) # else the already matched pattern is more specific # than the present one, because it contains no wildcard continue @@ -863,9 +863,9 @@ class StandaloneHTMLBuilder(Builder): try: output = self.templates.render(templatename, ctx) except UnicodeError: - self.warn("a Unicode error occurred when rendering the page %s. " - "Please make sure all config values that contain " - "non-ASCII content are Unicode strings." % pagename) + logger.warning("a Unicode error occurred when rendering the page %s. " + "Please make sure all config values that contain " + "non-ASCII content are Unicode strings.", pagename) return if not outfilename: @@ -876,7 +876,7 @@ class StandaloneHTMLBuilder(Builder): with codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace') as f: # type: ignore # NOQA f.write(output) except (IOError, OSError) as err: - self.warn("error writing file %s: %s" % (outfilename, err)) + logger.warning("error writing file %s: %s", outfilename, err) if self.copysource and ctx.get('sourcename'): # copy the source file for the "show source" link source_name = path.join(self.outdir, '_sources', @@ -1270,8 +1270,8 @@ class JSONHTMLBuilder(SerializingHTMLBuilder): def validate_config_values(app): # type: (Sphinx) -> None if app.config.html_translator_class: - app.warn('html_translator_class is deprecated. ' - 'Use Sphinx.set_translator() API instead.') + logger.warning('html_translator_class is deprecated. ' + 'Use Sphinx.set_translator() API instead.') def setup(app): diff --git a/sphinx/builders/latex.py b/sphinx/builders/latex.py index 15a127e7b..18cf0046f 100644 --- a/sphinx/builders/latex.py +++ b/sphinx/builders/latex.py @@ -76,16 +76,16 @@ class LaTeXBuilder(Builder): # type: () -> None preliminary_document_data = [list(x) for x in self.config.latex_documents] if not preliminary_document_data: - self.warn('no "latex_documents" config value found; no documents ' - 'will be written') + logger.warning('no "latex_documents" config value found; no documents ' + 'will be written') return # assign subdirs to titles self.titles = [] # type: List[Tuple[unicode, unicode]] for entry in preliminary_document_data: docname = entry[0] if docname not in self.env.all_docs: - self.warn('"latex_documents" config value references unknown ' - 'document %s' % docname) + logger.warning('"latex_documents" config value references unknown ' + 'document %s', docname) continue self.document_data.append(entry) # type: ignore if docname.endswith(SEP+'index'): @@ -241,50 +241,58 @@ class LaTeXBuilder(Builder): def validate_config_values(app): # type: (Sphinx) -> None if app.config.latex_toplevel_sectioning not in (None, 'part', 'chapter', 'section'): - app.warn('invalid latex_toplevel_sectioning, ignored: %s' % - app.config.latex_toplevel_sectioning) + logger.warning('invalid latex_toplevel_sectioning, ignored: %s' % + app.config.latex_toplevel_sectioning) app.config.latex_toplevel_sectioning = None # type: ignore if app.config.latex_use_parts: if app.config.latex_toplevel_sectioning: - app.warn('latex_use_parts conflicts with latex_toplevel_sectioning, ignored.') + logger.warning('latex_use_parts conflicts with ' + 'latex_toplevel_sectioning, ignored.') else: - app.warn('latex_use_parts is deprecated. Use latex_toplevel_sectioning instead.') + logger.warning('latex_use_parts is deprecated. ' + 'Use latex_toplevel_sectioning instead.') app.config.latex_toplevel_sectioning = 'part' # type: ignore if app.config.latex_use_modindex is not True: # changed by user - app.warn('latex_use_modindex is deprecated. Use latex_domain_indices instead.') + logger.warning('latex_use_modindex is deprecated. ' + 'Use latex_domain_indices instead.') if app.config.latex_preamble: if app.config.latex_elements.get('preamble'): - app.warn("latex_preamble conflicts with latex_elements['preamble'], ignored.") + logger.warning("latex_preamble conflicts with " + "latex_elements['preamble'], ignored.") else: - app.warn("latex_preamble is deprecated. Use latex_elements['preamble'] instead.") + logger.warning("latex_preamble is deprecated. " + "Use latex_elements['preamble'] instead.") app.config.latex_elements['preamble'] = app.config.latex_preamble if app.config.latex_paper_size != 'letter': if app.config.latex_elements.get('papersize'): - app.warn("latex_paper_size conflicts with latex_elements['papersize'], ignored.") + logger.warning("latex_paper_size conflicts with " + "latex_elements['papersize'], ignored.") else: - app.warn("latex_paper_size is deprecated. " - "Use latex_elements['papersize'] instead.") + logger.warning("latex_paper_size is deprecated. " + "Use latex_elements['papersize'] instead.") if app.config.latex_paper_size: app.config.latex_elements['papersize'] = app.config.latex_paper_size + 'paper' if app.config.latex_font_size != '10pt': if app.config.latex_elements.get('pointsize'): - app.warn("latex_font_size conflicts with latex_elements['pointsize'], ignored.") + logger.warning("latex_font_size conflicts with " + "latex_elements['pointsize'], ignored.") else: - app.warn("latex_font_size is deprecated. Use latex_elements['pointsize'] instead.") + logger.warning("latex_font_size is deprecated. " + "Use latex_elements['pointsize'] instead.") app.config.latex_elements['pointsize'] = app.config.latex_font_size if 'footer' in app.config.latex_elements: if 'postamble' in app.config.latex_elements: - app.warn("latex_elements['footer'] conflicts with " - "latex_elements['postamble'], ignored.") + logger.warning("latex_elements['footer'] conflicts with " + "latex_elements['postamble'], ignored.") else: - app.warn("latex_elements['footer'] is deprecated. " - "Use latex_elements['preamble'] instead.") + logger.warning("latex_elements['footer'] is deprecated. " + "Use latex_elements['preamble'] instead.") app.config.latex_elements['postamble'] = app.config.latex_elements['footer'] diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 6a3c03e35..d9e5d5696 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -233,7 +233,7 @@ class CheckExternalLinksBuilder(Builder): if status == 'working' and info == 'old': return if lineno: - logger.info('(line %4d) ' % lineno, nonl=1) + logger.info('(line %4d) ', lineno, nonl=1) if status == 'ignored': if info: logger.info(darkgray('-ignored- ') + uri + ': ' + info) @@ -247,8 +247,8 @@ class CheckExternalLinksBuilder(Builder): elif status == 'broken': self.write_entry('broken', docname, lineno, uri + ': ' + info) if self.app.quiet or self.app.warningiserror: - self.warn('broken link: %s (%s)' % (uri, info), - '%s:%s' % (self.env.doc2path(docname), lineno)) + logger.warning('broken link: %s (%s)', uri, info, + location=(self.env.doc2path(docname), lineno)) else: logger.info(red('broken ') + uri + red(' - ' + info)) elif status == 'redirected': diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index cd8684c79..20034b3f8 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -45,8 +45,8 @@ class ManualPageBuilder(Builder): def init(self): # type: () -> None if not self.config.man_pages: - self.warn('no "man_pages" config value found; no manual pages ' - 'will be written') + logger.warning('no "man_pages" config value found; no manual pages ' + 'will be written') def get_outdated_docs(self): # type: () -> Union[unicode, List[unicode]] diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index e46320456..804fd9587 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -124,16 +124,16 @@ class TexinfoBuilder(Builder): # type: () -> None preliminary_document_data = [list(x) for x in self.config.texinfo_documents] if not preliminary_document_data: - self.warn('no "texinfo_documents" config value found; no documents ' - 'will be written') + logger.warning('no "texinfo_documents" config value found; no documents ' + 'will be written') return # assign subdirs to titles self.titles = [] # type: List[Tuple[unicode, unicode]] for entry in preliminary_document_data: docname = entry[0] if docname not in self.env.all_docs: - self.warn('"texinfo_documents" config value references unknown ' - 'document %s' % docname) + logger.warning('"texinfo_documents" config value references unknown ' + 'document %s', docname) continue self.document_data.append(entry) # type: ignore if docname.endswith(SEP+'index'): @@ -240,7 +240,7 @@ class TexinfoBuilder(Builder): with open(fn, 'w') as mkfile: mkfile.write(TEXINFO_MAKEFILE) except (IOError, OSError) as err: - self.warn("error writing file %s: %s" % (fn, err)) + logger.warning("error writing file %s: %s", fn, err) logger.info(' done') diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py index d9616d6b5..7147baa5c 100644 --- a/sphinx/builders/text.py +++ b/sphinx/builders/text.py @@ -15,9 +15,12 @@ from os import path from docutils.io import StringOutput from sphinx.builders import Builder +from sphinx.util import logging from sphinx.util.osutil import ensuredir, os_path from sphinx.writers.text import TextWriter +logger = logging.getLogger(__name__) + class TextBuilder(Builder): name = 'text' @@ -65,7 +68,7 @@ class TextBuilder(Builder): with codecs.open(outfilename, 'w', 'utf-8') as f: f.write(self.writer.output) except (IOError, OSError) as err: - self.warn("error writing file %s: %s" % (outfilename, err)) + logger.warning("error writing file %s: %s", outfilename, err) def finish(self): pass diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py index 73d9e72be..fc43b4c12 100644 --- a/sphinx/builders/xml.py +++ b/sphinx/builders/xml.py @@ -16,9 +16,12 @@ from docutils import nodes from docutils.io import StringOutput from sphinx.builders import Builder +from sphinx.util import logging from sphinx.util.osutil import ensuredir, os_path from sphinx.writers.xml import XMLWriter, PseudoXMLWriter +logger = logging.getLogger(__name__) + class XMLBuilder(Builder): """ @@ -80,7 +83,7 @@ class XMLBuilder(Builder): with codecs.open(outfilename, 'w', 'utf-8') as f: f.write(self.writer.output) except (IOError, OSError) as err: - self.warn("error writing file %s: %s" % (outfilename, err)) + logger.warning("error writing file %s: %s", outfilename, err) def finish(self): pass diff --git a/sphinx/config.py b/sphinx/config.py index 7c163e1e3..27a184dce 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -219,7 +219,7 @@ class Config(object): if isinstance(value, binary_type) and nonascii_re.search(value): # type: ignore logger.warning('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')) + 'Please use Unicode strings, e.g. %r.', name, u'Content') def convert_overrides(self, name, value): # type: (unicode, Any) -> Any @@ -258,7 +258,7 @@ class Config(object): elif name in self._raw_config: self.__dict__[name] = self._raw_config[name] except ValueError as exc: - logger.warning("%s" % exc) + logger.warning("%s", exc) def init_values(self): # type: () -> None @@ -270,14 +270,14 @@ class Config(object): config.setdefault(realvalname, {})[key] = value # type: ignore continue elif valname not in self.values: - logger.warning('unknown config value %r in override, ignoring' % valname) + logger.warning('unknown config value %r in override, ignoring', valname) continue if isinstance(value, string_types): config[valname] = self.convert_overrides(valname, value) else: config[valname] = value except ValueError as exc: - logger.warning("%s" % exc) + logger.warning("%s", exc) for name in config: if name in self.values: self.__dict__[name] = config[name] diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 1ced8aba3..52d520a05 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -22,6 +22,7 @@ from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription +from sphinx.util import logging from sphinx.util.nodes import make_refnode from sphinx.util.pycompat import UnicodeMixin from sphinx.util.docfields import Field, GroupedField @@ -34,6 +35,8 @@ if False: from sphinx.config import Config # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + """ Important note on ids ---------------------------------------------------------------------------- @@ -3060,7 +3063,7 @@ class Symbol(object): msg = "Duplicate declaration, also defined in '%s'.\n" msg += "Declaration is '%s'." msg = msg % (ourChild.docname, name) - env.warn(otherChild.docname, msg) + logger.warning(msg, location=otherChild.docname) else: # Both have declarations, and in the same docname. # This can apparently happen, it should be safe to @@ -4872,7 +4875,7 @@ class CPPDomain(Domain): msg = "Duplicate declaration, also defined in '%s'.\n" msg += "Name of declaration is '%s'." msg = msg % (ourNames[name], name) - self.env.warn(docname, msg) + logger.warning(msg, docname) else: ourNames[name] = docname @@ -4882,7 +4885,7 @@ class CPPDomain(Domain): class Warner(object): def warn(self, msg): if emitWarnings: - env.warn_node(msg, node) + logger.warn_node(msg, node) warner = Warner() parser = DefinitionParser(target, warner, env.config) try: diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 562c0be3d..1da874ea2 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -21,6 +21,7 @@ from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType, Index from sphinx.directives import ObjectDescription +from sphinx.util import logging from sphinx.util.nodes import make_refnode from sphinx.util.docfields import Field, GroupedField, TypedField @@ -31,6 +32,8 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + # REs for Python signatures py_sig_re = re.compile( @@ -784,7 +787,7 @@ class PythonDomain(Domain): if not matches: return None elif len(matches) > 1: - env.warn_node( + logger.warn_node( 'more than one target found for cross-reference ' '%r: %s' % (target, ', '.join(match[0] for match in matches)), node) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 359690239..eb355ecf2 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -23,7 +23,7 @@ from sphinx.roles import XRefRole from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription -from sphinx.util import ws_re +from sphinx.util import ws_re, logging from sphinx.util.nodes import clean_astext, make_refnode if False: @@ -41,6 +41,8 @@ if False: RoleFunction = Callable[[unicode, unicode, unicode, int, Inliner, Dict, List[unicode]], Tuple[List[nodes.Node], List[nodes.Node]]] +logger = logging.getLogger(__name__) + # RE for option descriptions option_desc_re = re.compile(r'((?:/|--|-|\+)?[-\.?@#_a-zA-Z0-9]+)(=?\s*.*)') @@ -163,12 +165,10 @@ class Cmdoption(ObjectDescription): potential_option = potential_option.strip() m = option_desc_re.match(potential_option) # type: ignore if not m: - self.env.warn( - self.env.docname, - 'Malformed option description %r, should ' - 'look like "opt", "-opt args", "--opt args", ' - '"/opt args" or "+opt args"' % potential_option, - self.lineno) + logger.warning('Malformed option description %r, should ' + 'look like "opt", "-opt args", "--opt args", ' + '"/opt args" or "+opt args"', potential_option, + location=(self.env.docname, self.lineno)) continue optname, args = m.groups() if count: @@ -573,8 +573,8 @@ class StandardDomain(Domain): label = node[0].astext() if label in self.data['citations']: path = env.doc2path(self.data['citations'][label][0]) - env.warn_node('duplicate citation %s, other instance in %s' % - (label, path), node) + logger.warn_node('duplicate citation %s, other instance in %s' % + (label, path), node) self.data['citations'][label] = (docname, node['ids'][0]) def note_labels(self, env, docname, document): @@ -596,8 +596,8 @@ class StandardDomain(Domain): # link and object descriptions continue if name in labels: - env.warn_node('duplicate label %s, ' % name + 'other instance ' - 'in ' + env.doc2path(labels[name][0]), node) + logger.warn_node('duplicate label %s, ' % name + 'other instance ' + 'in ' + env.doc2path(labels[name][0]), node) anonlabels[name] = docname, labelid if node.tagname == 'section': sectname = clean_astext(node[0]) # node[0] == title node @@ -688,7 +688,7 @@ class StandardDomain(Domain): return None if env.config.numfig is False: - env.warn_node('numfig is disabled. :numref: is ignored.', node) + logger.warn_node('numfig is disabled. :numref: is ignored.', node) return contnode target_node = env.get_doctree(docname).ids.get(labelid) @@ -701,7 +701,7 @@ class StandardDomain(Domain): if fignumber is None: return contnode except ValueError: - env.warn_node("no number is assigned for %s: %s" % (figtype, labelid), node) + logger.warn_node("no number is assigned for %s: %s" % (figtype, labelid), node) return contnode try: @@ -711,7 +711,7 @@ class StandardDomain(Domain): title = env.config.numfig_format.get(figtype, '') if figname is None and '%{name}' in title: - env.warn_node('the link has no caption: %s' % title, node) + logger.warn_node('the link has no caption: %s' % title, node) return contnode else: fignum = '.'.join(map(str, fignumber)) @@ -725,10 +725,10 @@ class StandardDomain(Domain): # old style format (cf. "Fig.%s") newtitle = title % fignum except KeyError as exc: - env.warn_node('invalid numfig_format: %s (%r)' % (title, exc), node) + logger.warn_node('invalid numfig_format: %s (%r)' % (title, exc), node) return contnode except TypeError: - env.warn_node('invalid numfig_format: %s' % title, node) + logger.warn_node('invalid numfig_format: %s' % title, node) return contnode return self.build_reference_node(fromdocname, builder, diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 663442503..4b6e9bf31 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -447,7 +447,7 @@ class BuildEnvironment(object): if os.access(self.doc2path(docname), os.R_OK): self.found_docs.add(docname) else: - self.warn(docname, "document not readable. Ignored.") + logger.warning("document not readable. Ignored.", location=docname) # Current implementation is applying translated messages in the reading # phase.Therefore, in order to apply the updated message catalog, it is @@ -594,14 +594,14 @@ class BuildEnvironment(object): if ext_ok: continue if ext_ok is None: - app.warn('the %s extension does not declare if it ' - 'is safe for parallel reading, assuming it ' - 'isn\'t - please ask the extension author to ' - 'check and make it explicit' % extname) - app.warn('doing serial read') + logger.warning('the %s extension does not declare if it ' + 'is safe for parallel reading, assuming it ' + 'isn\'t - please ask the extension author to ' + 'check and make it explicit', extname) + logger.warning('doing serial read') else: - app.warn('the %s extension is not safe for parallel ' - 'reading, doing serial read' % extname) + logger.warning('the %s extension is not safe for parallel ' + 'reading, doing serial read', extname) par_ok = False break if par_ok: @@ -690,11 +690,11 @@ class BuildEnvironment(object): if lineend == -1: lineend = len(error.object) lineno = error.object.count(b'\n', 0, error.start) + 1 - self.warn(self.docname, 'undecodable source characters, ' - 'replacing with "?": %r' % - (error.object[linestart+1:error.start] + b'>>>' + - error.object[error.start:error.end] + b'<<<' + - error.object[error.end:lineend]), lineno) + logger.warning('undecodable source characters, replacing with "?": %r', + (error.object[linestart+1:error.start] + b'>>>' + + error.object[error.start:error.end] + b'<<<' + + error.object[error.end:lineend]), + location=(self.docname, lineno)) return (u'?', error.end) def read_doc(self, docname, app=None): @@ -724,8 +724,8 @@ class BuildEnvironment(object): if role_fn: roles._roles[''] = role_fn else: - self.warn(docname, 'default role %s not found' % - self.config.default_role) + logger.warning('default role %s not found', self.config.default_role, + location=docname) codecs.register_error('sphinx', self.warn_and_replace) # type: ignore @@ -816,16 +816,18 @@ class BuildEnvironment(object): def currmodule(self): # type () -> None """Backwards compatible alias. Will be removed.""" - self.warn(self.docname, 'env.currmodule is being referenced by an ' - 'extension; this API will be removed in the future') + logger.warning('env.currmodule is being referenced by an ' + 'extension; this API will be removed in the future', + location=self.docname) return self.ref_context.get('py:module') @property def currclass(self): # type: () -> None """Backwards compatible alias. Will be removed.""" - self.warn(self.docname, 'env.currclass is being referenced by an ' - 'extension; this API will be removed in the future') + logger.warning('env.currclass is being referenced by an ' + 'extension; this API will be removed in the future', + location=self.docname) return self.ref_context.get('py:class') def new_serialno(self, category=''): @@ -900,8 +902,8 @@ class BuildEnvironment(object): rel_filename, filename = self.relfn2path(targetname, docname) self.dependencies[docname].add(rel_filename) if not os.access(filename, os.R_OK): - self.warn_node('download file not readable: %s' % filename, - node) + logger.warn_node('download file not readable: %s' % filename, + node) continue uniquename = self.dlfiles.add_file(docname, filename) node['filename'] = uniquename @@ -919,8 +921,8 @@ class BuildEnvironment(object): if mimetype not in candidates: globbed.setdefault(mimetype, []).append(new_imgpath) except (OSError, IOError) as err: - self.warn_node('image file %s not readable: %s' % - (filename, err), node) + logger.warn_node('image file %s not readable: %s' % + (filename, err), node) for key, files in iteritems(globbed): candidates[key] = sorted(files, key=len)[0] # select by similarity @@ -932,13 +934,13 @@ class BuildEnvironment(object): node['candidates'] = candidates = {} imguri = node['uri'] if imguri.startswith('data:'): - self.warn_node('image data URI found. some builders might not support', node, - type='image', subtype='data_uri') + logger.warn_node('image data URI found. some builders might not support', node, + type='image', subtype='data_uri') candidates['?'] = imguri continue elif imguri.find('://') != -1: - self.warn_node('nonlocal image URI found: %s' % imguri, node, - type='image', subtype='nonlocal_uri') + logger.warn_node('nonlocal image URI found: %s' % imguri, node, + type='image', subtype='nonlocal_uri') candidates['?'] = imguri continue rel_imgpath, full_imgpath = self.relfn2path(imguri, docname) @@ -967,8 +969,8 @@ class BuildEnvironment(object): for imgpath in itervalues(candidates): self.dependencies[docname].add(imgpath) if not os.access(path.join(self.srcdir, imgpath), os.R_OK): - self.warn_node('image file not readable: %s' % imgpath, - node) + logger.warn_node('image file not readable: %s' % imgpath, + node) continue self.images.add_file(docname, imgpath) @@ -1152,7 +1154,7 @@ class BuildEnvironment(object): node.replace_self(newnode or contnode) # remove only-nodes that do not belong to our builder - process_only_nodes(doctree, builder.tags, warn_node=self.warn_node) + process_only_nodes(doctree, builder.tags) # allow custom references to be resolved builder.app.emit('doctree-resolved', doctree, fromdocname) @@ -1181,7 +1183,7 @@ class BuildEnvironment(object): (node['refdomain'], typ) else: msg = '%r reference target not found: %%(target)s' % typ - self.warn_node(msg % {'target': target}, node, type='ref', subtype=typ) + logger.warn_node(msg % {'target': target}, node, type='ref', subtype=typ) def _resolve_doc_reference(self, builder, refdoc, node, contnode): # type: (Builder, unicode, nodes.Node, nodes.Node) -> nodes.Node @@ -1232,9 +1234,9 @@ class BuildEnvironment(object): return None if len(results) > 1: nice_results = ' or '.join(':%s:' % r[0] for r in results) - self.warn_node('more than one target found for \'any\' cross-' - 'reference %r: could be %s' % (target, nice_results), - node) + logger.warn_node('more than one target found for \'any\' cross-' + 'reference %r: could be %s' % (target, nice_results), + node) res_role, newnode = results[0] # Override "any" class with the actual role type to get the styling # approximately correct. @@ -1255,7 +1257,7 @@ class BuildEnvironment(object): def traverse_toctree(parent, docname): if parent == docname: - self.warn(docname, 'self referenced toctree found. Ignored.') + logger.warning('self referenced toctree found. Ignored.', location=docname) return # traverse toctree by pre-order @@ -1295,4 +1297,5 @@ class BuildEnvironment(object): continue if 'orphan' in self.metadata[docname]: continue - self.warn(docname, 'document isn\'t included in any toctree') + logger.warning('document isn\'t included in any toctree', + location=docname) diff --git a/sphinx/environment/managers/indexentries.py b/sphinx/environment/managers/indexentries.py index 11e5ccbb0..43e3b4c83 100644 --- a/sphinx/environment/managers/indexentries.py +++ b/sphinx/environment/managers/indexentries.py @@ -16,7 +16,7 @@ from itertools import groupby from six import text_type from sphinx import addnodes -from sphinx.util import iteritems, split_index_msg, split_into +from sphinx.util import iteritems, split_index_msg, split_into, logging from sphinx.locale import _ from sphinx.environment.managers import EnvironmentManager @@ -27,6 +27,8 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + class IndexEntries(EnvironmentManager): name = 'indices' @@ -53,7 +55,7 @@ class IndexEntries(EnvironmentManager): for entry in node['entries']: split_index_msg(entry[0], entry[1]) except ValueError as exc: - self.env.warn_node(exc, node) + logger.warn_node(str(exc), node) node.parent.remove(node) else: for entry in node['entries']: @@ -119,9 +121,9 @@ class IndexEntries(EnvironmentManager): add_entry(first, _('see also %s') % second, None, link=False, key=index_key) else: - self.env.warn(fn, 'unknown index entry type %r' % type) + logger.warning('unknown index entry type %r', type, location=fn) except ValueError as err: - self.env.warn(fn, str(err)) + logger.warning(str(err), location=fn) # sort the index entries; put all symbols at the front, even those # following the letters in ASCII, this is where the chr(127) comes from diff --git a/sphinx/environment/managers/toctree.py b/sphinx/environment/managers/toctree.py index 6d92d9b12..64937b7fa 100644 --- a/sphinx/environment/managers/toctree.py +++ b/sphinx/environment/managers/toctree.py @@ -14,7 +14,7 @@ from six import iteritems from docutils import nodes from sphinx import addnodes -from sphinx.util import url_re +from sphinx.util import url_re, logging from sphinx.util.nodes import clean_astext, process_only_nodes from sphinx.transforms import SphinxContentsFilter from sphinx.environment.managers import EnvironmentManager @@ -25,6 +25,8 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + class Toctree(EnvironmentManager): name = 'toctree' @@ -169,7 +171,7 @@ class Toctree(EnvironmentManager): # the document does not exist anymore: return a dummy node that # renders to nothing return nodes.paragraph() - process_only_nodes(toc, builder.tags, warn_node=self.env.warn_node) + process_only_nodes(toc, builder.tags) for node in toc.traverse(nodes.reference): node['refuri'] = node['anchorname'] or '#' return toc @@ -296,16 +298,17 @@ class Toctree(EnvironmentManager): toc = nodes.bullet_list('', item) else: if ref in parents: - self.env.warn(ref, 'circular toctree references ' - 'detected, ignoring: %s <- %s' % - (ref, ' <- '.join(parents))) + logger.warning('circular toctree references ' + 'detected, ignoring: %s <- %s', + ref, ' <- '.join(parents), + location=ref) continue refdoc = ref toc = self.tocs[ref].deepcopy() maxdepth = self.env.metadata[ref].get('tocdepth', 0) if ref not in toctree_ancestors or (prune and maxdepth > 0): self._toctree_prune(toc, 2, maxdepth, collapse) - process_only_nodes(toc, builder.tags, warn_node=self.env.warn_node) + process_only_nodes(toc, builder.tags) if title and toc.children and len(toc.children) == 1: child = toc.children[0] for refnode in child.traverse(nodes.reference): @@ -314,13 +317,13 @@ class Toctree(EnvironmentManager): refnode.children = [nodes.Text(title)] if not toc.children: # empty toc means: no titles will show up in the toctree - self.env.warn_node( + logger.warn_node( 'toctree contains reference to document %r that ' 'doesn\'t have a title: no link will be generated' % ref, toctreenode) except KeyError: # this is raised if the included file does not exist - self.env.warn_node( + logger.warn_node( 'toctree contains reference to nonexisting document %r' % ref, toctreenode) else: diff --git a/sphinx/ext/autosectionlabel.py b/sphinx/ext/autosectionlabel.py index be769eb85..78cb3c042 100644 --- a/sphinx/ext/autosectionlabel.py +++ b/sphinx/ext/autosectionlabel.py @@ -10,8 +10,11 @@ """ from docutils import nodes +from sphinx.util import logging from sphinx.util.nodes import clean_astext +logger = logging.getLogger(__name__) + def register_sections_as_label(app, document): labels = app.env.domaindata['std']['labels'] @@ -23,8 +26,8 @@ def register_sections_as_label(app, document): sectname = clean_astext(node[0]) if name in labels: - app.env.warn_node('duplicate label %s, ' % name + 'other instance ' - 'in ' + app.env.doc2path(labels[name][0]), node) + logger.warn_node('duplicate label %s, ' % name + 'other instance ' + 'in ' + app.env.doc2path(labels[name][0]), node) anonlabels[name] = docname, labelid labels[name] = docname, labelid, sectname diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index cbe7c08f0..1a373ef46 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -608,13 +608,13 @@ def process_generate_options(app): suffix = get_rst_suffix(app) if suffix is None: - app.warn('autosummary generats .rst files internally. ' - 'But your source_suffix does not contain .rst. Skipped.') + logging.warning('autosummary generats .rst files internally. ' + 'But your source_suffix does not contain .rst. Skipped.') return generate_autosummary_docs(genfiles, builder=app.builder, - warn=app.warn, info=app.info, suffix=suffix, - base_path=app.srcdir) + warn=logger.warning, info=logger.info, + suffix=suffix, base_path=app.srcdir) def setup(app): diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 11e017f69..1698f936b 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -20,6 +20,7 @@ from six.moves import cPickle as pickle import sphinx from sphinx.builders import Builder +from sphinx.util import logging from sphinx.util.inspect import safe_getattr if False: @@ -27,6 +28,8 @@ if False: from typing import Any, Callable, IO, Pattern, Tuple # NOQA from sphinx.application import Sphinx # NOQA +logger = logging.getLogger(__name__) + # utility def write_header(f, text, char='-'): @@ -35,14 +38,14 @@ def write_header(f, text, char='-'): f.write(char * len(text) + '\n') -def compile_regex_list(name, exps, warnfunc): - # type: (unicode, unicode, Callable) -> List[Pattern] +def compile_regex_list(name, exps): + # type: (unicode, unicode) -> List[Pattern] lst = [] for exp in exps: try: lst.append(re.compile(exp)) except Exception: - warnfunc('invalid regex %r in %s' % (exp, name)) + logger.warning('invalid regex %r in %s', exp, name) return lst @@ -62,21 +65,18 @@ class CoverageBuilder(Builder): try: self.c_regexes.append((name, re.compile(exp))) except Exception: - self.warn('invalid regex %r in coverage_c_regexes' % exp) + logger.warning('invalid regex %r in coverage_c_regexes', exp) self.c_ignorexps = {} # type: Dict[unicode, List[Pattern]] for (name, exps) in iteritems(self.config.coverage_ignore_c_items): - self.c_ignorexps[name] = compile_regex_list( - 'coverage_ignore_c_items', exps, self.warn) - self.mod_ignorexps = compile_regex_list( - 'coverage_ignore_modules', self.config.coverage_ignore_modules, - self.warn) - self.cls_ignorexps = compile_regex_list( - 'coverage_ignore_classes', self.config.coverage_ignore_classes, - self.warn) - self.fun_ignorexps = compile_regex_list( - 'coverage_ignore_functions', self.config.coverage_ignore_functions, - self.warn) + self.c_ignorexps[name] = compile_regex_list('coverage_ignore_c_items', + exps) + self.mod_ignorexps = compile_regex_list('coverage_ignore_modules', + self.config.coverage_ignore_modules) + self.cls_ignorexps = compile_regex_list('coverage_ignore_classes', + self.config.coverage_ignore_classes) + self.fun_ignorexps = compile_regex_list('coverage_ignore_functions', + self.config.coverage_ignore_functions) def get_outdated_docs(self): # type: () -> unicode @@ -147,8 +147,7 @@ class CoverageBuilder(Builder): try: mod = __import__(mod_name, fromlist=['foo']) except ImportError as err: - self.warn('module %s could not be imported: %s' % - (mod_name, err)) + logger.warning('module %s could not be imported: %s', mod_name, err) self.py_undoc[mod_name] = {'error': err} continue diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index d8311c3b5..dafe2863e 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -270,7 +270,7 @@ Results of doctest builder run on %s def _warn_out(self, text): # type: (unicode) -> None if self.app.quiet or self.app.warningiserror: - self.warn(text) + logger.warning(text) else: logger.info(text, nonl=True) if isinstance(text, binary_type): @@ -347,9 +347,9 @@ Doctest summary for node in doctree.traverse(condition): source = 'test' in node and node['test'] or node.astext() if not source: - self.warn('no code/output in %s block at %s:%s' % - (node.get('testnodetype', 'doctest'), - self.env.doc2path(docname), node.line)) + logger.warning('no code/output in %s block at %s:%s', + node.get('testnodetype', 'doctest'), + self.env.doc2path(docname), node.line) code = TestCode(source, type=node.get('testnodetype', 'doctest'), lineno=node.line, options=node.get('options')) node_groups = node.get('groups', ['default']) @@ -442,9 +442,8 @@ Doctest summary doctest_encode(code[0].code, self.env.config.source_encoding), {}, # type: ignore # NOQA group.name, filename_str, code[0].lineno) except Exception: - self.warn('ignoring invalid doctest code: %r' % - code[0].code, - '%s:%s' % (filename, code[0].lineno)) + logger.warning('ignoring invalid doctest code: %r', code[0].code, + location=(filename, code[0].lineno)) continue if not test.examples: continue diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index a87d7ca58..19da59cf7 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -26,6 +26,7 @@ from docutils.statemachine import ViewList import sphinx from sphinx.errors import SphinxError from sphinx.locale import _ +from sphinx.util import logging from sphinx.util.i18n import search_image_for_language from sphinx.util.osutil import ensuredir, ENOENT, EPIPE, EINVAL @@ -34,6 +35,8 @@ if False: from typing import Any, Tuple # NOQA from sphinx.application import Sphinx # NOQA +logger = logging.getLogger(__name__) + mapname_re = re.compile(r'') diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 41f00e5a1..6ba27aedf 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -229,14 +229,14 @@ def fetch_inventory(app, uri, inv): else: f = open(path.join(app.srcdir, inv), 'rb') except Exception as err: - app.warn('intersphinx inventory %r not fetchable due to ' - '%s: %s' % (inv, err.__class__, err)) + logger.warning('intersphinx inventory %r not fetchable due to %s: %s', + inv, err.__class__, err) return try: if hasattr(f, 'url'): newinv = f.url # type: ignore if inv != newinv: - logger.info('intersphinx inventory has moved: %s -> %s' % (inv, newinv)) + logger.info('intersphinx inventory has moved: %s -> %s', inv, newinv) if uri in (inv, path.dirname(inv), path.dirname(inv) + '/'): uri = path.dirname(newinv) @@ -247,8 +247,8 @@ def fetch_inventory(app, uri, inv): except ValueError: raise ValueError('unknown or unsupported inventory version') except Exception as err: - app.warn('intersphinx inventory %r not readable due to ' - '%s: %s' % (inv, err.__class__.__name__, err)) + logger.warning('intersphinx inventory %r not readable due to %s: %s', + inv, err.__class__.__name__, err) else: return invdata @@ -274,7 +274,7 @@ def load_mappings(app): # new format name, (uri, inv) = key, value if not isinstance(name, string_types): - app.warn('intersphinx identifier %r is not string. Ignored' % name) + logger.warning('intersphinx identifier %r is not string. Ignored', name) continue else: # old format, no name @@ -295,8 +295,7 @@ def load_mappings(app): if '://' not in inv or uri not in cache \ or cache[uri][1] < cache_time: safe_inv_url = _get_safe_url(inv) # type: ignore - logger.info( - 'loading intersphinx inventory from %s...' % safe_inv_url) + logger.info('loading intersphinx inventory from %s...', safe_inv_url) invdata = fetch_inventory(app, uri, inv) if invdata: cache[uri] = (name, now, invdata) diff --git a/sphinx/ext/pngmath.py b/sphinx/ext/pngmath.py index 49c81b233..b1408129f 100644 --- a/sphinx/ext/pngmath.py +++ b/sphinx/ext/pngmath.py @@ -25,6 +25,7 @@ from docutils import nodes import sphinx from sphinx.errors import SphinxError, ExtensionError +from sphinx.util import logging from sphinx.util.png import read_png_depth, write_png_depth from sphinx.util.osutil import ensuredir, ENOENT, cd from sphinx.util.pycompat import sys_encoding @@ -36,6 +37,8 @@ if False: from sphinx.application import Sphinx # NOQA from sphinx.ext.mathbase import math as math_node, displaymath # NOQA +logger = logging.getLogger(__name__) + class MathExtError(SphinxError): category = 'Math extension error' @@ -133,9 +136,9 @@ def render_math(self, math): except OSError as err: if err.errno != ENOENT: # No such file or directory raise - self.builder.warn('LaTeX command %r cannot be run (needed for math ' - 'display), check the pngmath_latex setting' % - self.builder.config.pngmath_latex) + logger.warning('LaTeX command %r cannot be run (needed for math ' + 'display), check the pngmath_latex setting', + self.builder.config.pngmath_latex) self.builder._mathpng_warned_latex = True return None, None @@ -158,9 +161,9 @@ def render_math(self, math): except OSError as err: if err.errno != ENOENT: # No such file or directory raise - self.builder.warn('dvipng command %r cannot be run (needed for math ' - 'display), check the pngmath_dvipng setting' % - self.builder.config.pngmath_dvipng) + logger.warning('dvipng command %r cannot be run (needed for math ' + 'display), check the pngmath_dvipng setting', + self.builder.config.pngmath_dvipng) self.builder._mathpng_warned_dvipng = True return None, None stdout, stderr = p.communicate() @@ -206,7 +209,7 @@ def html_visit_math(self, node): sm = nodes.system_message(msg, type='WARNING', level=2, backrefs=[], source=node['latex']) sm.walkabout(self) - self.builder.warn('display latex %r: ' % node['latex'] + msg) + logger.warning('display latex %r: ' % node['latex'] + msg) raise nodes.SkipNode if fname is None: # something failed -- use text-only as a bad substitute @@ -234,7 +237,7 @@ def html_visit_displaymath(self, node): sm = nodes.system_message(msg, type='WARNING', level=2, backrefs=[], source=node['latex']) sm.walkabout(self) - self.builder.warn('inline latex %r: ' % node['latex'] + msg) + logger.warning('inline latex %r: ' % node['latex'] + msg) raise nodes.SkipNode self.body.append(self.starttag(node, 'div', CLASS='math')) self.body.append('

') @@ -252,7 +255,8 @@ def html_visit_displaymath(self, node): def setup(app): # type: (Sphinx) -> Dict[unicode, Any] - app.warn('sphinx.ext.pngmath has been deprecated. Please use sphinx.ext.imgmath instead.') + logger.warning('sphinx.ext.pngmath has been deprecated. ' + 'Please use sphinx.ext.imgmath instead.') try: mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None)) except ExtensionError: diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 5db878ad5..e8d8201df 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -18,6 +18,7 @@ from docutils.parsers.rst import directives import sphinx from sphinx.locale import _ from sphinx.environment import NoUri +from sphinx.util import logging from sphinx.util.nodes import set_source_info from docutils.parsers.rst import Directive from docutils.parsers.rst.directives.admonitions import BaseAdmonition @@ -28,6 +29,8 @@ if False: from sphinx.application import Sphinx # NOQA from sphinx.environment import BuildEnvironment # NOQA +logger = logging.getLogger(__name__) + class todo_node(nodes.Admonition, nodes.Element): pass @@ -97,7 +100,7 @@ def process_todos(app, doctree): }) if env.config.todo_emit_warnings: - env.warn_node("TODO entry found: %s" % node[1].astext(), node) + logger.warn_node("TODO entry found: %s" % node[1].astext(), node) class TodoList(Directive): diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index a4f93c144..95aabd6c2 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -39,7 +39,7 @@ def _get_full_modname(app, modname, attribute): except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s' % (attribute, modname)) + logger.verbose('Didn\'t find %s in %s', attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py index 8d469eabe..fad0d0038 100644 --- a/sphinx/transforms/__init__.py +++ b/sphinx/transforms/__init__.py @@ -177,12 +177,11 @@ class AutoIndexUpgrader(Transform): 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']): msg = ('4 column based index found. ' 'It might be a bug of extensions you use: %r' % node['entries']) - env.warn_node(msg, node) + logger.warn_node(msg, node) for i, entry in enumerate(node['entries']): if len(entry) == 4: node['entries'][i] = entry + (None,) diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 693ae663e..addd617d4 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -17,7 +17,7 @@ from docutils.utils import relative_path from docutils.transforms import Transform from sphinx import addnodes -from sphinx.util import split_index_msg +from sphinx.util import split_index_msg, logging from sphinx.util.i18n import find_catalog from sphinx.util.nodes import ( LITERAL_TYPE_NODES, IMAGE_TYPE_NODES, @@ -33,6 +33,8 @@ if False: from sphinx.application import Sphinx # NOQA from sphinx.config import Config # NOQA +logger = logging.getLogger(__name__) + def publish_msgstr(app, source, source_path, source_line, config, settings): # type: (Sphinx, unicode, unicode, int, Config, Dict) -> nodes.document @@ -272,8 +274,8 @@ class Locale(Transform): old_foot_refs = node.traverse(is_autonumber_footnote_ref) new_foot_refs = patch.traverse(is_autonumber_footnote_ref) if len(old_foot_refs) != len(new_foot_refs): - env.warn_node('inconsistent footnote references in ' - 'translated message', node) + logger.warn_node('inconsistent footnote references in ' + 'translated message', node) 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) @@ -307,8 +309,7 @@ class Locale(Transform): old_refs = node.traverse(is_refnamed_ref) new_refs = patch.traverse(is_refnamed_ref) if len(old_refs) != len(new_refs): - env.warn_node('inconsistent references in ' - 'translated message', node) + logger.warn_node('inconsistent references in translated message', node) old_ref_names = [r['refname'] for r in old_refs] new_ref_names = [r['refname'] for r in new_refs] orphans = list(set(old_ref_names) - set(new_ref_names)) @@ -336,8 +337,7 @@ class Locale(Transform): new_refs = patch.traverse(is_refnamed_footnote_ref) refname_ids_map = {} if len(old_refs) != len(new_refs): - env.warn_node('inconsistent references in ' - 'translated message', node) + logger.warn_node('inconsistent references in translated message', node) for old in old_refs: refname_ids_map[old["refname"]] = old["ids"] for new in new_refs: @@ -352,8 +352,7 @@ class Locale(Transform): new_refs = patch.traverse(addnodes.pending_xref) xref_reftarget_map = {} if len(old_refs) != len(new_refs): - env.warn_node('inconsistent term references in ' - 'translated message', node) + logger.warn_node('inconsistent term references in translated message', node) def get_ref_key(node): # type: (nodes.Node) -> Tuple[unicode, unicode, unicode] diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 2ce31a283..95b14be9a 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -23,7 +23,7 @@ from sphinx.util.console import colorize if False: # For type annotation - from typing import Any, Generator, IO, Tuple # NOQA + from typing import Any, Generator, IO, Tuple, Union # NOQA from docutils import nodes # NOQA from sphinx.application import Sphinx # NOQA @@ -422,12 +422,12 @@ def setup(app, status, warning): for handler in logger.handlers[:]: logger.removeHandler(handler) - info_handler = NewLineStreamHandler(SafeEncodingWriter(status)) + info_handler = NewLineStreamHandler(SafeEncodingWriter(status)) # type: ignore info_handler.addFilter(InfoFilter()) info_handler.setLevel(VERBOSITY_MAP.get(app.verbosity)) info_handler.setFormatter(ColorizeFormatter()) - warning_handler = WarningStreamHandler(SafeEncodingWriter(warning)) + warning_handler = WarningStreamHandler(SafeEncodingWriter(warning)) # type: ignore warning_handler.addFilter(WarningSuppressor(app)) warning_handler.addFilter(WarningIsErrorFilter(app)) warning_handler.addFilter(WarningLogRecordTranslator(app)) diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 8a58570bb..a5aa58444 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -18,6 +18,7 @@ from docutils import nodes from sphinx import addnodes from sphinx.locale import pairindextypes +from sphinx.util import logging if False: # For type annotation @@ -25,6 +26,8 @@ if False: from sphinx.builders import Builder # NOQA from sphinx.utils.tags import Tags # NOQA +logger = logging.getLogger(__name__) + class WarningStream(object): @@ -304,15 +307,14 @@ def inline_all_toctrees(builder, docnameset, docname, tree, colorfunc, traversed if includefile not in traversed: try: traversed.append(includefile) - builder.info(colorfunc(includefile) + " ", nonl=1) + logger.info(colorfunc(includefile) + " ", nonl=1) subtree = inline_all_toctrees(builder, docnameset, includefile, builder.env.get_doctree(includefile), colorfunc, traversed) docnameset.add(includefile) except Exception: - builder.warn('toctree contains ref to nonexisting ' - 'file %r' % includefile, - builder.env.doc2path(docname)) + logger.warning('toctree contains ref to nonexisting file %r', + includefile, location=docname) else: sof = addnodes.start_of_file(docname=includefile) sof.children = subtree.children @@ -350,8 +352,8 @@ def set_role_source_info(inliner, lineno, node): node.source, node.line = inliner.reporter.get_source_and_line(lineno) -def process_only_nodes(doctree, tags, warn_node=None): - # type: (nodes.Node, Tags, Callable) -> None +def process_only_nodes(doctree, tags): + # type: (nodes.Node, Tags) -> None # A comment on the comment() nodes being inserted: replacing by [] would # result in a "Losing ids" exception if there is a target node before # the only node, so we make sure docutils can transfer the id to @@ -360,10 +362,8 @@ def process_only_nodes(doctree, tags, warn_node=None): try: ret = tags.eval_condition(node['expr']) except Exception as err: - if warn_node is None: - raise err - warn_node('exception while evaluating only ' - 'directive expression: %s' % err, node) + logger.warn_node('exception while evaluating only ' + 'directive expression: %s' % err, node) node.replace_self(node.children or nodes.comment()) else: if ret: diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index b2d6587e5..0daaffd82 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -22,9 +22,12 @@ from docutils.writers.html4css1 import Writer, HTMLTranslator as BaseTranslator from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx16Warning from sphinx.locale import admonitionlabels, _ +from sphinx.util import logging from sphinx.util.images import get_image_size from sphinx.util.smartypants import sphinx_smarty_pants +logger = logging.getLogger(__name__) + # A good overview of the purpose behind these classes can be found here: # http://www.arnebrodowski.de/blog/write-your-own-restructuredtext-writer.html @@ -289,7 +292,7 @@ class HTMLTranslator(BaseTranslator): prefix = self.builder.config.numfig_format.get(figtype) if prefix is None: msg = 'numfig_format is not defined for %s' % figtype - self.builder.warn(msg) + logger.warning(msg) else: numbers = self.builder.fignumbers[key][figure_id] self.body.append(prefix % '.'.join(map(str, numbers)) + ' ') @@ -299,7 +302,7 @@ class HTMLTranslator(BaseTranslator): if figtype: if len(node['ids']) == 0: msg = 'Any IDs not assigned for %s node' % node.tagname - self.builder.env.warn_node(msg, node) + logger.warn_node(msg, node) else: append_fignumber(figtype, node['ids'][0]) @@ -522,8 +525,8 @@ class HTMLTranslator(BaseTranslator): if not ('width' in node and 'height' in node): size = get_image_size(os.path.join(self.builder.srcdir, olduri)) if size is None: - self.builder.env.warn_node('Could not obtain image size. ' - ':scale: option is ignored.', node) + logger.warn_node('Could not obtain image size. ' + ':scale: option is ignored.', node) else: if 'width' not in node: node['width'] = str(size[0]) @@ -755,10 +758,10 @@ class HTMLTranslator(BaseTranslator): self.body.append(self.starttag(node, 'tr', '', CLASS='field')) def visit_math(self, node, math_env=''): - self.builder.warn('using "math" markup without a Sphinx math extension ' - 'active, please use one of the math extensions ' - 'described at http://sphinx-doc.org/ext/math.html', - (self.builder.current_docname, node.line)) + logger.warning('using "math" markup without a Sphinx math extension ' + 'active, please use one of the math extensions ' + 'described at http://sphinx-doc.org/ext/math.html', + location=(self.builder.current_docname, node.line)) raise nodes.SkipNode def unknown_visit(self, node): diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index ab5ce9307..75335fd97 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -26,7 +26,7 @@ from sphinx import highlighting from sphinx.errors import SphinxError from sphinx.deprecation import RemovedInSphinx16Warning from sphinx.locale import admonitionlabels, _ -from sphinx.util import split_into +from sphinx.util import split_into, logging from sphinx.util.i18n import format_date from sphinx.util.nodes import clean_astext, traverse_parent from sphinx.util.template import LaTeXRenderer @@ -38,6 +38,7 @@ if False: from typing import Any, Callable, Iterator, Pattern, Tuple, Union # NOQA from sphinx.builder import Builder # NOQA +logger = logging.getLogger(__name__) BEGIN_DOC = r''' \begin{document} @@ -438,8 +439,8 @@ class LaTeXTranslator(nodes.NodeVisitor): if builder.config.language and not self.babel.is_supported_language(): # emit warning if specified language is invalid # (only emitting, nothing changed to processing) - self.builder.warn('no Babel option known for language %r' % - builder.config.language) + logger.warning('no Babel option known for language %r', + builder.config.language) # simply use babel.get_language() always, as get_language() returns # 'english' even if language is invalid or empty @@ -490,7 +491,7 @@ class LaTeXTranslator(nodes.NodeVisitor): tocdepth = document['tocdepth'] + self.top_sectionlevel - 2 maxdepth = len(self.sectionnames) - self.top_sectionlevel if tocdepth > maxdepth: - self.builder.warn('too large :maxdepth:, ignored.') + logger.warning('too large :maxdepth:, ignored.') tocdepth = maxdepth self.elements['tocdepth'] = '\\setcounter{tocdepth}{%d}' % tocdepth @@ -566,7 +567,7 @@ class LaTeXTranslator(nodes.NodeVisitor): for key in self.builder.config.latex_elements: if key not in self.elements: msg = _("Unknown configure key: latex_elements[%r] is ignored.") - self.builder.warn(msg % key) + logger.warning(msg % key) def restrict_footnote(self, node): # type: (nodes.Node) -> None @@ -891,8 +892,8 @@ class LaTeXTranslator(nodes.NodeVisitor): if self.this_is_the_title: if len(node.children) != 1 and not isinstance(node.children[0], nodes.Text): - self.builder.warn('document title is not a single Text node', - (self.curfilestack[-1], node.line)) + logger.warning('document title is not a single Text node', + location=(self.curfilestack[-1], node.line)) if not self.elements['title']: # text needs to be escaped since it is inserted into # the output literally @@ -930,10 +931,9 @@ class LaTeXTranslator(nodes.NodeVisitor): # Redirect body output until title is finished. self.pushbody([]) else: - self.builder.warn( - 'encountered title node not in section, topic, table, ' - 'admonition or sidebar', - (self.curfilestack[-1], node.line or '')) + logger.warning('encountered title node not in section, topic, table, ' + 'admonition or sidebar', + location=(self.curfilestack[-1], node.line or '')) self.body.append('\\sphinxstyleothertitle{') self.context.append('}\n') self.in_title = 1 @@ -1573,7 +1573,7 @@ class LaTeXTranslator(nodes.NodeVisitor): try: return rstdim_to_latexdim(width_str) except ValueError: - self.builder.warn('dimension unit %s is invalid. Ignored.' % width_str) + logger.warning('dimension unit %s is invalid. Ignored.', width_str) def is_inline(self, node): # type: (nodes.Node) -> bool @@ -1886,10 +1886,9 @@ class LaTeXTranslator(nodes.NodeVisitor): p1, p2 = [self.encode(x) for x in split_into(2, 'seealso', string)] self.body.append(r'\index{%s|see{%s}}' % (p1, p2)) else: - self.builder.warn( - 'unknown index entry type %s found' % type) + logger.warning('unknown index entry type %s found', type) except ValueError as err: - self.builder.warn(str(err)) + logger.warning(str(err)) raise nodes.SkipNode def visit_raw(self, node): @@ -1953,8 +1952,8 @@ class LaTeXTranslator(nodes.NodeVisitor): else: self.context.append('}}}') else: - self.builder.warn('unusable reference target found: %s' % uri, - (self.curfilestack[-1], node.line)) + logger.warning('unusable reference target found: %s', uri, + location=(self.curfilestack[-1], node.line)) self.context.append('') def depart_reference(self, node): @@ -2459,10 +2458,10 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_math(self, node): # type: (nodes.Node) -> None - self.builder.warn('using "math" markup without a Sphinx math extension ' - 'active, please use one of the math extensions ' - 'described at http://sphinx-doc.org/ext/math.html', - (self.curfilestack[-1], node.line)) + logger.warning('using "math" markup without a Sphinx math extension ' + 'active, please use one of the math extensions ' + 'described at http://sphinx-doc.org/ext/math.html', + location=(self.curfilestack[-1], node.line)) raise nodes.SkipNode visit_math_block = visit_math diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 249256576..715d1b395 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -21,9 +21,12 @@ from docutils.writers.manpage import ( from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx16Warning from sphinx.locale import admonitionlabels, _ +from sphinx.util import logging import sphinx.util.docutils from sphinx.util.i18n import format_date +logger = logging.getLogger(__name__) + class ManualPageWriter(Writer): def __init__(self, builder): @@ -437,9 +440,9 @@ class ManualPageTranslator(BaseTranslator): pass def visit_math(self, node): - self.builder.warn('using "math" markup without a Sphinx math extension ' - 'active, please use one of the math extensions ' - 'described at http://sphinx-doc.org/ext/math.html') + logger.warning('using "math" markup without a Sphinx math extension ' + 'active, please use one of the math extensions ' + 'described at http://sphinx-doc.org/ext/math.html') raise nodes.SkipNode visit_math_block = visit_math diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index edaa57d0f..e9543b1b2 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -23,6 +23,7 @@ from sphinx import addnodes, __display_version__ from sphinx.errors import ExtensionError from sphinx.deprecation import RemovedInSphinx16Warning from sphinx.locale import admonitionlabels, _ +from sphinx.util import logging from sphinx.util.i18n import format_date from sphinx.writers.latex import collected_footnote @@ -31,6 +32,8 @@ if False: from typing import Any, Callable, Iterator, Pattern, Tuple, Union # NOQA from sphinx.builders.texinfo import TexinfoBuilder # NOQA +logger = logging.getLogger(__name__) + COPYING = """\ @quotation @@ -651,9 +654,9 @@ class TexinfoTranslator(nodes.NodeVisitor): if isinstance(parent, (nodes.Admonition, nodes.sidebar, nodes.topic)): raise nodes.SkipNode elif not isinstance(parent, nodes.section): - self.builder.warn( - 'encountered title node not in section, topic, table, ' - 'admonition or sidebar', (self.curfilestack[-1], node.line)) + logger.warning('encountered title node not in section, topic, table, ' + 'admonition or sidebar', + location=(self.curfilestack[-1], node.line)) self.visit_rubric(node) else: try: @@ -1331,8 +1334,8 @@ class TexinfoTranslator(nodes.NodeVisitor): node.parent.get('literal_block'))): self.body.append('\n@caption{') else: - self.builder.warn('caption not inside a figure.', - (self.curfilestack[-1], node.line)) + logger.warning('caption not inside a figure.', + location=(self.curfilestack[-1], node.line)) def depart_caption(self, node): # type: (nodes.Node) -> None @@ -1434,13 +1437,13 @@ class TexinfoTranslator(nodes.NodeVisitor): def unimplemented_visit(self, node): # type: (nodes.Node) -> None - self.builder.warn("unimplemented node type: %r" % node, - (self.curfilestack[-1], node.line)) + logger.warning("unimplemented node type: %r", node, + location=(self.curfilestack[-1], node.line)) def unknown_visit(self, node): # type: (nodes.Node) -> None - self.builder.warn("unknown node type: %r" % node, - (self.curfilestack[-1], node.line)) + logger.warning("unknown node type: %r", node, + location=(self.curfilestack[-1], node.line)) def unknown_departure(self, node): # type: (nodes.Node) -> None @@ -1756,9 +1759,9 @@ class TexinfoTranslator(nodes.NodeVisitor): def visit_math(self, node): # type: (nodes.Node) -> None - self.builder.warn('using "math" markup without a Sphinx math extension ' - 'active, please use one of the math extensions ' - 'described at http://sphinx-doc.org/ext/math.html') + logger.warning('using "math" markup without a Sphinx math extension ' + 'active, please use one of the math extensions ' + 'described at http://sphinx-doc.org/ext/math.html') raise nodes.SkipNode visit_math_block = visit_math diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 21d41bea3..7ec69d3ae 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -22,12 +22,15 @@ from docutils.utils import column_width from sphinx import addnodes from sphinx.deprecation import RemovedInSphinx16Warning from sphinx.locale import admonitionlabels, _ +from sphinx.util import logging if False: # For type annotation from typing import Any, Callable, Tuple, Union # NOQA from sphinx.builders.text import TextBuilder # NOQA +logger = logging.getLogger(__name__) + class TextWrapper(textwrap.TextWrapper): """Custom subclass that uses a different word separator regex.""" @@ -1174,10 +1177,10 @@ class TextTranslator(nodes.NodeVisitor): def visit_math(self, node): # type: (nodes.Node) -> None - self.builder.warn('using "math" markup without a Sphinx math extension ' - 'active, please use one of the math extensions ' - 'described at http://sphinx-doc.org/ext/math.html', - (self.builder.current_docname, node.line)) + logger.warning('using "math" markup without a Sphinx math extension ' + 'active, please use one of the math extensions ' + 'described at http://sphinx-doc.org/ext/math.html', + location=(self.builder.current_docname, node.line)) raise nodes.SkipNode visit_math_block = visit_math diff --git a/tests/test_application.py b/tests/test_application.py index 1f4a30d97..2b765f9c6 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -47,26 +47,6 @@ def test_emit_with_nonascii_name_node(app, status, warning): app.emit('my_event', node) -@with_app() -def test_output(app, status, warning): - # info with newline - status.truncate(0) # __init__ writes to status - status.seek(0) - app.info("Nothing here...") - assert status.getvalue() == "Nothing here...\n" - # info without newline - status.truncate(0) - status.seek(0) - app.info("Nothing here...", True) - assert status.getvalue() == "Nothing here..." - - # warning - old_count = app._warncount - app.warn("Bad news!") - assert strip_escseq(warning.getvalue()) == "WARNING: Bad news!\n" - assert app._warncount == old_count + 1 - - @with_app() def test_extensions(app, status, warning): app.setup_extension('shutil') diff --git a/tests/test_environment.py b/tests/test_environment.py index f65a6f3f0..11de4a93b 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -9,35 +9,26 @@ :license: BSD, see LICENSE for details. """ -from six import PY3 +from six import StringIO -from util import TestApp, remove_unicode_literals, path +from util import TestApp, path from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder app = env = None -warnings = [] def setup_module(): global app, env - app = TestApp(srcdir='root-envtest') + app = TestApp(srcdir='root-envtest', warning=StringIO()) env = app.env - env.set_warnfunc(lambda *args, **kwargs: warnings.append(args)) def teardown_module(): app.cleanup() -def warning_emitted(file, text): - for warning in warnings: - if len(warning) == 2 and file in warning[1] and text in warning[0]: - return True - return False - - # Tests are run in the order they appear in the file, therefore we can # afford to not run update() in the setup but in its own test @@ -49,12 +40,12 @@ def test_first_update(): def test_images(): - assert warning_emitted('images', 'image file not readable: foo.png') - assert warning_emitted('images', 'nonlocal image URI found: ' - 'http://www.python.org/logo.png') + assert ('image file not readable: foo.png' + in app._warning.getvalue()) + assert ('nonlocal image URI found: http://www.python.org/logo.png' + in app._warning.getvalue()) tree = env.get_doctree('images') - app._warning.reset() htmlbuilder = StandaloneHTMLBuilder(app) htmlbuilder.imgpath = 'dummy' htmlbuilder.post_process_images(tree) @@ -64,7 +55,6 @@ def test_images(): assert set(htmlbuilder.images.values()) == \ set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg', 'img.foo.png']) - app._warning.reset() latexbuilder = LaTeXBuilder(app) latexbuilder.post_process_images(tree) assert set(latexbuilder.images.keys()) == \ diff --git a/tests/test_util_logging.py b/tests/test_util_logging.py index 37c4a2d2a..59c899369 100644 --- a/tests/test_util_logging.py +++ b/tests/test_util_logging.py @@ -296,9 +296,10 @@ def test_output_with_unencodable_char(app, status, warning): self.stream.write(object.encode('cp1252').decode('cp1252')) logging.setup(app, StreamWriter(status), warning) + logger = logging.getLogger(__name__) # info with UnicodeEncodeError status.truncate(0) status.seek(0) - app.info(u"unicode \u206d...") + logger.info(u"unicode \u206d...") assert status.getvalue() == "unicode ?...\n"