PKtJ@%xcleo/__init__.py# -*- coding: utf-8 -*- from .application import Application from .commands import Command from .helpers import ( DescriptorHelper, FormatterHelper, Helper, HelperSet, ProgressBar, ProgressIndicator, QuestionHelper, Table, TableCell, TableSeparator, TableStyle ) from .inputs import ( Input, InputDefinition, InputArgument, InputOption, ArgvInput, ListInput, argument, option ) from .outputs import Output, ConsoleOutput, StreamOutput, BufferedOutput from .testers import ApplicationTester, CommandTester PKyG[[ttcleo/_compat.py# -*- coding: utf-8 -*- import sys PY2 = sys.version_info[0] == 2 if PY2: long = long unicode = unicode basestring = basestring else: long = int unicode = str basestring = str def decode(string, encodings=None): if not PY2 and not isinstance(string, bytes): return string if PY2 and isinstance(string, unicode): return string if encodings is None: encodings = ['utf-8', 'latin1', 'ascii'] for encoding in encodings: try: return string.decode(encoding) except (UnicodeEncodeError, UnicodeDecodeError): pass return string.decode(encodings[0], errors='ignore') def encode(string, encodings=None): if not PY2 and isinstance(string, bytes): return string if PY2 and isinstance(string, str): return string if encodings is None: encodings = ['utf-8', 'latin1', 'ascii'] for encoding in encodings: try: return string.encode(encoding) except (UnicodeEncodeError, UnicodeDecodeError): pass return string.encode(encodings[0], errors='ignore') PK`iLի4VPVPcleo/application.py# -*- coding: utf-8 -*- import sys import traceback import os import re from io import UnsupportedOperation from pylev import levenshtein from collections import OrderedDict from .outputs.output import Output from .outputs.console_output import ConsoleOutput from .inputs.argv_input import ArgvInput from .inputs.list_input import ListInput from .inputs.input_argument import InputArgument from .inputs.input_option import InputOption from .inputs.input_definition import InputDefinition from .commands.command import Command from .commands.help_command import HelpCommand from .commands.list_command import ListCommand from .commands.completions_command import CompletionsCommand from .helpers import HelperSet, FormatterHelper, QuestionHelper from .terminal import Terminal from .exceptions import ( CleoException, CommandNotFound, AmbiguousCommand, NamespaceNotFound, AmbiguousNamespace ) class Application(object): """ An Application is the container for a collection of commands. This class is optimized for a standard CLI environment. Usage: >>> app = Application('myapp', '1.0 (stable)') >>> app.add(HelpCommand()) >>> app.run() """ def __init__(self, name='UNKNOWN', version='UNKNOWN', complete=True): """ Constructor :param name: The name of the application :type name: basestring :param version: The version of the application :type version: basestring """ self._name = name self._version = version self._catch_exceptions = True self._auto_exit = True self._commands = OrderedDict() self._default_command = 'list' self._definition = self.get_default_input_definition() self._want_helps = False self._helper_set = self.get_default_helper_set() self._terminal = Terminal() self._running_command = None self._complete = complete self._single_command = False for command in self.get_default_commands(): self.add(command) @property def terminal(self): return self._terminal def run(self, input_=None, output_=None): """ Runs the current application :param input_: An Input Instance :type input_: cleo.input.Input :param output_: An Output instance :type output_: Output :return: 0 if everything went fine, or an error code :rtype: int """ if input_ is None: input_ = ArgvInput() if output_ is None: output_ = ConsoleOutput() self.configure_io(input_, output_) try: status_code = self.do_run(input_, output_) except Exception as e: if not self._catch_exceptions: raise if output_.is_quiet(): output_.set_verbosity(Output.VERBOSITY_NORMAL) if isinstance(output_, ConsoleOutput): self.render_exception(e, output_.get_error_output()) else: self.render_exception(e, output_) if isinstance(e, CleoException): status_code = e.code else: status_code = e.errno if hasattr(e, 'errno') else 1 if status_code == 0: status_code = 1 if self._auto_exit: if status_code is None: status_code = 0 if status_code > 255: status_code = 255 exit(status_code) return status_code def do_run(self, input_, output_): """ Runs the current application :param input_: An Input Instance :type input_: cleo.inputs.Input :param output_: An Output instance :type output_: Output :return: 0 if everything went fine, or an error code :rtype: int """ if input_.has_parameter_option(['--version', '-V']): output_.writeln(self.get_long_version()) return 0 name = self.get_command_name(input_) if input_.has_parameter_option(['--help', '-h']): if not name: name = 'help' input_ = ListInput([('command', 'help')]) else: self._want_helps = True if not name: name = self._default_command input_ = ListInput([('command', name)]) # the command name MUST be the first element of the input command = None while command is None: try: command = self.find(name) except AmbiguousCommand as e: alternatives = e.alternatives if (not alternatives or not input_.is_interactive()): raise rest = '' if len(alternatives) > 2: rest = ' and {} more'.format(len(alternatives) - 2) commands = '{}, {}{}'.format( alternatives[0], alternatives[1], rest ) message = ( '\nCommand "{}" is ambiguous ({}).' .format(e.name, commands) ) output_.writeln(message) return 1 self._running_command = command status_code = command.run(input_, output_) self._running_command = None return status_code def set_helper_set(self, helper_set): self._helper_set = helper_set def get_helper_set(self): return self._helper_set def set_definition(self, definition): self._definition = definition def get_definition(self): if self._single_command: input_definition = self._definition input_definition.set_arguments() return input_definition return self._definition def get_help(self): return self.get_long_version() def set_catch_exceptions(self, boolean): self._catch_exceptions = boolean def set_auto_exit(self, boolean): self._auto_exit = boolean def get_name(self): return self._name def set_name(self, name): self._name = name def get_version(self): return self._version def set_version(self, version): self._version = version def get_long_version(self): if 'UNKNOWN' != self.get_name() and 'UNKNOWN' != self.get_version(): return '%s %s' % (self.get_name(), self.get_version()) return 'Console Tool' def register(self, name): return self.add(Command(name)) def add_commands(self, commands): for command in commands: self.add(command) def add(self, command): """ Adds a command object. If a command with the same name already exists, it will be overridden. :param command: A Command object or a dictionary defining the command :type command: Command or dict :return: The registered command :rtype: Command """ command.set_application(self) if not command.is_enabled(): command.set_application(None) return try: command.get_definition() except AttributeError: raise Exception( 'Command class "%s" is not correctly initialized.' 'You probably forgot to call the parent constructor.' % command.__class__.__name__ ) self._commands[command.get_name()] = command for alias in command.get_aliases(): self._commands[alias] = command return command def get(self, name): if name not in self._commands: raise Exception('The command "%s" does not exist.' % name) command = self._commands[name] if self._want_helps: self._want_helps = False help_command = self.get('help') help_command.set_command(command) return help_command return command def has(self, name): return name in self._commands def get_namespaces(self): namespaces = [] for command in self._commands.values(): namespaces.append(self.extract_namespace(command.get_name())) for alias in command.get_aliases(): namespaces.append(self.extract_namespace(alias)) namespaces = [ns for ns in namespaces if ns] seen = set() return [ns for ns in namespaces if not (ns in seen or seen.add(ns))] def find_namespace(self, namespace): all_namespaces = self.get_namespaces() expr = re.sub('([^:]+|)', lambda m: re.escape(m.group(1)) + '[^:]*', namespace) namespaces = sorted([ ns for ns in all_namespaces if re.findall('^%s' % expr, ns) ]) if not namespaces: alternatives = self.find_alternatives(namespace, all_namespaces) raise NamespaceNotFound(namespace, alternatives) exact = namespace in namespaces if len(namespaces) > 1 and not exact: raise AmbiguousNamespace(namespace, namespaces) return namespace if exact else namespaces[0] def find(self, name): all_commands = list(self._commands.keys()) expr = re.sub('([^:]+|)', lambda m: re.escape(m.group(1)) + '[^:]*', name) commands = sorted([ x for x in all_commands if re.findall('^%s' % expr, x) ]) filtered_commands = [ c for c in commands if re.findall('^%s$' % expr, c) ] if not commands or len(filtered_commands) < 1: pos = name.find(':') if pos >= 0: # Check if a namespace exists and contains commands self.find_namespace(name[:pos]) alternatives = self.find_alternatives(name, all_commands) raise CommandNotFound(name, alternatives) # Filter out aliases for commands which are already on the list if len(commands) > 1: command_list = self._commands def f(name_or_alias): command_name = command_list[name_or_alias].get_name() return command_name == name_or_alias or (command_name not in commands) commands = sorted([c for c in commands if f(c)]) exact = name in commands if len(commands) > 1 and not exact: raise AmbiguousCommand(name, commands) return self.get(name if exact else commands[0]) def all(self, namespace=None): if namespace is None: return self._commands commands = OrderedDict() for name, command in self._commands.items(): if namespace == self.extract_namespace(name, namespace.count(':') + 1): commands[name] = command return commands @classmethod def get_abbreviations(cls, names): abbrevs = {} for name in names: l = len(name) while l > 0: abbrev = name[:l] if not abbrev in abbrevs: abbrevs[abbrev] = [name] else: abbrevs[abbrev].append(name) l -= 1 for name in names: abbrevs[name] = [name] return abbrevs def render_exception(self, e, output_): tb = traceback.extract_tb(sys.exc_info()[2]) title = ' [%s] ' % e.__class__.__name__ l = len(title) width = self._terminal.width if not width: width = sys.maxsize formatter = output_.get_formatter() lines = [] for line in re.split('\r?\n', str(e)): for splitline in [line[x:x + (width - 4)] for x in range(0, len(line), width - 4)]: line_length = len( re.sub('\[[^m]*m', '', formatter.format(splitline))) + 4 lines.append((splitline, line_length)) l = max(line_length, l) messages = [''] empty_line = formatter.format('%s' % (' ' * l)) messages.append(empty_line) messages.append(formatter.format('%s%s' % (title, ' ' * max(0, l - len(title))))) for line in lines: messages.append( formatter.format(' %s %s' % (line[0], ' ' * (l - line[1]))) ) messages.append(empty_line) messages.append('') output_.writeln(messages, Output.OUTPUT_RAW) if Output.VERBOSITY_VERBOSE <= output_.get_verbosity(): output_.writeln('Exception trace:') for exc_info in tb: file_ = exc_info[0] line_number = exc_info[1] function = exc_info[2] line = exc_info[3] output_.writeln(' %s in %s() ' 'at line %s' % (file_, function, line_number)) output_.writeln(' %s' % line) output_.writeln('') if self._running_command is not None: output_.writeln('%s' % self._running_command.get_synopsis()) output_.writeln('') def configure_io(self, input_, output_): """ Configures the input and output instances based on the user arguments and options. :param input_: An Input instance :type input_: Input :param output_: An Output instance :type output_: Output """ if input_.has_parameter_option('--ansi'): output_.set_decorated(True) elif input_.has_parameter_option('--no-ansi'): output_.set_decorated(False) if input_.has_parameter_option(['--no-interaction', '-n']): input_.set_interactive(False) elif self.get_helper_set().has('question'): input_stream = self.get_helper_set().get('question').input_stream try: is_atty = hasattr(input_stream, 'fileno') if hasattr(input_stream, 'isatty'): is_atty = is_atty and input_stream.isatty() else: is_atty = is_atty and os.isatty(input_stream) except (UnsupportedOperation, TypeError): is_atty = False if not is_atty and os.getenv('SHELL_INTERACTIVE') is None: input_.set_interactive(False) if input_.has_parameter_option(['--quiet', '-q']): output_.set_verbosity(Output.VERBOSITY_QUIET) elif input_.has_parameter_option('-vvv')\ or input_.has_parameter_option('--verbose=3')\ or input_.get_parameter_option('--verbose') == 3: output_.set_verbosity(Output.VERBOSITY_DEBUG) elif input_.has_parameter_option('-vv')\ or input_.has_parameter_option('--verbose=2')\ or input_.get_parameter_option('--verbose') == 2: output_.set_verbosity(Output.VERBOSITY_VERY_VERBOSE) elif input_.has_parameter_option('-v')\ or input_.has_parameter_option('--verbose=1')\ or input_.get_parameter_option('--verbose') == 1: output_.set_verbosity(Output.VERBOSITY_VERBOSE) def get_command_name(self, input_): if self._single_command: return self._default_command return input_.get_first_argument() def get_default_input_definition(self): return InputDefinition([ InputArgument('command', InputArgument.REQUIRED, 'The command to execute'), InputOption('--help', '-h', InputOption.VALUE_NONE, 'Display this help message'), InputOption('--quiet', '-q', InputOption.VALUE_NONE, 'Do not output any message'), InputOption( '--verbose', '-v|vv|vvv', InputOption.VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, ' '2 for more verbose output and 3 for debug' ), InputOption('--version', '-V', InputOption.VALUE_NONE, 'Display this application version'), InputOption('--ansi', '', InputOption.VALUE_NONE, 'Force ANSI output'), InputOption('--no-ansi', '', InputOption.VALUE_NONE, 'Disable ANSI output'), InputOption('--no-interaction', '-n', InputOption.VALUE_NONE, 'Do not ask any interactive question') ]) def get_default_commands(self): commands = [HelpCommand(), ListCommand()] if self._complete: commands.append(CompletionsCommand()) return commands def get_default_helper_set(self): return HelperSet([ FormatterHelper(), QuestionHelper() ]) def sort_commands(self, commands): """ Sorts command in alphabetical order :param commands: A dict of commands :type commands: dict :return: A sorted list of commands """ namespaced_commands = {} for name, command in commands.items(): key = self.extract_namespace(name, 1) if not key: key = '_global' if key in namespaced_commands: namespaced_commands[key][name] = command else: namespaced_commands[key] = {name: command} for namespace, commands in namespaced_commands.items(): namespaced_commands[namespace] = sorted(commands.items(), key=lambda x: x[0]) namespaced_commands = sorted(namespaced_commands.items(), key=lambda x: x[0]) return namespaced_commands def extract_namespace(self, name, limit=None): parts = name.split(':') parts.pop() return ':'.join(parts[:limit] if limit else parts) def find_alternatives(self, name, collection): """ Finds alternatives of name in collection :param name: The string :type name: str :param collection: The collection :type collection: list :return: A sorted list of similar strings """ threshold = 1e3 alternatives = {} collection_parts = {} for item in collection: collection_parts[item] = item.split(':') for i, subname in enumerate(name.split(':')): for collection_name, parts in collection_parts.items(): exists = collection_name in alternatives if i not in parts and exists: alternatives[collection_name] += threshold continue elif i not in parts: continue lev = levenshtein(subname, parts[i]) if lev <= (len(subname) / 3) or parts[i].find(subname) != -1: if exists: alternatives[collection_name] += lev else: alternatives[collection_name] = lev elif exists: alternatives[collection_name] += threshold for item in collection: lev = levenshtein(name, item) if lev <= (len(name) / 3) or item.find(name) != -1: if item in alternatives: alternatives[item] = alternatives[item] - lev else: alternatives[item] = lev alts = [] for alt, score in alternatives.items(): if score < 2 * threshold: alts.append(alt) return alts def set_default_command(self, command_name, is_single_command=False): """ Sets the default Command name. :param command_name: The Command name :type command_name: str :param is_single_command: Set to True if there is only one command in this application :type is_single_command: bool """ self._default_command = command_name if is_single_command: # Ensure the command exists self.find(command_name) self._single_command = True return self PKtJdX~jjcleo/commands/__init__.py# -*- coding: utf-8 -*- from .base_command import BaseCommand from .command import Command, CommandError from .help_command import HelpCommand from .list_command import ListCommand from .completions_command import CompletionsCommand __all__ = [ 'BaseCommand', 'Command', 'CommandError', 'HelpCommand', 'ListCommand', 'CompletionsCommand' ] PK0pJY!!cleo/commands/base_command.py# -*- coding: utf-8 -*- import os import sys import re import copy import inspect from ..inputs.input import Input from ..inputs.input_definition import InputDefinition from ..inputs.input_argument import InputArgument from ..inputs.input_option import InputOption from ..outputs.output import Output class CommandError(Exception): pass class BaseCommand(object): def __init__(self, name=None): self._definition = InputDefinition() self._ignore_validation_errors = False self._application_definition_merged = False self._application_definition_merged_with_args = False self._application = None self._helper_set = None self._usages = [] self._synopsis = {} self._code = None if hasattr(self, 'aliases'): self.set_aliases(self.aliases) else: self.aliases = [] if hasattr(self, 'help'): self.set_help(self.help) else: self.help = '' self.name = name or getattr(self, 'name', None) if hasattr(self, 'description'): self.set_description(self.description) else: self.description = '' if hasattr(self, 'usages'): for usage in copy.copy(self.usages): self.add_usage(usage) else: self._usages = [] if name is not None: self.set_name(name) self.configure() if not self.name: raise Exception('The command name cannot be empty.') def ignore_validation_errors(self): self._ignore_validation_errors = True def set_application(self, application=None): self._application = application if application: self.set_helper_set(application.get_helper_set()) else: self._helper_set = None def set_helper_set(self, helper_set): self._helper_set = helper_set def get_helper_set(self): return self._helper_set def get_application(self): return self._application def is_enabled(self): return True def configure(self): if hasattr(self, 'arguments'): for argument in self.arguments: if isinstance(argument, InputArgument): self._definition.add_argument(argument) else: raise Exception('Invalid argument') if hasattr(self, 'options'): for option in self.options: if isinstance(option, InputOption): self._definition.add_option(option) else: raise Exception('Invalid option') def execute(self, input_, output_): raise NotImplementedError() def interact(self, input_, output_): pass def initialize(self, input_, output_): pass def run(self, input_, output_): """ Runs the command. :param input_: an Input instance :type input_: Input :param output_: an Output instance :type output_: Output :return: The command exit code :rtype: int """ # force the creation of the synopsis before the merge with the app definition self.get_synopsis(True) self.get_synopsis(False) # add the application arguments and options self.merge_application_definition() # bind the input against the command specific arguments/options try: input_.bind(self._definition) except Exception as e: if not self._ignore_validation_errors: raise self.initialize(input_, output_) if input_.is_interactive(): self.interact(input_, output_) input_.validate() if self._code: status_code = self._execute_code(input_, output_) else: status_code = self.execute(input_, output_) try: return int(float(status_code)) except (TypeError, ValueError): return 0 def _execute_code(self, input_, output): return self._code(input_, output) def set_code(self, code): if not callable(code): raise Exception('Invalid callable provided to Command.setCode().') self._code = code return self def merge_application_definition(self, merge_args=True): """ Merges the application definition with the command definition. This method should not be used directly. :param merge_args: Whether to merge or not the Application definition arguments to Command definition arguments :type merge_args: bool """ if self._application is None \ or (self._application_definition_merged and (self._application_definition_merged_with_args or not merge_args)): return if merge_args: current_arguments = self._definition.get_arguments() self._definition.set_arguments(self._application.get_definition().get_arguments()) self._definition.add_arguments(current_arguments) self._definition.add_options(self._application.get_definition().get_options()) self._application_definition_merged = True if merge_args: self._application_definition_merged_with_args = True def set_definition(self, definition): if isinstance(definition, InputDefinition): self._definition = definition else: self._definition.set_definition(definition) self._application_definition_merged = False return self def get_definition(self): return self._definition def get_native_definition(self): return self.get_definition() def add_argument(self, name, mode=None, description='', default=None, validator=None): self._definition.add_argument( InputArgument(name, mode, description, default, validator) ) return self def add_option(self, name, shortcut=None, mode=None, description='', default=None, validator=None): self._definition.add_option( InputOption(name, shortcut, mode, description, default, validator) ) return self def set_name(self, name): self.validate_name(name) self.name = name return self def get_name(self): return self.name def set_description(self, description): self.description = description return self def get_description(self): return self.description def set_help(self, help_): self.help = help_ return self def get_help(self): return self.help def set_aliases(self, aliases): for alias in aliases: self.validate_name(alias) self.aliases = aliases return self def get_aliases(self): return self.aliases def add_usage(self, usage): if usage.find(self.name) != 0: usage = '%s %s' % (self.name, usage) self._usages.append(usage) return self def get_usages(self): return self._usages def get_synopsis(self, short=False): key = 'long' if short: key = 'short' if key not in self._synopsis: self._synopsis[key] = ( '%s %s' % (self.name, self._definition.get_synopsis(short)) ).strip() return self._synopsis[key] def get_helper(self, name): return self._helper_set.get(name) def get_processed_help(self): name = self.name h = self.get_help() or self.get_description() h = h.replace('%script.full_name%', self._get_script_full_name()) h = h.replace('%script.name%', self._get_script_name()) h = h.replace('%command.full_name%', self._get_command_full_name()) h = h.replace('%command.name%', name) return h def _get_command_full_name(self): return inspect.stack()[-1][1] + ' ' + self.name def _get_script_full_name(self): return os.path.realpath(sys.argv[0]) def _get_script_name(self): return os.path.basename(self._get_script_full_name()) def validate_name(self, name): if not re.match('^[^:]+(:[^:]+)*$', name): raise CommandError('Command name "%s" is invalid.' % name) PKYoLk73 8 8cleo/commands/command.py# -*- coding: utf-8 -*- import re import contextlib from .base_command import BaseCommand, CommandError from ..inputs.list_input import ListInput from ..parser import Parser from ..styles import CleoStyle from ..outputs import Output, NullOutput from ..questions import Question, ChoiceQuestion, ConfirmationQuestion from ..helpers import Table from ..helpers.table_separator import TableSeparator from ..helpers.table_cell import TableCell from ..helpers.table_style import TableStyle from ..helpers.progress_indicator import ProgressIndicator class Command(BaseCommand): name = None signature = None description = '' help = '' verbosity = Output.VERBOSITY_NORMAL verbosity_map = { 'v': Output.VERBOSITY_VERBOSE, 'vv': Output.VERBOSITY_VERY_VERBOSE, 'vvv': Output.VERBOSITY_DEBUG, 'quiet': Output.VERBOSITY_QUIET, 'normal': Output.VERBOSITY_NORMAL } validation = None hidden = False def __init__(self, name=None): self.input = None self.output = None doc = self.__doc__ or super(self.__class__, self).__doc__ if doc: self._parse_doc(doc) if not self.signature: parent = super(self.__class__, self) if hasattr(parent, 'signature'): self.signature = parent.signature if self.signature: self._configure_using_fluent_definition() else: super(Command, self).__init__(name or self.name) def _parse_doc(self, doc): doc = doc.strip().split('\n', 1) if len(doc) > 1: self.description = doc[0].strip() self.signature = re.sub('\s{2,}', ' ', doc[1].strip()) else: self.description = doc[0].strip() def _configure_using_fluent_definition(self): """ Configure the console command using a fluent definition. """ definition = Parser.parse(self.signature) super(Command, self).__init__(definition['name']) for argument in definition['arguments']: if self.validation and argument.get_name() in self.validation: argument.set_validator(self.validation[argument.get_name()]) self.get_definition().add_argument(argument) for option in definition['options']: if self.validation and '--%s' % option.get_name() in self.validation: option.set_validator(self.validation['--%s' % option.get_name()]) self.get_definition().add_option(option) def run(self, i, o): """ Initialize command. :type i: cleo.inputs.input.Input :type o: cleo.outputs.output.Output """ self.input = i self.output = CleoStyle(i, o) return super(Command, self).run(i, o) def execute(self, i, o): """ Executes the command. :type i: cleo.inputs.input.Input :type o: cleo.outputs.output.Output """ return self.handle() def handle(self): """ Executes the command. """ raise NotImplementedError() def call(self, name, options=None): """ Call another command. :param name: The command name :type name: str :param options: The options :type options: list or None """ if options is None: options = [] command = self.get_application().find(name) options = [('command', command.get_name())] + options return command.run(ListInput(options), self.output.output) def call_silent(self, name, options=None): """ Call another command silently. :param name: The command name :type name: str :param options: The options :type options: list or None """ if options is None: options = [] command = self.get_application().find(name) options = [('command', command.get_name())] + options return command.run(ListInput(options), NullOutput()) def argument(self, key=None): """ Get the value of a command argument. :param key: The argument name :type key: str :rtype: mixed """ if key is None: return self.input.get_arguments() return self.input.get_argument(key) def option(self, key=None): """ Get the value of a command option. :param key: The option name :type key: str :rtype: mixed """ if key is None: return self.input.get_options() return self.input.get_option(key) def confirm(self, question, default=False, true_answer_regex='(?i)^y'): """ Confirm a question with the user. :param question: The question to ask :type question: str :param default: The default value :type default: bool :param true_answer_regex: A regex to match the "yes" answer :type true_answer_regex: str :rtype: bool """ return self.output.confirm(question, default, true_answer_regex) def ask(self, question, default=None): """ Prompt the user for input. :param question: The question to ask :type question: str :param default: The default value :type default: str or None :rtype: str """ if isinstance(question, Question): return self.get_helper('question').ask(self.input, self.output, question) return self.output.ask(question, default) def secret(self, question): """ Prompt the user for input but hide the answer from the console. :param question: The question to ask :type question: str :rtype: str """ return self.output.ask_hidden(question) def choice(self, question, choices, default=None, attempts=None, multiple=False): """ Give the user a single choice from an list of answers. :param question: The question to ask :type question: str :param choices: The available choices :type choices: list :param default: The default value :type default: str or None :param attempts: The max number of attempts :type attempts: int :param multiple: Multiselect :type multiple: int :rtype: str """ question = ChoiceQuestion(question, choices, default) question.max_attempts = attempts question.multiselect = multiple return self.output.ask_question(question) def create_question(self, question, type=None, **kwargs): """ Returns a Question of specified type. :param type: The type of the question :type type: str :rtype: mixed """ if not type: return Question(question, **kwargs) if type == 'choice': return ChoiceQuestion(question, **kwargs) if type == 'confirmation': return ConfirmationQuestion(question, **kwargs) def table(self, headers=None, rows=None, style='default'): """ Return a Table instance. :param headers: The table headers :type headers: list :param rows: The table rows :type rows: list :param style: The table style :type style: str """ table = Table(self.output) if headers: table.set_headers(headers) if rows: table.set_rows(rows) table.set_style(style) return table def render_table(self, headers, rows, style='default'): """ Format input to textual table. :param headers: The table headers :type headers: list :param rows: The table rows :type rows: list :param style: The tbale style :type style: str """ table = Table(self.output) table.set_style(style).set_headers(headers).set_rows(rows).render() def table_separator(self): """ Return a TableSeparator instance. :rtype: TableSeparator """ return TableSeparator() def table_cell(self, value, **options): """ Return a TableCell instance :param value: The cell value :type value: str :param options: The cell options :type options: dict :rtype: TableCell """ return TableCell(value, **options) def table_style(self): """ Return a TableStyle instance. :rtype: TableStyle """ return TableStyle() def write(self, text, style=None): """ Writes a string without a new line. Useful if you want to use overwrite(). :param text: The line to write :type text: str :param style: The style of the string :type style: str """ if style: styled = '<%s>%s' % (style, text) else: styled = text self.output.write(styled, newline=False) def line(self, text, style=None, verbosity=None): """ Write a string as information output. :param text: The line to write :type text: str :param style: The style of the string :type style: str :param verbosity: The verbosity :type verbosity: None or int str """ if style: styled = '<%s>%s' % (style, text) else: styled = text self.output.writeln(styled) def line_error(self, text, style=None, verbosity=None): """ Write a string as information output to stderr. :param text: The line to write :type text: str :param style: The style of the string :type style: str :param verbosity: The verbosity :type verbosity: None or int str """ if style: styled = '<%s>%s' % (style, text) else: styled = text self.output.write_error(styled) def info(self, text): """ Write a string as information output. :param text: The line to write :type text: str """ self.line(text, 'info') def comment(self, text): """ Write a string as comment output. :param text: The line to write :type text: str """ self.line(text, 'comment') def question(self, text): """ Write a string as question output. :param text: The line to write :type text: str """ self.line(text, 'question') def error(self, text, block=False): """ Write a string as error output. :param text: The line to write :type text: str """ if block: return self.output.error(text) self.line(text, 'error') def warning(self, text): """ Write a string as warning output. :param text: The line to write :type text: str """ self.output.warning(text) def list(self, elements): """ Write a list of elements. :param elements: The elements to write a list for :type elements: list """ self.output.listing(elements) def progress_bar(self, max=0): """ Creates a new progress bar :param max: The maximum number of steps :type max: int :rtype: ProgressBar """ return self.output.create_progress_bar(max) def progress_indicator(self, fmt=None, indicator_change_interval=100, indicator_values=None): """ Creates a new progress indicator. :param fmt: Indicator format :type fmt: str or None :param indicator_change_interval: Change interval in milliseconds :type indicator_change_interval: int :param indicator_values: Animated indicator characters :type indicator_values: list or None :rtype: ProgressIndicator """ return ProgressIndicator(self.output, fmt, indicator_change_interval, indicator_values) def spin(self, start_message, end_message, fmt=None, interval=100, values=None): """ Automatically spin a progress indicator. :param start_message: The message to display when starting :type start_message: str :param end_message: The message to display when finishing :type end_message: str :param fmt: Indicator format :type fmt: str or None :param interval: Change interval in milliseconds :type interval: int :param values: Animated indicator characters :type values: list or None :rtype: ProgressIndicator """ spinner = ProgressIndicator(self.output, fmt, interval, values) return spinner.auto(start_message, end_message) def set_style(self, name, fg=None, bg=None, options=None): """ Sets a new style :param name: The name of the style :type name: str :param fg: The foreground color :type fg: str :param bg: The background color :type bg: str :param options: The options :type options: list """ self.output.get_formatter().add_style(name, fg, bg, options) def overwrite(self, text, size=None): """ Overwrites the current line. It will not add a new line so use line('') if necessary. :param text: The text to write. :type text: str :param size: The number of characters to overwrite. :type size: int or None """ self.output.overwrite(text, size=size) def is_hidden(self): """ Returns whether the command is hidden or not. :rtype: bool """ return self.hidden def _parse_verbosity(self, level=None): if level in self.verbosity_map: level = self.verbosity_map[level] elif not isinstance(level, int): level = self.verbosity return level def _execute_code(self, i, o): return self._code(self) PKtJ%cleo/commands/completions/__init__.py# -*- coding: utf-8 -*- PKtJtd 1&cleo/commands/completions/templates.py# -*- coding: utf-8 -*- BASH_TEMPLATE = """%(function)s() { local cur script coms opts com COMPREPLY=() _get_comp_words_by_ref -n : cur words # for an alias, get the real script behind it if [[ $(type -t ${words[0]}) == "alias" ]]; then script=$(alias ${words[0]} | sed -E "s/alias ${words[0]}='(.*)'/\\1/") else script=${words[0]} fi # lookup for command for word in ${words[@]:1}; do if [[ $word != -* ]]; then com=$word break fi done # completing for an option if [[ ${cur} == --* ]] ; then opts="%(opts)s" case "$com" in %(command_list)s esac COMPREPLY=($(compgen -W "${opts}" -- ${cur})) __ltrim_colon_completions "$cur" return 0; fi # completing for a command if [[ $cur == $com ]]; then coms="%(coms)s" COMPREPLY=($(compgen -W "${coms}" -- ${cur})) __ltrim_colon_completions "$cur" return 0 fi } %(compdefs)s""" ZSH_TEMPLATE = """#compdef %(script_name)s %(function)s() { local state com cur cur=${words[${#words[@]}]} # lookup for command for word in ${words[@]:1}; do if [[ $word != -* ]]; then com=$word break fi done if [[ ${cur} == --* ]]; then state="option" opts=(%(opts)s) elif [[ $cur == $com ]]; then state="command" coms=(%(coms)s) fi case $state in (command) _describe 'command' coms ;; (option) case "$com" in %(command_list)s esac _describe 'option' opts ;; *) # fallback to file completion _arguments '*:file:_files' esac } %(function)s "$@" %(compdefs)s""" FISH_TEMPLATE = """function __fish%(function)s_no_subcommand for i in (commandline -opc) if contains -- $i %(cmds_names)s return 1 end end return 0 end # global options %(opts)s # commands %(cmds)s # command options %(cmds_opts)s""" TEMPLATES = { 'bash': BASH_TEMPLATE, 'zsh': ZSH_TEMPLATE, 'fish': FISH_TEMPLATE } PKtJU55$cleo/commands/completions_command.py# -*- coding: utf-8 -*- import os import hashlib import re import json import subprocess from pastel import Pastel from ..exceptions import InvalidArgument from ..helpers import DescriptorHelper from ..outputs import BufferedOutput from .._compat import encode from .command import Command from .completions.templates import TEMPLATES class CompletionsCommand(Command): """ Generate completion scripts for your shell. completions { shell? : The shell to generate scripts for. } { --alias=* : Alias for the current command. } """ SUPPORTED_SHELLS = ('bash', 'zsh', 'fish') hidden = True help = """ One can generate a completion script for `%script.name%` that is compatible with a given shell. The script is output on `stdout` allowing one to re-direct the output to the file of their choosing. Where you place the file will depend on which shell, and which operating system you are using. Your particular configuration may also determine where these scripts need to be placed. Here are some common set ups for the three supported shells under Unix and similar operating systems (such as GNU/Linux). BASH: Completion files are commonly stored in `/etc/bash_completion.d/` Run the command: `%script.name% %command.name% bash > /etc/bash_completion.d/%script.name%.bash-completion` This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect. FISH: Fish completion files are commonly stored in `$HOME/.config/fish/completions` Run the command: `%script.name% %command.name% fish > ~/.config/fish/completions/%script.name%.fish` This installs the completion script. You may have to log out and log back in to your shell session for the changes to take effect. ZSH: ZSH completions are commonly stored in any directory listed in your `$fpath` variable. To use these completions, you must either add the generated script to one of those directories, or add your own to this list. Adding a custom directory is often the safest best if you're unsure of which directory to use. First create the directory, for this example we'll create a hidden directory inside our `$HOME` directory `mkdir ~/.zfunc` Then add the following lines to your `.zshrc` just before `compinit` `fpath+=~/.zfunc` Now you can install the completions script using the following command `%script.name% %command.name% zsh > ~/.zfunc/_%script.name%` You must then either log out and log back in, or simply run `exec zsh` For the new completions to take affect. CUSTOM LOCATIONS: Alternatively, you could save these files to the place of your choosing, such as a custom directory inside your $HOME. Doing so will require you to add the proper directives, such as `source`ing inside your login script. Consult your shells documentation for how to add such directives. """ def handle(self): shell = self.argument('shell') if not shell: shell = self.get_shell_type() if shell not in self.SUPPORTED_SHELLS: raise InvalidArgument( '[shell] argument must be one of {}' .format(', '.join(self.SUPPORTED_SHELLS)) ) self.line(self.render(shell)) def render(self, shell): return getattr(self, 'render_{}'.format(shell))() def render_bash(self): template = TEMPLATES['bash'] script_path = self._get_script_full_name() script_name = self._get_script_name() aliases = [ script_name, script_path ] aliases += self.option('alias') function = self._generate_function_name(script_name, script_path) description = self._get_json_description() global_options = set([ '--' + o.get_name() for o in self.get_application().get_definition().get_options() ]) commands = [] options_descriptions = {} commands_options = {} for command in description['commands']: command_options = [] commands.append(command['name']) options = command['definition']['options'] for name in sorted(list(options.keys())): option = options[name] name = option['name'] description = option['description'] command_options.append(name) options_descriptions[name] = description commands_options[command['name']] = command_options compdefs = '\n'.join([ 'complete -o default -F {} {}'.format(function, alias) for alias in aliases ]) commands = sorted(commands) command_list = [] for i, command in enumerate(commands): options = set(commands_options[command]).difference(global_options) options = sorted(options) options = [ self._zsh_describe(opt, None).strip('"') for opt in options ] desc = [ ' ({})'.format(command), ' opts="${{opts}} {}"'.format(' '.join(options)), ' ;;', ] if i < len(commands) - 1: desc.append('') command_list.append('\n'.join(desc)) output = template % { 'script_name': script_name, 'function': function, 'opts': ' '.join(sorted(global_options)), 'coms': ' '.join(commands), 'command_list': '\n'.join(command_list), 'compdefs': compdefs } return output def render_zsh(self): template = TEMPLATES['zsh'] script_path = self._get_script_full_name() script_name = self._get_script_name() aliases = [script_path] aliases += self.option('alias') function = self._generate_function_name(script_name, script_path) description = self._get_json_description() global_options = set([ '--' + o.get_name() for o in self.get_application().get_definition().get_options() ]) commands_descriptions = [] options_descriptions = {} commands_options_descriptions = {} commands_options = {} for command in description['commands']: command_options = [] commands_options_descriptions[command['name']] = {} command_description = self._remove_decoration(command['description']) commands_descriptions.append( self._zsh_describe(command['name'], command_description) ) options = command['definition']['options'] for name in sorted(list(options.keys())): option = options[name] name = option['name'] description = self._remove_decoration(option['description']) command_options.append(name) options_descriptions[name] = description commands_options_descriptions[command['name']][name] = description commands_options[command['name']] = command_options compdefs = '\n'.join([ 'compdef {} {}'.format(function, alias) for alias in aliases ]) commands = sorted(list(commands_options.keys())) command_list = [] for i, command in enumerate(commands): options = set(commands_options[command]).difference(global_options) options = sorted(options) options = [ self._zsh_describe(opt, commands_options_descriptions[command][opt]) for opt in options ] desc = [ ' ({})'.format(command), ' opts+=({})'.format(' '.join(options)), ' ;;', ] if i < len(commands) - 1: desc.append('') command_list.append('\n'.join(desc)) opts = [] for opt in global_options: opts.append( self._zsh_describe(opt, options_descriptions[opt]) ) output = template % { 'script_name': script_name, 'function': function, 'opts': ' '.join(sorted(opts)), 'coms': ' '.join(sorted(commands_descriptions)), 'command_list': '\n'.join(command_list), 'compdefs': compdefs } return output def render_fish(self): template = TEMPLATES['fish'] script_path = self._get_script_full_name() script_name = self._get_script_name() aliases = [script_name] aliases += self.option('alias') function = self._generate_function_name(script_name, script_path) description = self._get_json_description() global_options = set([ '--' + o.get_name() for o in self.get_application().get_definition().get_options() ]) commands_descriptions = {} options_descriptions = {} commands_options_descriptions = {} commands_options = {} for command in description['commands']: command_options = [] commands_options_descriptions[command['name']] = {} commands_descriptions[command['name']] = self._remove_decoration(command['description']) options = command['definition']['options'] for name in sorted(list(options.keys())): option = options[name] name = option['name'] description = self._remove_decoration(option['description']) command_options.append(name) options_descriptions[name] = description commands_options_descriptions[command['name']][name] = description commands_options[command['name']] = command_options opts = [] for opt in sorted(global_options): opts.append( "complete -c {} -n '__fish{}_no_subcommand' " "-l {} -d '{}'" .format( script_name, function, opt[2:], options_descriptions[opt].replace("'", "\\'") ) ) cmds_names = sorted(list(commands_options.keys())) cmds = [] cmds_opts = [] for i, cmd in enumerate(cmds_names): cmds.append( "complete -c {} -f -n '__fish{}_no_subcommand' " "-a {} -d '{}'" .format( script_name, function, cmd, commands_descriptions[cmd].replace("'", "\\'") ) ) cmds_opts += ['# {}'.format(cmd)] options = set(commands_options[cmd]).difference(global_options) options = sorted(options) for opt in options: cmds_opts.append( "complete -c {} -A -n '__fish_seen_subcommand_from {}' " "-l {} -d '{}'" .format( script_name, cmd, opt[2:], commands_options_descriptions[cmd][opt].replace("'", "\\'") ) ) if i < len(cmds_names) - 1: cmds_opts.append('') output = template % { 'script_name': script_name, 'function': function, 'cmds_names': ' '.join(cmds_names), 'opts': '\n'.join(opts), 'cmds': '\n'.join(cmds), 'cmds_opts': '\n'.join(cmds_opts) } return output def get_shell_type(self): shell = os.getenv('SHELL') if not shell: raise RuntimeError( 'Could not read SHELL environment variable. ' 'Please specify your shell type by passing it as the first argument.' ) return os.path.basename(shell) def _get_json_description(self): helper = DescriptorHelper() output = BufferedOutput() helper.describe( output, self.get_application(), format='json' ) return json.loads(output.fetch()) def _generate_function_name(self, script_name, script_path): return '_{}_{}_complete'.format( self._sanitize_for_function_name(script_name), hashlib.md5(encode(script_path)).hexdigest()[0:16] ) def _sanitize_for_function_name(self, name): name = name.replace('-', '_') return re.sub('[^A-Za-z0-9_]+', '', name) def _zsh_describe(self, value, description=None): value = '"' + value.replace(':', '\\:') if description: description = re.sub(r'(["\'#&;`|*?~<>^()\[\]{}$\\\x0A\xFF])', r'\\\1', description) value += ':{}'.format(subprocess.list2cmdline([description]).strip('"')) value += '"' return value def _remove_decoration(self, text): return re.sub( '\033\[[^m]*m', '', self.output.get_formatter().format(text) ) PKp*Hkcleo/commands/help_command.py# -*- coding: utf-8 -*- from .command import Command from ..helpers import DescriptorHelper class HelpCommand(Command): """ Displays help for a command help {command_name=help : The command name} {--format=txt : The output format (txt, json, or md)} {--raw : To output raw command help} """ help = """The %command.name% command displays help for a given command: python %command.full_name% list You can also output the help in other formats by using the --format option: python %command.full_name% --format=json list To display the list of available commands, please use the list command.""" _command = None def set_command(self, command): self._command = command def handle(self): if self._command is None: self._command = self.get_application().find(self.argument('command_name')) helper = DescriptorHelper() helper.describe( self.output, self._command, format=self.option('format'), raw_text=self.option('raw') ) self._command = None PK|t,HRcleo/commands/list_command.py# -*- coding: utf-8 -*- from .command import Command from ..helpers import DescriptorHelper class ListCommand(Command): """ Lists commands list {namespace? : The namespace name} {--raw : To output raw command list} {--format=txt : The output format (txt, json, or md)} """ help = """The %command.name% command lists all commands: python %command.full_name% You can also display the commands for a specific namespace: python %command.full_name% test You can also output the information in other formats by using the --format option: python %command.full_name% --format=json It's also possible to get raw list of commands (useful for embedding command runner): python %command.full_name% --raw""" def get_native_definition(self): return self.__class__().get_definition() def handle(self): helper = DescriptorHelper() helper.describe( self.output, self.get_application(), format=self.option('format'), raw_text=self.option('raw'), namespace=self.argument('namespace') ) PK*H] <cleo/descriptors/__init__.py# -*- coding: utf-8 -*- from .application_description import ApplicationDescription from .text_descriptor import TextDescriptor from .json_descriptor import JsonDescriptor from .markdown_descriptor import MarkdownDescriptor PKuwI]۶ +cleo/descriptors/application_description.py# -*- coding: utf-8 -*- from collections import OrderedDict class ApplicationDescription(object): GLOBAL_NAMESPACE = '_global' def __init__(self, application, namespace=None): """ Constructor. :type application: Application :type namespace: str """ self._application = application self._namespace = namespace self._namespaces = OrderedDict() self._commands = OrderedDict() self._aliases = {} self._inspect_application() def get_namespaces(self): return self._namespaces def get_commands(self): return self._commands def get_command(self, name): if name not in self._commands and name not in self._aliases: raise ValueError('Command %s does not exist.' % name) return self._commands.get(name, self._aliases.get(name)) def _inspect_application(self): namespace = None if self._namespace: namespace = self._application.find_namespace(self._namespace) all = self._application.all(namespace) for namespace, commands in self._sort_commands(all): names = [] for name, command in commands: if not command.get_name() or command.is_hidden(): continue if command.get_name() == name: self._commands[name] = command else: self._aliases[name] = command names.append(name) self._namespaces[namespace] = {'id': namespace, 'commands': names} def _sort_commands(self, commands): """ Sorts command in alphabetical order :param commands: A dict of commands :type commands: dict :return: A sorted list of commands """ namespaced_commands = {} for name, command in commands.items(): key = self._application.extract_namespace(name, 1) if not key: key = '_global' if key in namespaced_commands: namespaced_commands[key][name] = command else: namespaced_commands[key] = {name: command} for namespace, commands in namespaced_commands.items(): namespaced_commands[namespace] = sorted(commands.items(), key=lambda x: x[0]) namespaced_commands = sorted(namespaced_commands.items(), key=lambda x: x[0]) return namespaced_commands PK]$H6 cleo/descriptors/descriptor.py# -*- coding: utf-8 -*- from ..inputs import ( InputArgument, InputOption, InputDefinition ) from ..commands import BaseCommand from ..exceptions import CleoException from ..outputs import Output class Descriptor(object): output = None def describe(self, output, obj, **options): """ Describes an InputArgument instance. :param output: An OutputInstance :type output: Output :param obj: The object to describe :type obj: mixed :param options: The options :type options: dict """ from ..application import Application self.output = output if isinstance(obj, InputArgument): self._describe_input_argument(obj, **options) elif isinstance(obj, InputOption): self._describe_input_option(obj, **options) elif isinstance(obj, InputDefinition): self._describe_input_definition(obj, **options) elif isinstance(obj, BaseCommand): self._describe_command(obj, **options) elif isinstance(obj, Application): self._describe_application(obj, **options) else: raise CleoException('Object of type "%s" is not describable' % obj.__class__.__name__) def _write(self, content, decorated=False): """ Writes content to output. :param content: The content to write :type content: str type decorated: bool """ output_type = Output.OUTPUT_RAW if decorated: output_type = Output.OUTPUT_NORMAL self.output.write(content, False, output_type) def _describe_input_argument(self, argument, **options): """ Describes an InputArgument instance. :type argument: InputArgument :type options: dict """ raise NotImplementedError() def _describe_input_option(self, option, **options): """ Describes an InputOption instance. :type argument: InputOption :type options: dict """ raise NotImplementedError() def _describe_input_definition(self, definition, **options): """ Describes an InputDefinition instance. :type argument: InputDefinition :type options: dict """ raise NotImplementedError() def _describe_command(self, command, **options): """ Describes a Command instance. :type argument: BaseCommand :type options: dict """ raise NotImplementedError() def _describe_application(self, application, **options): """ Describes an Application instance. :type argument: Application :type options: dict """ raise NotImplementedError() PK%$HI`M""#cleo/descriptors/json_descriptor.py# -*- coding: utf-8 -*- import re try: import simplejson as json except ImportError: import json from collections import OrderedDict from .descriptor import Descriptor from .application_description import ApplicationDescription class JsonDescriptor(Descriptor): def _describe_input_argument(self, argument, **options): """ Describes an InputArgument instance. :type argument: InputArgument :type options: dict """ self._write_data(self._get_input_argument_data(argument), **options) def _describe_input_option(self, option, **options): """ Describes an InputOption instance. :type argument: InputOption :type options: dict """ self._write_data(self._get_input_option_data(option), **options) def _describe_input_definition(self, definition, **options): """ Describes an InputDefinition instance. :type argument: InputDefinition :type options: dict """ self._write_data(self._get_input_definition_data(definition), **options) def _describe_command(self, command, **options): """ Describes a Command instance. :type argument: BaseCommand :type options: dict """ self._write_data(self._get_command_data(command), **options) def _describe_application(self, application, **options): """ Describes an Application instance. :type argument: Application :type options: dict """ described_namespace = options.get('namespace') description = ApplicationDescription(application, described_namespace) commands = [] for command in description.get_commands().values(): commands.append(self._get_command_data(command)) if described_namespace: data = { 'commands': commands, 'namespace': described_namespace } else: data = { 'commands': commands, 'namespaces': list(description.get_namespaces().values()) } self._write_data(data, **options) def _write_data(self, data, **options): actual_options = { 'separators': (',', ':') } actual_options.update(options.get('json_encoding', {})) self._write(json.dumps(data, **actual_options)) def _get_input_argument_data(self, argument): return OrderedDict([ ('name', argument.get_name()), ('is_required', argument.is_required()), ('is_list', argument.is_list()), ('description', re.sub('\s*[\r\n]\s*', ' ', argument.get_description())), ('default', argument.get_default()) ]) def _get_input_option_data(self, option): shortcut = '' if option.get_shortcut(): shortcut = '-' + '|-'.join(option.get_shortcut().split('|')) return OrderedDict([ ('name', '--' + option.get_name()), ('shortcut', shortcut), ('accept_value', option.accept_value()), ('is_value_required', option.is_value_required()), ('is_multiple', option.is_list()), ('description', re.sub('\s*[\r\n]\s*', ' ', option.get_description())), ('default', option.get_default()) ]) def _get_input_definition_data(self, definition): arguments = OrderedDict() for argument in definition.get_arguments(): arguments[argument.get_name()] = self._get_input_argument_data(argument) options = OrderedDict() for option in definition.get_options(): options[option.get_name()] = self._get_input_option_data(option) return OrderedDict([ ('arguments', arguments), ('options', options) ]) def _get_command_data(self, command): command.get_synopsis() command.merge_application_definition(False) return OrderedDict([ ('name', command.get_name()), ('usage', [command.get_synopsis()] + command.get_aliases() + command.get_usages()), ('description', command.get_description()), ('help', command.get_processed_help()), ('definition', self._get_input_definition_data(command.get_native_definition())) ]) PK*HB'cleo/descriptors/markdown_descriptor.py# -*- coding: utf-8 -*- import re from .descriptor import Descriptor from .application_description import ApplicationDescription class MarkdownDescriptor(Descriptor): def _describe_input_argument(self, argument, **options): """ Describes an InputArgument instance. :type argument: InputArgument :type options: dict """ name = argument.get_name() or '' required = 'yes' if argument.is_required() else 'no' is_list = 'yes' if argument.is_list() else 'no' description = re.sub('\s*[\r\n]\s*', '\n ', argument.get_description() or '') default = argument.get_default() lines = [ '**%s:**\n\n' % name, '* Name: %s\n' % name, '* Is required: %s\n' % required, '* Is list: %s\n' % is_list, '* Description: %s\n' % description, '* Default: `%s`' % default ] self._write_lines(lines) def _describe_input_option(self, option, **options): """ Describes an InputOption instance. :type argument: InputOption :type options: dict """ name = option.get_name() or '' shortcut = '' if option.get_shortcut(): shortcut = '`-' + '|-'.join(option.get_shortcut().split('|')) + '`' value_required = 'yes' if option.is_value_required() else 'no' accept_value = 'yes' if option.accept_value() else 'no' is_multiple = 'yes' if option.is_list() else 'no' description = re.sub('\s*[\r\n]\s*', '\n ', option.get_description() or '') default = option.get_default() lines = [ '**%s:**\n\n' % name, '* Name: `--%s`\n' % name, '* Shortcut: %s\n' % shortcut, '* Accept value: %s\n' % accept_value, '* Is value required: %s\n' % value_required, '* Is multiple: %s\n' % is_multiple, '* Description: %s\n' % description, '* Default: `%s`' % default ] self._write_lines(lines) def _describe_input_definition(self, definition, **options): """ Describes an InputDefinition instance. :type argument: InputDefinition :type options: dict """ if definition.get_arguments(): self._write('### Arguments:') for argument in definition.get_arguments(): self._write('\n\n') self._describe_input_argument(argument) if definition.get_options(): if definition.get_arguments(): self._write('\n\n') self._write('### Options:') for option in definition.get_options(): self._write('\n\n') self._describe_input_option(option) def _describe_command(self, command, **options): """ Describes a Command instance. :type argument: BaseCommand :type options: dict """ command.get_synopsis() command.merge_application_definition(False) usages = [command.get_synopsis()] + command.get_aliases() + command.get_usages() lines = [ '%s\n' % command.get_name(), '%s\n\n' % ('-' * len(command.get_name())), '* Description: %s\n' % (command.get_description() or ''), '* Usage:\n\n%s' % (''.join(list(map(lambda x: ' * `%s`\n' % x, usages)))) ] self._write_lines(lines) help = command.get_processed_help() if help: self._write('\n') self._write(help) if command.get_native_definition(): self._write('\n\n') self._describe_input_definition(command.get_native_definition()) def _describe_application(self, application, **options): """ Describes an Application instance. :type argument: Application :type options: dict """ described_namespace = options.get('namespace') description = ApplicationDescription(application, described_namespace) self._write('%s\n%s' % (application.get_name(), '=' * len(application.get_name()))) for namespace in description.get_namespaces().values(): if namespace['id'] != ApplicationDescription.GLOBAL_NAMESPACE: self._write('\n\n') self._write('**%s:**' % namespace['id']) self._write('\n\n') self._write('\n'.join(list(map(lambda x: '* %s' % x, namespace['commands'])))) for command in description.get_commands().values(): self._write('\n\n') self._describe_command(command) def _write_lines(self, lines, **options): for line in lines: self._write(line, **options) PKLS4Iܭ&&#cleo/descriptors/text_descriptor.py# -*- coding: utf-8 -*- import re try: import simplejson as json except ImportError: import json from .descriptor import Descriptor from .application_description import ApplicationDescription from ..inputs import InputDefinition class TextDescriptor(Descriptor): """ Text Descriptor """ def _describe_input_argument(self, argument, **options): """ Describes an InputArgument instance. :type argument: InputArgument :type options: dict """ default = argument.get_default() if default is not None and (not isinstance(default, list) or len(default)): default = ' [default: %s]' % self._format_default_value(default) else: default = '' total_width = options.get('total_width', len(argument.get_name())) spacing_width = total_width - len(argument.get_name()) + 2 self._write_text( ' %s%s%s%s' % ( argument.get_name(), ' ' * spacing_width, re.sub('\s*[\r\n]\s*', '\n' + (' ' * (total_width + 17)), argument.get_description() or ''), default ), **options ) def _describe_input_option(self, option, **options): """ Describes an InputOption instance. :type argument: InputOption :type options: dict """ accept_value = option.accept_value() default = option.get_default() if accept_value and default is not None and (not isinstance(default, list) or len(default)): default = ' [default: %s]' % self._format_default_value(default) else: default = '' value = '' if accept_value: value = '=%s' % option.get_name().upper() if option.is_value_optional(): value = '[%s]' % value total_width = options.get('total_width', self._calculate_total_width_for_options([option])) shortcut = option.get_shortcut() synopsis = '%s%s'\ % ('-%s, ' % shortcut if shortcut else ' ', '--%s%s' % (option.get_name(), value)) spacing_width = total_width - len(synopsis) + 2 self._write_text( ' %s%s%s%s%s' % ( synopsis, ' ' * spacing_width, re.sub('\s*[\r\n]\s*', '\n' + (' ' * (total_width + 17)), option.get_description() or ''), default, ' (multiple values allowed)' if option.is_list() else '' ), **options ) def _describe_input_definition(self, definition, **options): """ Describes an InputDefinition instance. :type argument: InputDefinition :type options: dict """ definition_options = definition.get_options() definition_arguments = definition.get_arguments() total_width = self._calculate_total_width_for_options(definition_options) for argument in definition_arguments: total_width = max(total_width, len(argument.get_name())) if definition_arguments: self._write_text('Arguments:', **options) self._write_text('\n') for argument in definition_arguments: self._describe_input_argument(argument, total_width=total_width, **options) self._write_text('\n') if definition_arguments and definition_options: self._write_text('\n') if definition_options: later_options = [] self._write_text('Options:', **options) for option in definition_options: if option.get_shortcut() and len(option.get_shortcut()) > 1: later_options.append(option) continue self._write_text('\n') self._describe_input_option(option, total_width=total_width, **options) for option in later_options: self._write_text('\n') self._describe_input_option(option, total_width=total_width, **options) def _describe_command(self, command, **options): """ Describes a Command instance. :type argument: BaseCommand :type options: dict """ command.get_synopsis(True) command.get_synopsis(False) command.merge_application_definition(False) self._write_text('Usage:', **options) for usage in [command.get_synopsis(True)] + command.get_aliases() + command.get_usages(): self._write_text('\n') self._write_text(' %s' % usage, **options) self._write_text('\n') definition = command.get_native_definition() if definition.get_options() or definition.get_arguments(): self._write_text('\n') self._describe_input_definition(definition, **options) self._write_text('\n') help = command.get_processed_help() if help: self._write_text('\n') self._write_text('Help:', **options) self._write_text('\n') self._write_text(' %s' % help.replace('\n', '\n '), **options) self._write_text('\n') def _describe_application(self, application, **options): """ Describes an Application instance. :type argument: Application :type options: dict """ described_namespace = options.get('namespace') description = ApplicationDescription(application, described_namespace) raw_text = options.get('raw_text') if raw_text: width = self._get_column_width(description.get_commands().values()) for command in description.get_commands().values(): self._write_text('%-*s %s' % (width, command.get_name(), command.get_description()), **options) self._write_text('\n') else: help = application.get_help() if help: self._write_text('%s\n\n' % help, **options) self._write_text('Usage:\n', **options) self._write_text(' command [options] [arguments]\n\n', **options) self._describe_input_definition( InputDefinition( application.get_definition().get_options() ), **options ) self._write_text('\n') self._write_text('\n') width = self._get_column_width(description.get_commands().values()) if described_namespace: self._write_text( 'Available commands for the "%s" namespace:' % described_namespace, **options ) else: self._write_text('Available commands:', **options) # add commands by namespace commands = description.get_commands() for namespace in description.get_namespaces().values(): if not described_namespace and namespace['id'] != ApplicationDescription.GLOBAL_NAMESPACE: self._write_text('\n') self._write_text(' %s' % namespace['id'], **options) for name in namespace['commands']: if name in commands: self._write_text('\n') spacing_width = width - len(name) command = commands[name] command_aliases = self._get_command_aliases_text(command) desc = description.get_command(name).get_description() self._write_text( ' %s%s%s' % (name, ' ' * spacing_width, command_aliases + desc), **options ) self._write_text('\n') def _get_command_aliases_text(self, command): """ Formats command aliases to show them in the command description. :param command: The command :type command: Command :rtype: str """ text = '' aliases = command.get_aliases() if aliases: text = '[{}] '.format('|'.join(aliases)) return text def _write_text(self, content, **options): raw = options.get('raw_text') if raw: content = re.sub(r'<[^>]*?>', '', content) self._write(content, not options.get('raw_output', False)) def _format_default_value(self, default): """ Formats input option/argument default value. :type default: mixed :rtype: str """ return json.dumps(default) def _get_column_width(self, commands): widths = [] for command in commands: widths.append(len(command.get_name())) for alias in command.get_aliases(): widths.append(len(alias)) return max(widths) + 2 def _calculate_total_width_for_options(self, options): total_width = 0 for option in options: # "-" + shortcut + ", --" + name shortcut = option.get_shortcut() or '' name_length = 1 + max(len(shortcut), 1) + 4 + len(option.get_name()) if option.accept_value(): value_length = 1 + len(option.get_name()) if option.is_value_optional(): value_length += 2 name_length += value_length total_width = max(total_width, name_length) return total_width PKm4I$O$==cleo/exceptions/__init__.py# -*- coding: utf-8 -*- from .exception import CleoException, UsageException from .command import ( CommandNotFound, AmbiguousCommand, NamespaceNotFound, AmbiguousNamespace ) from .input import ( InvalidArgument, InvalidOption, MissingArguments, TooManyArguments, BadOptionUsage, NoSuchOption ) PKtJ+cleo/exceptions/command.py# -*- coding: utf-8 -*- from .exception import CleoException class CommandNotFound(CleoException): def __init__(self, name, alternatives=None, code=1): if alternatives is None: alternatives = [] self._name = name self._alternatives = alternatives super(CommandNotFound, self).__init__(self.message, code=1) @property def message(self): message = 'Command "{}" is not defined.'.format(self._name) if self.alternatives: if len(self._alternatives) == 1: message += '\n\nDid you mean this?\n ' else: message += '\n\nDid you mean one of these?\n ' message += '\n '.join(self._alternatives) return message @property def name(self): return self._name @property def alternatives(self): return self._alternatives class NamespaceNotFound(CommandNotFound): @property def message(self): message = 'There are no commands defined in the "{}" namespace.'.format(self._name) if self.alternatives: if len(self._alternatives) == 1: message += '\n\nDid you mean this?\n ' else: message += '\n\nDid you mean one of these?\n ' message += '\n '.join(self._alternatives) return message class AmbiguousCommand(CommandNotFound): @property def message(self): message = '\nCommand "{}" is ambiguous ({}).'.format( self._name, self.get_abbreviation_suggestions() ) return message def get_abbreviation_suggestions(self): """ Returns abbreviated suggestions in string format. :rtype: str """ rest = '' if len(self._alternatives) > 2: rest = ' and {} more'.format(len(self._alternatives) - 2) return '{}, {}{}'.format( self._alternatives[0], self._alternatives[1], rest ) class AmbiguousNamespace(AmbiguousCommand): @property def message(self): message = 'The namespace "{}" is ambiguous ({}).'.format( self._name, self.get_abbreviation_suggestions() ) return message PKM4I@LOLLcleo/exceptions/exception.py# -*- coding: utf-8 -*- class CleoException(Exception): def __init__(self, message, code=1): self.code = code super(CleoException, self).__init__(message) class UsageException(CleoException): def __init__(self, message, code=2): super(UsageException, self).__init__(message, code) PK4IUΠmmcleo/exceptions/input.py# -*- coding: utf-8 -*- from .exception import UsageException class InvalidArgument(UsageException): pass class InvalidOption(UsageException): pass class MissingArguments(UsageException): pass class NoSuchOption(UsageException): pass class TooManyArguments(UsageException): pass class BadOptionUsage(UsageException): pass PKtJ~{#::cleo/formatters/__init__.py# -*- coding: utf-8 -*- from .formatter import Formatter PKtJY'!!cleo/formatters/formatter.py# -*- coding: utf-8 -*- from pastel import Pastel class Formatter(Pastel): def format(self, message): return self.colorize(message) def set_decorated(self, decorated): self.with_colors(decorated) def is_decorated(self): return self.is_colorized() PKtJ"ͳcleo/helpers/__init__.py# -*- coding: utf-8 -*- from .question_helper import QuestionHelper from .formatter_helper import FormatterHelper from .helper import Helper from .helper_set import HelperSet from .progress_helper import ProgressHelper from .progress_bar import ProgressBar from .progress_indicator import ProgressIndicator from .table_helper import TableHelper from .table import Table from .table_cell import TableCell from .table_separator import TableSeparator from .table_style import TableStyle from .descriptor_helper import DescriptorHelper __all__ = [ 'DescriptorHelper', 'QuestionHelper', 'FormatterHelper', 'Helper', 'HelperSet', 'ProgressHelper', 'ProgressBar', 'ProgressIndicator', 'TableHelper', 'Table', 'TableCell', 'TableSeparator', 'TableStyle' ] PK*H%Ä!cleo/helpers/descriptor_helper.py# -*- coding: utf-8 -*- from .helper import Helper from ..descriptors import TextDescriptor, JsonDescriptor, MarkdownDescriptor class DescriptorHelper(Helper): """ This class adds helper method to describe objects in various formats. """ def __init__(self): self._descriptors = {} self.register('txt', TextDescriptor()) self.register('json', JsonDescriptor()) self.register('md', MarkdownDescriptor()) def describe(self, output, obj, **options): """ Describes an object if supported. Available options are: * format: string, the output format name * raw_text: boolean, sets output type as raw :type output: Output :type obj: mixed """ actual_options = { 'raw_text': False, 'format': 'txt' } actual_options.update(options) if actual_options['format'] not in self._descriptors: raise ValueError('Unsupported format "%s".' % actual_options['format']) descriptor = self._descriptors[options['format']] descriptor.describe(output, obj, **options) def register(self, name, descriptor): """ Registers a descriptor. :param name: The name of the descriptor :type name: str :param descriptor: The descriptor to register :type descriptor: Descriptor :rtype: DescriptorHelper """ self._descriptors[name] = descriptor return self PKtJI| cleo/helpers/formatter_helper.py# -*- coding: utf-8 -*- from .helper import Helper from ..formatters import Formatter class FormatterHelper(Helper): name = 'formatter' def format_section(self, section, message, style='info'): return '<%s>[%s] %s' % (style, section, style, message) def format_block(self, messages, style, large=False): messages = [messages] if not isinstance(messages, (list, tuple)) else messages l = 0 lines = [] for message in messages: message = Formatter.escape(message) lines.append((' %s ' if large else ' %s ') % message) l = max(len(message) + (4 if large else 2), l) messages = [' ' * l] if large else [] for line in lines: messages.append(line + ' ' * (l - len(line))) if large: messages.append(' ' * l) messages = map(lambda m: '<%s>%s' % (style, m, style), messages) return '\n'.join(messages) def get_name(self): return 'formatter' PKHKZ1S S cleo/helpers/helper.py# -*- coding: utf-8 -*- from __future__ import division import math import re from .._compat import decode class Helper(object): helper_set = None time_formats = [ (0, '< 1 sec'), (2, '1 sec'), (59, 'secs', 1), (60, '1 min'), (3600, 'mins', 60), (5400, '1 hr'), (86400, 'hrs', 3600), (129600, '1 day'), (604800, 'days', 86400), ] def set_helper_set(self, helper_set=None): """ Sets the helper set associated with this helper. :param helper_set: A HelperSet instance :type helper_set: HelperSet """ self.helper_set = helper_set def get_helper_set(self): """ Gets the helper set associated with this helper. :return: A HelperSet instance :rtype: HelperSet """ return self.helper_set @classmethod def len(cls, string): """ Returns the length of a string. :param string: The string to return the length of :type string: str :return: The length of the string :rtype: int """ return len(decode(string)) @classmethod def format_time(cls, secs): """ Format a duration in seconds to a human readable representation. :param secs: The duration in seconds :type secs: int :return: The duration representation :rtype: str """ for fmt in cls.time_formats: if secs > fmt[0]: continue if len(fmt) == 2: return fmt[1] return '%s %s' % (int(math.ceil(secs / fmt[2])), fmt[1]) @classmethod def format_memory(cls, memory): """ Format a memory in bytes to a human readable representation. :param secs: The memory in bytes :type secs: int :return: The memory representation :rtype: str """ if memory >= 1024**3: return '%.1f GiB' % (memory / 1024**3) if memory >= 1024 **2: return '%.1f MiB' % (memory / 1024**2) if memory >= 1024: return '%.1f KiB' % (memory / 1024) return '%.1f B' % memory @classmethod def len_without_decoration(cls, formatter, string): is_decorated = formatter.is_decorated() formatter.set_decorated(False) # Remove <...> formatting string = formatter.format(string) # Remove already formatted characters string = re.sub('\033\[[^m]*m', '', string) formatter.set_decorated(is_decorated) return cls.len(string) PKYoLكecleo/helpers/helper_set.py# -*- coding: utf-8 -*- class HelperSet(object): def __init__(self, helpers=None): helpers = helpers or [] self._helpers = {} self._command = None for helper in helpers: self.set(helper, None) def set(self, helper, alias=None): self._helpers[helper.get_name()] = helper if alias is not None: self._helpers[alias] = helper helper.set_helper_set(self) def has(self, name): return name in self._helpers def get(self, name): if not self.has(name): raise Exception('The helper "%s" is not defined.' % name) return self._helpers[name] def set_command(self, command): self._command = command def get_command(self): return self._command PK;ZoLH<# + +cleo/helpers/progress_bar.py# -*- coding: utf-8 -*- from __future__ import division import time import re import math from ..outputs import Output, ConsoleOutput from ..exceptions import CleoException from .helper import Helper class ProgressBar(object): """ The ProgressBar provides helpers to display progress output. """ # Options bar_width = 28 bar_char = None empty_bar_char = '-' progress_char = '>' redraw_freq = 1 formats = { 'normal': ' %current%/%max% [%bar%] %percent:3s%%', 'normal_nomax': ' %current% [%bar%]', 'verbose': ' %current%/%max% [%bar%] %percent:3s%% %elapsed:-6s%', 'verbose_nomax': ' %current% [%bar%] %elapsed:6s%', 'very_verbose': ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', 'very_verbose_nomax': ' %current% [%bar%] %elapsed:6s%', 'debug': ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%', 'debug_nomax': ' %current% [%bar%] %elapsed:6s%' } def __init__(self, output, max=0): """ Constructor. :param output: An Output instance :type output: Output :param max: Maximum steps (0 if unknown) :type max: int """ if isinstance(output, ConsoleOutput): output = output.get_error_output() self._output = output self._max = 0 self._step_width = None self._set_max_steps(max) self._step = 0 self._percent = 0.0 self._format = None self._internal_format = None self._format_line_count = 0 self._last_messages_length = 0 self._should_overwrite = True if not self._output.is_decorated(): # Disable overwrite when output does not support ANSI codes. self._should_overwrite = False # Set a reasonable redraw frequency so output isn't flooded self.set_redraw_frequency(max / 10) self._messages = {} self._start_time = time.time() def set_message(self, message, name='message'): self._messages[name] = message def get_message(self, name='message'): return self._messages[name] def get_start_time(self): return self._start_time def get_max_steps(self): return self._max def get_progress(self): return self._step def get_progress_percent(self): return self._percent def set_bar_character(self, character): self.bar_char = character return self def get_bar_character(self): if self.bar_char is None: if self._max: return '=' return self.empty_bar_char return self.bar_char def get_bar_width(self): return self.bar_width def set_bar_width(self, width): self.bar_width = width return self def get_empty_bar_character(self): return self.empty_bar_char def set_empty_bar_character(self, character): self.empty_bar_char = character return self def get_progress_character(self): return self.progress_char def set_progress_character(self, character): self.progress_char = character return self def set_format(self, fmt): self._format = None self._internal_format = fmt def set_redraw_frequency(self, freq): self.redraw_freq = max(freq, 1) def start(self, max=None): """ Start the progress output. :param max: Number of steps to complete the bar (0 if indeterminate). None to leave unchanged. :type max: int or None """ self._start_time = time.time() self._step = 0 self._percent = 0.0 if max is not None: self._set_max_steps(max) self.display() def advance(self, step=1): """ Advances the progress output X steps. :param step: Number of steps to advance. :type step: int """ self.set_progress(self._step + step) def set_progress(self, step): """ Sets the current progress. :param step: The current progress :type step: int """ if self._max and step > self._max: self._max = step elif step < 0: step = 0 prev_period = int(self._step / self.redraw_freq) curr_period = int(step / self.redraw_freq) self._step = step if self._max: self._percent = self._step / self._max else: self._percent = 0.0 if prev_period != curr_period or self._max == step: self.display() def finish(self): """ Finish the progress output. """ if not self._max: self._max = self._step if self._step == self._max and not self._should_overwrite: return self.set_progress(self._max) def display(self): """ Ouput the current progress string. """ if self._output.get_verbosity() == Output.VERBOSITY_QUIET: return if self._format is None: self._set_real_format(self._internal_format or self._determine_best_format()) self._overwrite(re.sub('(?i)%([a-z\-_]+)(?:\:([^%]+))?%', self._overwrite_callback, self._format)) def _overwrite_callback(self, matches): if hasattr(self, '_formatter_%s' % matches.group(1)): text = str(getattr(self, '_formatter_%s' % matches.group(1))()) elif matches.group(1) in self._messages: text = self._messages[matches.group(1)] else: return matches.group(0) if matches.group(2): if matches.group(2).startswith('-'): text = text.ljust(int(matches.group(2).lstrip('-').rstrip('s'))) else: text = text.rjust(int(matches.group(2).rstrip('s'))) return text def clear(self): """ Removes the progress bar from the current line. This is useful if you wish to write some output while a progress bar is running. Call display() to show the progress bar again. """ if not self._should_overwrite: return if self._format is None: self._set_real_format(self._internal_format or self._determine_best_format()) self._overwrite('\n' * self._format_line_count) def _set_real_format(self, fmt): """ Sets the progress bar format. """ # try to use the _nomax variant if available if not self._max and fmt + '_nomax' in self.formats: self._format = self.formats[fmt + '_nomax'] elif fmt in self.formats: self._format = self.formats[fmt] else: self._format = fmt self._format_line_count = self._format.count('\n') def _set_max_steps(self, mx): """ Sets the progress bar maximal steps. :type mx: int """ self._max = max(0, mx) if self._max: self._step_width = Helper.len(str(self._max)) else: self._step_width = 4 def _overwrite(self, message): """ Overwrites a previous message to the output. :type message: str """ lines = message.split('\n') # Append whitespace to match the line's length if self._last_messages_length is not None: for i, line in enumerate(lines): if self._last_messages_length > Helper.len_without_decoration(self._output.get_formatter(), line): lines[i] = line.ljust(self._last_messages_length, '\x20') if self._should_overwrite: # move back to the beginning of the progress bar before redrawing it self._output.write('\x0D') elif self._step > 0: # move to new line self._output.writeln('') if self._format_line_count: self._output.write('\033[%dA' % self._format_line_count) self._output.write('\n'.join(lines)) self._last_messages_length = 0 for line in lines: length = Helper.len_without_decoration(self._output.get_formatter(), line) if length > self._last_messages_length: self._last_messages_length = length def _determine_best_format(self): verbosity = self._output.get_verbosity() if verbosity == Output.VERBOSITY_VERBOSE: if self._max: return 'verbose' return 'verbose_nomax' elif verbosity == Output.VERBOSITY_VERY_VERBOSE: if self._max: return 'very_verbose' return 'very_verbose_nomax' elif verbosity == Output.VERBOSITY_DEBUG: if self._max: return 'debug' return 'debug_nomax' if self._max: return 'normal' return 'normal_nomax' def _formatter_bar(self): if self._max: complete_bars = math.floor(self._percent * self.bar_width) else: complete_bars = math.floor(self.get_progress() % self.bar_width) display = self.get_bar_character() * int(complete_bars) if complete_bars < self.bar_width: empty_bars = ( self.bar_width - complete_bars - Helper.len_without_decoration(self._output.get_formatter(), self.progress_char) ) display += self.progress_char + self.empty_bar_char * int(empty_bars) return display def _formatter_elapsed(self): return Helper.format_time(time.time() - self._start_time) def _formatter_remaining(self): if not self._max: raise CleoException( 'Unable to display the remaining time ' 'if the maximum number of steps is not set.' ) if not self._step: remaining = 0 else: remaining = ( round( (time.time() - self._start_time) / self._step * (self._max - self._max) ) ) return Helper.format_time(remaining) def _formatter_estimated(self): if not self._max: raise CleoException( 'Unable to display the estimated time ' 'if the maximum number of steps is not set.' ) if not self._step: estimated = 0 else: estimated = ( round( (time.time() - self._start_time) / self._step * self._max ) ) return estimated def _formatter_current(self): return str(self._step).rjust(self._step_width, ' ') def _formatter_max(self): return self._max def _formatter_percent(self): return int(math.floor(self._percent * 100)) PKj)H&##cleo/helpers/progress_helper.py# -*- coding: utf-8 -*- import time import math import warnings from .helper import Helper from ..outputs.output import Output class ProgressHelper(Helper): """ The Progress class providers helpers to display progress output. """ FORMAT_QUIET = ' %percent%%' FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%' FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent% Elapsed: %elapsed%' FORMAT_QUIET_NOMAX = ' %current%' FORMAT_NORMAL_NOMAX = ' %current% [%bar%]' FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%' # options bar_width = 28 bar_char = '=' empty_bar_char = '-' progress_char = '>' display_format = None redraw_freq = 1 last_messages_length = None bar_char_original = None output = None current_step = 0 max_steps = 0 start_time = None default_format_vars = [ 'current', 'max', 'bar', 'percent', 'elapsed' ] format_vars = [] widths = { 'current': 4, 'max': 4, 'percent': 3, 'elapsed': 6 } time_formats = [ (0, '???'), (2, '1 sec'), (59, 'secs', 1), (60, '1 min'), (3600, 'mins', 60), (5400, '1 hr'), (86400, 'hrs', 3600), (129600, '1 day'), (604800, 'days', 86400) ] def __init__(self): warnings.warn('ProgressHelper class is deprecated. ' 'Use the ProgressBar class instead', DeprecationWarning) def set_bar_width(self, size): """ Sets the progress bar with :param size: The progress bar size :type size: int """ self.bar_width = size def set_bar_character(self, char): """ Sets the progress bar character :param char: The progress bar character :type char: str """ self.bar_char = char def set_empty_bar_character(self, char): """ Sets the empty bar character :param char: A character :type char: str """ self.empty_bar_char = char def set_progress_character(self, char): """ Sets the progress character :param char: A character :type char: str """ self.progress_char = char def set_display_format(self, display_format): """ Sets the progress bar format :param display_format: The display format :type display_format: str """ self.display_format = display_format def set_redraw_frequency(self, freq): """ Sets the redraw frequency :param freq: The redraw frequency in seconds :type freq: int """ self.redraw_freq = freq def start(self, output_, max_steps=None): """ Starts the progress output :param output_: An Output instance :type output_: Output :param max_steps: Maximum steps :type max_steps: int """ self.start_time = time.time() self.current_step = 0 self.max_steps = int(max_steps or 0) self.output = output_ if self.display_format is None: if self.output.get_verbosity() == Output.VERBOSITY_QUIET: self.display_format = self.FORMAT_QUIET_NOMAX if self.max_steps > 0: self.display_format = self.FORMAT_QUIET elif self.output.get_verbosity() == Output.VERBOSITY_VERBOSE: self.display_format = self.FORMAT_VERBOSE_NOMAX if self.max_steps > 0: self.display_format = self.FORMAT_VERBOSE else: self.display_format = self.FORMAT_NORMAL_NOMAX if self.max_steps > 0: self.display_format = self.FORMAT_NORMAL self.initialize() def advance(self, step=1, redraw=False): """ Advances the progress output X steps :param step: Number of steps to advance :type step: int :param redraw: Whether to redraw or not :type redraw: bool """ if self.start_time is None: raise Exception('You must start the progress bar before calling advance().') if self.current_step == 0: redraw = True self.current_step += step if redraw or self.current_step % self.redraw_freq == 0: self.display() def display(self, finish=False): """ Ouputs the current progress string :param finish: Forces the end result :type finish: bool """ if self.start_time is None: raise Exception('You must start the progress bar before calling display().') message = self.display_format for name, value in self.generate(finish).items(): message = message.replace('%' + name + '%', str(value)) self.overwrite(self.output, message) def finish(self): """ Finishes the progress output """ if self.start_time is None: raise Exception('You must start the progress bar before calling finish().') if not self.max_steps: self.bar_char = self.bar_char_original self.display(True) else: if self.current_step < self.max_steps: self.advance(self.max_steps - self.current_step) self.start_time = None self.output.writeln('') self.output = None def initialize(self): """ Initializes the progress output """ self.format_vars = [] for v in self.default_format_vars: if self.display_format.find('%' + v + '%') != -1: self.format_vars.append(v) if self.max_steps > 0: self.widths['max'] = len(str(self.max_steps)) self.widths['current'] = self.widths['max'] else: self.bar_char_original = self.bar_char self.bar_char = self.empty_bar_char def generate(self, finish=False): """ Generates the array map of format variables to values. :param finish: Forces the end result :type finish: bool :return: A dict of format vars and values :rtype: dict """ format_vars = {} percent = 0 if self.max_steps > 0: percent = round(float(self.current_step) / self.max_steps, 2) # bar if 'bar' in self.format_vars: if self.max_steps > 0: complete_bars = math.floor(percent * self.bar_width) else: if not finish: complete_bars = math.floor(self.current_step % self.bar_width) else: complete_bars = self.bar_width empty_bars = self.bar_width - complete_bars - len(self.progress_char) bar = self.bar_char * int(complete_bars) if complete_bars < self.bar_width: bar += self.progress_char bar += self.empty_bar_char * int(empty_bars) format_vars['bar'] = bar # elapsed if 'elapsed' in self.format_vars: elapsed = time.time() - self.start_time format_vars['elapsed'] = self.humane_time(elapsed).rjust(self.widths['elapsed'], ' ') # current if 'current' in self.format_vars: format_vars['current'] = str(self.current_step).rjust(self.widths['current'], ' ') # max steps if 'max' in self.format_vars: format_vars['max'] = self.max_steps # percent if 'percent' in self.format_vars: format_vars['percent'] = str(int(round(percent * 100))).rjust(self.widths['percent'], ' ') return format_vars def humane_time(self, secs): """ Converts seconds into human-readable format :param secs: Number of seconds :type secs: int :return: Time in human-readable format :rtype: str """ text = '' for time_format in self.time_formats: if secs < time_format[0]: if len(time_format) == 2: text = time_format[1] break else: text = str(int(math.ceil(secs / time_format[2]))) + ' ' + time_format[1] break return text def overwrite(self, output_, messages): """ Overwrites a previous message to the output. :param output_: An Output instance :type output_: Output :param messages: The message as an array of lines or a single string :type messages: list or str """ length = len(messages) # append whitespace to match the last line's length if self.last_messages_length is not None and self.last_messages_length > length: messages = messages.ljust(self.last_messages_length, '\x20') # carriage return output_.write('\x0D') output_.write(messages) self.last_messages_length = len(messages) def get_name(self): return 'progress' PKJZoLmg"cleo/helpers/progress_indicator.py# -*- coding: utf-8 -*- import time import re import threading from contextlib import contextmanager from ..exceptions import CleoException from ..outputs import Output from .helper import Helper class ProgressIndicator(object): formatters = None formats = { 'normal': ' %indicator% %message%', 'normal_no_ansi': ' %message%', 'verbose': ' %indicator% %message% (%elapsed:6s%)', 'verbose_no_ansi': ' %message% (%elapsed:6s%)', 'very_verbose': ' %indicator% %message% (%elapsed:6s%)', 'very_verbose_no_ansi': ' %message% (%elapsed:6s%)' } def __init__(self, output, fmt=None, indicator_change_interval=100, indicator_values=None): """ Constructor. :param output: An Output instance :type output: Output :param fmt: Indicator format :type fmt: str or None :param indicator_change_interval: Change interval in milliseconds :type indicator_change_interval: int :param indicator_values: Animated indicator characters :type indicator_values: list or None """ self._output = output if fmt is None: fmt = self._determine_best_format() if indicator_values is None: indicator_values = ['-', '\\', '|', '/'] if len(indicator_values) < 2: raise CleoException('Must have at least 2 indicator value characters.') self.format = self.formats[fmt] self.indicator_change_interval = indicator_change_interval self.indicator_values = indicator_values self._message = None self._indicator_update_time = None self._started = False self._indicator_current = 0 # Auto self._auto_running = None self._auto_thread = None self.start_time = time.time() def set_message(self, message): """ Sets the current indicator message. :param message: The message :type message: str or None """ self._message = message self._display() def get_message(self): return self._message @property def current_value(self): """ Gets the current animated indicator character. :rtype: str """ return self.indicator_values[self._indicator_current % len(self.indicator_values)] def start(self, message): """ Starts the indicator output. :type message: str """ if self._started: raise CleoException('Progress indicator already started.') self._message = message self._started = True self._last_message_length = 0 self.start_time = time.time() self._indicator_update_time = self._get_current_time_in_milliseconds() + self.indicator_change_interval self._indicator_current = 0 self._display() def advance(self): """ Advance the indicator. """ if not self._started: raise CleoException('Progress indicator has not yet been started.') if not self._output.is_decorated(): return current_time = self._get_current_time_in_milliseconds() if current_time < self._indicator_update_time: return self._indicator_update_time = current_time + self.indicator_change_interval self._indicator_current += 1 self._display() def finish(self, message, reset_indicator=False): """ Finish the indicator with message. """ if not self._started: raise CleoException('Progress indicator has not yet been started.') if self._auto_thread: self._auto_running.set() self._auto_thread.join() self._message = message if reset_indicator: self._indicator_current = 0 self._display() self._output.writeln('') self._started = False @contextmanager def auto(self, start_message, end_message): """ Auto progress. """ self._auto_running = threading.Event() self._auto_thread = threading.Thread(target=self._spin) self.start(start_message) self._auto_thread.start() try: yield self except Exception: self._output.writeln('') self._auto_running.set() self._auto_thread.join() raise self.finish(end_message, reset_indicator=True) def _spin(self): while not self._auto_running.is_set(): self.advance() time.sleep(0.1) def _display(self): if self._output.get_verbosity() == Output.VERBOSITY_QUIET: return self._overwrite(re.sub('(?i)%([a-z\-_]+)(?:\:([^%]+))?%', self._overwrite_callback, self.format)) def _overwrite_callback(self, matches): if hasattr(self, '_formatter_%s' % matches.group(1)): text = str(getattr(self, '_formatter_%s' % matches.group(1))()) else: text = matches.group(0) return text def _overwrite(self, message): """ Overwrites a previous message to the output. :param message: The message :type message: str """ if self._output.is_decorated(): self._output.write('\x0D\x1B[2K') self._output.write(message) else: self._output.writeln(message) def _determine_best_format(self): verbosity = self._output.get_verbosity() decorated = self._output.is_decorated() if verbosity == Output.VERBOSITY_VERBOSE: if decorated: return 'verbose' return 'verbose_no_ansi' elif verbosity in [Output.VERBOSITY_VERY_VERBOSE, Output.VERBOSITY_DEBUG]: if decorated: return 'very_verbose' return 'very_verbose_no_ansi' if decorated: return 'normal' return 'normal_no_ansi' def _get_current_time_in_milliseconds(self): return round(time.time() * 1000) def _formatter_indicator(self): return self.current_value def _formatter_message(self): return self.get_message() def _formatter_elapsed(self): return Helper.format_time(time.time() - self.start_time) PKtJ}((cleo/helpers/question_helper.py# -*- coding: utf-8 -*- import sys import os import subprocess import getpass from .helper import Helper from ..questions import Question, ChoiceQuestion, ConfirmationQuestion from ..outputs import ConsoleOutput from ..formatters import Formatter from ..validators import Validator, Callable from .._compat import decode class QuestionHelper(Helper): name = 'question' _input_stream = None stty = None def ask(self, input_, output, question): """ Asks a question to the user. :param input_: An Input instance :type input_: Input :param output: An Output instance :type output: Output :param question: The question to ask :type question: Question :return: The user answer :rtype: str """ if isinstance(output, ConsoleOutput): output = output.get_error_output() if not input_.is_interactive(): return question.default if not self._input_stream: self._input_stream = input_.get_stream() if not question.validator: return self._do_ask(output, question) interviewer = lambda: self._do_ask(output, question) return self._validate_attempts(interviewer, output, question) @property def input_stream(self): return self._input_stream or sys.stdin @input_stream.setter def input_stream(self, stream): self._input_stream = stream def _do_ask(self, output, question): """ Asks a question to the user. :param output: An Output instance :type output: Output :param question: The question to ask :type question: Question :rtype: mixed """ self._write_prompt(output, question) input_stream = self.input_stream autocomplete = question.autocompleter_values if autocomplete is None or not self._has_stty_available(): ret = False if question.hidden: try: ret = self._get_hidden_response(output, input_stream) except RuntimeError: if not question.hidden_fallback: raise if not ret: ret = self._read_from_input(input_stream) else: ret = self._autocomplete(output, question, input_stream) if len(ret) <= 0: ret = question.default if question.normalizer: return question.normalizer(ret) return ret def _write_prompt(self, output, question): """ Outputs the question prompt. :param output: An Output instance :type output: Output :param question: The question to ask :type question: Question """ message = question.question default = question.default if default is None: if isinstance(question, ChoiceQuestion): message = '{}: '.format(message) else: message = '{} '.format(message) elif isinstance(question, ConfirmationQuestion): message = '{} (yes/no) [{}] '.format( message, 'yes' if default else 'no' ) elif isinstance(question, ChoiceQuestion) and question.multiselect: choices = question.choices default = default.split(',') for i, value in enumerate(default): default[i] = choices[int(value.strip())] message = '{} [{}]:'.format( message, Formatter.escape(', '.join(default)) ) elif isinstance(question, ChoiceQuestion): choices = question.choices message = '{} [{}]:'.format( message, Formatter.escape(choices[int(default)]) ) if isinstance(question, ChoiceQuestion): width = max(*map(self.len, [str(k) for k, _ in enumerate(question.choices)])) messages = [message] for key, value in enumerate(question.choices): messages.append(' [{:{}}] {}'.format(key, width, value)) output.writeln(messages) message = question.prompt output.write(message) def _write_error(self, output, error): """ Outputs an error message. :param output: An Output instance :type output: Output :param error: A Exception instance :type error: Exception """ if self.helper_set is not None and self.helper_set.has('formatter'): message = self.helper_set.get('formatter').format_block(decode(str(error)), 'error') else: message = '%s' % decode(str(error)) output.writeln(message) def _autocomplete(self, output, question, input_stream): """ Autocomplete a question. :param output: An Output instance :type output: Output :param question: The question to ask :type question: Question :rtype: str """ autocomplete = question.autocompleter_values ret = '' i = 0 ofs = -1 matches = [x for x in autocomplete] num_matches = len(matches) stty_mode = decode(subprocess.check_output(['stty', '-g'])).rstrip('\n') # Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) subprocess.check_output(['stty', '-icanon', '-echo']) # Add highlighted text style output.get_formatter().add_style('hl', 'black', 'white') # Read a keypress while True: c = input_stream.read(1) # Backspace character if c == '\177': if num_matches == 0 and i != 0: i -= 1 # Move cursor backwards output.write('\033[1D') if i == 0: ofs = -1 matches = [x for x in autocomplete] num_matches = len(matches) else: num_matches = 0 # Pop the last character off the end of our string ret = ret[:i] # Did we read an escape sequence elif c == '\033': c += input_stream.read(2) # A = Up Arrow. B = Down Arrow if c[2] == 'A' or c[2] == 'B': if c[2] == 'A' and ofs == -1: ofs = 0 if num_matches == 0: continue ofs += -1 if c[2] == 'A' else 1 ofs = (num_matches + ofs) % num_matches elif ord(c) < 32: if c == '\t' or c == '\n': if num_matches > 0 and ofs != -1: ret = matches[ofs] # Echo out remaining chars for current match output.write(ret[i:]) i = len(ret) if c == '\n': output.write(c) break num_matches = 0 continue else: output.write(c) ret += c i += 1 num_matches = 0 ofs = 0 for value in autocomplete: # If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if value.startswith(ret) and i != len(value): num_matches += 1 matches[num_matches - 1] = value # Erase characters from cursor to end of line output.write('\033[K') if num_matches > 0 and ofs != -1: # Save cursor position output.write('\0337') # Write highlighted text output.write('' + matches[ofs][i:] + '') # Restore cursor position output.write('\0338') subprocess.call(['stty', '%s' % decode(stty_mode)]) return ret def _get_hidden_response(self, output, input_stream): """ Gets a hidden response from user. :param output: An Output instance :type output: Output :rtype: str """ if hasattr(output, 'output'): output = output.output return getpass.getpass('', stream=output) def _validate_attempts(self, interviewer, output, question): """ Validates an attempt. :param interviewer: A callable that will ask for a question and return the result :type interviewer: callable :param output: An Output instance :type output: Output :param question: The question to ask :type question: Question :return: The validate response :rtype: str """ error = None attempts = question.max_attempts if not isinstance(question.validator, Validator): validator = Callable(question.validator) else: validator = question.validator while attempts is None or attempts: if error is not None: self._write_error(output, error) try: return validator.validate(interviewer()) except Exception as e: error = e if attempts is not None: attempts -= 1 raise error def _read_from_input(self, stream): """ Read user input. :param stream: The input stream :return: """ if stream == sys.stdin: ret = stream.readline() else: ret = stream.readline(4096) if not ret: raise RuntimeError('Aborted') return decode(ret.strip()) def _has_stty_available(self): if self.stty is not None: return self.stty devnull = open(os.devnull, 'w') try: exit_code = subprocess.call(['stty'], stdout=devnull, stderr=devnull) except Exception: exit_code = 2 self.stty = exit_code == 0 return self.stty def get_name(self): return self.name PKj)H8{??cleo/helpers/table.py# -*- coding: utf-8 -*- from __future__ import division from collections import OrderedDict from ..exceptions import CleoException from .table_style import TableStyle from .table_cell import TableCell from .table_separator import TableSeparator from .helper import Helper class Table(object): """ Provides helpers to display a table. """ styles = None def __init__(self, output): """ Constructor. :param output: An Output instance :type output: Output """ self._output = output self._headers = [] self._rows = [] self._column_widths = {} self._number_of_columns = None self._style = None self._column_styles = {} if not self.__class__.styles: self.__class__.styles = self._init_styles() self.set_style('default') @classmethod def set_style_definition(cls, name, table_style): """ Sets a style definition. :param name: The name of the style :type name: str :param table_style: A TableStyle instance :type table_style: TableStyle """ if not cls.styles: cls.styles = cls._init_styles() cls.styles[name] = table_style def set_style(self, name): """ Sets table style. :param name: The name of the style :type name: str """ if isinstance(name, TableStyle): self._style = name elif name in self.styles: self._style = self.styles[name] else: raise CleoException('Style "%s" is not defined.' % name) return self def get_style(self): """ :rtype: TableStyle """ return self._style def set_column_style(self, column_index, name): """ Sets table column style. :param column_index: Colun index :type column_index: int :param name: The name of the style :type name: str or TableStyle :rtype: Table """ column_index = int(column_index) if isinstance(name, TableStyle): self._column_styles[column_index] = name elif name in self.styles: self._column_styles[column_index] = self.styles[name] else: raise CleoException('Style "%s" is not defined.' % name) def get_column_style(self, column_index): """ Gets the current style for a column. If style was not set, it returns the global table style. :param column_index: Colun index :type column_index: int :rtype: TableStyle """ if column_index in self._column_styles: return self._column_styles[column_index] return self._style def set_headers(self, headers): if headers and not isinstance(headers[0], list): headers = [headers] self._headers = headers return self def set_rows(self, rows): self._rows = [] self.add_rows(rows) return self def add_rows(self, rows): for row in rows: self.add_row(row) return self def add_row(self, row): if isinstance(row, TableSeparator): self._rows.append(row) return self if not isinstance(row, list): raise CleoException('A row must be a list or a TableSeparator instance.') self._rows.append(row) return self def set_row(self, column, row): self._rows[column] = row return self def render(self): """ Renders table to output. Example: +---------------+-----------------------+------------------+ | ISBN | Title | Author | +---------------+-----------------------+------------------+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri | | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +---------------+-----------------------+------------------+ """ self._calculate_number_of_columns() rows = self._build_table_rows(self._rows) headers = self._build_table_rows(self._headers) self._calculate_columns_width(headers + rows) self._render_row_separator() if headers: for header in headers: self._render_row(header, self._style.cell_header_format) self._render_row_separator() for row in rows: if isinstance(row, TableSeparator): self._render_row_separator() else: self._render_row(row, self._style.cell_row_format) if rows: self._render_row_separator() self._cleanup() def _render_row_separator(self): """ Renders horizontal header separator. Example: +-----+-----------+-------+ """ count = self._number_of_columns if not count: return if not self._style.horizontal_border_char and not self._style.crossing_char: return markup = self._style.crossing_char for column in range(0, count): markup += self._style.horizontal_border_char * self._column_widths[column]\ + self._style.crossing_char self._output.writeln(self._style.border_format % markup) def _render_column_separator(self): """ Renders vertical column separator. """ self._output.write(self._style.border_format % self._style.vertical_border_char) def _render_row(self, row, cell_format): """ Renders table row. Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | :param row: The row to render :type: row: list :param cell_format: The cell format :type cell_format: str """ if not row: return self._render_column_separator() for column in self._get_row_columns(row): self._render_cell(row, column, cell_format) self._render_column_separator() self._output.writeln('') def _render_cell(self, row, column, cell_format): """ Renders table cell with padding. :param row: The row to render :type: row: list :param column: The column to render :param cell_format: The cell format :type cell_format: str """ try: cell = row[column] except IndexError: cell = '' width = self._column_widths[column] if isinstance(cell, TableCell) and cell.colspan > 1: # add the width of the following columns(numbers of colspan). for next_column in range(column + 1, column + cell.colspan): width += self._get_column_separator_width() + self._column_widths[next_column] # Encoding fix width += len(cell) - Helper.len(cell) style = self.get_column_style(column) if isinstance(cell, TableSeparator): self._output.write(style.border_format % (style.horizontal_border_char * width)) else: width += Helper.len(cell) - Helper.len_without_decoration(self._output.get_formatter(), cell) content = style.cell_row_content_format % cell self._output.write(cell_format % getattr(content, style.pad_type)(width, style.padding_char)) def _calculate_number_of_columns(self): """ Calculate number of columns for this table. """ if self._number_of_columns is not None: return columns = [0] for row in self._headers + self._rows: if isinstance(row, TableSeparator): continue columns.append(self._get_number_of_columns(row)) self._number_of_columns = max(columns) def _build_table_rows(self, rows): unmerged_rows = OrderedDict() row_key = 0 while row_key < len(rows): rows = self._fill_next_rows(rows, row_key) # Remove any new line breaks and replace it with a new line for column, cell in enumerate(rows[row_key]): if '\n' not in cell: continue lines = cell.split('\n') for line_key, line in enumerate(lines): if isinstance(cell, TableCell): line = TableCell(line, colspan=cell.colspan) if 0 == line_key: rows[row_key][column] = line else: if row_key not in unmerged_rows: unmerged_rows[row_key] = OrderedDict() if line_key not in unmerged_rows[row_key]: unmerged_rows[row_key][line_key] = OrderedDict() unmerged_rows[row_key][line_key][column] = line row_key += 1 table_rows = [] for row_key, row in enumerate(rows): table_rows.append(self._fill_cells(row)) if row_key in unmerged_rows: for line in unmerged_rows[row_key]: if line <= len(table_rows): new_row = [] for column, value in enumerate(row): if column in unmerged_rows[row_key][line]: new_row.append(unmerged_rows[row_key][line][column]) else: new_row.append('') table_rows.append(new_row) else: for column in unmerged_rows[row_key][line]: table_rows[line][column] = unmerged_rows[row_key][line][column] return table_rows def _fill_next_rows(self, rows, line): """ Fill rows that contains rowspan > 1. :param rows: The rows to fill :type rows: list :type line: int :rtype: list """ unmerged_rows = OrderedDict() for column, cell in enumerate(rows[line]): if isinstance(cell, TableCell) and cell.rowspan > 1: nb_lines = cell.rowspan - 1 lines = [cell] if '\n' in cell: lines = cell.split('\n') if len(lines) > nb_lines: nb_lines = cell.count('\n') rows[line][column] = TableCell(lines[0], colspan=cell.colspan) # Create a two dimensional array (rowspan x colspan) placeholder = OrderedDict([(k, OrderedDict()) for k in range(line + 1, line + 1 + nb_lines)]) for k, v in unmerged_rows.items(): if k in placeholder: for l, m in unmerged_rows[k].items(): if l in placeholder[k]: placeholder[k][l].update(m) else: placeholder[k][l] = m else: placeholder[k] = v unmerged_rows = placeholder for unmerged_row_key, unmerged_row in unmerged_rows.items(): value = '' if unmerged_row_key - line < len(lines): value = lines[unmerged_row_key - line] unmerged_rows[unmerged_row_key][column] = TableCell(value, colspan=cell.colspan) for unmerged_row_key, unmerged_row in unmerged_rows.items(): # we need to know if unmerged_row will be merged or inserted into rows if (unmerged_row_key < len(rows) and isinstance(rows[unmerged_row_key], list) and (self._get_number_of_columns(rows[unmerged_row_key]) + self._get_number_of_columns(list(unmerged_rows[unmerged_row_key].values())) <= self._number_of_columns)): # insert cell into row at cell_key position for cell_key, cell in unmerged_row.items(): rows[unmerged_row_key].insert(cell_key, cell) else: row = self._copy_row(rows, unmerged_row_key - 1) for column, cell in unmerged_row.items(): if len(cell): row[column] = unmerged_row[column] rows.insert(unmerged_row_key, row) return rows def _fill_cells(self, row): """ Fill cells for a row that contains colspan > 1. :type row: list :rtype: list """ new_row = [] for column, cell in enumerate(row): new_row.append(cell) if isinstance(cell, TableCell) and cell.colspan > 1: for position in range(column + 1, column + cell.colspan): # insert empty value at column position new_row.append('') if new_row: return new_row return row def _copy_row(self, rows, line): """ Copy a row :type rows: list :type line: int :rtype: list """ row = [x for x in rows[line]] for cell_key, cell_value in enumerate(row): row[cell_key] = '' if isinstance(cell_value, TableCell): row[cell_key] = TableCell('', colspan=cell_value.colspan) return row def _get_number_of_columns(self, row): """ Gets number of columns by row. :param row: The row :type row: list :rtype: int """ columns = len(row) for column in row: if isinstance(column, TableCell): columns += column.colspan - 1 return columns def _get_row_columns(self, row): """ Gets list of columns for the given row. :type row: list :rtype: list """ columns = list(range(0, self._number_of_columns)) for cell_key, cell in enumerate(row): if isinstance(cell, TableCell) and cell.colspan > 1: # exclude grouped columns. columns = [x for x in columns if x not in list(range(cell_key + 1, cell_key + cell.colspan))] return columns def _calculate_columns_width(self, rows): """ Calculates columns widths. """ for column in range(0, self._number_of_columns): lengths = [] for row in rows: if isinstance(row, TableSeparator): continue lengths.append(self._get_cell_width(row, column)) self._column_widths[column] = max(lengths) + len(self._style.cell_row_content_format) - 2 def _get_column_separator_width(self): return len(self._style.border_format % self._style.vertical_border_char) def _get_cell_width(self, row, column): """ Gets cell width. :type row: list :type column: int :rtype: int """ try: cell = row[column] cell_width = Helper.len_without_decoration(self._output.get_formatter(), cell) if isinstance(cell, TableCell) and cell.colspan > 1: # we assume that cell value will be across more than one column. cell_width = cell_width // cell.colspan return cell_width except IndexError: return 0 def _cleanup(self): self._column_widths = {} self._number_of_columns = None @classmethod def _init_styles(cls): borderless = TableStyle() borderless.set_horizontal_border_char('=') borderless.set_vertical_border_char(' ') borderless.set_crossing_char(' ') compact = TableStyle() compact.set_horizontal_border_char('') compact.set_vertical_border_char(' ') compact.set_crossing_char('') compact.set_cell_row_content_format('%s') return { 'default': TableStyle(), 'borderless': borderless, 'compact': compact } PKYG^>cleo/helpers/table_cell.py# -*- coding: utf-8 -*- class TableCell(str): """ Represents a table cell """ _options = { 'rowspan': 1, "colspan": 1 } def __new__(cls, value = '', **options): self = super(TableCell, cls).__new__(cls, value) return self def __init__(self, value, **options): super(TableCell, self).__init__() for key in self._options: if key not in options: options[key] = self._options[key] self.options = options @property def colspan(self): return self.options['colspan'] @property def rowspan(self): return self.options['rowspan'] PKj)Hnrcleo/helpers/table_helper.py# -*- coding: utf-8 -*- import warnings from .helper import Helper from .table import Table from ..outputs import NullOutput class TableHelper(Helper): """ Provides helpers to display table output. """ LAYOUT_DEFAULT = 0 LAYOUT_BORDERLESS = 1 LAYOUT_COMPACT = 2 def __init__(self): warnings.warn('TableHelper class is deprecated. ' 'Use the Table class instead', DeprecationWarning) self._table = Table(NullOutput()) def set_layout(self, layout): """ Sets table layout type. :param layout: self.LAYOUT_* :type layout: int :rtype: TableHelper """ if layout == self.LAYOUT_BORDERLESS: self._table.set_style('borderless') elif layout == self.LAYOUT_COMPACT: self._table.set_style('compact') elif layout == self.LAYOUT_DEFAULT: self._table.set_style('default') else: raise Exception('Invalid table layout "%s".' % layout) return self def set_headers(self, headers): self._table.set_headers(headers) return self def set_rows(self, rows): self._table.set_rows(rows) return self def add_rows(self, rows): self._table.add_rows(rows) return self def add_row(self, row): self._table.add_row(row) return self def set_row(self, column, row): self._table.set_row(column, row) return self def set_padding_char(self, padding_char): self._table.get_style().set_padding_char(padding_char) return self def set_horizontal_border_char(self, horizontal_border_char): self._table.get_style().set_horizontal_border_char(horizontal_border_char) return self def set_vertical_border_char(self, vertical_border_char): self._table.get_style().set_vertical_border_char(vertical_border_char) return self def set_crossing_char(self, crossing_char): self._table.get_style().set_crossing_char(crossing_char) return self def set_cell_header_format(self, cell_header_format): self._table.get_style().set_cell_header_format(cell_header_format) return self def set_cell_row_format(self, cell_row_format): self._table.get_style().set_cell_row_format(cell_row_format) return self def set_cell_row_content_format(self, cell_row_content_format): self._table.get_style().set_cell_row_content_format(cell_row_content_format) return self def set_border_format(self, border_format): self._table.get_style().set_border_format(border_format) return self def set_pad_type(self, pad_type): self._table.get_style().set_pad_type(pad_type) return self def render(self, output): """ Renders table to output. Example: +---------------+-----------------------+------------------+ | ISBN | Title | Author | +---------------+-----------------------+------------------+ | 99921-58-10-7 | Divine Comedy | Dante Alighieri | | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | +---------------+-----------------------+------------------+ :param output_: Output :type output_: Output """ self._table._output = output return self._table.render() def get_name(self): return 'table' PK䎟GNcleo/helpers/table_separator.py# -*- coding: utf-8 -*- from .table_cell import TableCell class TableSeparator(TableCell): """ Marks a row as being a separator. """ def __init__(self, **options): super(TableSeparator, self).__init__('', **options) PK&"Hf&::cleo/helpers/table_style.py# -*- coding: utf-8 -*- from ..exceptions import CleoException class TableStyle(object): padding_char = ' ' horizontal_border_char = '-' vertical_border_char = '|' crossing_char = '+' cell_header_format = '%s' cell_row_format = '%s' cell_row_content_format = ' %s ' border_format = '%s' pad_type = 'ljust' def set_padding_char(self, padding_char): if not padding_char: raise CleoException('Padding char must not be empty') self.padding_char = padding_char return self def set_horizontal_border_char(self, horizontal_border_char): self.horizontal_border_char = horizontal_border_char return self def set_vertical_border_char(self, vertical_border_char): self.vertical_border_char = vertical_border_char return self def set_crossing_char(self, crossing_char): self.crossing_char = crossing_char return self def set_cell_header_format(self, cell_header_format): self.cell_header_format = cell_header_format return self def set_cell_row_format(self, cell_row_format): self.cell_row_format = cell_row_format return self def set_cell_row_content_format(self, cell_row_content_format): self.cell_row_content_format = cell_row_content_format return self def set_border_format(self, border_format): self.border_format = border_format return self def set_pad_type(self, pad_type): pad_types = { 'left': 'rjust', 'right': 'ljust', 'center': 'center' } if pad_type not in pad_types: raise CleoException('Invalid pad type. Must be either "left", "right" or "center".') self.pad_type = pad_types[pad_type] return self PK/3I#y/cleo/inputs/__init__.py# -*- coding: utf-8 -*- from .argv_input import ArgvInput from .input import Input from .input_argument import InputArgument from .input_definition import InputDefinition from .input_option import InputOption from .list_input import ListInput from .api import argument, option __all__ = [ 'argument', 'option', 'ArgvInput', 'Input', 'InputArgument', 'InputDefinition', 'InputOption', 'ListInput' ] PK 3Iicleo/inputs/api.py# -*- coding: utf-8 -*- from .input_argument import InputArgument from .input_option import InputOption def argument(name, description='', required=False, default=None, is_list=False, validator=None): """ Helper function to create a new argument. :param name: The name of the argument. :type name: str :param description: A helpful description of the argument. :type description: str :param required: Whether the argument is required or not. :type required: bool :param default: The default value of the argument. :type default: mixed :param is_list: Whether the argument should be a list or not. :type list: bool :param validator: An optional validator. :type validator: Validator or str :rtype: InputArgument """ mode = InputArgument.OPTIONAL if required: mode = InputArgument.REQUIRED if is_list: mode |= InputArgument.IS_LIST return InputArgument(name, mode, description, default, validator) def option(name, shortcut=None, description='', flag=True, value_required=None, is_list=False, default=None, validator=None): """ Helper function to create an option. :param name: The name of the option :type name: str :param shortcut: The shortcut (Optional) :type shortcut: str or None :param description: The description of the option. :type description: str :param flag: Whether the option is a flag or not. :type flag: bool :param value_required: Whether a value is required or not. :type value_required: bool or None :param is_list: Whether the option is a list or not. :type is_list: bool :param default: The default value. :type default: mixed :param validator: An optional validator. :type validator: Validator or str :rtype: InputOption """ mode = InputOption.VALUE_IS_FLAG if value_required is True: mode = InputOption.VALUE_REQUIRED elif value_required is False: mode = InputOption.VALUE_OPTIONAL if is_list: mode |= InputOption.VALUE_IS_LIST return InputOption( name, shortcut, mode, description, default, validator ) PKYoL2 1: if self.definition.has_shortcut(name[0])\ and self.definition.get_option_for_shortcut(name[0]).accept_value(): # an option with a value (with no space) self.add_short_option(name[0], name[1:]) else: self.parse_short_option_set(name) else: if self.definition.has_shortcut(name) and self.definition.get_option_for_shortcut(name).accept_value(): try: value = self._parsed.pop(0) except IndexError: value = None if value and value.startswith('-'): self._parsed.insert(0, value) value = None self.add_short_option(name, value) else: self.add_short_option(name, None) def parse_short_option_set(self, name): l = len(name) for i in range(0, l): if not self.definition.has_shortcut(name[i]): raise NoSuchOption('The "-%s" option does not exist.' % name[i]) option = self.definition.get_option_for_shortcut(name[i]) if option.accept_value(): self.add_long_option(option.get_name(), None if l - 1 == i else name[i + 1:]) break else: self.add_long_option(option.get_name(), None) def parse_long_option(self, token): name = token[2:] pos = name.find('=') if pos != -1: self.add_long_option(name[:pos], name[pos + 1:]) else: if self.definition.has_option(name) and self.definition.get_option(name).accept_value(): try: value = self._parsed.pop(0) except IndexError: value = None if value and value.startswith('-'): self._parsed.insert(0, value) value = None self.add_long_option(name, value) else: self.add_long_option(name, None) def parse_argument(self, token): c = len(self.arguments) # if input is expecting another argument, add it if self.definition.has_argument(c): arg = self.definition.get_argument(c) self.arguments[arg.get_name()] = [token] if arg.is_list() else token elif self.definition.has_argument(c - 1) and self.definition.get_argument(c - 1).is_list(): arg = self.definition.get_argument(c - 1) self.arguments[arg.get_name()].append(token) # unexpected argument else: raise TooManyArguments('Too many arguments.') def add_short_option(self, shortcut, value): if not self.definition.has_shortcut(shortcut): raise NoSuchOption('The "-%s" option does not exist.' % shortcut) self.add_long_option(self.definition.get_option_for_shortcut(shortcut).get_name(), value) def add_long_option(self, name, value): if not self.definition.has_option(name): raise NoSuchOption('The "--%s" option does not exist.' % name) option = self.definition.get_option(name) if value is False: value = None if value is not None and not option.accept_value(): raise BadOptionUsage('The "--%s" option does not accept a value.' % name) if value is None and option.accept_value() and len(self._parsed): # if option accepts an optional or mandatory argument # let's see if there is one provided try: nxt = self._parsed.pop(0) except IndexError: nxt = None if nxt and len(nxt) >= 1 and nxt[0] != '-': value = nxt elif not nxt: value = '' else: self._parsed.insert(0, nxt) # This test is here to handle cases like --foo= # and foo option value is optional if value == '': value = None if value is None: if option.is_value_required(): raise BadOptionUsage('The "--%s" option requires a value.' % name) if not option.is_list(): value = option.get_default() if option.is_value_optional() else True if option.is_list(): if name not in self.options: self.options[name] = [value] else: self.options[name].append(value) else: self.options[name] = value def get_first_argument(self): for token in self._tokens: if token and token[0] == '-': continue return token def has_parameter_option(self, values): values = [values] if not isinstance(values, (list, tuple)) else values for token in self._tokens: for value in values: if token == value or token.find(value + '=') == 0: return True return False def get_parameter_option(self, values, default=False): values = [values] if not isinstance(values, (list, tuple)) else values tokens = self._tokens[:] while True: try: token = tokens.pop(0) except IndexError: break for value in values: if token == value or token.find(value + '=') == 0: pos = token.find('=') if pos != -1: return token[pos + 1:] return tokens.pop(0) return default def __str__(self): def stringify(token): m = re.match('^(-[^=]+=)(.+)', token) if m: return m.group(1) + self.escape_token(m.group(2)) if token and token[0] != '-': return self.escape_token(token) return token tokens = map(stringify, self._tokens) return ' '.join(tokens) PKIZ4I^RYYcleo/inputs/input.py# -*- coding: utf-8 -*- import re from .input_definition import InputDefinition from ..validators import ValidationError, Callable, Validator from .input_argument import InvalidArgument from .input_option import InvalidOption from ..exceptions import MissingArguments, NoSuchOption class Input(object): interactive = True def __init__(self, definition=None): self.interactive = True self._stream = None if definition is None: self.arguments = {} self.options = {} self.definition = InputDefinition() else: self.bind(definition) self.validate() def bind(self, definition): self.arguments = {} self.options = {} self.definition = definition self.parse() def has_parameter_option(self, values): """ Returns true if the raw parameters (not parsed) contain a value. This method is to be used to introspect the input parameters before they have been validated. It must be used carefully. :param values: The values to look for in the raw parameters (can be a list) :type values: str|list :return: True if the value is in the raw parameters :rtype: bool """ raise NotImplementedError() def get_parameter_option(self, values, default=False): """ Returns the value of a raw option (not parsed). This method is to be used to introspect the input parameters before they have been validated. It must be used carefully. :param values: The values to look for in the raw parameters (can be a list) :type values: str|list :param default: The default value to return if no result is found :type default: mixed :return: True if the value is in the raw parameters :rtype: bool """ raise NotImplementedError() def parse(self): raise NotImplementedError() def validate(self): if len(self.get_arguments()) < self.definition.get_argument_required_count(): raise MissingArguments('Not enough arguments') self.validate_arguments() self.validate_options() def validate_arguments(self): """ Validates the arguments :raise: InvalidArgument """ for arg_name, arg_value in self.get_arguments().items(): arg = self.definition.get_argument(arg_name) validator = arg.get_validator() if validator: if not isinstance(validator, Validator): if callable(validator): validator = Callable(validator) else: raise Exception('Invalid validator specified for argument %s' % arg.name) try: validated_value = validator.validate(arg_value) if validated_value is not None: self.arguments[arg_name] = validated_value except ValidationError as e: raise InvalidArgument(arg, e.msg, e.value) def validate_options(self): """ Validates the options :raise: InvalidOptionValue """ for opt_name, opt_value in self.get_options().items(): opt = self.definition.get_option(opt_name) validator = opt.get_validator() if validator: if not isinstance(validator, Validator): if callable(validator): validator = Callable(validator) else: raise Exception('Invalid validator specified for option %s' % opt.name) try: validated_value = validator.validate(opt_value) if validated_value is not None: self.options[opt_name] = validated_value except ValidationError as e: raise InvalidOption(opt, e.msg, e.value) def is_interactive(self): return self.interactive def set_interactive(self, interactive): self.interactive = interactive def get_arguments(self): return dict(self.definition.get_argument_defaults(), **self.arguments) def get_argument(self, name): if not self.definition.has_argument(name): raise Exception('Argument "%s" does not exist' % name) return self.arguments.get(name, self.definition.get_argument(name).get_default()) def set_argument(self, name, value): if not self.definition.has_argument(name): raise Exception('Argument "%s" does not exist') self.arguments[name] = value def has_argument(self, name): return self.definition.has_argument(name) def get_options(self): return dict(self.definition.get_option_defaults(), **self.options) def get_option(self, name): if not self.has_option(name): raise NoSuchOption('Option "%s" does not exist' % name) return self.options.get(name, self.definition.get_option(name).get_default()) def set_option(self, name, value): if not self.definition.has_option(name): raise NoSuchOption('Option "%s" does not exist' % name) self.options[name] = value def has_option(self, name): return self.definition.has_option(name) def escape_token(self, token): if re.match('^[\w-]+$', token): return token else: return "\\'".join("'" + p + "'" for p in token.split("'")) def set_stream(self, stream): self._stream = stream def get_stream(self): return self._stream PKYoLid%%cleo/inputs/input_argument.py# -*- coding: utf-8 -*- from ..validators import ValidationError, VALIDATORS class InvalidArgument(ValidationError): def __init__(self, argument, msg, value=ValidationError._UNDEFINED): self.argument = argument self.msg = msg self.value = value super(ValidationError, self).__init__(str(self)) def to_s(self): if self.value != self._UNDEFINED: return 'Invalid value %s (%s) ' \ 'for argument %s: %s'\ % (repr(self.value), self.value.__class__.__name__, self.argument.get_name(), self.msg) return self.msg class InputArgument(object): """ Represents a command line argument. """ REQUIRED = 1 OPTIONAL = 2 IS_LIST = 4 def __init__(self, name, mode=None, description='', default=None, validator=None): """ Constructor :param name: The argument name :type name: str :param mode: The argument mode: REQUIRED or OPTIONAL :type mode: int or None :param description: A description text :type description: str :param default: The default value (for OPTIONAL mode only) :type default: mixed :param validator: A Validator instance or a callable :type validator: Validator or callable """ if mode is None: mode = self.OPTIONAL elif not isinstance(mode, int) or mode > 7 or mode < 1: raise Exception('Argument mode "%s" is not valid.' % mode) self._name = name self._mode = mode self._description = description or '' self._validator = VALIDATORS.get(validator) self.set_default(default) def get_name(self): """ Returns the argument name :return: The argument name :rtype: str """ return self._name def is_required(self): """ Returns True if the argument is required. :return: True if parameter mode is REQUIRED, False otherwise :rtype: bool """ return self.__class__.REQUIRED == (self.__class__.REQUIRED & self._mode) def is_list(self): """ Returns True if the argument can take multiple values :return: True if mode is IS_LIST, False otherwise :rtype: bool """ return self.__class__.IS_LIST == (self.__class__.IS_LIST & self._mode) def set_default(self, default=None): """ Sets the default value. :param default: The default value :type default: mixed """ if self.is_required() and default is not None: raise Exception('Cannot set a default value except for InputArgument::OPTIONAL mode.') if self.is_list(): if default is None: default = [] elif not isinstance(default, list): raise Exception('A default value for an array argument must be an array.') self._default = default def get_default(self): """ Returns the default value. :return: The default value :rtype: mixed """ return self._default def get_description(self): """ Returns the description text. :return: The description text :rtype: str """ return self._description def get_validator(self): """ Returns the validator :return: The validator :rtype: Validator or callable """ return self._validator def set_validator(self, validator): """ Sets the valdidator. :param validator: A Validator instance :type validator: Validator """ self._validator = VALIDATORS.get(validator) return self PKYoLqv~cleo/inputs/input_definition.py# -*- coding: utf-8 -*- try: import ujson as json except ImportError: import json try: from collections import OrderedDict except ImportError: # python 2.6 or earlier, use backport from ordereddict import OrderedDict from .input_option import InputOption class InputDefinition(object): def __init__(self, definition=None): definition = definition or [] self._arguments = OrderedDict() self._required_count = 0 self._has_an_array_argument = False self._has_optional = False self._options = OrderedDict() self._shortcuts = OrderedDict() self.set_definition(definition) def set_definition(self, definition): arguments = [] options = [] for item in definition: if isinstance(item, InputOption): options.append(item) else: arguments.append(item) self.set_arguments(arguments) self.set_options(options) def set_arguments(self, arguments=None): arguments = arguments or [] self._arguments = OrderedDict() self._required_count = 0 self._has_an_array_argument = False self._has_optional = False self.add_arguments(arguments) def add_arguments(self, arguments=None): arguments = arguments or [] for argument in arguments: self.add_argument(argument) def add_argument(self, argument): if argument.get_name() in self._arguments: raise Exception('An argument with name "%s" already exists.' % argument.get_name()) if self._has_an_array_argument: raise Exception('Cannot add an argument after a list argument.') if argument.is_required() and self._has_optional: raise Exception('Cannot add a required argument after an optional one.') if argument.is_list(): self._has_an_array_argument = True if argument.is_required(): self._required_count += 1 else: self._has_optional = True self._arguments[argument.get_name()] = argument def get_argument(self, name): arguments = list(self._arguments.values()) if isinstance(name, int) else self._arguments if not self.has_argument(name): raise Exception('The "%s" argument does not exist.' % name) return arguments[name] def has_argument(self, name): arguments = list(self._arguments.values()) if isinstance(name, int) else self._arguments try: arguments[name] return True except (KeyError, IndexError): return False def get_arguments(self): """ Gets the list of InputArguments objects. :return: A list of InputArguments objects :rtype: list """ return list(self._arguments.values()) def get_argument_count(self): return len(self._arguments) if not self._has_an_array_argument else 10000000 def get_argument_required_count(self): return self._required_count def get_argument_defaults(self): values = {} for argument in self._arguments.values(): if not argument.is_required(): values[argument.get_name()] = argument.get_default() return values def set_options(self, options=None): options = options or [] self._options = OrderedDict() self._shortcuts = OrderedDict() self.add_options(options) def add_options(self, options=None): options = options or [] for option in options: self.add_option(option) def add_option(self, option): if option.get_name() in self._options \ and not option.equals(self._options[option.get_name()]): raise Exception('An option named "%s" already exists.' % option.get_name()) elif option.get_shortcut() in self._shortcuts \ and not option.equals(self._options[self._shortcuts[option.get_shortcut()]]): raise Exception('An option with shortcut "%s" already exists.' % option.get_shortcut()) self._options[option.get_name()] = option if option.get_shortcut(): for shortcut in option.get_shortcut().split('|'): self._shortcuts[shortcut] = option.get_name() def get_option(self, name): if not self.has_option(name): raise Exception('The "--%s" option does not exist.' % name) return self._options[name] def has_option(self, name): return name in self._options def get_options(self): return list(self._options.values()) def has_shortcut(self, name): return name in self._shortcuts def get_option_for_shortcut(self, shortcut): return self.get_option(self.shortcut_to_name(shortcut)) def get_option_defaults(self): values = {} for option in self._options.values(): values[option.get_name()] = option.get_default() return values def shortcut_to_name(self, shortcut): if not self.has_shortcut(shortcut): raise Exception('The "-%s" option does not exist.' % shortcut) return self._shortcuts[shortcut] def get_synopsis(self, short=False): elements = [] if short and self.get_options(): elements.append('[options]') elif not short: for option in self.get_options(): value = '' if option.accept_value(): left = '' right = '' if option.is_value_optional(): left = '[' right = ']' value = ' %s%s%s' % (left, option.get_name().upper(), right) shortcut = '-%s|' % option.get_shortcut() if option.get_shortcut() else '' elements.append('[%s--%s%s]' % (shortcut, option.get_name(), value)) if len(elements) and self.get_arguments(): elements.append('[--]') for argument in self.get_arguments(): element = '<%s>' % argument.get_name() if not argument.is_required(): element = '[%s]' % element elif argument.is_list(): element = '%s (%s)' % (element, element) if argument.is_list(): element += '...' elements.append(element) return ' '.join(elements) def format_default_value(self, default): return json.dumps(default) PKYoLjBcleo/inputs/input_option.py# -*- coding: utf-8 -*- import re from ..validators import ValidationError, VALIDATORS class InvalidOption(ValidationError): def __init__(self, option, msg, value=ValidationError._UNDEFINED): self.option = option self.msg = msg self.value = value super(ValidationError, self).__init__(str(self)) def to_s(self): if self.value != self._UNDEFINED: return 'Invalid value %s (%s) for option %s: %s'\ % (repr(self.value), self.value.__class__.__name__, self.option.get_name(), self.msg) return self.msg class InputOption(object): """ Represents a command line option. """ VALUE_NONE = VALUE_IS_FLAG = 1 VALUE_REQUIRED = 2 VALUE_OPTIONAL = 4 VALUE_IS_LIST = 8 def __init__(self, name, shortcut=None, mode=None, description='', default=None, validator=None): """ Constructor :param name: The option name :type name: str :param shortcut: The option shortcut :type shortcut: str or None or list :param mode: The argument mode: VALUE_NONE or VALUE_REQUIRED or VALUE_OPTIONAL :type mode: int or None :param description: A description text :type description: str :param default: The default value (must be null for VALUE_REQUIRED or VALUE_NONE) :type default: mixed :param validator: A Validator instance or a callable :type validator: Validator or callable """ if name.startswith('--'): name = name[2:] if not name: raise Exception('An option name cannot be empty.') if not shortcut: shortcut = None if shortcut is not None: if isinstance(shortcut, list): shortcut = '|'.join(shortcut) shortcuts = re.split('\|-?', shortcut.lstrip('-')) shortcuts = list(filter(lambda x: x.strip() != '', shortcuts)) shortcut = '|'.join(shortcuts) if not shortcut: raise Exception('An option shortcut cannot be empty.') if mode is None: mode = self.__class__.VALUE_NONE elif not isinstance(mode, int) or mode > 15 or mode < 1: raise Exception('Option mode "%s" is not valid.' % mode) self._name = name self._shortcut = shortcut self._mode = mode self._description = description or '' self._validator = VALIDATORS.get(validator) self.set_default(default) def get_shortcut(self): """ Returns the option shortcut. :return: The option shortcut :rtype: str """ return self._shortcut def get_name(self): """ Returns the option name. :return: The option name :rtype: str """ return self._name def accept_value(self): """ Returns true if the option accepts a value. :return: True if value mode is not VALUE_NONE, False otherwise :rtype: bool """ return self.is_value_required() or self.is_value_optional() def is_value_required(self): """ Returns True if the option requires a value. :return: True if value mode is VALUE_REQUIRED, False otherwise """ return self.__class__.VALUE_REQUIRED == (self.__class__.VALUE_REQUIRED & self._mode) def is_value_optional(self): """ Returns True if the option takes an optional value. :return: True if value mode is VALUE_OPTIONAL, False otherwise """ return self.__class__.VALUE_OPTIONAL == (self.__class__.VALUE_OPTIONAL & self._mode) def is_flag(self): """ Returns True if the option is a flag :return: True if value mode is VALUE_NONE, False otherwise """ return self.__class__.VALUE_NONE == (self.__class__.VALUE_NONE & self._mode) def is_list(self): """ Returns True if the option can take multiple values :return: True if mode is VALUE_IS_LIST, False otherwise :rtype: bool """ return self.__class__.VALUE_IS_LIST == (self.__class__.VALUE_IS_LIST & self._mode) def set_default(self, default=None): """ Sets the default value. :param default: The default value :type default: mixed """ if self.__class__.VALUE_NONE == self._mode and default is not None: raise Exception('Cannot set a default value when using InputOption::VALUE_NONE mode.') if self.is_list(): if default is None: default = [] elif not isinstance(default, list): raise Exception('A default value for an array option must be an array.') self._default = default if self.accept_value() else False def get_default(self): """ Returns the default value. :return: The default value :rtype: mixed """ return self._default def get_description(self): """ Returns the description text. :return: The description text :rtype: basestring """ return self._description def get_validator(self): """ Returns the validator :return: The validator :rtype: Validator or callable """ return self._validator def set_validator(self, validator): """ Sets the valdidator. :param validator: A Validator instance :type validator: Validator """ self._validator = VALIDATORS.get(validator) return self def equals(self, option): """ Checks whether the given option equals this one. :param option: option to compare :type option: InputOption :rtype: bool """ return option.get_name() == self.get_name()\ and option.get_shortcut() == self.get_shortcut()\ and option.get_default() == self.get_default()\ and option.is_list() == self.is_list()\ and option.is_value_required() == self.is_value_required()\ and option.is_value_optional() == self.is_value_optional() PKj)HVӀmmcleo/inputs/list_input.py# -*- coding: utf-8 -*- from .input import Input class ListInput(Input): """ ListInput represents an input provided as an array. Usage: >>> input_ = ListInput([('name', 'foo'), ('--bar', 'foobar')]) """ def __init__(self, parameters, definition=None): """ Constructor :param parameters: A dict of parameters :type parameters: list :param definition: An InputDefinition instance :type definition: InputDefinition """ self.interactive = False self.parameters = parameters super(ListInput, self).__init__(definition) def get_first_argument(self): """ Returns the first argument from the raw parameters (not parsed) :return: The value of the first argument or None otherwise :rtype: str """ for item in self.parameters: if isinstance(item, tuple): key = item[0] value = item[1] else: key = item value = item if key and '-' == key[0]: continue return value def has_parameter_option(self, values): """ Returns true if the raw parameters (not parsed) contain a value. This method is to be used to introspect the input parameters before they have been validated. It must be used carefully. :param values: The values to look for in the raw parameters (can be a list) :type values: str or list :return: True if the value is contained in the raw parameters :rtype: bool """ if not isinstance(values, list): values = [values] for item in self.parameters: if isinstance(item, tuple): key = item[0] else: key = item if key in values: return True return False def get_parameter_option(self, values, default=False): """ Returns the value of a raw option (not parsed). This method is to be used to introspect the input parameters before they have been validated. It must be used carefully. :param values: The values to look for in the raw parameters (can be a list) :type values: str or list :param default: The default value to return if no result is found :type default: mixed :return: The option value :rtype: mixed """ if not isinstance(values, list): values = [values] for item in self.parameters: if isinstance(item, tuple): key = item[0] value = item[1] else: key = item value = None if key in values: return value return default def parse(self): """ Processes command line arguments. """ for item in self.parameters: if isinstance(item, tuple): key = item[0] value = item[1] else: key = item value = None if key.startswith('--'): self.add_long_option(key[2:], value) elif key[0] == '-': self.add_short_option(key[1:], value) else: self.add_argument(key, value) def add_short_option(self, shortcut, value): """ Adds a short option value :param shortcut: The short option key :type shortcut: str :param value: The value for the option :type value: mixed """ if not self.definition.has_shortcut(shortcut): raise Exception('The "-%s" option does not exist.' % shortcut) self.add_long_option(self.definition.get_option_for_shortcut(shortcut).get_name(), value) def add_long_option(self, name, value): """ Adds a long option value :param name: The long option key :type name: str :param value: The value for the option :type value: mixed """ if not self.definition.has_option(name): raise Exception('The "--%s" option does not exist.' % name) option = self.definition.get_option(name) if value is None: if option.is_value_required(): raise Exception('The "--%s" option requires a value.' % name) value = option.get_default() if option.is_value_optional() else True self.options[name] = value def add_argument(self, name, value): """ Adds an argument value :param name: The argument key :type name: str :param value: The value for the argument :type value: mixed """ if not self.definition.has_argument(name): raise Exception('The "%s" argument does not exist.' % name) self.arguments[name] = value PK& "HQK__cleo/outputs/__init__.py# -*- coding: utf-8 -*- from .buffered_output import BufferedOutput from .console_output import ConsoleOutput from .output import Output, OutputError from .stream_output import StreamOutput from .null_output import NullOutput __all__ = [ 'BufferedOutput', 'ConsoleOutput', 'Output', 'OutputError', 'StreamOutput', 'NullOutput' ] PKj)HJ cleo/outputs/buffered_output.py# -*- coding: utf-8 -*- from .output import Output class BufferedOutput(Output): buffer = '' def fetch(self): """ Empties buffer and returns its content. :rtype: str """ content = self.buffer self.buffer = '' return content def do_write(self, message, newline): self.buffer += message if newline: self.buffer += '\n' PKtJ5l^##cleo/outputs/console_output.py# -*- coding: utf-8 -*- import sys from .stream_output import StreamOutput class ConsoleOutput(StreamOutput): def __init__(self, verbosity=StreamOutput.VERBOSITY_NORMAL, decorated=None, formatter=None): output_stream = sys.stdout super(ConsoleOutput, self).__init__(output_stream, verbosity, decorated) self.stderr = StreamOutput(sys.stderr, verbosity, decorated, formatter) def set_decorated(self, decorated): super(ConsoleOutput, self).set_decorated(decorated) self.stderr.set_decorated(decorated) def set_formatter(self, formatter): super(ConsoleOutput, self).set_formatter(formatter) self.stderr.set_formatter(formatter) def set_verbosity(self, level): super(ConsoleOutput, self).set_verbosity(level) self.stderr.set_verbosity(level) def get_error_output(self): return self.stderr def set_error_output(self, error): self.stderr = error PKtJىcleo/outputs/null_output.py# -*- coding: utf-8 -*- from .output import Output from ..formatters import Formatter class NullOutput(Output): def set_formatter(self, formatter): pass def get_formatter(self): return Formatter() def set_decorated(self, decorated): pass def is_decorated(self): return False def set_verbosity(self, level): pass def get_verbosity(self): return self.VERBOSITY_QUIET def writeln(self, messages, output_type=Output.OUTPUT_NORMAL): pass def write(self, messages, newline=False, output_type=Output.OUTPUT_NORMAL): pass PKtJ\cleo/outputs/output.py# -*- coding: utf-8 -*- from ..formatters import Formatter class OutputError(Exception): pass class Output(object): VERBOSITY_QUIET = 16 VERBOSITY_NORMAL = 32 VERBOSITY_VERBOSE = 64 VERBOSITY_VERY_VERBOSE = 128 VERBOSITY_DEBUG = 256 OUTPUT_NORMAL = 0 OUTPUT_RAW = 1 OUTPUT_PLAIN = 2 def __init__(self, verbosity=VERBOSITY_NORMAL, decorated=False, formatter=None): self.verbosity = self.VERBOSITY_NORMAL if verbosity is None else verbosity self.formatter = formatter or Formatter(decorated) def set_formatter(self, formatter): self.formatter = formatter def get_formatter(self): return self.formatter def set_decorated(self, decorated): self.formatter.set_decorated(decorated) def is_decorated(self): return self.formatter.is_decorated() def set_verbosity(self, level): self.verbosity = int(level) def get_verbosity(self): return self.verbosity def is_quiet(self): return self.VERBOSITY_QUIET == self.verbosity def is_verbose(self): return self.VERBOSITY_VERBOSE == self.verbosity def is_very_verbose(self): return self.VERBOSITY_VERY_VERBOSE == self.verbosity def is_debug(self): return self.VERBOSITY_DEBUG == self.verbosity def write(self, messages, newline=False, output_type=OUTPUT_NORMAL): if self.verbosity == self.VERBOSITY_QUIET: return if not isinstance(messages, (list, tuple)): messages = [messages] for message in messages: if output_type == self.OUTPUT_NORMAL: message = self.formatter.colorize(message) elif output_type == self.OUTPUT_RAW: pass elif output_type == self.OUTPUT_PLAIN: message = self.formatter.colorize(message) else: raise OutputError('Unknown output type given (%s)' % output_type) self.do_write(message, newline) def writeln(self, messages, output_type=OUTPUT_NORMAL): self.write(messages, True, output_type) def do_write(self, message, newline): raise NotImplementedError() PKtJJ7ncleo/outputs/stream_output.py# -*- coding: utf-8 -*- import os import platform from io import UnsupportedOperation from .output import Output from .._compat import encode class StreamOutput(Output): def __init__(self, stream, verbosity=Output.VERBOSITY_NORMAL, decorated=None, formatter=None): if not hasattr(stream, 'write') or not callable(stream.write): raise Exception('The StreamOutput class needs a stream ' 'as its first argument.') self.stream = stream if decorated is None: decorated = self.has_color_support(decorated) super(StreamOutput, self).__init__(verbosity, decorated) def get_stream(self): return self.stream def do_write(self, message, newline): message = (message + (os.linesep if newline else '')) # This try/catch block is a small hack # to handle the cases where the stream is a class # like BytesIO that accepts only bytes object try: self.stream.write(message) except TypeError: message = encode(message) self.stream.write(message) self.stream.flush() def has_color_support(self, decorated): if platform.system().lower() == 'windows': return ( os.getenv('ANSICON') is not None or 'ON' == os.getenv('ConEmuANSI') or 'xterm' == os.getenv('Term') ) if not hasattr(self.stream, 'fileno'): return False try: return os.isatty(self.stream.fileno()) except UnsupportedOperation: return False def flush(self): return self.stream.flush() PKj)H]lݎcleo/parser.py# -*- coding: utf-8 -*- import re import os from .exceptions import CleoException from .inputs.input_argument import InputArgument from .inputs.input_option import InputOption class Parser(object): @classmethod def parse(cls, expression): """ Parse the given console command definition into a dict. :param expression: The expression to parse :type expression: str :rtype: dict """ parsed = { 'name': None, 'arguments': [], 'options': [] } if not expression.strip(): raise CleoException('Console command signature is empty.') expression = expression.replace(os.linesep, '') matches = re.match('[^\s]+', expression) if not matches: raise CleoException('Unable to determine command name from signature.') name = matches.group(0) parsed['name'] = name tokens = re.findall('\{\s*(.*?)\s*\}', expression) if tokens: parsed.update(cls._parameters(tokens)) return parsed @classmethod def _parameters(cls, tokens): """ Extract all of the parameters from the tokens. :param tokens: The tokens to extract the parameters from :type tokens: list :rtype: dict """ arguments = [] options = [] for token in tokens: if not token.startswith('--'): arguments.append(cls._parse_argument(token)) else: options.append(cls._parse_option(token)) return { 'arguments': arguments, 'options': options } @classmethod def _parse_argument(cls, token): """ Parse an argument expression. :param token: The argument expression :type token: str :rtype: InputArgument """ description = None validator = None if ' : ' in token: token, description = tuple(token.split(' : ', 2)) token = token.strip() description = description.strip() # Checking validator: matches = re.match('(.*)\((.*?)\)', token) if matches: token = matches.group(1).strip() validator = matches.group(2).strip() if token.endswith('?*'): return InputArgument( token.rstrip('?*'), InputArgument.IS_LIST, description ) elif token.endswith('*'): return InputArgument( token.rstrip('*'), InputArgument.IS_LIST | InputArgument.REQUIRED, description ) elif token.endswith('?'): return InputArgument( token.rstrip('?'), InputArgument.OPTIONAL, description ) matches = re.match('(.+)\=(.+)', token) if matches: return InputArgument( matches.group(1), InputArgument.OPTIONAL, description, matches.group(2) ) return InputArgument(token, InputArgument.REQUIRED, description, validator=validator) @classmethod def _parse_option(cls, token): """ Parse an option expression. :param token: The option expression :type token: str :rtype: InputOption """ description = None validator = None if ' : ' in token: token, description = tuple(token.split(' : ', 2)) token = token.strip() description = description.strip() # Checking validator: matches = re.match('(.*)\((.*?)\)', token) if matches: token = matches.group(1).strip() validator = matches.group(2).strip() shortcut = None matches = re.split('\s*\|\s*', token, 2) if len(matches) > 1: shortcut = matches[0].lstrip('-') token = matches[1] default = None mode = InputOption.VALUE_NONE if token.endswith('=*'): mode = InputOption.VALUE_REQUIRED | InputOption.VALUE_IS_LIST token = token.rstrip('=*') elif token.endswith('=?*'): mode = InputOption.VALUE_OPTIONAL | InputOption.VALUE_IS_LIST token = token.rstrip('=?*') elif token.endswith("=?"): mode = InputOption.VALUE_OPTIONAL token = token.rstrip('=?') elif token.endswith('='): mode = InputOption.VALUE_REQUIRED token = token.rstrip('=') matches = re.match('(.+)(\=[\?\*]*)(.+)', token) if matches: token = matches.group(1) operator = matches.group(2) default = matches.group(3) if operator == '=*': mode = InputOption.VALUE_REQUIRED | InputOption.VALUE_IS_LIST elif operator == '=?*': mode = InputOption.VALUE_OPTIONAL | InputOption.VALUE_IS_LIST elif operator == '=?': mode = InputOption.VALUE_OPTIONAL elif operator == '=': mode = InputOption.VALUE_REQUIRED return InputOption(token, shortcut, mode, description, default, validator=validator) PKWGڹ[cleo/questions/__init__.py# -*- coding: utf-8 -*- from .question import Question from .confirmation_question import ConfirmationQuestion from .choice_question import ChoiceQuestion PKj)HcU6 6 !cleo/questions/choice_question.py# -*- coding: utf-8 -*- import re from .question import Question from ..validators import Choice from ..exceptions import CleoException from .._compat import basestring, decode class SelectChoiceValidator(Choice): def __init__(self, question, validator=None): """ Constructor. :param question: A ChoiceQuestion instance :type question: ChoiceQuestion """ super(SelectChoiceValidator, self).__init__(question.choices, validator) self.question = question def validate(self, selected): """ Validate a choice. :param selected: The choice :type selected: str :return: bool """ # Collapse all spaces. if not isinstance(selected, basestring): selected = decode(str(selected)) selected_choices = selected.replace(' ', '') if self.question.multiselect: # Check for a separated comma values if not re.match('^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$', selected_choices): raise CleoException(self.question.error_message % selected) selected_choices = selected_choices.split(',') else: selected_choices = [selected] multiselect_choices = [] for value in selected_choices: results = [] for key, choice in enumerate(self.values): if choice == value: results.append(key) if len(results) > 1: raise CleoException( 'The provided answer is ambiguous. Value should be one of %s.' % ' or '.join(results) ) try: result = self.values.index(value) result = self.values[result] except ValueError: try: value = int(value) if value < len(self.values): result = self.values[value] else: result = False except ValueError: result = False if result is False: raise CleoException(self.question.error_message % value) multiselect_choices.append(result) if self.question.multiselect: return multiselect_choices return multiselect_choices[0] class ChoiceQuestion(Question): """ Represents a choice question. """ multiselect = False prompt = ' > ' error_message = 'Value "%s" is invalid' def __init__(self, question, choices, default=None): """ Constructor. :param question: The question to ask to the user :type question: str :param choices: The list of available choices :type choices: list :param default: The default answer to return :type default: mixed """ super(ChoiceQuestion, self).__init__(question, default) self.choices = choices self.validator = SelectChoiceValidator(self).validate self.autocompleter_values = choices PKj)HpN%__'cleo/questions/confirmation_question.py# -*- coding: utf-8 -*- import re from .question import Question class ConfirmationQuestion(Question): """ Represents a yes/no question. """ def __init__(self, question, default=True, true_answer_regex='(?i)^y'): """ Constructor. :param question: The question to ask to the user :type question: str :param default: The default answer to return, True or False :type default: bool :param true_answer_regex: A regex to match the "yes" answer :type true_answer_regex: str """ super(ConfirmationQuestion, self).__init__(question, default) self.true_answer_regex = true_answer_regex self.normalizer = self._get_default_normalizer def _get_default_normalizer(self, answer): """ Default answer normalizer. """ if isinstance(answer, bool): return answer answer_is_true = re.match(self.true_answer_regex, answer) is not None if self.default is False: return answer and answer_is_true return not answer or answer_is_true PK`iLXS2[[cleo/questions/question.py# -*- coding: utf-8 -*- from ..exceptions import CleoException class Question(object): """ Represents a Question """ def __init__(self, question, default=None): """ Constructor. :param question: The question to ask the user :type question: str :param default: The default answer to return if the user enters nothing :type default: mixed """ self.question = question self.default = default self._attempts = None self._hidden = False self.hidden_fallback = True self._autocompleter_values = None self.validator = None self.normalizer = None @property def hidden(self): return self._hidden @hidden.setter def hidden(self, value): if self.autocompleter_values: raise CleoException('A hidden question cannot use the autocompleter.') self._hidden = value @property def autocompleter_values(self): return self._autocompleter_values @autocompleter_values.setter def autocompleter_values(self, values): """ Sets values for the autocompleter. :param values: The autocomplete values :type values: list or None """ if values is not None and not isinstance(values, list): raise CleoException('Autocompleter values can be either a list or None.') if self.hidden: raise CleoException('A hidden question cannot use the autocompleter.') self._autocompleter_values = values @property def max_attempts(self): return self._attempts @max_attempts.setter def max_attempts(self, attempts): if attempts is not None and attempts < 1: raise CleoException('Maximum number of attempts must be a positive value.') self._attempts = attempts PK "Haacleo/styles/__init__.py# -*- coding: utf-8 -*- from .output_style import OutputStyle from .cleo_style import CleoStyle PKtJr9cleo/styles/cleo_style.py# -*- coding: utf-8 -*- import os import copy import textwrap from .output_style import OutputStyle from ..outputs import BufferedOutput from ..helpers import Helper, Table, QuestionHelper from ..questions import Question, ConfirmationQuestion, ChoiceQuestion from ..formatters import Formatter class CleoStyle(OutputStyle): MAX_LINE_LENGTH = 120 def __init__(self, input, output): super(CleoStyle, self).__init__(output) self._input = input self._buffered_output = BufferedOutput(output.get_verbosity(), False, copy.deepcopy(output.get_formatter())) self._line_length = min(self._get_terminal_width(), self.MAX_LINE_LENGTH) def block(self, messages, type=None, style=None, prefix=' ', padding=False, type_style=None, indent_on_type=False): """ Formats a message as a block of text. :param messages: The message to write in the block :type messages: str or list :param type: The block type (added in [] on first line) :type type: str or None :param style: The style to apply to the whole block :type style: str or None :param prefix: The prefix for the block :type prefix: str :param padding: Whether to add vertical padding :type padding: bool """ self._auto_prepend_block() if not isinstance(messages, list): messages = [messages] lines = [] # Add type if type is not None: messages[0] = '[%s] %s' % (type, messages[0]) if indent_on_type: for key, message in enumerate(messages[1:]): key = key + 1 messages[key] = ' ' * (Helper.len(type) + 1 + 2) + message # Wrap and add newlines for each element for key, message in enumerate(messages): message = Formatter.escape(message) wrap_limit = self._line_length - Helper.len(prefix) lines += os.linesep.join(textwrap.wrap(message, wrap_limit)).split(os.linesep) if messages and key < len(messages) - 1: lines.append('') if padding and self.is_decorated(): lines.insert(0, '') lines.append('') new_lines = [] for line in lines: line = '%s%s' % (prefix, line) line += ' ' * (self._line_length - Helper.len_without_decoration(self.get_formatter(), line)) if style: line = '<%s>%s' % (style, line) new_lines.append(line) if type and type_style: n = 0 if not padding else 1 split = new_lines[n].split('[%s]' % type) if len(split) > 1: new_lines[n] = split[0] + '[<%s>%s]' % (type_style, type) + split[1] else: new_lines[n] = '[<%s>%s]' % (type_style, type) + split[0] self.writeln(new_lines) self.new_line() def title(self, message): self._auto_prepend_block() self.writeln([ '%s' % message, '%s' % ('=' * Helper.len_without_decoration(self.get_formatter(), message)) ]) self.new_line() def section(self, message): self._auto_prepend_block() self.writeln([ '%s' % message, '%s' % ('-' * Helper.len_without_decoration(self.get_formatter(), message)) ]) self.new_line() def listing(self, elements): self._auto_prepend_text() elements = list(map(lambda element: ' * %s' % element, elements)) self.writeln(elements) self.new_line() def comment(self, message): self._auto_prepend_text() if not isinstance(message, list): message = [message] for msg in message: self.writeln(' // %s' % msg) def success(self, message): self.block(message, 'OK', type_style='fg=green') def error(self, message): self.block(message, 'ERROR', 'fg=white;bg=red', padding=True) def warning(self, message): self.block(message, 'WARNING', type_style='fg=red') def note(self, message): self.block(message, 'NOTE', type_style='fg=blue') def caution(self, message): self.block(message, 'CAUTION', type_style='fg=red', indent_on_type=True) def table(self, headers, rows): headers = list(map(lambda header: '%s' % header, headers)) table = Table(self) table.set_headers(headers) table.set_rows(rows) table.render() self.new_line() def ask(self, question, default=None, validator=None): question = Question(question) question.validator = validator return self.ask_question(question) def ask_hidden(self, question, validator=None): question = Question(question) question.hidden = True question.validator = validator return self.ask_question(question) def confirm(self, question, default=True, true_answer_regex='(?i)^y'): return self.ask_question( ConfirmationQuestion(question, default, true_answer_regex) ) def choice(self, question, choices, default=None): if default is not None: default = choices[default] return self.ask_question(ChoiceQuestion(question, choices, default)) def ask_question(self, question): """ Asks a question. :param question: The question to ask :type question: Question :rtype: str """ if self._input.is_interactive(): self._auto_prepend_block() answer = QuestionHelper().ask(self._input, self, question) if self._input.is_interactive(): self.new_line() self._buffered_output.write('\n') return answer def writeln(self, messages, type=OutputStyle.OUTPUT_NORMAL): super(CleoStyle, self).writeln(messages, type) self._buffered_output.writeln(self._reduce_buffer(messages), type) def write(self, messages, newline=False, type=OutputStyle.OUTPUT_NORMAL): super(CleoStyle, self).write(messages, newline, type) self._buffered_output.write(self._reduce_buffer(messages), newline, type) def write_error(self, messages, newline=False, type=OutputStyle.OUTPUT_NORMAL): super(CleoStyle, self).write_error(messages, newline, type) self._buffered_output.write(self._reduce_buffer(messages), newline, type) def new_line(self, count=1): super(CleoStyle, self).new_line(count) self._buffered_output.write('\n' * count) def overwrite(self, messages, newline=False, size=None, type=OutputStyle.OUTPUT_NORMAL): super(CleoStyle, self).overwrite(messages, newline, size, type) self._buffered_output.write(self._reduce_buffer(messages), newline, type) def _get_terminal_width(self): from ..application import Application application = Application() width = application.terminal.width if width: return width return self.MAX_LINE_LENGTH def _auto_prepend_block(self): chars = self._buffered_output.fetch().replace(os.linesep, '\n')[:-2] if not chars: return self.new_line() # Prepend new line for each non LF chars (This means no blank line was output before) self.new_line(2 - chars.count('\n')) def _auto_prepend_text(self): fetched = self._buffered_output.fetch() # Prepend new line if last char isn't EOL: if fetched and fetched[-1] != '\n': self.new_line() def _reduce_buffer(self, messages): # We need to know if the two last chars are PHP_EOL # Preserve the last 4 chars inserted (PHP_EOL on windows is two chars) in the history buffer if not isinstance(messages, list): messages = [messages] return list(map(lambda m: m[:-4], [self._buffered_output.fetch()] + messages)) PKtJNcleo/styles/output_style.py# -*- coding: utf-8 -*- import os from ..helpers import ProgressBar, Helper from ..outputs import Output, ConsoleOutput class OutputStyle(Output): """ Decorates output to add console style guide helpers. """ def __init__(self, output): """ Constructor. :param output: An Output instance :type output: Output """ self._output = output self._last_message = '' self._last_message_err = '' @property def output(self): return self._output def title(self, message): """ Formats a command title. :type message: str """ raise NotImplementedError() def section(self, message): """ Formats a section title. :type message: str """ raise NotImplementedError() def listing(self, elements): """ Formats a list. :type elements: list """ raise NotImplementedError() def text(self, message): """ Formats informational text. :type message: str or list """ raise NotImplementedError() def success(self, message): """ Formats a success result bar. :type message: str or list """ raise NotImplementedError() def error(self, message): """ Formats an error result bar. :type message: str or list """ raise NotImplementedError() def warning(self, message): """ Formats a warning result bar. :type message: str or list """ raise NotImplementedError() def note(self, message): """ Formats a note admonition. :type message: str or list """ raise NotImplementedError() def caution(self, message): """ Formats a caution admonition. :type message: str or list """ raise NotImplementedError() def table(self, headers, rows): """ Formats a table. :type headers: list :type rows: list """ raise NotImplementedError() def ask(self, question, default=None, validator=None): """ Asks a question :type question: str :type default: str or None :type validator: callable or None :rtype: str """ raise NotImplementedError() def ask_hidden(self, question, validator=None): """ Asks a question with the user input hidden. :type question: str :type validator: callable or None :rtype: str """ raise NotImplementedError() def confirm(self, question, default=True): """ Asks for confirmation. :type question: str :type default: bool :rtype: bool """ raise NotImplementedError() def choice(self, question, choices, default=None): """ Asks a choice question. :type question: str :type choices: list :type default: str or int or None :rtype: str """ raise NotImplementedError() def new_line(self, count=1): """ Add newline(s). :param count: The number of newlines :type count: int """ self._output.write(os.linesep * count) def create_progress_bar(self, max=0): """ Create a new progress bar :type max: int :rtype ProgressHelper """ return ProgressBar(self, max) def write(self, messages, newline=False, type=Output.OUTPUT_NORMAL): self._do_write(messages, newline, False, type) def write_error(self, messages, newline=False, type=Output.OUTPUT_NORMAL): self._do_write(messages, newline, True, type) def _do_write(self, messages, newline, stderr, type): if not isinstance(messages, list): messages = [messages] if stderr and isinstance(self._output, ConsoleOutput): self._output.get_error_output().write(messages, newline, type) self._last_message_err = '\n'.join(messages) return self._output.write(messages, newline, type) self._last_message = '\n'.join(messages) def writeln(self, messages, type=Output.OUTPUT_NORMAL): self.write(messages, True, type) def overwrite(self, messages, newline=False, size=None, type=Output.OUTPUT_NORMAL): self._do_overwrite(messages, newline, size, False, type) def _do_overwrite(self, messages, newline, size, stderr, type): # messages can be a list, let's convert it to string anyway if not isinstance(messages, list): messages = [messages] messages = '\n'.join(messages) # since overwrite is supposed to overwrite last message... if size is None: # removing possible formatting of lastMessage with strip_tags if stderr: message = self._last_message_err else: message = self._last_message size = Helper.len_without_decoration(self._output.get_formatter(), message) # ...let's fill its length with backspaces self._do_write('\x08' * size, False, stderr, type) # write the new message self._do_write(messages, False, stderr, type) fill = size - Helper.len_without_decoration(self._output.get_formatter(), messages) if fill > 0: # whitespace whatever has left self._do_write(' ' * fill, False, stderr, type) # move the cursor back self._do_write('\x08' * fill, False, stderr, type) if newline: self._do_write('', True, stderr, type) if stderr: self._last_message_err = messages else: self._last_message = messages def set_formatter(self, formatter): self._output.set_formatter(formatter) def get_formatter(self): return self._output.get_formatter() def set_decorated(self, decorated): self._output.get_formatter().set_decorated(bool(decorated)) def is_decorated(self): return self._output.is_decorated() def set_verbosity(self, level): self._output.set_verbosity(level) def get_verbosity(self): return self._output.get_verbosity() def is_quiet(self): return self._output.is_quiet() def is_verbose(self): return self._output.is_verbose() def is_very_verbose(self): return self._output.is_very_verbose() def is_debug(self): return self._output.is_debug() PK\.I| | cleo/terminal.py# -*- coding: utf-8 -*- import os import platform import struct import subprocess import shlex class Terminal(object): """ Represents the current terminal. """ def __init__(self): self._width = None self._height = None @property def width(self): width = os.getenv('COLUMNS', '').strip() if width: return int(width) if self._width is None: self._init_dimensions() return self._width @property def height(self): height = os.getenv('LINES', '').strip() if height: return int(height) if self._height is None: self._init_dimensions() return self._height def _init_dimensions(self): current_os = platform.system().lower() dimensions = None if current_os.lower() == 'windows': dimensions = self._get_terminal_size_windows() if dimensions is None: dimensions = self._get_terminal_size_tput() elif current_os.lower() in ['linux', 'darwin'] or current_os.startswith('cygwin'): dimensions = self._get_terminal_size_linux() if dimensions is None: dimensions = 80, 25 self._width, self._height = dimensions def _get_terminal_size_windows(self): try: from ctypes import windll, create_string_buffer # stdin handle is -10 # stdout handle is -11 # stderr handle is -12 h = windll.kernel32.GetStdHandle(-12) csbi = create_string_buffer(22) res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) if res: (bufx, bufy, curx, cury, wattr, left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) sizex = right - left + 1 sizey = bottom - top + 1 return sizex, sizey except: pass def _get_terminal_size_tput(self): # get terminal width # src: http://stackoverflow.com/questions/263890/how-do-i-find-the-width-height-of-a-terminal-window try: cols = int(subprocess.check_call(shlex.split('tput cols'))) rows = int(subprocess.check_call(shlex.split('tput lines'))) return (cols, rows) except: pass def _get_terminal_size_linux(self): def ioctl_GWINSZ(fd): try: import fcntl import termios cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234')) return cr except: pass cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2) if not cr: try: fd = os.open(os.ctermid(), os.O_RDONLY) cr = ioctl_GWINSZ(fd) os.close(fd) except: pass if not cr: try: cr = (os.environ['LINES'], os.environ['COLUMNS']) except: return None return int(cr[1]), int(cr[0]) PKehDcleo/testers/__init__.py# -*- coding: utf-8 -*- from .application_tester import ApplicationTester from .command_tester import CommandTester __all__ = [ 'ApplicationTester', 'CommandTester' ] PKYoL˺F@ "cleo/testers/application_tester.py# -*- coding: utf-8 -*- import os from io import BytesIO from ..inputs.list_input import ListInput from ..outputs.stream_output import StreamOutput class ApplicationTester(object): """ Eases the testing of console applications. """ def __init__(self, application): """ Constructor :param application: A Application instance to test :type application: Application """ self._application = application self._input = None self._output = None self._inputs = [] def run(self, input_, options=None): """ Executes the command Available options: * interactive: Sets the input interactive flag * decorated: Sets the output decorated flag * verbosity: Sets the output verbosity flag :param input_: A dict of argument and options :type input_: list :param options: A dict of options :type options: dict :return: The command exit code :rtype: integer """ options = options or {} self._input = ListInput(input_) if self._inputs: self._input.set_stream(self._create_stream(self._inputs)) if 'interactive' in options: self._input.set_interactive(options['interactive']) self._output = StreamOutput(BytesIO()) if 'decorated' in options: self._output.set_decorated(options['decorated']) if 'verbosity' in options: self._output.set_verbosity(options['verbosity']) self._application.run(self._input, self._output) def get_display(self, normalize=False): """ Gets the display returned by the last execution command :return: The display :rtype: str """ self._output.get_stream().seek(0) display = self._output.get_stream().read().decode('utf-8') if normalize: display = display.replace(os.linesep, '\n') return display def get_input(self): """ Gets the input instance used by the last execution of the command. :return: The current input instance :rtype: Input """ return self._input def get_output(self): """ Gets the output instance used by the last execution of the command. :return: The current output instance :rtype: Output """ return self._output def set_inputs(self, inputs): """ Sets the user inputs. :param inputs: The user inputs :type inputs: list :rtype: CommandTester """ self._inputs = inputs return self def _create_stream(self, inputs): """ Create a stream from inputs. :type inputs: list :rtype: """ stream = BytesIO() stream.write(os.linesep.join(inputs).encode()) stream.seek(0) return stream PKYoLKj cleo/testers/command_tester.py# -*- coding: utf-8 -*- import os from io import BytesIO from ..inputs.list_input import ListInput from ..outputs.stream_output import StreamOutput class CommandTester(object): """ Eases the testing of console commands. """ def __init__(self, command): """ Constructor :param command: A Command instance to test :type command: Command """ self._command = command self._input = None self._output = None self._inputs = [] self._status_code = None @property def status_code(self): return self._status_code def execute(self, input_, options=None): """ Executes the command Available options: * interactive: Sets the input interactive flag * decorated: Sets the output decorated flag * verbosity: Sets the output verbosity flag :param input_: A dict of argument and options :type input_: list :param options: A dict of options :type options: dict :return: The command exit code :rtype: integer """ options = options or {} self._input = ListInput(input_) if self._inputs: self._input.set_stream(self._create_stream(self._inputs)) if 'interactive' in options: self._input.set_interactive(options['interactive']) self._output = StreamOutput(BytesIO()) if 'decorated' in options: self._output.set_decorated(options['decorated']) if 'verbosity' in options: self._output.set_verbosity(options['verbosity']) self._status_code = self._command.run(self._input, self._output) return self._status_code def get_display(self, normalize=False): """ Gets the display returned by the last execution command :return: The display :rtype: str """ self._output.get_stream().seek(0) display = self._output.get_stream().read().decode('utf-8') if normalize: display = display.replace(os.linesep, '\n') return display def get_input(self): """ Gets the input instance used by the last execution of the command. :return: The current input instance :rtype: Input """ return self._input def get_output(self): """ Gets the output instance used by the last execution of the command. :return: The current output instance :rtype: Output """ return self._output def set_inputs(self, inputs): """ Sets the user inputs. :param inputs: The user inputs :type inputs: list :rtype: CommandTester """ self._inputs = inputs return self def _create_stream(self, inputs): """ Create a stream from inputs. :type inputs: list :rtype: """ stream = BytesIO() stream.write(os.linesep.join(inputs).encode()) stream.seek(0) return stream PKj)Hn/cleo/validators/__init__.py# -*- coding: utf-8 -*- from .._compat import basestring class ValidationError(ValueError): _UNDEFINED = object() def __init__(self, msg, value=_UNDEFINED): self.msg = msg self.value = value super(ValidationError, self).__init__(str(self)) def __str__(self): return self.to_s() def to_s(self): if self.value != self._UNDEFINED: return 'Invalid value %s (%s): %s'\ % (repr(self.value), self.value.__class__.__name__, self.msg) return self.msg class Validator(object): def validate(self, value): """Check if ``value`` is valid. Adaptors should return the adapted value in this method. :param value: The value to validate and to adapt :raise ValidationError: If ``value`` is invalid. """ raise NotImplementedError def is_valid(self, value): """Check if the value is valid. :return: ``True`` if the value is valid, ``False`` if invalid. """ try: self.validate(value) return True except (ValidationError, ValueError): return False def error(self, value): """Helper method that can be called when ``value`` is deemed invalid. Can be overriden to provide customized :py:exc:`ValidationError` subclasses. """ raise ValidationError("must be %s" % self.humanized_name, value) @property def humanized_name(self): """Return a human-friendly string name for this validator.""" return getattr(self, 'name', self.__class__.__name__) class ValidatorSet(object): def __init__(self, validators=None): self.validators = {} validators = validators or [] for validator in validators: self.register(validator) def register(self, validator): if not issubclass(validator, Validator): raise Exception('A validator must be a subclass of the Validator class') if hasattr(validator, 'name'): if isinstance(validator.name, (list, tuple)): for name in validator.name: self._register_validator(name, validator) else: self._register_validator(validator.name, validator) def get(self, validator): if isinstance(validator, Validator): return validator if isinstance(validator, basestring): if validator in self.validators: return self.validators[validator]() elif validator == 'string': return None else: raise Exception('Unable to find a validator with name "%s"' % validator) if validator is basestring: return None if validator is int: return Integer() if validator is float: return Float() if validator is bool: return Boolean() return None def _register_validator(self, name, validator): if validator.name in self.validators: raise Exception('A validator with name "%s" is already registered' % validator.name) self.validators[name] = validator # Default validators class Callable(Validator): """A validator that accepts a callable. Attributes: - callable: The callable """ def __init__(self, callable_): """ :param callable_: The callable :type callable_: callable """ if not callable(callable_): raise TypeError('"callable" argument is not callable') self.callable = callable_ def validate(self, value): try: return self.callable(value) except (TypeError, ValueError) as e: raise ValidationError(str(e)) class Enum(Validator): """A validator that accepts only a finite set of values. Attributes: - values: The collection of valid values. """ name = ('enum', 'choice') values = () def __init__(self, values=None, validator=None): super(Enum, self).__init__() if values is None: values = self.values try: seen = set() seen_add = seen.add self.values = [x for x in values if not (x in seen or seen_add(x))] except TypeError: self.values = list(values) self.validator = validator def validate(self, value): if self.validator: value = self.validator.validate(value) try: if value in self.values: return value except TypeError: pass self.error(value) @property def humanized_name(self): return "one of {%s}" % ", ".join(map(repr, self.values)) Choice = Enum class Boolean(Validator): """A validator that accepts booleans Accepted values are 1, true, yes, y, on and their negatives or native boolean types (True, False). """ name = 'boolean' def validate(self, value): if isinstance(value, str): value_ = value.lower() if value_ in ['1', 'true', 'yes', 'y', 'on']: return True if value_ in ['0', 'false', 'no', 'n', 'off']: return False self.error(value) elif not isinstance(value, bool): self.error(value) return value class Integer(Validator): """A validator that accepts integers """ name = 'integer' def validate(self, value): try: return int(value) except ValueError: self.error(value) class Float(Validator): """A validator that accepts floats """ name = 'float' def validate(self, value): try: return float(value) except ValueError: self.error(value) class Range(Validator): """A validator that restricts a value to be in a specified range. The range can be of anything that can be compared to the specified value, like integers, floats or string. """ name = 'range' def __init__(self, min=None, max=None, include_min=True, include_max=True, validator=Integer()): self.min = min self.max = max self.include_min = include_min self.include_max = include_max self.validator = validator def validate(self, value): if self.validator: value = self.validator.validate(value) if self.min is not None: if value < self.min \ or (value == self.min and self.include_min is False): self.error(value) if self.max is not None: if value > self.max \ or (value == self.max and self.include_max is False): self.error(value) return value @property def humanized_name(self): left_boundary = '[' if self.include_min else ']' right_boundary = ']' if self.include_max else '[' return "in range %s%s, %s%s"\ % (left_boundary, repr(self.min), repr(self.max), right_boundary) VALIDATORS = ValidatorSet([ Boolean, Enum, Integer, Float, Range ]) PK!HeUVecleo-0.6.3.dist-info/WHEEL HM K-*ϳR03rOK-J,/R(O-)T03zd&Y)r$U&UD"PK!H@cleo-0.6.3.dist-info/METADATA;v۸hI^eQNx6gc! PS }}./r;MRE ;Uȉ,[e6PrgJfAs%t^(;rq,`)g(nxx8ż0M[X{Hթ }wzZ;q\t\scKWhH5P pʿ6{crW0~$m_i(>IyD2霞jmB3VCxNPs{g3~[3vP,WGcdڣ'?m >GEኢ8z:R+IavT$^H1Yue1W3Upǔup+DڤBXxH;4)jbR <SJ%.LZʡ gG7X/X+%dZsYU`[ZYG޾xJ↜LhԠ[@!I/55g&~D(^ 1/-+uQ|+]]Hw%n4<:rl**@, ĦbdT6m'AaIg|EzJ*ԇ@tbFuįhdk]S;Vf* ݝNOkT.w 9#r&Bڥ-sjfӹ"/NjUuaKMtpC_WF 84~(b:O|5ovChdx ]{ݖuj5Q^׹=^ror윗g]ka+3/ڱA-΋Mϲ.-D}eY2jW AV:V$ÆA иIu,V&e{(xAO)ns(:fO+3+%9e$ (ܛ(pIU:ׄ0S!AC֖؅x~U-RfNrVU1`knEbO42Y8U ]dZd=5252'ju5H ӫzo<|xA< OB4P93 'P307#$q)؇z^g UL/pJ)Mb1G@e띒nrJU?g'__^~7oNO.54S+<KsW 4GLA-%?Z(pv%(gSS IǺÐneqoH;Yyc-@EjfPގ%m{s8Bl 9CZf5T듗|[5.gl^~G [TOuZ;"'e"ox3M BZRQN)cN@, 0/WT dDڀݴ4nDfDwԖՒ#L4f jҧɛ*iEP8 "˭IKVa$1"TB@kGн}G;aK);=W)1G!g>C ҧN;s^LzTsr8٬ɫ$R:X*0KP\oEu3F1rfԪ{\K/|HEH&SxX']TyqM`]-~V>E2To̴Ft H,¯$*V5 ֖1ݺj KF]8]Ke^k==׭ʙy]ߢѝ$98!Mߟ@5XP.@ǕI"\7 }yz~ƥM)/6Z\% ʈM*XheO/FoO/f=sYH|#r1 ʂϥ`nEK 1&+Sc8I*č3}N4/>IŰ;&H}̙[wV%ji1W`Q4ĉ:nتU+V˜4lrk]NN(,8_E4 )>*-Ah@Q0(An)nd0>\C%B{i VIyaxPU%(B]T<bizVd&1."4xoᐥ#/T ֐]gR[;L9A6˚4ճV>aX8[2L~̈́(COX1ˡf }4&8 ϣgQ< UVr1{fTy5^9vCZ˕LcG?@Ii LuQjR:,VzΓՎ6:'ݯI W-]T[0(o@UdKZI: X+t]T'$&0,y҆΀i$G'ȇq?1yM}ؐUr4Ņ*#Ztɳp~Ȧ0=_gu}|.v_8wV8D$LmoNF xS;^\BK;BVL꽒ݕVAU#@rE iͪɄk8Wht՚Vr|8qy ކZ,l0lIC9ŝfu{ryKҾ2ϪЋ:!6' ;v\C̄ ʖ5뼛O;DD<ڃ~MDAٰcRV#36hhqxUXv>[/[Wӯ}"bnOX#AA]gk}6\!=jMjک6iT0E|׿mNo:06|ѲF⽚N!\mV/7u=m.)&7dVxzǏWtRA<;qR԰q4)>cG]NW9J8%ϫLUsi 8gbt\G5OkAxa*-ho/:XzeѤ;Mu@TE|@Zwp~4rt+>m;\G*Z"nԊJ<ƒzw X瀃7Dd&,KsV9eX@i|C p V |>|06D`AD{5@eGV*68WASmO9z6/ir?b8™a92hjrv /mjm*Mh ĉ/ٽeMSѪy9>c#x@/4 @_KI8D sdrDŸ>-\CrIVS,fSї2>d [C޻jݪ(~̞8bqʪ&g YG*ơj'!2G|_$KPI4& Bs߃ uxy|FC_> KKgjY1j7T̍!=mB KXp]B fk MEA(~7~/Wծ UAdn]f7fڇ梧~g7!GoD>|WdYt>`#|n?T* lXS EifXgpdI["ةK <7Ǻ!Qq&.и~ke+®1?u1<A3%/ׅ8GPK!H2 cleo-0.6.3.dist-info/RECORD}ɚڲYtqR#BZk4~cDQ_ltnH=gSȩ`|=k-R(2` ;&Mzft5 B~#ʂd^0smޘ5+ӈSsQvꡝT+&;t$7$Y ċ7d(m1U^R9KBV6FxlgFgfj* Ơ@"W#sg:dq "I>^n*͎@p}sz;:WCZe#>2B sW@FWb[F|d JQil~RqOeՍQMdd{7F2.4˫3&hﺂu}qp |qA&P>]?W&-FF&|9_u"xa4*7XLT2WUfy iE AWq ?R"/EPFxJdLY;6ۻyFJQ̜uD(sb’bX5[zWS_Ͽ"LHVW+JIhwL]yL{OkDƪAJq`v);T/kyg;BpM7u"| ~ @Խ%#TxW Cw!W^_>mJSŹ BVX39 !*j e|϶#HYs>bUZp>.Rvh~:#ɯ jdFݔ Z5Q$r KAVga /oTӶpZq,B\rW~4T:KO`9#yD\Va%ލ C/̬nSa=S]cF}8& S)Kj.gM_y7LBqZ6= , yNxw GһcCKPްfIQ=A׭䬻Q{I%ZvYK?XKޖƉ7'٨!ܛsNyѳ+a {NUQT:Nw;/"yWp䠼jQ#bL@ωXya0WQ 8H 3JMd! T*{H 8>M5.JTm4dHEz(>k1ÖS;훤0J.vTĨP|X^3X #ե6Q:tW^Az4`Y:rI/ܬ?8>]{bĂI6qQ1=u؏Dthe4/!?bz_^V[Ns,b9lwe U~Wh!Tc^NPm`ߺJ,F{G}>;nȫ^cMNQO<~xH&5 7#Hy[03:D)\ʢ'HwjYpЗ&T ds|x{s":~yiU[ +.ɉ.!T-Srilb?AdA~=x^p{vWaXWB͊L8D0͜V:>}G|}TtiZ~^;f:ȨL~M^ 3-S*OuFc| 7j4AZ,wV/Ɋ}Qrm/v?ZK>i#+sh뉗ױht@[@ Ͱ}w @C#ˋehJa$Zt"|E#90YjyȰ›bgLØK͈Z D\anoh۾R{ rUh;PkJD68Ȁ&:7~~<>*&x~?{"”,leT sL眿 QmSw5}?7W춛mGTm zGsۜWk3A[s,tt7IQSlۺ6ꈱ0 S4p`:nkYV#"<x5l%PԞD 4+{5ePli+G|\eei)\1~~yj,#k?PKtJ@%xcleo/__init__.pyPKyG[[ttHcleo/_compat.pyPK`iLի4VPVPcleo/application.pyPKtJdX~jjpWcleo/commands/__init__.pyPK0pJY!!Ycleo/commands/base_command.pyPKYoLk73 8 8kzcleo/commands/command.pyPKtJ%cleo/commands/completions/__init__.pyPKtJtd 1& cleo/commands/completions/templates.pyPKtJU55$cleo/commands/completions_command.pyPKp*Hk)cleo/commands/help_command.pyPK|t,HRcleo/commands/list_command.pyPK*H] <cleo/descriptors/__init__.pyPKuwI]۶ + cleo/descriptors/application_description.pyPK]$H6 cleo/descriptors/descriptor.pyPK%$HI`M""#%cleo/descriptors/json_descriptor.pyPK*HB'"cleo/descriptors/markdown_descriptor.pyPKLS4Iܭ&&#5cleo/descriptors/text_descriptor.pyPKm4I$O$==\cleo/exceptions/__init__.pyPKtJ+^cleo/exceptions/command.pyPKM4I@LOLLgcleo/exceptions/exception.pyPK4IUΠmmhcleo/exceptions/input.pyPKtJ~{#::-jcleo/formatters/__init__.pyPKtJY'!!jcleo/formatters/formatter.pyPKtJ"ͳkcleo/helpers/__init__.pyPK*H%Ä!Gocleo/helpers/descriptor_helper.pyPKtJI| {ucleo/helpers/formatter_helper.pyPKHKZ1S S ycleo/helpers/helper.pyPKYoLكe;cleo/helpers/helper_set.pyPK;ZoLH<# + +cleo/helpers/progress_bar.pyPKj)H&##ֲcleo/helpers/progress_helper.pyPKJZoLmg"cleo/helpers/progress_indicator.pyPKtJ}((cleo/helpers/question_helper.pyPKj)H8{??Fcleo/helpers/table.pyPKYG^>AYcleo/helpers/table_cell.pyPKj)Hnr\cleo/helpers/table_helper.pyPK䎟GNdjcleo/helpers/table_separator.pyPK&"Hf&::kcleo/helpers/table_style.pyPK/3I#y/scleo/inputs/__init__.pyPK 3Iitcleo/inputs/api.pyPKYoL2