# Source code for fontTools.misc.loggingTools

    import sys
    import logging
    import timeit
    from functools import wraps
    from collections.abc import Mapping, Callable
    import warnings
    from logging import PercentStyle

    # default logging level used by Timer class
    TIME_LEVEL = logging.DEBUG

    # per-level format strings used by the default formatter
    # (the level name is not printed for INFO and DEBUG messages)
    DEFAULT_FORMATS = {
    	"*": "%(levelname)s: %(message)s",
    	"INFO": "%(message)s",
    	"DEBUG": "%(message)s",
    	}

    class LevelFormatter(logging.Formatter):
    	"""Log formatter with level-specific formatting.

    	Formatter class which optionally takes a dict of logging levels to
    	format strings, allowing to customise the log records appearance for
    	specific levels.

    	Attributes:
    		fmt: A dictionary mapping logging levels to format strings.
    			The ``*`` key identifies the default format string.
    		datefmt: As per py:class:`logging.Formatter`
    		style: As per py:class:`logging.Formatter`

    	>>> import sys
    	>>> handler = logging.StreamHandler(sys.stdout)
    	>>> formatter = LevelFormatter(
    	...     fmt={
    	...         '*':     '[%(levelname)s] %(message)s',
    	...         'DEBUG': '%(name)s [%(levelname)s] %(message)s',
    	...         'INFO':  '%(message)s',
    	...     })
    	>>> handler.setFormatter(formatter)
    	>>> log = logging.getLogger('test')
    	>>> log.setLevel(logging.DEBUG)
    	>>> log.addHandler(handler)
    	>>> log.debug('this uses a custom format string')
    	test [DEBUG] this uses a custom format string
    	>>> log.info('this also uses a custom format string')
    	this also uses a custom format string
    	>>> log.warning("this one uses the default format string")
    	[WARNING] this one uses the default format string
    	"""

    	def __init__(self, fmt=None, datefmt=None, style="%"):
    		if style != '%':
    			raise ValueError(
    				"only '%' percent style is supported in both python 2 and 3")
    		if fmt is None:
    			fmt = DEFAULT_FORMATS
    		if isinstance(fmt, str):
    			default_format = fmt
    			custom_formats = {}
    		elif isinstance(fmt, Mapping):
    			custom_formats = dict(fmt)
    			default_format = custom_formats.pop("*", None)
    		else:
    			raise TypeError('fmt must be a str or a dict of str: %r' % fmt)
    		super(LevelFormatter, self).__init__(default_format, datefmt)
    		self.default_format = self._fmt
    		self.custom_formats = {}
    		for level, fmt in custom_formats.items():
    			level = logging._checkLevel(level)
    			self.custom_formats[level] = fmt

    	def format(self, record):
    		if self.custom_formats:
    			fmt = self.custom_formats.get(record.levelno, self.default_format)
    			if self._fmt != fmt:
    				self._fmt = fmt
    				# for python >= 3.2, _style needs to be set if _fmt changes
    				if PercentStyle:
    					self._style = PercentStyle(fmt)
    		return super(LevelFormatter, self).format(record)

    def configLogger(**kwargs):
    	"""A more sophisticated logging system configuation manager.

    	This is more or less the same as :py:func:`logging.basicConfig`,
    	with some additional options and defaults.

    	The default behaviour is to create a ``StreamHandler`` which writes to
    	sys.stderr, set a formatter using the ``DEFAULT_FORMATS`` strings, and add
    	the handler to the top-level library logger ("fontTools").

    	A number of optional keyword arguments may be specified, which can alter
    	the default behaviour.

    	Args:

    		logger: Specifies the logger name or a Logger instance to be
    			configured. (Defaults to "fontTools" logger). Unlike ``basicConfig``,
    			this function can be called multiple times to reconfigure a logger.
    			If the logger or any of its children already exists before the call is
    			made, they will be reset before the new configuration is applied.
    		filename: Specifies that a ``FileHandler`` be created, using the
    			specified filename, rather than a ``StreamHandler``.
    		filemode: Specifies the mode to open the file, if filename is
    			specified. (If filemode is unspecified, it defaults to ``a``).
    		format: Use the specified format string for the handler. This
    			argument also accepts a dictionary of format strings keyed by
    			level name, to allow customising the records appearance for
    			specific levels. The special ``'*'`` key is for 'any other' level.
    		datefmt: Use the specified date/time format.
    		level: Set the logger level to the specified level.
    		stream: Use the specified stream to initialize the StreamHandler. Note
    			that this argument is incompatible with ``filename`` - if both
    			are present, ``stream`` is ignored.
    		handlers: If specified, this should be an iterable of already created
    			handlers, which will be added to the logger. Any handler in the
    			list which does not have a formatter assigned will be assigned the
    			formatter created in this function.
    		filters: If specified, this should be an iterable of already created
    			filters. If the ``handlers`` do not already have filters assigned,
    			these filters will be added to them.
    		propagate: All loggers have a ``propagate`` attribute which determines
    			whether to continue searching for handlers up the logging hierarchy.
    			If not provided, the "propagate" attribute will be set to ``False``.
    	"""
    	# using kwargs to enforce keyword-only arguments in py2.
    	handlers = kwargs.pop("handlers", None)
    	if handlers is None:
    		if "stream" in kwargs and "filename" in kwargs:
    			raise ValueError("'stream' and 'filename' should not be "
    							 "specified together")
    	else:
    		if "stream" in kwargs or "filename" in kwargs:
    			raise ValueError("'stream' or 'filename' should not be "
    							 "specified together with 'handlers'")
    	if handlers is None:
    		filename = kwargs.pop("filename", None)
    		mode = kwargs.pop("filemode", 'a')
    		if filename:
    			h = logging.FileHandler(filename, mode)
    		else:
    			stream = kwargs.pop("stream", None)
    			h = logging.StreamHandler(stream)
    		handlers = [h]
    	# By default, the top-level library logger is configured.
    	logger = kwargs.pop("logger", "fontTools")
    	if not logger or isinstance(logger, str):
    		# empty "" or None means the 'root' logger
    		logger = logging.getLogger(logger)
    	# before (re)configuring, reset named logger and its children (if exist)
    	_resetExistingLoggers(parent=logger.name)
    	# use DEFAULT_FORMATS if 'format' is None
    	fs = kwargs.pop("format", None)
    	dfs = kwargs.pop("datefmt", None)
    	# XXX: '%' is the only format style supported on both py2 and 3
    	style = kwargs.pop("style", '%')
    	fmt = LevelFormatter(fs, dfs, style)
    	filters = kwargs.pop("filters", [])
    	for h in handlers:
    		if h.formatter is None:
    			h.setFormatter(fmt)
    		if not h.filters:
    			for f in filters:
    				h.addFilter(f)
    		logger.addHandler(h)
    	if logger.name != "root":
    		# stop searching up the hierarchy for handlers
    		logger.propagate = kwargs.pop("propagate", False)
    	# set a custom severity level
    	level = kwargs.pop("level", None)
    	if level is not None:
    		logger.setLevel(level)
    	if kwargs:
    		keys = ', '.join(kwargs.keys())
    		raise ValueError('Unrecognised argument(s): %s' % keys)

    def _resetExistingLoggers(parent="root"):
    	""" Reset the logger named 'parent' and all its children to their initial
    	state, if they already exist in the current configuration.
    	"""
    	root = logging.root
    	# get sorted list of all existing loggers
    	existing = sorted(root.manager.loggerDict.keys())
    	if parent == "root":
    		# all the existing loggers are children of 'root'
    		loggers_to_reset = [parent] + existing
    	elif parent not in existing:
    		# nothing to do
    		return
    	elif parent in existing:
    		loggers_to_reset = [parent]
    		# collect children, starting with the entry after parent name
    		i = existing.index(parent) + 1
    		prefixed = parent + "."
    		pflen = len(prefixed)
    		num_existing = len(existing)
    		while i < num_existing:
    			if existing[i][:pflen] == prefixed:
    				loggers_to_reset.append(existing[i])
    			i += 1
    	for name in loggers_to_reset:
    		if name == "root":
    			root.setLevel(logging.WARNING)
    			for h in root.handlers[:]:
    				root.removeHandler(h)
    			for f in root.filters[:]:
    				root.removeFilters(f)
    			root.disabled = False
    		else:
    			logger = root.manager.loggerDict[name]
    			logger.level = logging.NOTSET
    			logger.handlers = []
    			logger.filters = []
    			logger.propagate = True
    			logger.disabled = False

    class Timer(object):
    	""" Keeps track of overall time and split/lap times.

    	>>> import time
    	>>> timer = Timer()
    	>>> time.sleep(0.01)
    	>>> print("First lap:", timer.split())
    	First lap: ...
    	>>> time.sleep(0.02)
    	>>> print("Second lap:", timer.split())
    	Second lap: ...
    	>>> print("Overall time:", timer.time())
    	Overall time: ...

    	Can be used as a context manager inside with-statements.

    	>>> with Timer() as t:
    	...     time.sleep(0.01)
    	>>> print("%0.3f seconds" % t.elapsed)
    	0... seconds

    	If initialised with a logger, it can log the elapsed time automatically
    	upon exiting the with-statement.

    	>>> import logging
    	>>> log = logging.getLogger("my-fancy-timer-logger")
    	>>> configLogger(logger=log, level="DEBUG", format="%(message)s", stream=sys.stdout)
    	>>> with Timer(log, 'do something'):
    	...     time.sleep(0.01)
    	Took ... to do something

    	The same Timer instance, holding a reference to a logger, can be reused
    	in multiple with-statements, optionally with different messages or levels.

    	>>> timer = Timer(log)
    	>>> with timer():
    	...     time.sleep(0.01)
    	elapsed time: ...s
    	>>> with timer('redo it', level=logging.INFO):
    	...     time.sleep(0.02)
    	Took ... to redo it

    	It can also be used as a function decorator to log the time elapsed to run
    	the decorated function.

    	>>> @timer()
    	... def test1():
    	...    time.sleep(0.01)
    	>>> @timer('run test 2', level=logging.INFO)
    	... def test2():
    	...    time.sleep(0.02)
    	>>> test1()
    	Took ... to run 'test1'
    	>>> test2()
    	Took ... to run test 2
    	"""

    	# timeit.default_timer choses the most accurate clock for each platform
    	_time = timeit.default_timer
    	default_msg = "elapsed time: %(time).3fs"
    	default_format = "Took %(time).3fs to %(msg)s"

    	def __init__(self, logger=None, msg=None, level=None, start=None):
    		self.reset(start)
    		if logger is None:
    			for arg in ('msg', 'level'):
    				if locals().get(arg) is not None:
    					raise ValueError(
    						"'%s' can't be specified without a 'logger'" % arg)
    		self.logger = logger
    		self.level = level if level is not None else TIME_LEVEL
    		self.msg = msg

    	def reset(self, start=None):
    		""" Reset timer to 'start_time' or the current time. """
    		if start is None:
    			self.start = self._time()
    		else:
    			self.start = start
    		self.last = self.start
    		self.elapsed = 0.0

    	def time(self):
    		""" Return the overall time (in seconds) since the timer started. """
    		return self._time() - self.start

    	def split(self):
    		""" Split and return the lap time (in seconds) in between splits. """
    		current = self._time()
    		self.elapsed = current - self.last
    		self.last = current
    		return self.elapsed

    	def formatTime(self, msg, time):
    		""" Format 'time' value in 'msg' and return formatted string.
    		If 'msg' contains a '%(time)' format string, try to use that.
    		Otherwise, use the predefined 'default_format'.
    		If 'msg' is empty or None, fall back to 'default_msg'.
    		"""
    		if not msg:
    			msg = self.default_msg
    		if msg.find("%(time)") < 0:
    			msg = self.default_format % {"msg": msg, "time": time}
    		else:
    			try:
    				msg = msg % {"time": time}
    			except (KeyError, ValueError):
    				pass  # skip if the format string is malformed
    		return msg

    	def __enter__(self):
    		""" Start a new lap """
    		self.last = self._time()
    		self.elapsed = 0.0
    		return self

    	def __exit__(self, exc_type, exc_value, traceback):
    		""" End the current lap. If timer has a logger, log the time elapsed,
    		using the format string in self.msg (or the default one).
    		"""
    		time = self.split()
    		if self.logger is None or exc_type:
    			# if there's no logger attached, or if any exception occurred in
    			# the with-statement, exit without logging the time
    			return
    		message = self.formatTime(self.msg, time)
    		# Allow log handlers to see the individual parts to facilitate things
    		# like a server accumulating aggregate stats.
    		msg_parts = { 'msg': self.msg, 'time': time }
    		self.logger.log(self.level, message, msg_parts)

    	def __call__(self, func_or_msg=None, **kwargs):
    		""" If the first argument is a function, return a decorator which runs
    		the wrapped function inside Timer's context manager.
    		Otherwise, treat the first argument as a 'msg' string and return an updated
    		Timer instance, referencing the same logger.
    		A 'level' keyword can also be passed to override self.level.
    		"""
    		if isinstance(func_or_msg, Callable):
    			func = func_or_msg
    			# use the function name when no explicit 'msg' is provided
    			if not self.msg:
    				self.msg = "run '%s'" % func.__name__

    			@wraps(func)
    			def wrapper(*args, **kwds):
    				with self:
    					return func(*args, **kwds)
    			return wrapper
    		else:
    			msg = func_or_msg or kwargs.get("msg")
    			level = kwargs.get("level", self.level)
    			return self.__class__(self.logger, msg, level)

    	def __float__(self):
    		return self.elapsed

    	def __int__(self):
    		return int(self.elapsed)

    	def __str__(self):
    		return "%.3f" % self.elapsed

    class ChannelsFilter(logging.Filter):
    	"""Provides a hierarchical filter for log entries based on channel names.

    	Filters out records emitted from a list of enabled channel names,
    	including their children. It works the same as the ``logging.Filter``
    	class, but allows the user to specify multiple channel names.

    	>>> import sys
    	>>> handler = logging.StreamHandler(sys.stdout)
    	>>> handler.setFormatter(logging.Formatter("%(message)s"))
    	>>> filter = ChannelsFilter("A.B", "C.D")
    	>>> handler.addFilter(filter)
    	>>> root = logging.getLogger()
    	>>> root.addHandler(handler)
    	>>> root.setLevel(level=logging.DEBUG)
    	>>> logging.getLogger('A.B').debug('this record passes through')
    	this record passes through
    	>>> logging.getLogger('A.B.C').debug('records from children also pass')
    	records from children also pass
    	>>> logging.getLogger('C.D').debug('this one as well')
    	this one as well
    	>>> logging.getLogger('A.B.').debug('also this one')
    	also this one
    	>>> logging.getLogger('A.F').debug('but this one does not!')
    	>>> logging.getLogger('C.DE').debug('neither this one!')
    	"""

    	def __init__(self, *names):
    		self.names = names
    		self.num = len(names)
    		self.lengths = {n: len(n) for n in names}

    	def filter(self, record):
    		if self.num == 0:
    			return True
    		for name in self.names:
    			nlen = self.lengths[name]
    			if name == record.name:
    				return True
    			elif (record.name.find(name, 0, nlen) == 0
    					and record.name[nlen] == "."):
    				return True
    		return False

    class CapturingLogHandler(logging.Handler):
    	def __init__(self, logger, level):
    		super(CapturingLogHandler, self).__init__(level=level)
    		self.records = []
    		if isinstance(logger, str):
    			self.logger = logging.getLogger(logger)
    		else:
    			self.logger = logger

    	def __enter__(self):
    		self.original_disabled = self.logger.disabled
    		self.original_level = self.logger.level
    		self.original_propagate = self.logger.propagate

    		self.logger.addHandler(self)
    		self.logger.setLevel(self.level)
    		self.logger.disabled = False
    		self.logger.propagate = False

    		return self

    	def __exit__(self, type, value, traceback):
    		self.logger.removeHandler(self)
    		self.logger.setLevel(self.original_level)
    		self.logger.disabled = self.original_disabled
    		self.logger.propagate = self.original_propagate

    		return self

    	def emit(self, record):
    		self.records.append(record)

    	def assertRegex(self, regexp, msg=None):
    		import re
    		pattern = re.compile(regexp)
    		for r in self.records:
    			if pattern.search(r.getMessage()):
    				return True
    		if msg is None:
    			msg = "Pattern '%s' not found in logger records" % regexp
    		assert 0, msg

    class LogMixin(object):
    	""" Mixin class that adds logging functionality to another class.

    	You can define a new class that subclasses from ``LogMixin`` as well as
    	other base classes through multiple inheritance.
    	All instances of that class will have a ``log`` property that returns
    	a ``logging.Logger`` named after their respective ``<module>.<class>``.

    	For example:

    	>>> class BaseClass(object):
    	...     pass
    	>>> class MyClass(LogMixin, BaseClass):
    	...     pass
    	>>> a = MyClass()
    	>>> isinstance(a.log, logging.Logger)
    	True
    	>>> print(a.log.name)
    	fontTools.misc.loggingTools.MyClass
    	>>> class AnotherClass(MyClass):
    	...     pass
    	>>> b = AnotherClass()
    	>>> isinstance(b.log, logging.Logger)
    	True
    	>>> print(b.log.name)
    	fontTools.misc.loggingTools.AnotherClass
    	"""

    	@property
    	def log(self):
    		if not hasattr(self, "_log"):
    			name = ".".join(
    				(self.__class__.__module__, self.__class__.__name__)
    			)
    			self._log = logging.getLogger(name)
    		return self._log

    def deprecateArgument(name, msg, category=UserWarning):
    	""" Raise a warning about deprecated function argument 'name'. """
    	warnings.warn(
    		"%r is deprecated; %s" % (name, msg), category=category, stacklevel=3)

    def deprecateFunction(msg, category=UserWarning):
    	""" Decorator to raise a warning when a deprecated function is called. """
    	def decorator(func):
    		@wraps(func)
    		def wrapper(*args, **kwargs):
    			warnings.warn(
    				"%r is deprecated; %s" % (func.__name__, msg),
    				category=category, stacklevel=2)
    			return func(*args, **kwargs)
    		return wrapper
    	return decorator

    if __name__ == "__main__":
    	import doctest
    	sys.exit(doctest.testmod(optionflags=doctest.ELLIPSIS).failed)