Merge pull request #453 from bradmw/notify

Reporter (tells user about important stuff)
This commit is contained in:
schoen 2015-05-29 12:55:35 -07:00
commit 569a70f6aa
4 changed files with 175 additions and 0 deletions

View file

@ -1,5 +1,6 @@
"""Let's Encrypt CLI."""
# TODO: Sanity check all input. Be sure to avoid shell code etc...
import atexit
import argparse
import logging
import os
@ -20,6 +21,7 @@ from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt import le_util
from letsencrypt import log
from letsencrypt import reporter
from letsencrypt.display import util as display_util
from letsencrypt.display import ops as display_ops
@ -359,6 +361,11 @@ def main(args=sys.argv[1:]):
displayer = display_util.NcursesDisplay()
zope.component.provideUtility(displayer)
# Reporter
report = reporter.Reporter()
zope.component.provideUtility(report)
atexit.register(report.print_messages)
# Logging
level = -args.verbose_count * 10
logger = logging.getLogger()

View file

@ -351,3 +351,30 @@ class IValidator(zope.interface.Interface):
def hsts(name):
"""Verify HSTS header is enabled."""
class IReporter(zope.interface.Interface):
"""Interface to collect and display information to the user."""
HIGH_PRIORITY = zope.interface.Attribute(
"Used to denote high priority messages")
MEDIUM_PRIORITY = zope.interface.Attribute(
"Used to denote medium priority messages")
LOW_PRIORITY = zope.interface.Attribute(
"Used to denote low priority messages")
def add_message(self, msg, priority, on_crash=False):
"""Adds msg to the list of messages to be printed.
:param str msg: Message to be displayed to the user.
:param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or
LOW_PRIORITY.
:param bool on_crash: Whether or not the message should be printed if
the program exits abnormally.
"""
def print_messages(self):
"""Prints messages to the user and clears the message queue."""

68
letsencrypt/reporter.py Normal file
View file

@ -0,0 +1,68 @@
"""Collects and displays information to the user."""
import collections
import Queue
import sys
import textwrap
import zope.interface
from letsencrypt import interfaces
class Reporter(object):
"""Collects and displays information to the user.
:ivar `Queue.PriorityQueue` messages: Messages to be displayed to the user.
"""
zope.interface.implements(interfaces.IReporter)
HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY = xrange(3)
_RESET = '\033[0m'
_BOLD = '\033[1m'
_msg_type = collections.namedtuple('Msg', 'priority, text, on_crash')
def __init__(self):
self.messages = Queue.PriorityQueue()
def add_message(self, msg, priority, on_crash=False):
"""Adds msg to the list of messages to be printed.
:param str msg: Message to be displayed to the user.
:param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or
LOW_PRIORITY.
:param bool on_crash: Whether or not the message should be printed if
the program exits abnormally.
"""
assert priority >= self.HIGH_PRIORITY and priority <= self.LOW_PRIORITY
self.messages.put(self._msg_type(priority, msg, on_crash))
def print_messages(self):
"""Prints messages to the user and clears the message queue.
If there is an unhandled exception, only messages for which on_crash is
True are printed.
"""
bold_on = False
if not self.messages.empty():
no_exception = sys.exc_info()[0] is None
bold_on = sys.stdout.isatty()
if bold_on:
sys.stdout.write(self._BOLD)
print 'IMPORTANT NOTES:'
wrapper = textwrap.TextWrapper(initial_indent=' - ',
subsequent_indent=' '*3)
while not self.messages.empty():
msg = self.messages.get()
if no_exception or msg.on_crash:
if bold_on and msg.priority > self.HIGH_PRIORITY:
sys.stdout.write(self._RESET)
bold_on = False
print wrapper.fill(msg.text)
if bold_on:
sys.stdout.write(self._RESET)

View file

@ -0,0 +1,73 @@
"""Tests for letsencrypt/reporter.py"""
import StringIO
import sys
import unittest
class ReporterTest(unittest.TestCase):
def setUp(self):
from letsencrypt import reporter
self.reporter = reporter.Reporter()
self.old_stdout = sys.stdout
sys.stdout = StringIO.StringIO()
def tearDown(self):
sys.stdout = self.old_stdout
def test_tty_print_empty(self):
sys.stdout.isatty = lambda: True
self.test_no_tty_print_empty()
def test_no_tty_print_empty(self):
self.reporter.print_messages()
self.assertEqual(sys.stdout.getvalue(), "")
try:
raise ValueError
except ValueError:
self.reporter.print_messages()
self.assertEqual(sys.stdout.getvalue(), "")
def test_tty_successful_exit(self):
sys.stdout.isatty = lambda: True
self._successful_exit_common()
def test_no_tty_successful_exit(self):
self._successful_exit_common()
def test_tty_unsuccessful_exit(self):
sys.stdout.isatty = lambda: True
self._unsuccessful_exit_common()
def test_no_tty_unsuccessful_exit(self):
self._unsuccessful_exit_common()
def _successful_exit_common(self):
self._add_messages()
self.reporter.print_messages()
output = sys.stdout.getvalue()
self.assertTrue("IMPORTANT NOTES:" in output)
self.assertTrue("High" in output)
self.assertTrue("Med" in output)
self.assertTrue("Low" in output)
def _unsuccessful_exit_common(self):
self._add_messages()
try:
raise ValueError
except ValueError:
self.reporter.print_messages()
output = sys.stdout.getvalue()
self.assertTrue("IMPORTANT NOTES:" in output)
self.assertTrue("High" in output)
self.assertTrue("Med" not in output)
self.assertTrue("Low" not in output)
def _add_messages(self):
self.reporter.add_message("High", self.reporter.HIGH_PRIORITY, True)
self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY)
self.reporter.add_message("Low", self.reporter.LOW_PRIORITY)
if __name__ == "__main__":
unittest.main() # pragma: no cover