Use sphinx.util.logging instead app.warn()

This commit is contained in:
Takeshi KOMIYA 2016-12-20 23:21:30 +09:00
parent 6d4e645409
commit 85dcd7baa8
14 changed files with 130 additions and 136 deletions

View File

@ -177,11 +177,11 @@ class Sphinx(object):
self.tags = Tags(tags)
self.config = Config(confdir, CONFIG_FILENAME,
confoverrides or {}, self.tags)
self.config.check_unicode(self.warn)
self.config.check_unicode()
# defer checking types until i18n has been initialized
# initialize some limited config variables before loading extensions
self.config.pre_init_values(self.warn)
self.config.pre_init_values()
# check the Sphinx version if requested
if self.config.needs_sphinx and self.config.needs_sphinx > sphinx.__display_version__:
@ -227,7 +227,7 @@ class Sphinx(object):
)
# now that we know all config values, collect them from conf.py
self.config.init_values(self.warn)
self.config.init_values()
# check extension versions if requested
if self.config.needs_extensions:
@ -251,7 +251,7 @@ class Sphinx(object):
# set up translation infrastructure
self._init_i18n()
# check all configuration values for permissible types
self.config.check_types(self.warn)
self.config.check_types()
# set up source_parsers
self._init_source_parsers()
# set up the build environment
@ -528,9 +528,9 @@ class Sphinx(object):
if extension in self._extensions:
return
if extension in EXTENSION_BLACKLIST:
self.warn('the extension %r was already merged with Sphinx since version %s; '
'this extension is ignored.' % (
extension, EXTENSION_BLACKLIST[extension]))
logger.warning('the extension %r was already merged with Sphinx since version %s; '
'this extension is ignored.',
extension, EXTENSION_BLACKLIST[extension])
return
self._setting_up_extension.append(extension)
try:
@ -540,8 +540,8 @@ class Sphinx(object):
raise ExtensionError('Could not import extension %s' % extension,
err)
if not hasattr(mod, 'setup'):
self.warn('extension %r has no setup() function; is it really '
'a Sphinx extension module?' % extension)
logger.warning('extension %r has no setup() function; is it really '
'a Sphinx extension module?', extension)
ext_meta = None
else:
try:
@ -561,9 +561,9 @@ class Sphinx(object):
if not ext_meta.get('version'):
ext_meta['version'] = 'unknown version'
except Exception:
self.warn('extension %r returned an unsupported object from '
'its setup() function; it should return None or a '
'metadata dictionary' % extension)
logger.warning('extension %r returned an unsupported object from '
'its setup() function; it should return None or a '
'metadata dictionary', extension)
ext_meta = {'version': 'unknown version'}
self._extensions[extension] = mod
self._extension_metadata[extension] = ext_meta
@ -668,10 +668,10 @@ class Sphinx(object):
self.debug('[app] adding node: %r', (node, kwds))
if not kwds.pop('override', False) and \
hasattr(nodes.GenericNodeVisitor, 'visit_' + node.__name__):
self.warn('while setting up extension %s: node class %r is '
'already registered, its visitors will be overridden' %
(self._setting_up_extension, node.__name__),
type='app', subtype='add_node')
logger.warning('while setting up extension %s: node class %r is '
'already registered, its visitors will be overridden',
self._setting_up_extension, node.__name__,
type='app', subtype='add_node')
nodes._add_node_class_names([node.__name__])
for key, val in iteritems(kwds):
try:
@ -722,10 +722,10 @@ class Sphinx(object):
self.debug('[app] adding directive: %r',
(name, obj, content, arguments, options))
if name in directives._directives:
self.warn('while setting up extension %s: directive %r is '
'already registered, it will be overridden' %
(self._setting_up_extension[-1], name),
type='app', subtype='add_directive')
logger.warning('while setting up extension %s: directive %r is '
'already registered, it will be overridden',
self._setting_up_extension[-1], name,
type='app', subtype='add_directive')
directives.register_directive(
name, self._directive_helper(obj, content, arguments, **options))
@ -733,10 +733,10 @@ class Sphinx(object):
# type: (unicode, Any) -> None
self.debug('[app] adding role: %r', (name, role))
if name in roles._roles:
self.warn('while setting up extension %s: role %r is '
'already registered, it will be overridden' %
(self._setting_up_extension[-1], name),
type='app', subtype='add_role')
logger.warning('while setting up extension %s: role %r is '
'already registered, it will be overridden',
self._setting_up_extension[-1], name,
type='app', subtype='add_role')
roles.register_local_role(name, role)
def add_generic_role(self, name, nodeclass):
@ -745,10 +745,10 @@ class Sphinx(object):
# register_canonical_role
self.debug('[app] adding generic role: %r', (name, nodeclass))
if name in roles._roles:
self.warn('while setting up extension %s: role %r is '
'already registered, it will be overridden' %
(self._setting_up_extension[-1], name),
type='app', subtype='add_generic_role')
logger.warning('while setting up extension %s: role %r is '
'already registered, it will be overridden',
self._setting_up_extension[-1], name,
type='app', subtype='add_generic_role')
role = roles.GenericRole(name, nodeclass)
roles.register_local_role(name, role)
@ -891,10 +891,10 @@ class Sphinx(object):
# type: (unicode, Parser) -> None
self.debug('[app] adding search source_parser: %r, %r', suffix, parser)
if suffix in self._additional_source_parsers:
self.warn('while setting up extension %s: source_parser for %r is '
'already registered, it will be overridden' %
(self._setting_up_extension[-1], suffix),
type='app', subtype='add_source_parser')
logger.warning('while setting up extension %s: source_parser for %r is '
'already registered, it will be overridden',
self._setting_up_extension[-1], suffix,
type='app', subtype='add_source_parser')
self._additional_source_parsers[suffix] = parser

View File

@ -19,7 +19,7 @@ except ImportError:
from docutils import nodes
from sphinx.util import i18n, path_stabilize
from sphinx.util import i18n, path_stabilize, logging
from sphinx.util.osutil import SEP, relative_uri
from sphinx.util.i18n import find_catalog
from sphinx.util.console import bold, darkgreen # type: ignore
@ -284,13 +284,9 @@ class Builder(object):
self.info(bold('building [%s]' % self.name) + ': ' + summary)
# while reading, collect all warnings from docutils
warnings = []
self.env.set_warnfunc(lambda *args, **kwargs: warnings.append((args, kwargs)))
updated_docnames = set(self.env.update(self.config, self.srcdir,
self.doctreedir, self.app))
self.env.set_warnfunc(self.warn)
for warning, kwargs in warnings:
self.warn(*warning, **kwargs)
with logging.pending_logging():
updated_docnames = set(self.env.update(self.config, self.srcdir,
self.doctreedir, self.app))
doccount = len(updated_docnames)
self.info(bold('looking for now-outdated files... '), nonl=1)
@ -376,25 +372,23 @@ class Builder(object):
self.info('done')
warnings = [] # type: List[Tuple[Tuple, Dict]]
self.env.set_warnfunc(lambda *args, **kwargs: warnings.append((args, kwargs)))
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,
nproc=self.app.parallel - 1)
else:
self._write_serial(sorted(docnames), warnings)
self.env.set_warnfunc(self.warn)
self._write_serial(sorted(docnames))
def _write_serial(self, docnames, warnings):
# type: (Sequence[unicode], List[Tuple[Tuple, Dict]]) -> None
for docname in self.app.status_iterator(
docnames, 'writing output... ', darkgreen, len(docnames)):
doctree = self.env.get_and_resolve_doctree(docname, self)
self.write_doc_serialized(docname, doctree)
self.write_doc(docname, doctree)
for warning, kwargs in warnings:
self.warn(*warning, **kwargs)
def _write_serial(self, docnames):
# type: (Sequence[unicode]) -> None
with logging.pending_logging():
for docname in self.app.status_iterator(
docnames, 'writing output... ', darkgreen, len(docnames)):
doctree = self.env.get_and_resolve_doctree(docname, self)
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

View File

@ -38,8 +38,7 @@ class ChangesBuilder(Builder):
def init(self):
# type: () -> None
self.create_template_bridge()
Theme.init_themes(self.confdir, self.config.html_theme_path,
warn=self.warn)
Theme.init_themes(self.confdir, self.config.html_theme_path)
self.theme = Theme('default')
self.templates.init(self, self.theme)

View File

@ -159,10 +159,9 @@ class StandaloneHTMLBuilder(Builder):
def init_templates(self):
# type: () -> None
Theme.init_themes(self.confdir, self.config.html_theme_path,
warn=self.warn)
Theme.init_themes(self.confdir, self.config.html_theme_path)
themename, themeoptions = self.get_theme_config()
self.theme = Theme(themename, warn=self.warn)
self.theme = Theme(themename)
self.theme_options = themeoptions.copy()
self.create_template_bridge()
self.templates.init(self, self.theme)
@ -314,8 +313,7 @@ class StandaloneHTMLBuilder(Builder):
lufmt = self.config.html_last_updated_fmt
if lufmt is not None:
self.last_updated = format_date(lufmt or _('%b %d, %Y'),
language=self.config.language,
warn=self.warn)
language=self.config.language)
else:
self.last_updated = None

View File

@ -16,6 +16,7 @@ from six import PY2, PY3, iteritems, string_types, binary_type, text_type, integ
from sphinx.errors import ConfigError
from sphinx.locale import l_
from sphinx.util import logging
from sphinx.util.i18n import format_date
from sphinx.util.osutil import cd
from sphinx.util.pycompat import execfile_, NoneType
@ -25,6 +26,8 @@ if False:
from typing import Any, Callable, Tuple # NOQA
from sphinx.util.tags import Tags # NOQA
logger = logging.getLogger(__name__)
nonascii_re = re.compile(br'[\x80-\xff]')
copyright_year_re = re.compile(r'^((\d{4}-)?)(\d{4})(?=[ ,])')
@ -166,8 +169,8 @@ class Config(object):
config[k] = copyright_year_re.sub('\g<1>%s' % format_date('%Y'), # type: ignore # NOQA
config[k])
def check_types(self, warn):
# type: (Callable) -> None
def check_types(self):
# type: () -> None
# check all values for deviation from the default value's type, since
# that can result in TypeErrors all over the place
# NB. since config values might use l_() we have to wait with calling
@ -186,7 +189,7 @@ class Config(object):
current = self[name]
if isinstance(permitted, ENUM):
if not permitted.match(current):
warn(CONFIG_ENUM_WARNING.format(
logger.warning(CONFIG_ENUM_WARNING.format(
name=name, current=current, candidates=permitted.candidates))
else:
if type(current) is type(default):
@ -201,22 +204,22 @@ class Config(object):
continue # at least we share a non-trivial base class
if permitted:
warn(CONFIG_PERMITTED_TYPE_WARNING.format(
logger.warning(CONFIG_PERMITTED_TYPE_WARNING.format(
name=name, current=type(current),
permitted=str([cls.__name__ for cls in permitted])))
else:
warn(CONFIG_TYPE_WARNING.format(
logger.warning(CONFIG_TYPE_WARNING.format(
name=name, current=type(current), default=type(default)))
def check_unicode(self, warn):
# type: (Callable) -> None
def check_unicode(self):
# type: () -> None
# check all string values for non-ASCII characters in bytestrings,
# since that can result in UnicodeErrors all over the place
for name, value in iteritems(self._raw_config):
if isinstance(value, binary_type) and nonascii_re.search(value): # type: ignore
warn('the config value %r is set to a string with non-ASCII '
'characters; this can lead to Unicode errors occurring. '
'Please use Unicode strings, e.g. %r.' % (name, u'Content'))
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'))
def convert_overrides(self, name, value):
# type: (unicode, Any) -> Any
@ -244,8 +247,8 @@ class Config(object):
else:
return value
def pre_init_values(self, warn):
# type: (Callable) -> None
def pre_init_values(self):
# type: () -> None
"""Initialize some limited config variables before loading extensions"""
variables = ['needs_sphinx', 'suppress_warnings', 'html_translator_class']
for name in variables:
@ -255,10 +258,10 @@ class Config(object):
elif name in self._raw_config:
self.__dict__[name] = self._raw_config[name]
except ValueError as exc:
warn(exc)
logger.warning("%s" % exc)
def init_values(self, warn):
# type: (Callable) -> None
def init_values(self):
# type: () -> None
config = self._raw_config
for valname, value in iteritems(self.overrides):
try:
@ -267,14 +270,14 @@ class Config(object):
config.setdefault(realvalname, {})[key] = value # type: ignore
continue
elif valname not in self.values:
warn('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:
warn(exc)
logger.warning("%s" % exc)
for name in config:
if name in self.values:
self.__dict__[name] = config[name]

View File

@ -11,6 +11,7 @@
from six import text_type
from sphinx.util import logging
from sphinx.util.pycompat import htmlescape
from sphinx.util.texescape import tex_hl_escape_map_new
from sphinx.ext import doctest
@ -26,6 +27,8 @@ from pygments.styles import get_style_by_name
from pygments.util import ClassNotFound
from sphinx.pygments_styles import SphinxStyle, NoneStyle
logger = logging.getLogger(__name__)
lexers = dict(
none = TextLexer(stripnl=False),
python = PythonLexer(stripnl=False),
@ -92,7 +95,7 @@ class PygmentsBridge(object):
return '\\begin{Verbatim}[commandchars=\\\\\\{\\}]\n' + \
source + '\\end{Verbatim}\n'
def highlight_block(self, source, lang, opts=None, warn=None, force=False, **kwargs):
def highlight_block(self, source, lang, opts=None, location=None, force=False, **kwargs):
if not isinstance(source, text_type):
source = source.decode()
@ -120,11 +123,9 @@ class PygmentsBridge(object):
try:
lexer = lexers[lang] = get_lexer_by_name(lang, **(opts or {}))
except ClassNotFound:
if warn:
warn('Pygments lexer name %r is not known' % lang)
lexer = lexers['none']
else:
raise
logger.warning('Pygments lexer name %r is not known', lang,
location=location)
lexer = lexers['none']
else:
lexer.add_filter('raiseonerror')
@ -137,17 +138,16 @@ class PygmentsBridge(object):
formatter = self.get_formatter(**kwargs)
try:
hlsource = highlight(source, lexer, formatter)
except ErrorToken as exc:
except ErrorToken:
# this is most probably not the selected language,
# so let it pass unhighlighted
if lang == 'default':
pass # automatic highlighting failed.
elif warn:
warn('Could not lex literal_block as "%s". '
'Highlighting skipped.' % lang,
type='misc', subtype='highlighting_failure')
else:
raise exc
logger.warning('Could not lex literal_block as "%s". '
'Highlighting skipped.', lang,
type='misc', subtype='highlighting_failure',
location=location)
hlsource = highlight(source, lexers['none'], formatter)
if self.dest == 'html':
return hlsource

View File

@ -26,6 +26,9 @@ except ImportError:
from sphinx import package_dir
from sphinx.errors import ThemeError
from sphinx.util import logging
logger = logging.getLogger(__name__)
if False:
# For type annotation
@ -43,8 +46,8 @@ class Theme(object):
themepath = [] # type: List[unicode]
@classmethod
def init_themes(cls, confdir, theme_path, warn=None):
# type: (unicode, unicode, Callable) -> None
def init_themes(cls, confdir, theme_path):
# type: (unicode, unicode) -> None
"""Search all theme paths for available themes."""
cls.themepath = list(theme_path)
cls.themepath.append(path.join(package_dir, 'themes'))
@ -62,9 +65,8 @@ class Theme(object):
tname = theme[:-4]
tinfo = zfile
except Exception:
if warn:
warn('file %r on theme path is not a valid '
'zipfile or contains no theme' % theme)
logger.warning('file %r on theme path is not a valid '
'zipfile or contains no theme', theme)
continue
else:
if not path.isfile(path.join(themedir, theme, THEMECONF)):
@ -105,8 +107,8 @@ class Theme(object):
cls.themes[name] = (path.join(themedir, name), None)
return
def __init__(self, name, warn=None):
# type: (unicode, Callable) -> None
def __init__(self, name):
# type: (unicode) -> None
if name not in self.themes:
self.load_extra_theme(name)
if name not in self.themes:
@ -162,7 +164,7 @@ class Theme(object):
raise ThemeError('no theme named %r found, inherited by %r' %
(inherit, name))
else:
self.base = Theme(inherit, warn=warn)
self.base = Theme(inherit)
def get_confstr(self, section, name, default=NODEFAULT):
# type: (unicode, unicode, Any) -> Any

View File

@ -34,7 +34,6 @@ class DefaultSubstitutions(Transform):
def apply(self):
# type: () -> None
env = self.document.settings.env
config = self.document.settings.env.config
# only handle those not otherwise defined in the document
to_handle = default_substitutions - set(self.document.substitution_defs)
@ -45,7 +44,7 @@ class DefaultSubstitutions(Transform):
if refname == 'today' and not text:
# special handling: can also specify a strftime format
text = format_date(config.today_fmt or _('%b %d, %Y'),
language=config.language, warn=env.warn)
language=config.language)
ref.replace_self(nodes.Text(text, text))

View File

@ -22,9 +22,12 @@ from babel.messages.pofile import read_po
from babel.messages.mofile import write_mo
from sphinx.errors import SphinxError
from sphinx.util import logging
from sphinx.util.osutil import SEP, walk
from sphinx.deprecation import RemovedInSphinx16Warning
logger = logging.getLogger(__name__)
if False:
# For type annotation
from typing import Callable # NOQA
@ -171,8 +174,8 @@ date_format_mappings = {
}
def babel_format_date(date, format, locale, warn=None, formatter=babel.dates.format_date):
# type: (datetime, unicode, unicode, Callable, Callable) -> unicode
def babel_format_date(date, format, locale, formatter=babel.dates.format_date):
# type: (datetime, unicode, unicode, Callable) -> unicode
if locale is None:
locale = 'en'
@ -187,15 +190,13 @@ def babel_format_date(date, format, locale, warn=None, formatter=babel.dates.for
# fallback to English
return formatter(date, format, locale='en')
except AttributeError:
if warn:
warn('Invalid date format. Quote the string by single quote '
'if you want to output it directly: %s' % format)
logger.warning('Invalid date format. Quote the string by single quote '
'if you want to output it directly: %s', format)
return format
def format_date(format, date=None, language=None, warn=None):
# type: (str, datetime, unicode, Callable) -> unicode
def format_date(format, date=None, language=None):
# type: (str, datetime, unicode) -> unicode
if format is None:
format = 'medium'
@ -213,7 +214,7 @@ def format_date(format, date=None, language=None, warn=None):
warnings.warn('LDML format support will be dropped at Sphinx-1.6',
RemovedInSphinx16Warning)
return babel_format_date(date, format, locale=language, warn=warn,
return babel_format_date(date, format, locale=language,
formatter=babel.dates.format_datetime)
else:
# consider the format as ustrftime's and try to convert it to babel's

View File

@ -364,11 +364,10 @@ class HTMLTranslator(BaseTranslator):
else:
opts = {}
def warner(msg, **kwargs):
self.builder.warn(msg, (self.builder.current_docname, node.line), **kwargs)
highlighted = self.highlighter.highlight_block(
node.rawsource, lang, opts=opts, warn=warner, linenos=linenos,
**highlight_args)
node.rawsource, lang, opts=opts, linenos=linenos,
location=(self.builder.current_docname, node.line), **highlight_args
)
starttag = self.starttag(node, 'div', suffix='',
CLASS='highlight-%s' % lang)
self.body.append(starttag + highlighted + '</div>\n')

View File

@ -2155,12 +2155,10 @@ class LaTeXTranslator(nodes.NodeVisitor):
else:
opts = {}
def warner(msg, **kwargs):
# type: (unicode) -> None
self.builder.warn(msg, (self.curfilestack[-1], node.line), **kwargs)
hlcode = self.highlighter.highlight_block(code, lang, opts=opts,
warn=warner, linenos=linenos,
**highlight_args)
hlcode = self.highlighter.highlight_block(
code, lang, opts=opts, linenos=linenos,
location=(self.curfilestack[-1], node.line), **highlight_args
)
# workaround for Unicode issue
hlcode = hlcode.replace(u'', u'@texteuro[]')
if self.in_footnote:

View File

@ -40,10 +40,10 @@ with "\\?": b?'here: >>>(\\\\|/)xbb<<<'
"""
HTML_WARNINGS = ENV_WARNINGS + """\
%(root)s/index.rst:\\d+: WARNING: no matching candidate for image URI u'foo.\\*'
%(root)s/index.rst:\\d+: WARNING: Could not lex literal_block as "c". Highlighting skipped.
%(root)s/index.rst:\\d+: WARNING: unknown option: &option
%(root)s/index.rst:\\d+: WARNING: citation not found: missing
%(root)s/index.rst:\\d+: WARNING: no matching candidate for image URI u'foo.\\*'
%(root)s/index.rst:\\d+: WARNING: Could not lex literal_block as "c". Highlighting skipped.
"""
if PY3:

View File

@ -87,7 +87,8 @@ def test_extension_values(app, status, warning):
@with_tempdir
def test_errors_warnings(dir):
@mock.patch("sphinx.config.logger")
def test_errors_warnings(dir, logger):
# test the error for syntax errors in the config file
(dir / 'conf.py').write_text(u'project = \n', encoding='ascii')
raises_msg(ConfigError, 'conf.py', Config, dir, 'conf.py', {}, None)
@ -97,8 +98,9 @@ def test_errors_warnings(dir):
u'# -*- coding: utf-8\n\nproject = u"Jägermeister"\n',
encoding='utf-8')
cfg = Config(dir, 'conf.py', {}, None)
cfg.init_values(lambda warning: 1/0)
cfg.init_values()
assert cfg.project == u'Jägermeister'
assert logger.called is False
# test the warning for bytestrings with non-ascii content
# bytestrings with non-ascii content are a syntax error in python3 so we
@ -108,13 +110,10 @@ def test_errors_warnings(dir):
(dir / 'conf.py').write_text(
u'# -*- coding: latin-1\nproject = "fooä"\n', encoding='latin-1')
cfg = Config(dir, 'conf.py', {}, None)
warned = [False]
def warn(msg):
warned[0] = True
cfg.check_unicode(warn)
assert warned[0]
assert logger.warning.called is False
cfg.check_unicode()
assert logger.warning.called is True
@with_tempdir
@ -152,14 +151,16 @@ def test_needs_sphinx():
@with_tempdir
def test_config_eol(tmpdir):
@mock.patch("sphinx.config.logger")
def test_config_eol(tmpdir, logger):
# test config file's eol patterns: LF, CRLF
configfile = tmpdir / 'conf.py'
for eol in (b'\n', b'\r\n'):
configfile.write_bytes(b'project = "spam"' + eol)
cfg = Config(tmpdir, 'conf.py', {}, None)
cfg.init_values(lambda warning: 1/0)
cfg.init_values()
assert cfg.project == u'spam'
assert logger.called is False
@with_app(confoverrides={'master_doc': 123,

View File

@ -9,9 +9,9 @@
:license: BSD, see LICENSE for details.
"""
import mock
from pygments.lexer import RegexLexer
from pygments.token import Text, Name
from pygments.filters import ErrorToken
from pygments.formatters.html import HtmlFormatter
from sphinx.highlighting import PygmentsBridge
@ -89,7 +89,8 @@ def test_trim_doctest_flags():
PygmentsBridge.html_formatter = HtmlFormatter
def test_default_highlight():
@mock.patch('sphinx.highlighting.logger')
def test_default_highlight(logger):
bridge = PygmentsBridge('html')
# default: highlights as python3
@ -107,8 +108,7 @@ def test_default_highlight():
'<span class="s2">&quot;Hello sphinx world&quot;</span>\n</pre></div>\n')
# python3: raises error if highlighting failed
try:
ret = bridge.highlight_block('reST ``like`` text', 'python3')
assert False, "highlight_block() does not raise any exceptions"
except ErrorToken:
pass # raise parsing error
ret = bridge.highlight_block('reST ``like`` text', 'python3')
logger.warning.assert_called_with('Could not lex literal_block as "%s". '
'Highlighting skipped.', 'python3',
type='misc', subtype='highlighting_failure', location=None)