/ letsencrypt / reporter.py
reporter.py
 1  """Collects and displays information to the user."""
 2  import collections
 3  import logging
 4  import os
 5  import Queue
 6  import sys
 7  import textwrap
 8  
 9  import zope.interface
10  
11  from letsencrypt import interfaces
12  from letsencrypt import le_util
13  
14  
15  logger = logging.getLogger(__name__)
16  
17  
18  class Reporter(object):
19      """Collects and displays information to the user.
20  
21      :ivar `Queue.PriorityQueue` messages: Messages to be displayed to
22          the user.
23  
24      """
25      zope.interface.implements(interfaces.IReporter)
26  
27      HIGH_PRIORITY = 0
28      """High priority constant. See `add_message`."""
29      MEDIUM_PRIORITY = 1
30      """Medium priority constant. See `add_message`."""
31      LOW_PRIORITY = 2
32      """Low priority constant. See `add_message`."""
33  
34      _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash')
35  
36      def __init__(self):
37          self.messages = Queue.PriorityQueue()
38  
39      def add_message(self, msg, priority, on_crash=True):
40          """Adds msg to the list of messages to be printed.
41  
42          :param str msg: Message to be displayed to the user.
43  
44          :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`,
45              or `LOW_PRIORITY`.
46  
47          :param bool on_crash: Whether or not the message should be
48              printed if the program exits abnormally.
49  
50          """
51          assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY
52          self.messages.put(self._msg_type(priority, msg, on_crash))
53          logger.info("Reporting to user: %s", msg)
54  
55      def atexit_print_messages(self, pid=os.getpid()):
56          """Function to be registered with atexit to print messages.
57  
58          :param int pid: Process ID
59  
60          """
61          # This ensures that messages are only printed from the process that
62          # created the Reporter.
63          if pid == os.getpid():
64              self.print_messages()
65  
66      def print_messages(self):
67          """Prints messages to the user and clears the message queue.
68  
69          If there is an unhandled exception, only messages for which
70          ``on_crash`` is ``True`` are printed.
71  
72          """
73          bold_on = False
74          if not self.messages.empty():
75              no_exception = sys.exc_info()[0] is None
76              bold_on = sys.stdout.isatty()
77              if bold_on:
78                  print le_util.ANSI_SGR_BOLD
79              print 'IMPORTANT NOTES:'
80              first_wrapper = textwrap.TextWrapper(
81                  initial_indent=' - ', subsequent_indent=(' ' * 3))
82              next_wrapper = textwrap.TextWrapper(
83                  initial_indent=first_wrapper.subsequent_indent,
84                  subsequent_indent=first_wrapper.subsequent_indent)
85          while not self.messages.empty():
86              msg = self.messages.get()
87              if no_exception or msg.on_crash:
88                  if bold_on and msg.priority > self.HIGH_PRIORITY:
89                      sys.stdout.write(le_util.ANSI_SGR_RESET)
90                      bold_on = False
91                  lines = msg.text.splitlines()
92                  print first_wrapper.fill(lines[0])
93                  if len(lines) > 1:
94                      print "\n".join(
95                          next_wrapper.fill(line) for line in lines[1:])
96          if bold_on:
97              sys.stdout.write(le_util.ANSI_SGR_RESET)