mirror of
https://github.com/certbot/certbot.git
synced 2026-06-03 22:08:07 -04:00
Remove the curses dialog, thereby deprecating the --help and --dialog command line options (#3665)
* Remove the curses dialog, thereby deprecating the --help and --dialog command line options * Deprecate --dialog and suppress --text
This commit is contained in:
parent
ce252bd6c9
commit
d54cb76432
31 changed files with 41 additions and 526 deletions
|
|
@ -103,7 +103,6 @@ addons:
|
|||
- python-dev
|
||||
- python-virtualenv
|
||||
- gcc
|
||||
- dialog
|
||||
- libaugeas0
|
||||
- libssl-dev
|
||||
- libffi-dev
|
||||
|
|
|
|||
1
Vagrantfile
vendored
1
Vagrantfile
vendored
|
|
@ -29,6 +29,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
# VM needs more memory to run test suite, got "OSError: [Errno 12]
|
||||
# Cannot allocate memory" when running
|
||||
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest
|
||||
# We may no longer need this.
|
||||
v.memory = 1024
|
||||
|
||||
# Handle cases when the host is behind a private network by making the
|
||||
|
|
|
|||
|
|
@ -100,5 +100,4 @@ def _vhost_menu(domain, vhosts):
|
|||
def _more_info_vhost(vhost):
|
||||
zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
"Virtual Host Information:{0}{1}{0}{2}".format(
|
||||
os.linesep, "-" * (display_util.WIDTH - 4), str(vhost)),
|
||||
height=display_util.HEIGHT)
|
||||
os.linesep, "-" * (display_util.WIDTH - 4), str(vhost)))
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ class HelpfulArgumentParser(object):
|
|||
|
||||
# Do any post-parsing homework here
|
||||
|
||||
if self.verb == "renew" and not parsed_args.dialog_mode:
|
||||
if self.verb == "renew":
|
||||
parsed_args.noninteractive_mode = True
|
||||
|
||||
if parsed_args.staging or parsed_args.dry_run:
|
||||
|
|
@ -388,17 +388,6 @@ class HelpfulArgumentParser(object):
|
|||
if parsed_args.must_staple:
|
||||
parsed_args.staple = True
|
||||
|
||||
# Avoid conflicting args
|
||||
conficting_args = ["quiet", "noninteractive_mode", "text_mode"]
|
||||
if parsed_args.dialog_mode:
|
||||
for arg in conficting_args:
|
||||
if getattr(parsed_args, arg):
|
||||
raise errors.Error(
|
||||
("Conflicting values for displayer."
|
||||
" {0} conflicts with dialog_mode").format(arg))
|
||||
elif parsed_args.verbose_count > flag_default("verbose_count"):
|
||||
parsed_args.text_mode = True
|
||||
|
||||
if parsed_args.validate_hooks:
|
||||
hooks.validate_hooks(parsed_args)
|
||||
|
||||
|
|
@ -677,16 +666,13 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
|||
"e.g. -vvv.")
|
||||
helpful.add(
|
||||
None, "-t", "--text", dest="text_mode", action="store_true",
|
||||
help="Use the text output instead of the curses UI.")
|
||||
help=argparse.SUPPRESS)
|
||||
helpful.add(
|
||||
[None, "automation"], "-n", "--non-interactive", "--noninteractive",
|
||||
dest="noninteractive_mode", action="store_true",
|
||||
help="Run without ever asking for user input. This may require "
|
||||
"additional command line flags; the client will try to explain "
|
||||
"which ones are required if it finds one missing")
|
||||
helpful.add(
|
||||
None, "--dialog", dest="dialog_mode", action="store_true",
|
||||
help="Run using interactive dialog menus")
|
||||
helpful.add(
|
||||
[None, "run", "certonly"],
|
||||
"-d", "--domains", "--domain", dest="domains",
|
||||
|
|
@ -890,6 +876,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
|||
" shell constructs, so you can use this switch to disable it.")
|
||||
|
||||
helpful.add_deprecated_argument("--agree-dev-preview", 0)
|
||||
helpful.add_deprecated_argument("--dialog", 0)
|
||||
|
||||
_create_subparsers(helpful)
|
||||
_paths_parser(helpful)
|
||||
|
|
|
|||
|
|
@ -219,7 +219,6 @@ def success_installation(domains):
|
|||
_gen_https_names(domains),
|
||||
os.linesep,
|
||||
os.linesep.join(_gen_ssl_lab_urls(domains))),
|
||||
height=(10 + len(domains)),
|
||||
pause=False)
|
||||
|
||||
|
||||
|
|
@ -241,7 +240,6 @@ def success_renewal(domains, action):
|
|||
os.linesep,
|
||||
os.linesep.join(_gen_ssl_lab_urls(domains)),
|
||||
action),
|
||||
height=(14 + len(domains)),
|
||||
pause=False)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
"""Certbot display."""
|
||||
import logging
|
||||
import os
|
||||
import textwrap
|
||||
|
||||
import dialog
|
||||
import six
|
||||
import zope.interface
|
||||
|
||||
|
|
@ -12,17 +10,7 @@ from certbot import errors
|
|||
from certbot.display import completer
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WIDTH = 72
|
||||
HEIGHT = 20
|
||||
|
||||
DSELECT_HELP = (
|
||||
"Use the arrow keys or Tab to move between window elements. Space can be "
|
||||
"used to complete the input path with the selected element in the "
|
||||
"directory window. Pressing enter will select the currently highlighted "
|
||||
"button.")
|
||||
"""Help text on how to use dialog's dselect."""
|
||||
|
||||
# Display exit codes
|
||||
OK = "ok"
|
||||
|
|
@ -59,170 +47,6 @@ def _wrap_lines(msg):
|
|||
|
||||
return os.linesep.join(fixed_l)
|
||||
|
||||
|
||||
def _clean(dialog_result):
|
||||
"""Treat sundy python-dialog return codes as CANCEL
|
||||
|
||||
:param tuple dialog_result: (code, result)
|
||||
:returns: the argument but with unknown codes set to -1 (Error)
|
||||
:rtype: tuple
|
||||
"""
|
||||
code, result = dialog_result
|
||||
if code in (OK, HELP):
|
||||
return dialog_result
|
||||
elif code in (CANCEL, ESC):
|
||||
return (CANCEL, result)
|
||||
else:
|
||||
logger.debug("Surprising dialog return code %s", code)
|
||||
return (CANCEL, result)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IDisplay)
|
||||
class NcursesDisplay(object):
|
||||
"""Ncurses-based display."""
|
||||
|
||||
def __init__(self, width=WIDTH, height=HEIGHT):
|
||||
super(NcursesDisplay, self).__init__()
|
||||
self.dialog = dialog.Dialog(autowidgetsize=True)
|
||||
assert OK == self.dialog.DIALOG_OK, "What kind of absurdity is this?"
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def notification(self, message, height=10, pause=False):
|
||||
# pylint: disable=unused-argument
|
||||
"""Display a notification to the user and wait for user acceptance.
|
||||
|
||||
.. todo:: It probably makes sense to use one of the transient message
|
||||
types for pause. It isn't straightforward how best to approach
|
||||
the matter though given the context of our messages.
|
||||
http://pythondialog.sourceforge.net/doc/widgets.html#displaying-transient-messages
|
||||
|
||||
:param str message: Message to display
|
||||
:param int height: Height of the dialog box
|
||||
:param bool pause: Not applicable to NcursesDisplay
|
||||
|
||||
"""
|
||||
self.dialog.msgbox(message)
|
||||
|
||||
def menu(self, message, choices, ok_label="OK", cancel_label="Cancel",
|
||||
help_label="", **unused_kwargs):
|
||||
"""Display a menu.
|
||||
|
||||
:param str message: title of menu
|
||||
|
||||
:param choices: menu lines, len must be > 0
|
||||
:type choices: list of tuples (`tag`, `item`) tags must be unique or
|
||||
list of items (tags will be enumerated)
|
||||
|
||||
:param str ok_label: label of the OK button
|
||||
:param str help_label: label of the help button
|
||||
:param dict unused_kwargs: absorbs default / cli_args
|
||||
|
||||
:returns: tuple of the form (`code`, `index`) where
|
||||
`code` - display exit code
|
||||
`int` - index of the selected item
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
menu_options = {
|
||||
"choices": choices,
|
||||
"ok_label": ok_label,
|
||||
"cancel_label": cancel_label,
|
||||
"help_button": bool(help_label),
|
||||
"help_label": help_label,
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"menu_height": self.height - 6,
|
||||
}
|
||||
|
||||
# Can accept either tuples or just the actual choices
|
||||
if choices and isinstance(choices[0], tuple):
|
||||
# pylint: disable=star-args
|
||||
code, selection = _clean(self.dialog.menu(message, **menu_options))
|
||||
|
||||
# Return the selection index
|
||||
for i, choice in enumerate(choices):
|
||||
if choice[0] == selection:
|
||||
return code, i
|
||||
|
||||
return code, -1
|
||||
|
||||
else:
|
||||
# "choices" is not formatted the way the dialog.menu expects...
|
||||
menu_options["choices"] = [
|
||||
(str(i), choice) for i, choice in enumerate(choices, 1)
|
||||
]
|
||||
# pylint: disable=star-args
|
||||
code, index = _clean(self.dialog.menu(message, **menu_options))
|
||||
|
||||
if code == CANCEL or index == "":
|
||||
return code, -1
|
||||
|
||||
return code, int(index) - 1
|
||||
|
||||
def input(self, message, **unused_kwargs):
|
||||
"""Display an input box to the user.
|
||||
|
||||
:param str message: Message to display that asks for input.
|
||||
:param dict _kwargs: absorbs default / cli_args
|
||||
|
||||
:returns: tuple of the form (`code`, `string`) where
|
||||
`code` - display exit code
|
||||
`string` - input entered by the user
|
||||
|
||||
"""
|
||||
return self.dialog.inputbox(message)
|
||||
|
||||
def yesno(self, message, yes_label="Yes", no_label="No", **unused_kwargs):
|
||||
"""Display a Yes/No dialog box.
|
||||
|
||||
Yes and No label must begin with different letters.
|
||||
|
||||
:param str message: message to display to user
|
||||
:param str yes_label: label on the "yes" button
|
||||
:param str no_label: label on the "no" button
|
||||
:param dict _kwargs: absorbs default / cli_args
|
||||
|
||||
:returns: if yes_label was selected
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
return self.dialog.DIALOG_OK == self.dialog.yesno(
|
||||
message, yes_label=yes_label, no_label=no_label)
|
||||
|
||||
def checklist(self, message, tags, default_status=True, **unused_kwargs):
|
||||
"""Displays a checklist.
|
||||
|
||||
:param message: Message to display before choices
|
||||
:param list tags: where each is of type :class:`str` len(tags) > 0
|
||||
:param bool default_status: If True, items are in a selected state by
|
||||
default.
|
||||
:param dict _kwargs: absorbs default / cli_args
|
||||
|
||||
|
||||
:returns: tuple of the form (`code`, `list_tags`) where
|
||||
`code` - display exit code
|
||||
`list_tags` - list of str tags selected by the user
|
||||
|
||||
"""
|
||||
choices = [(tag, "", default_status) for tag in tags]
|
||||
return self.dialog.checklist(message, choices=choices)
|
||||
|
||||
def directory_select(self, message, **unused_kwargs):
|
||||
"""Display a directory selection screen.
|
||||
|
||||
:param str message: prompt to give the user
|
||||
|
||||
:returns: tuple of the form (`code`, `string`) where
|
||||
`code` - display exit code
|
||||
`string` - input entered by the user
|
||||
|
||||
"""
|
||||
root_directory = os.path.abspath(os.sep)
|
||||
return self.dialog.dselect(
|
||||
filepath=root_directory, help_button=True, title=message)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IDisplay)
|
||||
class FileDisplay(object):
|
||||
"""File-based display."""
|
||||
|
|
@ -231,12 +55,11 @@ class FileDisplay(object):
|
|||
super(FileDisplay, self).__init__()
|
||||
self.outfile = outfile
|
||||
|
||||
def notification(self, message, height=10, pause=True):
|
||||
def notification(self, message, pause=True):
|
||||
# pylint: disable=unused-argument
|
||||
"""Displays a notification and waits for user acceptance.
|
||||
|
||||
:param str message: Message to display
|
||||
:param int height: No effect for FileDisplay
|
||||
:param bool pause: Whether or not the program should pause for the
|
||||
user's confirmation
|
||||
|
||||
|
|
@ -496,12 +319,11 @@ class NoninteractiveDisplay(object):
|
|||
msg += "\n\n(You can set this with the {0} flag)".format(cli_flag)
|
||||
raise errors.MissingCommandlineFlag(msg)
|
||||
|
||||
def notification(self, message, height=10, pause=False):
|
||||
def notification(self, message, pause=False):
|
||||
# pylint: disable=unused-argument
|
||||
"""Displays a notification without waiting for user acceptance.
|
||||
|
||||
:param str message: Message to display to stdout
|
||||
:param int height: No effect for NoninteractiveDisplay
|
||||
:param bool pause: The NoninteractiveDisplay waits for no keyboard
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -378,11 +378,10 @@ class IInstaller(IPlugin):
|
|||
class IDisplay(zope.interface.Interface):
|
||||
"""Generic display."""
|
||||
|
||||
def notification(message, height, pause):
|
||||
def notification(message, pause):
|
||||
"""Displays a string message
|
||||
|
||||
:param str message: Message to display
|
||||
:param int height: Height of dialog box if applicable
|
||||
:param bool pause: Whether or not the application should pause for
|
||||
confirmation (if available)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
"""Logging utilities."""
|
||||
import logging
|
||||
|
||||
import dialog
|
||||
|
||||
from certbot.display import util as display_util
|
||||
|
||||
|
||||
class DialogHandler(logging.Handler): # pylint: disable=too-few-public-methods
|
||||
"""Logging handler using dialog info box.
|
||||
|
||||
:ivar int height: Height of the info box (without padding).
|
||||
:ivar int width: Width of the info box (without padding).
|
||||
:ivar list lines: Lines to be displayed in the info box.
|
||||
:ivar d: Instance of :class:`dialog.Dialog`.
|
||||
|
||||
"""
|
||||
|
||||
PADDING_HEIGHT = 2
|
||||
PADDING_WIDTH = 4
|
||||
|
||||
def __init__(self, level=logging.NOTSET, height=display_util.HEIGHT,
|
||||
width=display_util.WIDTH - 4, d=None):
|
||||
# Handler not new-style -> no super
|
||||
logging.Handler.__init__(self, level)
|
||||
self.height = height
|
||||
self.width = width
|
||||
# "dialog" collides with module name...
|
||||
self.d = dialog.Dialog() if d is None else d
|
||||
self.lines = []
|
||||
|
||||
def emit(self, record):
|
||||
"""Emit message to a dialog info box.
|
||||
|
||||
Only show the last (self.height) lines; note that lines can wrap
|
||||
at self.width, so a single line could actually be multiple
|
||||
lines.
|
||||
|
||||
"""
|
||||
for line in self.format(record).splitlines():
|
||||
# check for lines that would wrap
|
||||
cur_out = line
|
||||
while len(cur_out) > self.width:
|
||||
# find first space before self.width chars into cur_out
|
||||
last_space_pos = cur_out.rfind(' ', 0, self.width)
|
||||
|
||||
if last_space_pos == -1:
|
||||
# no spaces, just cut them off at whatever
|
||||
self.lines.append(cur_out[0:self.width])
|
||||
cur_out = cur_out[self.width:]
|
||||
else:
|
||||
# cut off at last space
|
||||
self.lines.append(cur_out[0:last_space_pos])
|
||||
cur_out = cur_out[last_space_pos + 1:]
|
||||
if cur_out != '':
|
||||
self.lines.append(cur_out)
|
||||
|
||||
# show last 16 lines
|
||||
content = '\n'.join(self.lines[-self.height:])
|
||||
|
||||
# add the padding around the box
|
||||
self.d.infobox(
|
||||
content, self.height + self.PADDING_HEIGHT,
|
||||
self.width + self.PADDING_WIDTH)
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
"""Certbot main entry point."""
|
||||
from __future__ import print_function
|
||||
import atexit
|
||||
import dialog
|
||||
import functools
|
||||
import logging.handlers
|
||||
import os
|
||||
|
|
@ -27,7 +26,6 @@ from certbot import errors
|
|||
from certbot import hooks
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot import log
|
||||
from certbot import reporter
|
||||
from certbot import renewal
|
||||
from certbot import storage
|
||||
|
|
@ -614,14 +612,9 @@ def setup_log_file_handler(config, logfile, fmt):
|
|||
return handler, log_file_path
|
||||
|
||||
|
||||
def _cli_log_handler(config, level, fmt):
|
||||
if config.text_mode or config.noninteractive_mode or config.verb == "renew":
|
||||
handler = colored_logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt))
|
||||
else:
|
||||
handler = log.DialogHandler()
|
||||
# dialog box is small, display as less as possible
|
||||
handler.setFormatter(logging.Formatter("%(message)s"))
|
||||
def _cli_log_handler(level, fmt):
|
||||
handler = colored_logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt))
|
||||
handler.setLevel(level)
|
||||
return handler
|
||||
|
||||
|
|
@ -641,7 +634,7 @@ def setup_logging(config):
|
|||
level = -config.verbose_count * 10
|
||||
file_handler, log_file_path = setup_log_file_handler(
|
||||
config, logfile=logfile, fmt=file_fmt)
|
||||
cli_handler = _cli_log_handler(config, level, cli_fmt)
|
||||
cli_handler = _cli_log_handler(level, cli_fmt)
|
||||
|
||||
# TODO: use fileConfig?
|
||||
|
||||
|
|
@ -687,10 +680,7 @@ def _handle_exception(exc_type, exc_value, trace, config):
|
|||
# Here we're passing a client or ACME error out to the client at the shell
|
||||
# Tell the user a bit about what happened, without overwhelming
|
||||
# them with a full traceback
|
||||
if issubclass(exc_type, dialog.error):
|
||||
err = exc_value.complete_message()
|
||||
else:
|
||||
err = traceback.format_exception_only(exc_type, exc_value)[0]
|
||||
err = traceback.format_exception_only(exc_type, exc_value)[0]
|
||||
# Typical error from the ACME module:
|
||||
# acme.messages.Error: urn:ietf:params:acme:error:malformed :: The
|
||||
# request message was malformed :: Error creating new registration
|
||||
|
|
@ -764,10 +754,8 @@ def main(cli_args=sys.argv[1:]):
|
|||
displayer = display_util.NoninteractiveDisplay(open(os.devnull, "w"))
|
||||
elif config.noninteractive_mode:
|
||||
displayer = display_util.NoninteractiveDisplay(sys.stdout)
|
||||
elif config.text_mode:
|
||||
displayer = display_util.FileDisplay(sys.stdout)
|
||||
else:
|
||||
displayer = display_util.NcursesDisplay()
|
||||
displayer = display_util.FileDisplay(sys.stdout)
|
||||
zope.component.provideUtility(displayer)
|
||||
|
||||
# Reporter
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ s.serve_forever()" """
|
|||
# pylint: disable=no-self-use
|
||||
# TODO: IDisplay wraps messages, breaking the command
|
||||
#answer = zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
# message=message, height=25, pause=True)
|
||||
# message=message, pause=True)
|
||||
sys.stdout.write(message)
|
||||
six.moves.input("Press ENTER to continue")
|
||||
|
||||
|
|
|
|||
|
|
@ -119,8 +119,7 @@ def choose_plugin(prepared, question):
|
|||
z_util(interfaces.IDisplay).notification(
|
||||
"The selected plugin encountered an error while parsing "
|
||||
"your server configuration and cannot be used. The error "
|
||||
"was:\n\n{0}".format(plugin_ep.prepare()),
|
||||
height=display_util.HEIGHT, pause=False)
|
||||
"was:\n\n{0}".format(plugin_ep.prepare()), pause=False)
|
||||
else:
|
||||
return plugin_ep
|
||||
elif code == display_util.HELP:
|
||||
|
|
@ -128,8 +127,7 @@ def choose_plugin(prepared, question):
|
|||
msg = "Reported Error: %s" % prepared[index].prepare()
|
||||
else:
|
||||
msg = prepared[index].init().more_info()
|
||||
z_util(interfaces.IDisplay).notification(
|
||||
msg, height=display_util.HEIGHT)
|
||||
z_util(interfaces.IDisplay).notification(msg)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ def already_listening_socket(port, renewer=False):
|
|||
"Port {0} is already in use by another process. This will "
|
||||
"prevent us from binding to that port. Please stop the "
|
||||
"process that is populating the port in question and try "
|
||||
"again. {1}".format(port, extra), height=13)
|
||||
"again. {1}".format(port, extra))
|
||||
return True
|
||||
finally:
|
||||
testsocket.close()
|
||||
|
|
@ -151,8 +151,7 @@ def already_listening_psutil(port, renewer=False):
|
|||
"The program {0} (process ID {1}) is already listening "
|
||||
"on TCP port {2}. This will prevent us from binding to "
|
||||
"that port. Please stop the {0} program temporarily "
|
||||
"and then try again.{3}".format(name, pid, port, extra),
|
||||
height=13)
|
||||
"and then try again.{3}".format(name, pid, port, extra))
|
||||
return True
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
# Perhaps the result of a race where the process could have
|
||||
|
|
|
|||
|
|
@ -140,9 +140,8 @@ to serve all files under specified web root ({0})."""
|
|||
code, webroot = display.directory_select(
|
||||
"Input the webroot for {0}:".format(domain))
|
||||
if code == display_util.HELP:
|
||||
# Help can currently only be selected
|
||||
# when using the ncurses interface
|
||||
display.notification(display_util.DSELECT_HELP)
|
||||
# Displaying help is not currently implemented
|
||||
return None
|
||||
elif code == display_util.CANCEL:
|
||||
return None
|
||||
else: # code == display_util.OK
|
||||
|
|
|
|||
|
|
@ -111,8 +111,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
|
||||
self.assertTrue(mock_display.notification.called)
|
||||
for call in mock_display.notification.call_args_list:
|
||||
self.assertTrue(imaginary_dir in call[0][0] or
|
||||
display_util.DSELECT_HELP == call[0][0])
|
||||
self.assertTrue(imaginary_dir in call[0][0])
|
||||
|
||||
self.assertTrue(mock_display.directory_select.called)
|
||||
for call in mock_display.directory_select.call_args_list:
|
||||
|
|
|
|||
|
|
@ -15,8 +15,6 @@ from certbot import errors
|
|||
from certbot import interfaces
|
||||
from certbot import util
|
||||
|
||||
from certbot.display import util as display_util
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -183,7 +181,7 @@ class Reverter(object):
|
|||
if for_logging:
|
||||
return os.linesep.join(output)
|
||||
zope.component.getUtility(interfaces.IDisplay).notification(
|
||||
os.linesep.join(output), display_util.HEIGHT)
|
||||
os.linesep.join(output))
|
||||
|
||||
def add_to_temp_checkpoint(self, save_files, save_notes):
|
||||
"""Add files to temporary checkpoint.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import dialog
|
||||
import functools
|
||||
import itertools
|
||||
import os
|
||||
|
|
@ -51,6 +50,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self.config_dir = os.path.join(self.tmp_dir, 'config')
|
||||
self.work_dir = os.path.join(self.tmp_dir, 'work')
|
||||
self.logs_dir = os.path.join(self.tmp_dir, 'logs')
|
||||
os.mkdir(self.logs_dir)
|
||||
self.standard_args = ['--config-dir', self.config_dir,
|
||||
'--work-dir', self.work_dir,
|
||||
'--logs-dir', self.logs_dir, '--text']
|
||||
|
|
@ -98,6 +98,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self.assertTrue("--configurator" in out)
|
||||
self.assertTrue("how a cert is deployed" in out)
|
||||
self.assertTrue("--manual-test-mode" in out)
|
||||
self.assertTrue("--text" not in out)
|
||||
self.assertTrue("--dialog" not in out)
|
||||
|
||||
out = self._help_output(['-h', 'nginx'])
|
||||
if "nginx" in plugins:
|
||||
|
|
@ -163,12 +165,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self._cli_missing_flag(args, "--agree-tos")
|
||||
|
||||
@mock.patch('certbot.main.renew')
|
||||
def test_gui(self, renew):
|
||||
def test_no_gui(self, renew):
|
||||
args = ['renew', '--dialog']
|
||||
# --text conflicts with --dialog
|
||||
self.standard_args.remove('--text')
|
||||
# --dialog should have no effect
|
||||
self._call(args)
|
||||
self.assertFalse(renew.call_args[0][0].noninteractive_mode)
|
||||
self.assertTrue(renew.call_args[0][0].noninteractive_mode)
|
||||
|
||||
@mock.patch('certbot.main.client.acme_client.Client')
|
||||
@mock.patch('certbot.main._determine_account')
|
||||
|
|
@ -656,9 +657,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
log_out="not yet due", should_renew=False)
|
||||
|
||||
def _dump_log(self):
|
||||
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
|
||||
print("Logs:")
|
||||
print(lf.read())
|
||||
print("Logs:")
|
||||
log_path = os.path.join(self.logs_dir, "letsencrypt.log")
|
||||
if os.path.exists(log_path):
|
||||
with open(log_path) as lf:
|
||||
print(lf.read())
|
||||
|
||||
def _make_lineage(self, testfile):
|
||||
"""Creates a lineage defined by testfile.
|
||||
|
|
@ -977,13 +980,6 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
mock_sys.exit.assert_called_with(''.join(
|
||||
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
|
||||
|
||||
# Test dialog errors
|
||||
exception = dialog.error(message="test message")
|
||||
main._handle_exception(
|
||||
dialog.DialogError, exc_value=exception, trace=None, config=None)
|
||||
error_msg = mock_sys.exit.call_args_list[-1][0][0]
|
||||
self.assertTrue("test message" in error_msg)
|
||||
|
||||
def test_read_file(self):
|
||||
rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo'))
|
||||
self.assertRaises(
|
||||
|
|
@ -1070,17 +1066,6 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self.assertTrue(
|
||||
email in mock_utility().add_message.call_args[0][0])
|
||||
|
||||
def test_conflicting_args(self):
|
||||
args = ['renew', '--dialog', '--text']
|
||||
self.assertRaises(errors.Error, self._call, args)
|
||||
|
||||
def test_text_mode_when_verbose(self):
|
||||
parse = self._get_argument_parser()
|
||||
short_args = ['-v']
|
||||
namespace = parse(short_args)
|
||||
self.assertTrue(namespace.text_mode)
|
||||
|
||||
|
||||
class DetermineAccountTest(unittest.TestCase):
|
||||
"""Tests for certbot.cli._determine_account."""
|
||||
|
||||
|
|
|
|||
|
|
@ -13,122 +13,6 @@ CHOICES = [("First", "Description1"), ("Second", "Description2")]
|
|||
TAGS = ["tag1", "tag2", "tag3"]
|
||||
TAGS_CHOICES = [("1", "tag1"), ("2", "tag2"), ("3", "tag3")]
|
||||
|
||||
|
||||
class NcursesDisplayTest(unittest.TestCase):
|
||||
"""Test ncurses display.
|
||||
|
||||
Since this is mostly a wrapper, it might be more helpful to test the
|
||||
actual dialog boxes. The test file located in ./tests/display.py
|
||||
(relative to the root of the repository) will actually display the
|
||||
various boxes but requires the user to do the verification. If
|
||||
something seems amiss please use that test script to debug it, the
|
||||
automatic tests rely on too much mocking.
|
||||
|
||||
"""
|
||||
def setUp(self):
|
||||
super(NcursesDisplayTest, self).setUp()
|
||||
self.displayer = display_util.NcursesDisplay()
|
||||
|
||||
self.default_menu_options = {
|
||||
"choices": CHOICES,
|
||||
"ok_label": "OK",
|
||||
"cancel_label": "Cancel",
|
||||
"help_button": False,
|
||||
"help_label": "",
|
||||
"width": display_util.WIDTH,
|
||||
"height": display_util.HEIGHT,
|
||||
"menu_height": display_util.HEIGHT - 6,
|
||||
}
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.msgbox")
|
||||
def test_notification(self, mock_msgbox):
|
||||
"""Kind of worthless... one liner."""
|
||||
self.displayer.notification("message")
|
||||
self.assertEqual(mock_msgbox.call_count, 1)
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.menu")
|
||||
def test_menu_tag_and_desc(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.OK, "First")
|
||||
|
||||
ret = self.displayer.menu("Message", CHOICES)
|
||||
mock_menu.assert_called_with("Message", **self.default_menu_options)
|
||||
|
||||
self.assertEqual(ret, (display_util.OK, 0))
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.menu")
|
||||
def test_menu_tag_and_desc_cancel(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.CANCEL, "")
|
||||
|
||||
ret = self.displayer.menu("Message", CHOICES)
|
||||
|
||||
mock_menu.assert_called_with("Message", **self.default_menu_options)
|
||||
|
||||
self.assertEqual(ret, (display_util.CANCEL, -1))
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.menu")
|
||||
def test_menu_desc_only(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.OK, "1")
|
||||
|
||||
ret = self.displayer.menu("Message", TAGS, help_label="More Info")
|
||||
|
||||
self.default_menu_options.update(
|
||||
choices=TAGS_CHOICES, help_button=True, help_label="More Info")
|
||||
mock_menu.assert_called_with("Message", **self.default_menu_options)
|
||||
|
||||
self.assertEqual(ret, (display_util.OK, 0))
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.menu")
|
||||
def test_menu_desc_only_help(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.HELP, "2")
|
||||
|
||||
ret = self.displayer.menu("Message", TAGS, help_label="More Info")
|
||||
|
||||
self.assertEqual(ret, (display_util.HELP, 1))
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.menu")
|
||||
def test_menu_desc_only_cancel(self, mock_menu):
|
||||
mock_menu.return_value = (display_util.CANCEL, "")
|
||||
|
||||
ret = self.displayer.menu("Message", TAGS, help_label="More Info")
|
||||
|
||||
self.assertEqual(ret, (display_util.CANCEL, -1))
|
||||
|
||||
@mock.patch("certbot.display.util."
|
||||
"dialog.Dialog.inputbox")
|
||||
def test_input(self, mock_input):
|
||||
mock_input.return_value = (mock.MagicMock(), mock.MagicMock())
|
||||
self.displayer.input("message")
|
||||
self.assertEqual(mock_input.call_count, 1)
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.yesno")
|
||||
def test_yesno(self, mock_yesno):
|
||||
mock_yesno.return_value = display_util.OK
|
||||
|
||||
self.assertTrue(self.displayer.yesno("message"))
|
||||
|
||||
mock_yesno.assert_called_with(
|
||||
"message", yes_label="Yes", no_label="No")
|
||||
|
||||
@mock.patch("certbot.display.util."
|
||||
"dialog.Dialog.checklist")
|
||||
def test_checklist(self, mock_checklist):
|
||||
mock_checklist.return_value = (mock.MagicMock(), mock.MagicMock())
|
||||
self.displayer.checklist("message", TAGS)
|
||||
|
||||
choices = [
|
||||
(TAGS[0], "", True),
|
||||
(TAGS[1], "", True),
|
||||
(TAGS[2], "", True),
|
||||
]
|
||||
mock_checklist.assert_called_with("message", choices=choices)
|
||||
|
||||
@mock.patch("certbot.display.util.dialog.Dialog.dselect")
|
||||
def test_directory_select(self, mock_dselect):
|
||||
mock_dselect.return_value = (mock.MagicMock(), mock.MagicMock())
|
||||
self.displayer.directory_select("message")
|
||||
self.assertEqual(mock_dselect.call_count, 1)
|
||||
|
||||
|
||||
class FileOutputDisplayTest(unittest.TestCase):
|
||||
"""Test stdout display.
|
||||
|
||||
|
|
@ -142,7 +26,7 @@ class FileOutputDisplayTest(unittest.TestCase):
|
|||
self.displayer = display_util.FileDisplay(self.mock_stdout)
|
||||
|
||||
def test_notification_no_pause(self):
|
||||
self.displayer.notification("message", 10, False)
|
||||
self.displayer.notification("message", False)
|
||||
string = self.mock_stdout.write.call_args[0][0]
|
||||
|
||||
self.assertTrue("message" in string)
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
"""Tests for certbot.log."""
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
|
||||
class DialogHandlerTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.d = mock.MagicMock()
|
||||
|
||||
from certbot.log import DialogHandler
|
||||
self.handler = DialogHandler(height=2, width=6, d=self.d)
|
||||
self.handler.PADDING_HEIGHT = 2
|
||||
self.handler.PADDING_WIDTH = 4
|
||||
|
||||
def test_adds_padding(self):
|
||||
self.handler.emit(logging.makeLogRecord({}))
|
||||
self.d.infobox.assert_called_once_with(mock.ANY, 4, 10)
|
||||
|
||||
def test_args_in_msg_get_replaced(self):
|
||||
assert len('123456') <= self.handler.width
|
||||
self.handler.emit(logging.makeLogRecord(
|
||||
{'msg': '123%s', 'args': (456,)}))
|
||||
self.d.infobox.assert_called_once_with('123456', mock.ANY, mock.ANY)
|
||||
|
||||
def test_wraps_nospace_is_greedy(self):
|
||||
assert len('1234567') > self.handler.width
|
||||
self.handler.emit(logging.makeLogRecord({'msg': '1234567'}))
|
||||
self.d.infobox.assert_called_once_with('123456\n7', mock.ANY, mock.ANY)
|
||||
|
||||
def test_wraps_at_whitespace(self):
|
||||
assert len('123 567') > self.handler.width
|
||||
self.handler.emit(logging.makeLogRecord({'msg': '123 567'}))
|
||||
self.d.infobox.assert_called_once_with('123\n567', mock.ANY, mock.ANY)
|
||||
|
||||
def test_only_last_lines_are_printed(self):
|
||||
assert len('a\nb\nc'.split()) > self.handler.height
|
||||
self.handler.emit(logging.makeLogRecord({'msg': 'a\n\nb\nc'}))
|
||||
self.d.infobox.assert_called_once_with('b\nc', mock.ANY, mock.ANY)
|
||||
|
||||
def test_non_str(self):
|
||||
self.handler.emit(logging.makeLogRecord({'msg': {'foo': 'bar'}}))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -11,7 +11,6 @@ from certbot import colored_logging
|
|||
from certbot import constants
|
||||
from certbot import configuration
|
||||
from certbot import errors
|
||||
from certbot import log
|
||||
from certbot.plugins import disco as plugins_disco
|
||||
|
||||
class MainTest(unittest.TestCase):
|
||||
|
|
@ -55,9 +54,9 @@ class ObtainCertTest(unittest.TestCase):
|
|||
mock_notification = self.mock_get_utility().notification
|
||||
mock_notification.side_effect = self._assert_no_pause
|
||||
mock_auth.return_value = ('reinstall', mock.ANY)
|
||||
self._call('certonly --webroot -d example.com -t'.split())
|
||||
self._call('certonly --webroot -d example.com'.split())
|
||||
|
||||
def _assert_no_pause(self, message, height=42, pause=True):
|
||||
def _assert_no_pause(self, message, pause=True):
|
||||
# pylint: disable=unused-argument
|
||||
self.assertFalse(pause)
|
||||
|
||||
|
|
@ -89,7 +88,7 @@ class SetupLoggingTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.config = mock.Mock(
|
||||
logs_dir=tempfile.mkdtemp(),
|
||||
noninteractive_mode=False, quiet=False, text_mode=False,
|
||||
noninteractive_mode=False, quiet=False,
|
||||
verbose_count=constants.CLI_DEFAULTS['verbose_count'])
|
||||
|
||||
def tearDown(self):
|
||||
|
|
@ -107,7 +106,7 @@ class SetupLoggingTest(unittest.TestCase):
|
|||
cli_handler = mock_get_logger().addHandler.call_args_list[0][0][0]
|
||||
self.assertEqual(cli_handler.level, -self.config.verbose_count * 10)
|
||||
self.assertTrue(
|
||||
isinstance(cli_handler, log.DialogHandler))
|
||||
isinstance(cli_handler, colored_logging.StreamHandler))
|
||||
|
||||
@mock.patch('certbot.main.logging.getLogger')
|
||||
def test_quiet_mode(self, mock_get_logger):
|
||||
|
|
|
|||
|
|
@ -248,7 +248,6 @@ BootstrapDebCommon() {
|
|||
python-dev \
|
||||
$virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
$augeas_pkg \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
|
|
@ -307,7 +306,6 @@ BootstrapRpmCommon() {
|
|||
|
||||
pkgs="
|
||||
gcc
|
||||
dialog
|
||||
augeas-libs
|
||||
openssl
|
||||
openssl-devel
|
||||
|
|
@ -361,7 +359,6 @@ BootstrapSuseCommon() {
|
|||
python-devel \
|
||||
python-virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-lenses \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
|
|
@ -380,7 +377,6 @@ BootstrapArchCommon() {
|
|||
python2
|
||||
python-virtualenv
|
||||
gcc
|
||||
dialog
|
||||
augeas
|
||||
openssl
|
||||
libffi
|
||||
|
|
@ -404,7 +400,6 @@ BootstrapGentooCommon() {
|
|||
PACKAGES="
|
||||
dev-lang/python:2.7
|
||||
dev-python/virtualenv
|
||||
dev-util/dialog
|
||||
app-admin/augeas
|
||||
dev-libs/openssl
|
||||
dev-libs/libffi
|
||||
|
|
@ -449,7 +444,6 @@ BootstrapMac() {
|
|||
fi
|
||||
|
||||
$pkgcmd augeas
|
||||
$pkgcmd dialog
|
||||
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
|
||||
-o "$(which python)" = "/usr/bin/python" ]; then
|
||||
# We want to avoid using the system Python because it requires root to use pip.
|
||||
|
|
@ -496,7 +490,6 @@ BootstrapMageiaCommon() {
|
|||
if ! $SUDO urpmi --force \
|
||||
git \
|
||||
gcc \
|
||||
cdialog \
|
||||
python-augeas \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
|
|
@ -701,9 +694,6 @@ pyRFC3339==1.0 \
|
|||
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
|
||||
python-augeas==0.5.0 \
|
||||
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
|
||||
python2-pythondialog==3.3.0 \
|
||||
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
|
||||
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
|
||||
pytz==2015.7 \
|
||||
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
|
||||
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ BootstrapArchCommon() {
|
|||
python2
|
||||
python-virtualenv
|
||||
gcc
|
||||
dialog
|
||||
augeas
|
||||
openssl
|
||||
libffi
|
||||
|
|
|
|||
|
|
@ -94,7 +94,6 @@ BootstrapDebCommon() {
|
|||
python-dev \
|
||||
$virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
$augeas_pkg \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ BootstrapGentooCommon() {
|
|||
PACKAGES="
|
||||
dev-lang/python:2.7
|
||||
dev-python/virtualenv
|
||||
dev-util/dialog
|
||||
app-admin/augeas
|
||||
dev-libs/openssl
|
||||
dev-libs/libffi
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ BootstrapMac() {
|
|||
fi
|
||||
|
||||
$pkgcmd augeas
|
||||
$pkgcmd dialog
|
||||
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
|
||||
-o "$(which python)" = "/usr/bin/python" ]; then
|
||||
# We want to avoid using the system Python because it requires root to use pip.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ BootstrapMageiaCommon() {
|
|||
if ! $SUDO urpmi --force \
|
||||
git \
|
||||
gcc \
|
||||
cdialog \
|
||||
python-augeas \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ BootstrapRpmCommon() {
|
|||
|
||||
pkgs="
|
||||
gcc
|
||||
dialog
|
||||
augeas-libs
|
||||
openssl
|
||||
openssl-devel
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ BootstrapSuseCommon() {
|
|||
python-devel \
|
||||
python-virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-lenses \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
|
|
|
|||
|
|
@ -110,9 +110,6 @@ pyRFC3339==1.0 \
|
|||
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
|
||||
python-augeas==0.5.0 \
|
||||
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
|
||||
python2-pythondialog==3.3.0 \
|
||||
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
|
||||
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
|
||||
pytz==2015.7 \
|
||||
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
|
||||
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
|
||||
|
|
|
|||
6
setup.py
6
setup.py
|
|
@ -51,12 +51,6 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
# Debian squeeze support, cf. #280
|
||||
if sys.version_info[0] == 2:
|
||||
install_requires.append('python2-pythondialog>=3.2.2rc1')
|
||||
else:
|
||||
install_requires.append('pythondialog>=3.2.2rc1')
|
||||
|
||||
# env markers in extras_require cause problems with older pip: #517
|
||||
# Keep in sync with conditional_requirements.py.
|
||||
if sys.version_info < (2, 7):
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ def test_visual(displayer, choices):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
for displayer in util.NcursesDisplay(), util.FileDisplay(sys.stdout):
|
||||
test_visual(displayer, util_test.CHOICES)
|
||||
displayer = util.FileDisplay(sys.stdout):
|
||||
test_visual(displayer, util_test.CHOICES)
|
||||
|
|
|
|||
1
tox.ini
1
tox.ini
|
|
@ -38,7 +38,6 @@ deps =
|
|||
py{26,27}-oldest: dnspython>=1.12
|
||||
py{26,27}-oldest: psutil==2.1.0
|
||||
py{26,27}-oldest: PyOpenSSL==0.13
|
||||
py{26,27}-oldest: python2-pythondialog==3.2.2rc1
|
||||
|
||||
[testenv:py33]
|
||||
commands =
|
||||
|
|
|
|||
Loading…
Reference in a new issue