/ 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)