diff options
Diffstat (limited to 'cliff')
-rw-r--r-- | cliff/__init__.py | 0 | ||||
-rw-r--r-- | cliff/app.py | 305 | ||||
-rw-r--r-- | cliff/command.py | 50 | ||||
-rw-r--r-- | cliff/commandmanager.py | 71 | ||||
-rw-r--r-- | cliff/complete.py | 190 | ||||
-rw-r--r-- | cliff/display.py | 82 | ||||
-rw-r--r-- | cliff/formatters/__init__.py | 0 | ||||
-rw-r--r-- | cliff/formatters/base.py | 48 | ||||
-rw-r--r-- | cliff/formatters/commaseparated.py | 35 | ||||
-rw-r--r-- | cliff/formatters/shell.py | 38 | ||||
-rw-r--r-- | cliff/formatters/table.py | 64 | ||||
-rw-r--r-- | cliff/help.py | 80 | ||||
-rw-r--r-- | cliff/interactive.py | 115 | ||||
-rw-r--r-- | cliff/lister.py | 66 | ||||
-rw-r--r-- | cliff/show.py | 55 | ||||
-rw-r--r-- | cliff/tests/__init__.py | 0 | ||||
-rw-r--r-- | cliff/tests/test_app.py | 389 | ||||
-rw-r--r-- | cliff/tests/test_command.py | 22 | ||||
-rw-r--r-- | cliff/tests/test_commandmanager.py | 121 | ||||
-rw-r--r-- | cliff/tests/test_complete.py | 130 | ||||
-rw-r--r-- | cliff/tests/test_help.py | 116 | ||||
-rw-r--r-- | cliff/tests/test_lister.py | 55 | ||||
-rw-r--r-- | cliff/tests/test_show.py | 54 |
23 files changed, 0 insertions, 2086 deletions
diff --git a/cliff/__init__.py b/cliff/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/cliff/__init__.py +++ /dev/null diff --git a/cliff/app.py b/cliff/app.py deleted file mode 100644 index 4a93801..0000000 --- a/cliff/app.py +++ /dev/null @@ -1,305 +0,0 @@ -"""Application base class. -""" - -import argparse -import codecs -import locale -import logging -import logging.handlers -import os -import sys - -from .complete import CompleteCommand -from .help import HelpAction, HelpCommand -from .interactive import InteractiveApp - -# Make sure the cliff library has a logging handler -# in case the app developer doesn't set up logging. -# For py26 compat, create a NullHandler - -if hasattr(logging, 'NullHandler'): - NullHandler = logging.NullHandler -else: - class NullHandler(logging.Handler): - def handle(self, record): - pass - - def emit(self, record): - pass - - def createLock(self): - self.lock = None - -logging.getLogger('cliff').addHandler(NullHandler()) - - -LOG = logging.getLogger(__name__) - - -class App(object): - """Application base class. - - :param description: one-liner explaining the program purpose - :paramtype description: str - :param version: application version number - :paramtype version: str - :param command_manager: plugin loader - :paramtype command_manager: cliff.commandmanager.CommandManager - :param stdin: Standard input stream - :paramtype stdin: readable I/O stream - :param stdout: Standard output stream - :paramtype stdout: writable I/O stream - :param stderr: Standard error output stream - :paramtype stderr: writable I/O stream - :param interactive_app_factory: callable to create an - interactive application - :paramtype interactive_app_factory: cliff.interactive.InteractiveApp - """ - - NAME = os.path.splitext(os.path.basename(sys.argv[0]))[0] - - CONSOLE_MESSAGE_FORMAT = '%(message)s' - LOG_FILE_MESSAGE_FORMAT = \ - '[%(asctime)s] %(levelname)-8s %(name)s %(message)s' - DEFAULT_VERBOSE_LEVEL = 1 - DEFAULT_OUTPUT_ENCODING = 'utf-8' - - def __init__(self, description, version, command_manager, - stdin=None, stdout=None, stderr=None, - interactive_app_factory=InteractiveApp): - """Initialize the application. - """ - self.command_manager = command_manager - self.command_manager.add_command('help', HelpCommand) - self.command_manager.add_command('complete', CompleteCommand) - self._set_streams(stdin, stdout, stderr) - self.interactive_app_factory = interactive_app_factory - self.parser = self.build_option_parser(description, version) - self.interactive_mode = False - self.interpreter = None - - def _set_streams(self, stdin, stdout, stderr): - locale.setlocale(locale.LC_ALL, '') - if sys.version_info[:2] == (2, 6): - # Configure the input and output streams. If a stream is - # provided, it must be configured correctly by the - # caller. If not, make sure the versions of the standard - # streams used by default are wrapped with encodings. This - # works around a problem with Python 2.6 fixed in 2.7 and - # later (http://hg.python.org/cpython/rev/e60ef17561dc/). - lang, encoding = locale.getdefaultlocale() - encoding = (getattr(sys.stdout, 'encoding', None) - or encoding - or self.DEFAULT_OUTPUT_ENCODING - ) - self.stdin = stdin or codecs.getreader(encoding)(sys.stdin) - self.stdout = stdout or codecs.getwriter(encoding)(sys.stdout) - self.stderr = stderr or codecs.getwriter(encoding)(sys.stderr) - else: - self.stdin = stdin or sys.stdin - self.stdout = stdout or sys.stdout - self.stderr = stderr or sys.stderr - - def build_option_parser(self, description, version, - argparse_kwargs=None): - """Return an argparse option parser for this application. - - Subclasses may override this method to extend - the parser with more global options. - - :param description: full description of the application - :paramtype description: str - :param version: version number for the application - :paramtype version: str - :param argparse_kwargs: extra keyword argument passed to the - ArgumentParser constructor - :paramtype extra_kwargs: dict - """ - argparse_kwargs = argparse_kwargs or {} - parser = argparse.ArgumentParser( - description=description, - add_help=False, - **argparse_kwargs - ) - parser.add_argument( - '--version', - action='version', - version='%(prog)s {0}'.format(version), - ) - parser.add_argument( - '-v', '--verbose', - action='count', - dest='verbose_level', - default=self.DEFAULT_VERBOSE_LEVEL, - help='Increase verbosity of output. Can be repeated.', - ) - parser.add_argument( - '--log-file', - action='store', - default=None, - help='Specify a file to log output. Disabled by default.', - ) - parser.add_argument( - '-q', '--quiet', - action='store_const', - dest='verbose_level', - const=0, - help='suppress output except warnings and errors', - ) - parser.add_argument( - '-h', '--help', - action=HelpAction, - nargs=0, - default=self, # tricky - help="show this help message and exit", - ) - parser.add_argument( - '--debug', - default=False, - action='store_true', - help='show tracebacks on errors', - ) - return parser - - def configure_logging(self): - """Create logging handlers for any log output. - """ - root_logger = logging.getLogger('') - root_logger.setLevel(logging.DEBUG) - - # Set up logging to a file - if self.options.log_file: - file_handler = logging.FileHandler( - filename=self.options.log_file, - ) - formatter = logging.Formatter(self.LOG_FILE_MESSAGE_FORMAT) - file_handler.setFormatter(formatter) - root_logger.addHandler(file_handler) - - # Always send higher-level messages to the console via stderr - console = logging.StreamHandler(self.stderr) - console_level = {0: logging.WARNING, - 1: logging.INFO, - 2: logging.DEBUG, - }.get(self.options.verbose_level, logging.DEBUG) - console.setLevel(console_level) - formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT) - console.setFormatter(formatter) - root_logger.addHandler(console) - return - - def run(self, argv): - """Equivalent to the main program for the application. - - :param argv: input arguments and options - :paramtype argv: list of str - """ - try: - self.options, remainder = self.parser.parse_known_args(argv) - self.configure_logging() - self.interactive_mode = not remainder - self.initialize_app(remainder) - except Exception as err: - if hasattr(self, 'options'): - debug = self.options.debug - else: - debug = True - if debug: - LOG.exception(err) - raise - else: - LOG.error(err) - return 1 - result = 1 - if self.interactive_mode: - result = self.interact() - else: - result = self.run_subcommand(remainder) - return result - - # FIXME(dhellmann): Consider moving these command handling methods - # to a separate class. - def initialize_app(self, argv): - """Hook for subclasses to take global initialization action - after the arguments are parsed but before a command is run. - Invoked only once, even in interactive mode. - - :param argv: List of arguments, including the subcommand to run. - Empty for interactive mode. - """ - return - - def prepare_to_run_command(self, cmd): - """Perform any preliminary work needed to run a command. - - :param cmd: command processor being invoked - :paramtype cmd: cliff.command.Command - """ - return - - def clean_up(self, cmd, result, err): - """Hook run after a command is done to shutdown the app. - - :param cmd: command processor being invoked - :paramtype cmd: cliff.command.Command - :param result: return value of cmd - :paramtype result: int - :param err: exception or None - :paramtype err: Exception - """ - return - - def interact(self): - self.interpreter = self.interactive_app_factory(self, - self.command_manager, - self.stdin, - self.stdout, - ) - self.interpreter.cmdloop() - return 0 - - def run_subcommand(self, argv): - try: - subcommand = self.command_manager.find_command(argv) - except ValueError as err: - if self.options.debug: - raise - else: - LOG.error(err) - return 2 - cmd_factory, cmd_name, sub_argv = subcommand - cmd = cmd_factory(self, self.options) - err = None - result = 1 - try: - self.prepare_to_run_command(cmd) - full_name = (cmd_name - if self.interactive_mode - else ' '.join([self.NAME, cmd_name]) - ) - cmd_parser = cmd.get_parser(full_name) - parsed_args = cmd_parser.parse_args(sub_argv) - result = cmd.run(parsed_args) - except Exception as err: - if self.options.debug: - LOG.exception(err) - else: - LOG.error(err) - try: - self.clean_up(cmd, result, err) - except Exception as err2: - if self.options.debug: - LOG.exception(err2) - else: - LOG.error('Could not clean up: %s', err2) - if self.options.debug: - raise - else: - try: - self.clean_up(cmd, result, None) - except Exception as err3: - if self.options.debug: - LOG.exception(err3) - else: - LOG.error('Could not clean up: %s', err3) - return result diff --git a/cliff/command.py b/cliff/command.py deleted file mode 100644 index 116ae5c..0000000 --- a/cliff/command.py +++ /dev/null @@ -1,50 +0,0 @@ - -import abc -import argparse -import inspect - - -class Command(object): - """Base class for command plugins. - - :param app: Application instance invoking the command. - :paramtype app: cliff.app.App - """ - __metaclass__ = abc.ABCMeta - - def __init__(self, app, app_args): - self.app = app - self.app_args = app_args - return - - def get_description(self): - """Return the command description. - """ - return inspect.getdoc(self.__class__) or '' - - def get_parser(self, prog_name): - """Return an :class:`argparse.ArgumentParser`. - """ - parser = argparse.ArgumentParser( - description=self.get_description(), - prog=prog_name, - ) - return parser - - @abc.abstractmethod - def take_action(self, parsed_args): - """Override to do something useful. - """ - - def run(self, parsed_args): - """Invoked by the application when the command is run. - - Developers implementing commands should override - :meth:`take_action`. - - Developers creating new command base classes (such as - :class:`Lister` and :class:`ShowOne`) should override this - method to wrap :meth:`take_action`. - """ - self.take_action(parsed_args) - return 0 diff --git a/cliff/commandmanager.py b/cliff/commandmanager.py deleted file mode 100644 index 2f83eb2..0000000 --- a/cliff/commandmanager.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Discover and lookup command plugins. -""" - -import logging - -import pkg_resources - - -LOG = logging.getLogger(__name__) - - -class EntryPointWrapper(object): - """Wrap up a command class already imported to make it look like a plugin. - """ - - def __init__(self, name, command_class): - self.name = name - self.command_class = command_class - - def load(self): - return self.command_class - - -class CommandManager(object): - """Discovers commands and handles lookup based on argv data. - - :param namespace: String containing the setuptools entrypoint namespace - for the plugins to be loaded. For example, - ``'cliff.formatter.list'``. - :param convert_underscores: Whether cliff should convert underscores to - to spaces in entry_point commands. - """ - def __init__(self, namespace, convert_underscores=True): - self.commands = {} - self.namespace = namespace - self.convert_underscores = convert_underscores - self._load_commands() - - def _load_commands(self): - for ep in pkg_resources.iter_entry_points(self.namespace): - LOG.debug('found command %r', ep.name) - cmd_name = (ep.name.replace('_', ' ') - if self.convert_underscores - else ep.name) - self.commands[cmd_name] = ep - return - - def __iter__(self): - return iter(self.commands.items()) - - def add_command(self, name, command_class): - self.commands[name] = EntryPointWrapper(name, command_class) - - def find_command(self, argv): - """Given an argument list, find a command and - return the processor and any remaining arguments. - """ - search_args = argv[:] - name = '' - while search_args: - if search_args[0].startswith('-'): - raise ValueError('Invalid command %r' % search_args[0]) - next_val = search_args.pop(0) - name = '%s %s' % (name, next_val) if name else next_val - if name in self.commands: - cmd_ep = self.commands[name] - cmd_factory = cmd_ep.load() - return (cmd_factory, name, search_args) - else: - raise ValueError('Unknown command %r' % - (argv,)) diff --git a/cliff/complete.py b/cliff/complete.py deleted file mode 100644 index fa1dd5c..0000000 --- a/cliff/complete.py +++ /dev/null @@ -1,190 +0,0 @@ - -"""Bash completion for the CLI. -""" - -import logging - -import six -import stevedore - -from cliff import command - - -class CompleteDictionary: - """dictionary for bash completion - """ - - def __init__(self): - self._dictionary = {} - - def add_command(self, command, actions): - optstr = ' '.join(opt for action in actions - for opt in action.option_strings) - dicto = self._dictionary - for subcmd in command[:-1]: - dicto = dicto.setdefault(subcmd, {}) - dicto[command[-1]] = optstr - - def get_commands(self): - return ' '.join(k for k in sorted(self._dictionary.keys())) - - def _get_data_recurse(self, dictionary, path): - ray = [] - keys = sorted(dictionary.keys()) - for cmd in keys: - name = path + "_" + cmd if path else cmd - value = dictionary[cmd] - if isinstance(value, six.string_types): - ray.append((name, value)) - else: - cmdlist = ' '.join(sorted(value.keys())) - ray.append((name, cmdlist)) - ray += self._get_data_recurse(value, name) - return ray - - def get_data(self): - return sorted(self._get_data_recurse(self._dictionary, "")) - - -class CompleteShellBase(object): - """base class for bash completion generation - """ - def __init__(self, name, output): - self.name = str(name) - self.output = output - - def write(self, cmdo, data): - self.output.write(self.get_header()) - self.output.write(" cmds='{0}'\n".format(cmdo)) - for datum in data: - self.output.write(' cmds_{0}=\'{1}\'\n'.format(*datum)) - self.output.write(self.get_trailer()) - - -class CompleteNoCode(CompleteShellBase): - """completion with no code - """ - def __init__(self, name, output): - super(CompleteNoCode, self).__init__(name, output) - - def get_header(self): - return '' - - def get_trailer(self): - return '' - - -class CompleteBash(CompleteShellBase): - """completion for bash - """ - def __init__(self, name, output): - super(CompleteBash, self).__init__(name, output) - - def get_header(self): - return ('_' + self.name + """() -{ - local cur prev words - COMPREPLY=() - _get_comp_words_by_ref -n : cur prev words - - # Command data: -""") - - def get_trailer(self): - return (""" - cmd="" - words[0]="" - completed="${cmds}" - for var in "${words[@]:1}" - do - if [[ ${var} == -* ]] ; then - break - fi - if [ -z "${cmd}" ] ; then - proposed="${var}" - else - proposed="${cmd}_${var}" - fi - local i="cmds_${proposed}" - local comp="${!i}" - if [ -z "${comp}" ] ; then - break - fi - if [[ ${comp} == -* ]] ; then - if [[ ${cur} != -* ]] ; then - completed="" - break - fi - fi - cmd="${proposed}" - completed="${comp}" - done - - if [ -z "${completed}" ] ; then - COMPREPLY=( $( compgen -f -- "$cur" ) $( compgen -d -- "$cur" ) ) - else - COMPREPLY=( $(compgen -W "${completed}" -- ${cur}) ) - fi - return 0 -} -complete -F _""" + self.name + ' ' + self.name + '\n') - - -class CompleteCommand(command.Command): - """print bash completion command - """ - - log = logging.getLogger(__name__ + '.CompleteCommand') - - def __init__(self, app, app_args): - super(CompleteCommand, self).__init__(app, app_args) - self._formatters = stevedore.ExtensionManager( - namespace='cliff.formatter.completion', - ) - - def get_parser(self, prog_name): - parser = super(CompleteCommand, self).get_parser(prog_name) - parser.add_argument( - "--name", - default=None, - metavar='<command_name>', - help="Command name to support with command completion" - ) - parser.add_argument( - "--shell", - default='bash', - metavar='<shell>', - choices=sorted(self._formatters.names()), - help="Shell being used. Use none for data only (default: bash)" - ) - return parser - - def get_actions(self, command): - the_cmd = self.app.command_manager.find_command(command) - cmd_factory, cmd_name, search_args = the_cmd - cmd = cmd_factory(self.app, search_args) - if self.app.interactive_mode: - full_name = (cmd_name) - else: - full_name = (' '.join([self.app.NAME, cmd_name])) - cmd_parser = cmd.get_parser(full_name) - return cmd_parser._get_optional_actions() - - def take_action(self, parsed_args): - self.log.debug('take_action(%s)' % parsed_args) - - name = parsed_args.name or self.app.NAME - try: - shell_factory = self._formatters[parsed_args.shell].plugin - except KeyError: - raise RuntimeError('Unknown shell syntax %r' % parsed_args.shell) - shell = shell_factory(name, self.app.stdout) - - dicto = CompleteDictionary() - for cmd in self.app.command_manager: - command = cmd[0].split() - dicto.add_command(command, self.get_actions(command)) - - shell.write(dicto.get_commands(), dicto.get_data()) - - return 0 diff --git a/cliff/display.py b/cliff/display.py deleted file mode 100644 index edea1f3..0000000 --- a/cliff/display.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Application base class for displaying data. -""" -import abc -import logging - -import stevedore - -from .command import Command - - -LOG = logging.getLogger(__name__) - - -class DisplayCommandBase(Command): - """Command base class for displaying data about a single object. - """ - __metaclass__ = abc.ABCMeta - - def __init__(self, app, app_args): - super(DisplayCommandBase, self).__init__(app, app_args) - self.formatters = self._load_formatter_plugins() - - @abc.abstractproperty - def formatter_namespace(self): - "String specifying the namespace to use for loading formatter plugins." - - @abc.abstractproperty - def formatter_default(self): - "String specifying the name of the default formatter." - - def _load_formatter_plugins(self): - # Here so tests can override - return stevedore.ExtensionManager( - self.formatter_namespace, - invoke_on_load=True, - ) - - def get_parser(self, prog_name): - parser = super(DisplayCommandBase, self).get_parser(prog_name) - formatter_group = parser.add_argument_group( - title='output formatters', - description='output formatter options', - ) - formatter_choices = sorted(self.formatters.names()) - formatter_default = self.formatter_default - if formatter_default not in formatter_choices: - formatter_default = formatter_choices[0] - formatter_group.add_argument( - '-f', '--format', - dest='formatter', - action='store', - choices=formatter_choices, - default=formatter_default, - help='the output format, defaults to %s' % formatter_default, - ) - formatter_group.add_argument( - '-c', '--column', - action='append', - default=[], - dest='columns', - metavar='COLUMN', - help='specify the column(s) to include, can be repeated', - ) - for formatter in self.formatters: - formatter.obj.add_argument_group(parser) - return parser - - @abc.abstractmethod - def produce_output(self, parsed_args, column_names, data): - """Use the formatter to generate the output. - - :param parsed_args: argparse.Namespace instance with argument values - :param column_names: sequence of strings containing names - of output columns - :param data: iterable with values matching the column names - """ - - def run(self, parsed_args): - self.formatter = self.formatters[parsed_args.formatter].obj - column_names, data = self.take_action(parsed_args) - self.produce_output(parsed_args, column_names, data) - return 0 diff --git a/cliff/formatters/__init__.py b/cliff/formatters/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/cliff/formatters/__init__.py +++ /dev/null diff --git a/cliff/formatters/base.py b/cliff/formatters/base.py deleted file mode 100644 index 43b8f17..0000000 --- a/cliff/formatters/base.py +++ /dev/null @@ -1,48 +0,0 @@ -"""Base classes for formatters. -""" - -import abc - - -class Formatter(object): - __metaclass__ = abc.ABCMeta - - @abc.abstractmethod - def add_argument_group(self, parser): - """Add any options to the argument parser. - - Should use our own argument group. - """ - - -class ListFormatter(Formatter): - """Base class for formatters that know how to deal with multiple objects. - """ - __metaclass__ = abc.ABCMeta - - @abc.abstractmethod - def emit_list(self, column_names, data, stdout, parsed_args): - """Format and print the list from the iterable data source. - - :param column_names: names of the columns - :param data: iterable data source, one tuple per object - with values in order of column names - :param stdout: output stream where data should be written - :param parsed_args: argparse namespace from our local options - """ - - -class SingleFormatter(Formatter): - """Base class for formatters that work with single objects. - """ - __metaclass__ = abc.ABCMeta - - @abc.abstractmethod - def emit_one(self, column_names, data, stdout, parsed_args): - """Format and print the values associated with the single object. - - :param column_names: names of the columns - :param data: iterable data source with values in order of column names - :param stdout: output stream where data should be written - :param parsed_args: argparse namespace from our local options - """ diff --git a/cliff/formatters/commaseparated.py b/cliff/formatters/commaseparated.py deleted file mode 100644 index 1e320f3..0000000 --- a/cliff/formatters/commaseparated.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Output formatters using csv format. -""" - -import csv - -from .base import ListFormatter - - -class CSVLister(ListFormatter): - - QUOTE_MODES = { - 'all': csv.QUOTE_ALL, - 'minimal': csv.QUOTE_MINIMAL, - 'nonnumeric': csv.QUOTE_NONNUMERIC, - 'none': csv.QUOTE_NONE, - } - - def add_argument_group(self, parser): - group = parser.add_argument_group('CSV Formatter') - group.add_argument( - '--quote', - choices=sorted(self.QUOTE_MODES.keys()), - dest='quote_mode', - default='nonnumeric', - help='when to include quotes, defaults to nonnumeric', - ) - - def emit_list(self, column_names, data, stdout, parsed_args): - writer = csv.writer(stdout, - quoting=self.QUOTE_MODES[parsed_args.quote_mode], - ) - writer.writerow(column_names) - for row in data: - writer.writerow(row) - return diff --git a/cliff/formatters/shell.py b/cliff/formatters/shell.py deleted file mode 100644 index d1c392b..0000000 --- a/cliff/formatters/shell.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Output formatters using shell syntax. -""" - -from .base import SingleFormatter - - -class ShellFormatter(SingleFormatter): - - def add_argument_group(self, parser): - group = parser.add_argument_group( - title='shell formatter', - description='a format a UNIX shell can parse (variable="value")', - ) - group.add_argument( - '--variable', - action='append', - default=[], - dest='variables', - metavar='VARIABLE', - help='specify the variable(s) to include, can be repeated', - ) - group.add_argument( - '--prefix', - action='store', - default='', - dest='prefix', - help='add a prefix to all variable names', - ) - - def emit_one(self, column_names, data, stdout, parsed_args): - variable_names = [c.lower().replace(' ', '_') - for c in column_names - ] - desired_columns = parsed_args.variables - for name, value in zip(variable_names, data): - if name in desired_columns or not desired_columns: - stdout.write('%s%s="%s"\n' % (parsed_args.prefix, name, value)) - return diff --git a/cliff/formatters/table.py b/cliff/formatters/table.py deleted file mode 100644 index e625a25..0000000 --- a/cliff/formatters/table.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Output formatters using prettytable. -""" - -import prettytable - -from .base import ListFormatter, SingleFormatter - - -class TableFormatter(ListFormatter, SingleFormatter): - - ALIGNMENTS = { - int: 'r', - str: 'l', - float: 'r', - } - try: - ALIGNMENTS[unicode] = 'l' - except NameError: - pass - - def add_argument_group(self, parser): - pass - - def emit_list(self, column_names, data, stdout, parsed_args): - x = prettytable.PrettyTable( - column_names, - print_empty=False, - ) - x.padding_width = 1 - # Figure out the types of the columns in the - # first row and set the alignment of the - # output accordingly. - data_iter = iter(data) - try: - first_row = next(data_iter) - except StopIteration: - pass - else: - for value, name in zip(first_row, column_names): - alignment = self.ALIGNMENTS.get(type(value), 'l') - x.align[name] = alignment - # Now iterate over the data and add the rows. - x.add_row(first_row) - for row in data_iter: - x.add_row(row) - formatted = x.get_string(fields=column_names) - stdout.write(formatted) - stdout.write('\n') - return - - def emit_one(self, column_names, data, stdout, parsed_args): - x = prettytable.PrettyTable(field_names=('Field', 'Value'), - print_empty=False) - x.padding_width = 1 - # Align all columns left because the values are - # not all the same type. - x.align['Field'] = 'l' - x.align['Value'] = 'l' - for name, value in zip(column_names, data): - x.add_row((name, value)) - formatted = x.get_string(fields=('Field', 'Value')) - stdout.write(formatted) - stdout.write('\n') - return diff --git a/cliff/help.py b/cliff/help.py deleted file mode 100644 index 9d96111..0000000 --- a/cliff/help.py +++ /dev/null @@ -1,80 +0,0 @@ -import argparse -import sys -import traceback - -from .command import Command - - -class HelpAction(argparse.Action): - """Provide a custom action so the -h and --help options - to the main app will print a list of the commands. - - The commands are determined by checking the CommandManager - instance, passed in as the "default" value for the action. - """ - def __call__(self, parser, namespace, values, option_string=None): - app = self.default - parser.print_help(app.stdout) - app.stdout.write('\nCommands:\n') - command_manager = app.command_manager - for name, ep in sorted(command_manager): - try: - factory = ep.load() - except Exception as err: - app.stdout.write('Could not load %r\n' % ep) - if namespace.debug: - traceback.print_exc(file=app.stdout) - continue - try: - cmd = factory(app, None) - except Exception as err: - app.stdout.write('Could not instantiate %r: %s\n' % (ep, err)) - if namespace.debug: - traceback.print_exc(file=app.stdout) - continue - one_liner = cmd.get_description().split('\n')[0] - app.stdout.write(' %-13s %s\n' % (name, one_liner)) - sys.exit(0) - - -class HelpCommand(Command): - """print detailed help for another command - """ - - def get_parser(self, prog_name): - parser = super(HelpCommand, self).get_parser(prog_name) - parser.add_argument('cmd', - nargs='*', - help='name of the command', - ) - return parser - - def take_action(self, parsed_args): - if parsed_args.cmd: - try: - the_cmd = self.app.command_manager.find_command( - parsed_args.cmd, - ) - cmd_factory, cmd_name, search_args = the_cmd - except ValueError: - # Did not find an exact match - cmd = parsed_args.cmd[0] - fuzzy_matches = [k[0] for k in self.app.command_manager - if k[0].startswith(cmd) - ] - if not fuzzy_matches: - raise - self.app.stdout.write('Command "%s" matches:\n' % cmd) - for fm in fuzzy_matches: - self.app.stdout.write(' %s\n' % fm) - return - cmd = cmd_factory(self.app, search_args) - full_name = (cmd_name - if self.app.interactive_mode - else ' '.join([self.app.NAME, cmd_name]) - ) - cmd_parser = cmd.get_parser(full_name) - else: - cmd_parser = self.get_parser(' '.join([self.app.NAME, 'help'])) - cmd_parser.print_help(self.app.stdout) - return 0 diff --git a/cliff/interactive.py b/cliff/interactive.py deleted file mode 100644 index 619c4a2..0000000 --- a/cliff/interactive.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Application base class. -""" - -import itertools -import logging -import logging.handlers -import shlex - -import cmd2 - -LOG = logging.getLogger(__name__) - - -class InteractiveApp(cmd2.Cmd): - """Provides "interactive mode" features. - - Refer to the cmd2_ and cmd_ documentation for details - about subclassing and configuring this class. - - .. _cmd2: http://packages.python.org/cmd2/index.html - .. _cmd: http://docs.python.org/library/cmd.html - - :param parent_app: The calling application (expected to be derived - from :class:`cliff.main.App`). - :param command_manager: A :class:`cliff.commandmanager.CommandManager` - instance. - :param stdin: Standard input stream - :param stdout: Standard output stream - """ - - use_rawinput = True - doc_header = "Shell commands (type help <topic>):" - app_cmd_header = "Application commands (type help <topic>):" - - def __init__(self, parent_app, command_manager, stdin, stdout): - self.parent_app = parent_app - self.prompt = '(%s) ' % parent_app.NAME - self.command_manager = command_manager - cmd2.Cmd.__init__(self, 'tab', stdin=stdin, stdout=stdout) - - def default(self, line): - # Tie in the the default command processor to - # dispatch commands known to the command manager. - # We send the message through our parent app, - # since it already has the logic for executing - # the subcommand. - line_parts = shlex.split(line.parsed.raw) - self.parent_app.run_subcommand(line_parts) - - def completedefault(self, text, line, begidx, endidx): - # Tab-completion for commands known to the command manager. - # Does not handle options on the commands. - if not text: - completions = sorted(n for n, v in self.command_manager) - else: - completions = sorted(n for n, v in self.command_manager - if n.startswith(text) - ) - return completions - - def help_help(self): - # Use the command manager to get instructions for "help" - self.default('help help') - - def do_help(self, arg): - if arg: - # Check if the arg is a builtin command or something - # coming from the command manager - arg_parts = shlex.split(arg) - method_name = '_'.join( - itertools.chain( - ['do'], - itertools.takewhile(lambda x: not x.startswith('-'), - arg_parts) - ) - ) - # Have the command manager version of the help - # command produce the help text since cmd and - # cmd2 do not provide help for "help" - if hasattr(self, method_name): - return cmd2.Cmd.do_help(self, arg) - # Dispatch to the underlying help command, - # which knows how to provide help for extension - # commands. - self.default(self.parsed('help ' + arg)) - else: - cmd2.Cmd.do_help(self, arg) - cmd_names = sorted([n for n, v in self.command_manager]) - self.print_topics(self.app_cmd_header, cmd_names, 15, 80) - return - - def get_names(self): - # Override the base class version to filter out - # things that look like they should be hidden - # from the user. - return [n - for n in cmd2.Cmd.get_names(self) - if not n.startswith('do__') - ] - - def precmd(self, statement): - # Pre-process the parsed command in case it looks like one of - # our subcommands, since cmd2 does not handle multi-part - # command names by default. - line_parts = shlex.split(statement.parsed.raw) - try: - the_cmd = self.command_manager.find_command(line_parts) - cmd_factory, cmd_name, sub_argv = the_cmd - except ValueError: - # Not a plugin command - pass - else: - statement.parsed.command = cmd_name - statement.parsed.args = ' '.join(sub_argv) - return statement diff --git a/cliff/lister.py b/cliff/lister.py deleted file mode 100644 index 1b6c3b6..0000000 --- a/cliff/lister.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Application base class for providing a list of data as output. -""" -import abc - -try: - from itertools import compress -except ImportError: - # for py26 compat - from itertools import izip - - def compress(data, selectors): - return (d for d, s in izip(data, selectors) if s) - -import logging - -from .display import DisplayCommandBase - - -LOG = logging.getLogger(__name__) - - -class Lister(DisplayCommandBase): - """Command base class for providing a list of data as output. - """ - __metaclass__ = abc.ABCMeta - - @property - def formatter_namespace(self): - return 'cliff.formatter.list' - - @property - def formatter_default(self): - return 'table' - - @abc.abstractmethod - def take_action(self, parsed_args): - """Return a tuple containing the column names and an iterable - containing the data to be listed. - """ - - def produce_output(self, parsed_args, column_names, data): - if not parsed_args.columns: - columns_to_include = column_names - data_gen = data - else: - columns_to_include = [c for c in column_names - if c in parsed_args.columns - ] - if not columns_to_include: - raise ValueError('No recognized column names in %s' % - str(parsed_args.columns)) - # Set up argument to compress() - selector = [(c in columns_to_include) - for c in column_names] - # Generator expression to only return the parts of a row - # of data that the user has expressed interest in - # seeing. We have to convert the compress() output to a - # list so the table formatter can ask for its length. - data_gen = (list(compress(row, selector)) - for row in data) - self.formatter.emit_list(columns_to_include, - data_gen, - self.app.stdout, - parsed_args, - ) - return 0 diff --git a/cliff/show.py b/cliff/show.py deleted file mode 100644 index 855b2d2..0000000 --- a/cliff/show.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Application base class for displaying data about a single object. -""" -import abc -import itertools -import logging - -from .display import DisplayCommandBase - - -LOG = logging.getLogger(__name__) - - -class ShowOne(DisplayCommandBase): - """Command base class for displaying data about a single object. - """ - __metaclass__ = abc.ABCMeta - - @property - def formatter_namespace(self): - return 'cliff.formatter.show' - - @property - def formatter_default(self): - return 'table' - - @abc.abstractmethod - def take_action(self, parsed_args): - """Return a two-part tuple with a tuple of column names - and a tuple of values. - """ - - def produce_output(self, parsed_args, column_names, data): - if not parsed_args.columns: - columns_to_include = column_names - else: - columns_to_include = [c for c in column_names - if c in parsed_args.columns] - # Set up argument to compress() - selector = [(c in columns_to_include) - for c in column_names] - data = list(itertools.compress(data, selector)) - self.formatter.emit_one(columns_to_include, - data, - self.app.stdout, - parsed_args) - return 0 - - def dict2columns(self, data): - """Implement the common task of converting a dict-based object - to the two-column output that ShowOne expects. - """ - if not data: - return ({}, {}) - else: - return zip(*sorted(data.items())) diff --git a/cliff/tests/__init__.py b/cliff/tests/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/cliff/tests/__init__.py +++ /dev/null diff --git a/cliff/tests/test_app.py b/cliff/tests/test_app.py deleted file mode 100644 index e3bc12e..0000000 --- a/cliff/tests/test_app.py +++ /dev/null @@ -1,389 +0,0 @@ -# -*- encoding: utf-8 -*- -from argparse import ArgumentError -try: - from StringIO import StringIO -except ImportError: - # Probably python 3, that test won't be run so ignore the error - pass -import sys - -import nose -import mock - -from cliff.app import App -from cliff.command import Command -from cliff.commandmanager import CommandManager - - -def make_app(): - cmd_mgr = CommandManager('cliff.tests') - - # Register a command that succeeds - command = mock.MagicMock(spec=Command) - command_inst = mock.MagicMock(spec=Command) - command_inst.run.return_value = 0 - command.return_value = command_inst - cmd_mgr.add_command('mock', command) - - # Register a command that fails - err_command = mock.Mock(name='err_command', spec=Command) - err_command_inst = mock.Mock(spec=Command) - err_command_inst.run = mock.Mock( - side_effect=RuntimeError('test exception') - ) - err_command.return_value = err_command_inst - cmd_mgr.add_command('error', err_command) - - app = App('testing interactive mode', - '1', - cmd_mgr, - stderr=mock.Mock(), # suppress warning messages - ) - return app, command - - -def test_no_args_triggers_interactive_mode(): - app, command = make_app() - app.interact = mock.MagicMock(name='inspect') - app.run([]) - app.interact.assert_called_once_with() - - -def test_interactive_mode_cmdloop(): - app, command = make_app() - app.interactive_app_factory = mock.MagicMock( - name='interactive_app_factory' - ) - assert app.interpreter is None - app.run([]) - assert app.interpreter is not None - app.interactive_app_factory.return_value.cmdloop.assert_called_once_with() - - -def test_initialize_app(): - app, command = make_app() - app.initialize_app = mock.MagicMock(name='initialize_app') - app.run(['mock']) - app.initialize_app.assert_called_once_with(['mock']) - - -def test_prepare_to_run_command(): - app, command = make_app() - app.prepare_to_run_command = mock.MagicMock(name='prepare_to_run_command') - app.run(['mock']) - app.prepare_to_run_command.assert_called_once_with(command()) - - -def test_clean_up_success(): - app, command = make_app() - app.clean_up = mock.MagicMock(name='clean_up') - app.run(['mock']) - app.clean_up.assert_called_once_with(command.return_value, 0, None) - - -def test_clean_up_error(): - app, command = make_app() - - app.clean_up = mock.MagicMock(name='clean_up') - app.run(['error']) - - app.clean_up.assert_called_once() - call_args = app.clean_up.call_args_list[0] - assert call_args == mock.call(mock.ANY, 1, mock.ANY) - args, kwargs = call_args - assert isinstance(args[2], RuntimeError) - assert args[2].args == ('test exception',) - - -def test_clean_up_error_debug(): - app, command = make_app() - - app.clean_up = mock.MagicMock(name='clean_up') - try: - app.run(['--debug', 'error']) - except RuntimeError as err: - assert app.clean_up.call_args_list[0][0][2] is err - else: - assert False, 'Should have had an exception' - - app.clean_up.assert_called_once() - call_args = app.clean_up.call_args_list[0] - assert call_args == mock.call(mock.ANY, 1, mock.ANY) - args, kwargs = call_args - assert isinstance(args[2], RuntimeError) - assert args[2].args == ('test exception',) - - -def test_error_handling_clean_up_raises_exception(): - app, command = make_app() - - app.clean_up = mock.MagicMock( - name='clean_up', - side_effect=RuntimeError('within clean_up'), - ) - app.run(['error']) - - app.clean_up.assert_called_once() - call_args = app.clean_up.call_args_list[0] - assert call_args == mock.call(mock.ANY, 1, mock.ANY) - args, kwargs = call_args - assert isinstance(args[2], RuntimeError) - assert args[2].args == ('test exception',) - - -def test_error_handling_clean_up_raises_exception_debug(): - app, command = make_app() - - app.clean_up = mock.MagicMock( - name='clean_up', - side_effect=RuntimeError('within clean_up'), - ) - try: - app.run(['--debug', 'error']) - except RuntimeError as err: - if not hasattr(err, '__context__'): - # The exception passed to clean_up is not the exception - # caused *by* clean_up. This test is only valid in python - # 2 because under v3 the original exception is re-raised - # with the new one as a __context__ attribute. - assert app.clean_up.call_args_list[0][0][2] is not err - else: - assert False, 'Should have had an exception' - - app.clean_up.assert_called_once() - call_args = app.clean_up.call_args_list[0] - assert call_args == mock.call(mock.ANY, 1, mock.ANY) - args, kwargs = call_args - assert isinstance(args[2], RuntimeError) - assert args[2].args == ('test exception',) - - -def test_normal_clean_up_raises_exception(): - app, command = make_app() - - app.clean_up = mock.MagicMock( - name='clean_up', - side_effect=RuntimeError('within clean_up'), - ) - app.run(['mock']) - - app.clean_up.assert_called_once() - call_args = app.clean_up.call_args_list[0] - assert call_args == mock.call(mock.ANY, 0, None) - - -def test_normal_clean_up_raises_exception_debug(): - app, command = make_app() - - app.clean_up = mock.MagicMock( - name='clean_up', - side_effect=RuntimeError('within clean_up'), - ) - app.run(['--debug', 'mock']) - - app.clean_up.assert_called_once() - call_args = app.clean_up.call_args_list[0] - assert call_args == mock.call(mock.ANY, 0, None) - - -def test_build_option_parser_conflicting_option_should_throw(): - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - def build_option_parser(self, description, version): - parser = super(MyApp, self).build_option_parser(description, - version) - parser.add_argument( - '-h', '--help', - default=self, # tricky - help="show this help message and exit", - ) - - # TODO: tests should really use unittest2. - try: - MyApp() - except ArgumentError: - pass - else: - raise Exception('Exception was not thrown') - - -def test_option_parser_conflicting_option_custom_arguments_should_not_throw(): - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - def build_option_parser(self, description, version): - argparse_kwargs = {'conflict_handler': 'resolve'} - parser = super(MyApp, self).build_option_parser( - description, - version, - argparse_kwargs=argparse_kwargs) - parser.add_argument( - '-h', '--help', - default=self, # tricky - help="show this help message and exit", - ) - - MyApp() - - -def test_output_encoding_default(): - # The encoding should come from getdefaultlocale() because - # stdout has no encoding set. - if sys.version_info[:2] != (2, 6): - raise nose.SkipTest('only needed for python 2.6') - data = '\xc3\xa9' - u_data = data.decode('utf-8') - - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - stdout = StringIO() - getdefaultlocale = lambda: ('ignored', 'utf-8') - - with mock.patch('sys.stdout', stdout): - with mock.patch('locale.getdefaultlocale', getdefaultlocale): - app = MyApp() - app.stdout.write(u_data) - actual = stdout.getvalue() - assert data == actual - - -def test_output_encoding_cliff_default(): - # The encoding should come from cliff.App.DEFAULT_OUTPUT_ENCODING - # because the other values are missing or None - if sys.version_info[:2] != (2, 6): - raise nose.SkipTest('only needed for python 2.6') - data = '\xc3\xa9' - u_data = data.decode('utf-8') - - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - stdout = StringIO() - getdefaultlocale = lambda: ('ignored', None) - - with mock.patch('sys.stdout', stdout): - with mock.patch('locale.getdefaultlocale', getdefaultlocale): - app = MyApp() - app.stdout.write(u_data) - actual = stdout.getvalue() - assert data == actual - - -def test_output_encoding_sys(): - # The encoding should come from sys.stdout because it is set - # there. - if sys.version_info[:2] != (2, 6): - raise nose.SkipTest('only needed for python 2.6') - data = '\xc3\xa9' - u_data = data.decode('utf-8') - - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - stdout = StringIO() - stdout.encoding = 'utf-8' - getdefaultlocale = lambda: ('ignored', 'utf-16') - - with mock.patch('sys.stdout', stdout): - with mock.patch('locale.getdefaultlocale', getdefaultlocale): - app = MyApp() - app.stdout.write(u_data) - actual = stdout.getvalue() - assert data == actual - - -def test_error_encoding_default(): - # The encoding should come from getdefaultlocale() because - # stdout has no encoding set. - if sys.version_info[:2] != (2, 6): - raise nose.SkipTest('only needed for python 2.6') - data = '\xc3\xa9' - u_data = data.decode('utf-8') - - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - stderr = StringIO() - getdefaultlocale = lambda: ('ignored', 'utf-8') - - with mock.patch('sys.stderr', stderr): - with mock.patch('locale.getdefaultlocale', getdefaultlocale): - app = MyApp() - app.stderr.write(u_data) - actual = stderr.getvalue() - assert data == actual - - -def test_error_encoding_sys(): - # The encoding should come from sys.stdout (not sys.stderr) - # because it is set there. - if sys.version_info[:2] != (2, 6): - raise nose.SkipTest('only needed for python 2.6') - data = '\xc3\xa9' - u_data = data.decode('utf-8') - - class MyApp(App): - def __init__(self): - super(MyApp, self).__init__( - description='testing', - version='0.1', - command_manager=CommandManager('tests'), - ) - - stdout = StringIO() - stdout.encoding = 'utf-8' - stderr = StringIO() - getdefaultlocale = lambda: ('ignored', 'utf-16') - - with mock.patch('sys.stdout', stdout): - with mock.patch('sys.stderr', stderr): - with mock.patch('locale.getdefaultlocale', getdefaultlocale): - app = MyApp() - app.stderr.write(u_data) - actual = stderr.getvalue() - assert data == actual - - -def test_unknown_cmd(): - app, command = make_app() - assert app.run(['hell']) == 2 - - -def test_unknown_cmd_debug(): - app, command = make_app() - try: - app.run(['--debug', 'hell']) == 2 - except ValueError as err: - assert "['hell']" in ('%s' % err) diff --git a/cliff/tests/test_command.py b/cliff/tests/test_command.py deleted file mode 100644 index 39fde51..0000000 --- a/cliff/tests/test_command.py +++ /dev/null @@ -1,22 +0,0 @@ - -from cliff.command import Command - - -class TestCommand(Command): - """Description of command. - """ - - def take_action(self, parsed_args): - return - - -def test_get_description(): - cmd = TestCommand(None, None) - desc = cmd.get_description() - assert desc == "Description of command.\n " - - -def test_get_parser(): - cmd = TestCommand(None, None) - parser = cmd.get_parser('NAME') - assert parser.prog == 'NAME' diff --git a/cliff/tests/test_commandmanager.py b/cliff/tests/test_commandmanager.py deleted file mode 100644 index 9a50a5e..0000000 --- a/cliff/tests/test_commandmanager.py +++ /dev/null @@ -1,121 +0,0 @@ - -import mock - -from cliff.commandmanager import CommandManager - - -class TestCommand(object): - @classmethod - def load(cls): - return cls - - def __init__(self): - return - - -class TestCommandManager(CommandManager): - def _load_commands(self): - self.commands = { - 'one': TestCommand, - 'two words': TestCommand, - 'three word command': TestCommand, - } - - -def test_lookup_and_find(): - def check(mgr, argv): - cmd, name, remaining = mgr.find_command(argv) - assert cmd - assert name == ' '.join(argv) - assert not remaining - mgr = TestCommandManager('test') - for expected in [['one'], - ['two', 'words'], - ['three', 'word', 'command'], - ]: - yield check, mgr, expected - return - - -def test_lookup_with_remainder(): - def check(mgr, argv): - cmd, name, remaining = mgr.find_command(argv) - assert cmd - assert remaining == ['--opt'] - mgr = TestCommandManager('test') - for expected in [['one', '--opt'], - ['two', 'words', '--opt'], - ['three', 'word', 'command', '--opt'], - ]: - yield check, mgr, expected - return - - -def test_find_invalid_command(): - mgr = TestCommandManager('test') - - def check_one(argv): - try: - mgr.find_command(argv) - except ValueError as err: - assert '-b' in ('%s' % err) - else: - assert False, 'expected a failure' - for argv in [['a', '-b'], - ['-b'], - ]: - yield check_one, argv - - -def test_find_unknown_command(): - mgr = TestCommandManager('test') - try: - mgr.find_command(['a', 'b']) - except ValueError as err: - assert "['a', 'b']" in ('%s' % err) - else: - assert False, 'expected a failure' - - -def test_add_command(): - mgr = TestCommandManager('test') - mock_cmd = mock.Mock() - mgr.add_command('mock', mock_cmd) - found_cmd, name, args = mgr.find_command(['mock']) - assert found_cmd is mock_cmd - - -def test_load_commands(): - testcmd = mock.Mock(name='testcmd') - testcmd.name.replace.return_value = 'test' - mock_pkg_resources = mock.Mock(return_value=[testcmd]) - with mock.patch('pkg_resources.iter_entry_points', - mock_pkg_resources) as iter_entry_points: - mgr = CommandManager('test') - assert iter_entry_points.called_once_with('test') - names = [n for n, v in mgr] - assert names == ['test'] - - -def test_load_commands_keep_underscores(): - testcmd = mock.Mock() - testcmd.name = 'test_cmd' - mock_pkg_resources = mock.Mock(return_value=[testcmd]) - with mock.patch('pkg_resources.iter_entry_points', - mock_pkg_resources) as iter_entry_points: - mgr = CommandManager('test', convert_underscores=False) - assert iter_entry_points.called_once_with('test') - names = [n for n, v in mgr] - assert names == ['test_cmd'] - - -def test_load_commands_replace_underscores(): - testcmd = mock.Mock() - testcmd.name = 'test_cmd' - mock_pkg_resources = mock.Mock(return_value=[testcmd]) - with mock.patch('pkg_resources.iter_entry_points', - mock_pkg_resources) as iter_entry_points: - mgr = CommandManager('test', convert_underscores=True) - assert iter_entry_points.called_once_with('test') - names = [n for n, v in mgr] - assert names == ['test cmd'] diff --git a/cliff/tests/test_complete.py b/cliff/tests/test_complete.py deleted file mode 100644 index b5a6bbf..0000000 --- a/cliff/tests/test_complete.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Bash completion tests -""" - -import mock - -from cliff.app import App -from cliff.commandmanager import CommandManager -from cliff import complete - - -def test_complete_dictionary(): - sot = complete.CompleteDictionary() - sot.add_command("image delete".split(), - [mock.Mock(option_strings=["1"])]) - sot.add_command("image list".split(), - [mock.Mock(option_strings=["2"])]) - sot.add_command("image create".split(), - [mock.Mock(option_strings=["3"])]) - sot.add_command("volume type create".split(), - [mock.Mock(option_strings=["4"])]) - sot.add_command("volume type delete".split(), - [mock.Mock(option_strings=["5"])]) - assert "image volume" == sot.get_commands() - result = sot.get_data() - assert "image" == result[0][0] - assert "create delete list" == result[0][1] - assert "image_create" == result[1][0] - assert "3" == result[1][1] - assert "image_delete" == result[2][0] - assert "1" == result[2][1] - assert "image_list" == result[3][0] - assert "2" == result[3][1] - - -class FakeStdout: - def __init__(self): - self.content = [] - - def write(self, text): - self.content.append(text) - - def make_string(self): - result = '' - for line in self.content: - result = result + line - return result - - -def given_cmdo_data(): - cmdo = "image server" - data = [("image", "create"), - ("image_create", "--eolus"), - ("server", "meta ssh"), - ("server_meta_delete", "--wilson"), - ("server_ssh", "--sunlight")] - return cmdo, data - - -def then_data(content): - assert " cmds='image server'\n" in content - assert " cmds_image='create'\n" in content - assert " cmds_image_create='--eolus'\n" in content - assert " cmds_server='meta ssh'\n" in content - assert " cmds_server_meta_delete='--wilson'\n" in content - assert " cmds_server_ssh='--sunlight'\n" in content - - -def test_complete_no_code(): - output = FakeStdout() - sot = complete.CompleteNoCode("doesNotMatter", output) - sot.write(*given_cmdo_data()) - then_data(output.content) - - -def test_complete_bash(): - output = FakeStdout() - sot = complete.CompleteBash("openstack", output) - sot.write(*given_cmdo_data()) - then_data(output.content) - assert "_openstack()\n" in output.content[0] - assert "complete -F _openstack openstack\n" in output.content[-1] - - -def test_complete_command_parser(): - sot = complete.CompleteCommand(mock.Mock(), mock.Mock()) - parser = sot.get_parser('nothing') - assert "nothing" == parser.prog - assert "print bash completion command\n " == parser.description - - -def given_complete_command(): - cmd_mgr = CommandManager('cliff.tests') - app = App('testing', '1', cmd_mgr, stdout=FakeStdout()) - sot = complete.CompleteCommand(app, mock.Mock()) - cmd_mgr.add_command('complete', complete.CompleteCommand) - return sot, app, cmd_mgr - - -def then_actions_equal(actions): - optstr = ' '.join(opt for action in actions - for opt in action.option_strings) - assert '-h --help --name --shell' == optstr - - -def test_complete_command_get_actions(): - sot, app, cmd_mgr = given_complete_command() - app.interactive_mode = False - actions = sot.get_actions(["complete"]) - then_actions_equal(actions) - - -def test_complete_command_get_actions_interactive(): - sot, app, cmd_mgr = given_complete_command() - app.interactive_mode = True - actions = sot.get_actions(["complete"]) - then_actions_equal(actions) - - -def test_complete_command_take_action(): - sot, app, cmd_mgr = given_complete_command() - parsed_args = mock.Mock() - parsed_args.name = "test_take" - parsed_args.shell = "bash" - content = app.stdout.content - assert 0 == sot.take_action(parsed_args) - assert "_test_take()\n" in content[0] - assert "complete -F _test_take test_take\n" in content[-1] - assert " cmds='complete help'\n" in content - assert " cmds_complete='-h --help --name --shell'\n" in content - assert " cmds_help='-h --help'\n" in content diff --git a/cliff/tests/test_help.py b/cliff/tests/test_help.py deleted file mode 100644 index bdf9d71..0000000 --- a/cliff/tests/test_help.py +++ /dev/null @@ -1,116 +0,0 @@ -try: - from StringIO import StringIO -except: - from io import StringIO - -import mock - -from cliff.app import App -from cliff.command import Command -from cliff.commandmanager import CommandManager -from cliff.help import HelpCommand - - -class TestParser(object): - - def print_help(self, stdout): - stdout.write('TestParser') - - -class TestCommand(Command): - - @classmethod - def load(cls): - return cls - - def get_parser(self, ignore): - # Make it look like this class is the parser - # so parse_args() is called. - return TestParser() - - def take_action(self, args): - return - - -class TestCommandManager(CommandManager): - def _load_commands(self): - self.commands = { - 'one': TestCommand, - 'two words': TestCommand, - 'three word command': TestCommand, - } - - -def test_show_help_for_command(): - # FIXME(dhellmann): Are commands tied too closely to the app? Or - # do commands know too much about apps by using them to get to the - # command manager? - stdout = StringIO() - app = App('testing', '1', TestCommandManager('cliff.test'), stdout=stdout) - app.NAME = 'test' - help_cmd = HelpCommand(app, mock.Mock()) - parser = help_cmd.get_parser('test') - parsed_args = parser.parse_args(['one']) - try: - help_cmd.run(parsed_args) - except SystemExit: - pass - assert stdout.getvalue() == 'TestParser' - - -def test_list_matching_commands(): - # FIXME(dhellmann): Are commands tied too closely to the app? Or - # do commands know too much about apps by using them to get to the - # command manager? - stdout = StringIO() - app = App('testing', '1', TestCommandManager('cliff.test'), stdout=stdout) - app.NAME = 'test' - help_cmd = HelpCommand(app, mock.Mock()) - parser = help_cmd.get_parser('test') - parsed_args = parser.parse_args(['t']) - try: - help_cmd.run(parsed_args) - except SystemExit: - pass - help_output = stdout.getvalue() - assert 'Command "t" matches:' in help_output - assert 'two' in help_output - assert 'three' in help_output - - -def test_list_matching_commands_no_match(): - # FIXME(dhellmann): Are commands tied too closely to the app? Or - # do commands know too much about apps by using them to get to the - # command manager? - stdout = StringIO() - app = App('testing', '1', TestCommandManager('cliff.test'), stdout=stdout) - app.NAME = 'test' - help_cmd = HelpCommand(app, mock.Mock()) - parser = help_cmd.get_parser('test') - parsed_args = parser.parse_args(['z']) - try: - help_cmd.run(parsed_args) - except SystemExit: - pass - except ValueError: - pass - else: - assert False, 'Should have seen a ValueError' - - -def test_show_help_for_help(): - # FIXME(dhellmann): Are commands tied too closely to the app? Or - # do commands know too much about apps by using them to get to the - # command manager? - stdout = StringIO() - app = App('testing', '1', TestCommandManager('cliff.test'), stdout=stdout) - app.NAME = 'test' - help_cmd = HelpCommand(app, mock.Mock()) - parser = help_cmd.get_parser('test') - parsed_args = parser.parse_args([]) - try: - help_cmd.run(parsed_args) - except SystemExit: - pass - help_text = stdout.getvalue() - assert 'usage: test help [-h]' in help_text diff --git a/cliff/tests/test_lister.py b/cliff/tests/test_lister.py deleted file mode 100644 index 7d1876f..0000000 --- a/cliff/tests/test_lister.py +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env python - -import weakref - -from cliff.lister import Lister - -import mock - - -class FauxFormatter(object): - - def __init__(self): - self.args = [] - self.obj = weakref.proxy(self) - - def emit_list(self, columns, data, stdout, args): - self.args.append((columns, data)) - - -class ExerciseLister(Lister): - - def _load_formatter_plugins(self): - return { - 'test': FauxFormatter(), - } - return - - def take_action(self, parsed_args): - return ( - parsed_args.columns, - [('a', 'A'), ('b', 'B')], - ) - - -# def run(self, parsed_args): -# self.formatter = self.formatters[parsed_args.formatter] -# column_names, data = self.take_action(parsed_args) -# self.produce_output(parsed_args, column_names, data) -# return 0 - -def test_formatter_args(): - app = mock.Mock() - test_lister = ExerciseLister(app, []) - - parsed_args = mock.Mock() - parsed_args.columns = ('Col1', 'Col2') - parsed_args.formatter = 'test' - - test_lister.run(parsed_args) - f = test_lister.formatters['test'] - assert len(f.args) == 1 - args = f.args[0] - assert args[0] == list(parsed_args.columns) - data = list(args[1]) - assert data == [['a', 'A'], ['b', 'B']] diff --git a/cliff/tests/test_show.py b/cliff/tests/test_show.py deleted file mode 100644 index 41df5e1..0000000 --- a/cliff/tests/test_show.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python - -from cliff.show import ShowOne - -import mock - - -class FauxFormatter(object): - - def __init__(self): - self.args = [] - - def emit_list(self, columns, data, stdout, args): - self.args.append((columns, data)) - - -class ExerciseShowOne(ShowOne): - - def load_formatter_plugins(self): - self.formatters = { - 'test': FauxFormatter(), - } - return - - def take_action(self, parsed_args): - return ( - parsed_args.columns, - [('a', 'A'), ('b', 'B')], - ) - - -# def test_formatter_args(): -# app = mock.Mock() -# test_lister = ExerciseLister(app, []) - -# parsed_args = mock.Mock() -# parsed_args.columns = ('Col1', 'Col2') -# parsed_args.formatter = 'test' - -# test_lister.run(parsed_args) -# f = test_lister.formatters['test'] -# assert len(f.args) == 1 -# args = f.args[0] -# assert args[0] == list(parsed_args.columns) -# data = list(args[1]) -# assert data == [['a', 'A'], ['b', 'B']] - -def test_dict2columns(): - app = mock.Mock() - test_show = ExerciseShowOne(app, []) - d = {'a': 'A', 'b': 'B', 'c': 'C'} - expected = [('a', 'b', 'c'), ('A', 'B', 'C')] - actual = list(test_show.dict2columns(d)) - assert expected == actual |