errors.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. from __future__ import absolute_import
  5. import sys
  6. from contextlib import contextmanager
  7. class ErrorMessage(Exception):
  8. '''Exception type raised from errors.error() and errors.fatal()'''
  9. class AccumulatedErrors(Exception):
  10. '''Exception type raised from errors.accumulate()'''
  11. class ErrorCollector(object):
  12. '''
  13. Error handling/logging class. A global instance, errors, is provided for
  14. convenience.
  15. Warnings, errors and fatal errors may be logged by calls to the following
  16. functions:
  17. errors.warn(message)
  18. errors.error(message)
  19. errors.fatal(message)
  20. Warnings only send the message on the logging output, while errors and
  21. fatal errors send the message and throw an ErrorMessage exception. The
  22. exception, however, may be deferred. See further below.
  23. Errors may be ignored by calling:
  24. errors.ignore_errors()
  25. After calling that function, only fatal errors throw an exception.
  26. The warnings, errors or fatal errors messages may be augmented with context
  27. information when a context is provided. Context is defined by a pair
  28. (filename, linenumber), and may be set with errors.context() used as a
  29. context manager:
  30. with errors.context(filename, linenumber):
  31. errors.warn(message)
  32. Arbitrary nesting is supported, both for errors.context calls:
  33. with errors.context(filename1, linenumber1):
  34. errors.warn(message)
  35. with errors.context(filename2, linenumber2):
  36. errors.warn(message)
  37. as well as for function calls:
  38. def func():
  39. errors.warn(message)
  40. with errors.context(filename, linenumber):
  41. func()
  42. Errors and fatal errors can have their exception thrown at a later time,
  43. allowing for several different errors to be reported at once before
  44. throwing. This is achieved with errors.accumulate() as a context manager:
  45. with errors.accumulate():
  46. if test1:
  47. errors.error(message1)
  48. if test2:
  49. errors.error(message2)
  50. In such cases, a single AccumulatedErrors exception is thrown, but doesn't
  51. contain information about the exceptions. The logged messages do.
  52. '''
  53. out = sys.stderr
  54. WARN = 1
  55. ERROR = 2
  56. FATAL = 3
  57. _level = ERROR
  58. _context = []
  59. _count = None
  60. def ignore_errors(self, ignore=True):
  61. if ignore:
  62. self._level = self.FATAL
  63. else:
  64. self._level = self.ERROR
  65. def _full_message(self, level, msg):
  66. if level >= self._level:
  67. level = 'Error'
  68. else:
  69. level = 'Warning'
  70. if self._context:
  71. file, line = self._context[-1]
  72. return "%s: %s:%d: %s" % (level, file, line, msg)
  73. return "%s: %s" % (level, msg)
  74. def _handle(self, level, msg):
  75. msg = self._full_message(level, msg)
  76. if level >= self._level:
  77. if self._count is None:
  78. raise ErrorMessage(msg)
  79. self._count += 1
  80. print >>self.out, msg
  81. def fatal(self, msg):
  82. self._handle(self.FATAL, msg)
  83. def error(self, msg):
  84. self._handle(self.ERROR, msg)
  85. def warn(self, msg):
  86. self._handle(self.WARN, msg)
  87. def get_context(self):
  88. if self._context:
  89. return self._context[-1]
  90. @contextmanager
  91. def context(self, file, line):
  92. if file and line:
  93. self._context.append((file, line))
  94. yield
  95. if file and line:
  96. self._context.pop()
  97. @contextmanager
  98. def accumulate(self):
  99. assert self._count is None
  100. self._count = 0
  101. yield
  102. count = self._count
  103. self._count = None
  104. if count:
  105. raise AccumulatedErrors()
  106. @property
  107. def count(self):
  108. # _count can be None.
  109. return self._count if self._count else 0
  110. errors = ErrorCollector()