PK!5Ϊcleo/__init__.py# -*- coding: utf-8 -*- from .application import Application from .commands import Command from .testers import ApplicationTester, CommandTester __version__ = "0.7.0" PK!ysscleo/_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!L+5cleo/application.pyfrom typing import Optional from typing import Tuple from clikit.console_application import ConsoleApplication from .commands import BaseCommand from .commands.completions_command import CompletionsCommand from .config import ApplicationConfig class Application(ConsoleApplication, 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=None, version=None, complete=True, config=None ): # type: (str, str, bool, Optional[ApplicationConfig]) -> None if config is None: config = ApplicationConfig(name, version) super(Application, self).__init__(config) if complete: self.add(CompletionsCommand()) def add_commands(self, *commands): # type: (Tuple[BaseCommand]) -> None for command in commands: self.add(command) def add(self, command): # type: (BaseCommand) -> Application """ Adds a command object. """ self.add_command(command.config) command.set_application(self) return self def find(self, name): # type: (str) -> BaseCommand names = name.split(" ") command = self.get_command(names[0]) for name in names[1:]: command = command.get_sub_command(name) return command.config.handler PK![wcleo/commands/__init__.py# -*- coding: utf-8 -*- from .base_command import BaseCommand from .command import Command __all__ = ["BaseCommand", "Command"] PK!* * cleo/commands/base_command.pyfrom typing import Optional from clikit.api.args import Args from clikit.api.command import Command as CliKitCommand from clikit.api.config.command_config import CommandConfig from cleo.io import ConsoleIO class CommandError(Exception): pass class BaseCommand(object): name = None description = None help = None arguments = [] options = [] aliases = [] enabled = True hidden = False commands = [] def __init__(self): self._application = None self._config = CommandConfig(self.name) self._config.set_description(self.description) self._config.set_help(self.help) for argument in self.arguments: self._config.add_argument( argument.name, argument.flags, argument.description, argument.default ) for option in self.options: self._config.add_option( option.name, option.shortcut, option.flags, option.description, option.default, ) for alias in self.aliases: self._config.add_alias(alias) if not self.enabled: self._config.disable() if self.hidden: self._config.hide() if self.commands: for command in self.commands: self.add_sub_command(command) self._config.set_handler(self) @property def config(self): # type: () -> CommandConfig return self._config @property def application(self): return self._application def handle( self, args, io, command ): # type: (Args, ConsoleIO, CliKitCommand) -> Optional[int] raise NotImplementedError() def set_application(self, application): self._application = application for command in self.commands: command.set_application(application) def add_sub_command(self, command): # type: (BaseCommand) -> None self._config.add_sub_command_config(command.config) command.set_application(self.application) def default(self, default=True): # type: (bool) -> BaseCommand self._config.default(default) return self def anonymous(self): # type: () -> BaseCommand self._config.anonymous() return self PK!MmT!!cleo/commands/command.pyimport re from typing import Optional from clikit.api.args import Args from clikit.api.args.format import ArgsFormat from clikit.api.formatter import Style from clikit.api.io import IO from clikit.args import StringArgs from clikit.io import NullIO from clikit.ui.components import ChoiceQuestion from clikit.ui.components import ConfirmationQuestion from clikit.ui.components import ProgressIndicator from clikit.ui.components import Question from clikit.ui.components import Table from clikit.ui.style import TableStyle from cleo.io import ConsoleIO from cleo.parser import Parser from .base_command import BaseCommand class Command(BaseCommand): signature = None validation = None TABLE_STYLES = { "ascii": TableStyle.ascii(), "borderless": TableStyle.borderless(), "solid": TableStyle.solid(), "compact": TableStyle.compact(), } def __init__(self): self._args = Args(ArgsFormat()) self._io = None self._command = None super(Command, self).__init__() 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() self._config.set_handler_method("wrap_handle") @property def io(self): # type: () -> ConsoleIO return self._io def _parse_doc(self, doc): doc = doc.strip().split("\n", 1) if len(doc) > 1: self._config.set_description(doc[0].strip()) self.signature = re.sub(r"\s{2,}", " ", doc[1].strip()) else: self._config.set_description(doc[0].strip()) def _configure_using_fluent_definition(self): """ Configure the console command using a fluent definition. """ definition = Parser.parse(self.signature) self._config.set_name(definition["name"]) for name, flags, description, default in definition["arguments"]: self._config.add_argument(name, flags, description, default) for long_name, short_name, flags, description, default in definition["options"]: self._config.add_option(long_name, short_name, flags, description, default) def wrap_handle( self, args, io, command ): # type: (Args, IO, CliKitCommand) -> Optional[int] self._args = args self._io = io self._command = command return self.handle() def handle(self): # type: () -> Optional[int] """ Executes the command. """ raise NotImplementedError() def call(self, name, args=None): # type: (str, Optional[str]) -> int """ Call another command. """ if args is None: args = "" args = StringArgs(args) command = self.application.get_command(name) return command.run(args, self.io) def call_silent(self, name, args=None): # type: (str, Optional[str]) -> int """ Call another command. """ if args is None: args = "" args = StringArgs(args) command = self.application.get_command(name) return command.run(args, NullIO()) def argument(self, key=None): """ Get the value of a command argument. """ if key is None: return self._args.arguments() return self._args.argument(key) def option(self, key=None): """ Get the value of a command option. """ if key is None: return self._args.options() return self._args.option(key) def confirm(self, question, default=False, true_answer_regex="(?i)^y"): """ Confirm a question with the user. """ return self._io.confirm(question, default, true_answer_regex) def ask(self, question, default=None): """ Prompt the user for input. """ if isinstance(question, Question): return self._io.ask_question(question) return self._io.ask(question, default) def secret(self, question): """ Prompt the user for input but hide the answer from the console. """ return self._io.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. """ question = ChoiceQuestion(question, choices, default) question.set_max_attempts(attempts) question.set_multi_select(multiple) return self._io.ask_question(question) def create_question(self, question, type=None, **kwargs): """ Returns a Question of specified type. """ if not type: return Question(question, **kwargs) if type == "choice": return ChoiceQuestion(question, **kwargs) if type == "confirmation": return ConfirmationQuestion(question, **kwargs) def table(self, header=None, rows=None, style=None): """ Return a Table instance. """ if style is not None: style = self.TABLE_STYLES[style] table = Table(style) if header: table.set_header_row(header) if rows: table.set_rows(rows) return table def render_table(self, headers, rows, style=None): """ Format input to textual table. """ table = self.table(headers, rows, style) table.render(self._io) def write(self, text, style=None): """ Writes a string without a new line. Useful if you want to use overwrite(). """ if style: styled = "<%s>%s" % (style, text) else: styled = text self._io.write(styled) def line(self, text, style=None, verbosity=None): """ Write a string as information output. """ if style: styled = "<%s>%s" % (style, text) else: styled = text self._io.write_line(styled, verbosity) def line_error(self, text, style=None, verbosity=None): """ Write a string as information output to stderr. """ if style: styled = "<%s>%s" % (style, text) else: styled = text self._io.error_line(styled, verbosity) 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 progress_bar(self, max=0): """ Creates a new progress bar :param max: The maximum number of steps :type max: int :rtype: ProgressBar """ return self._io.progress_bar(max) def progress_indicator(self, fmt=None, interval=100, values=None): """ Creates a new progress indicator. """ return ProgressIndicator(self.io, fmt, interval, values) def spin(self, start_message, end_message, fmt=None, interval=100, values=None): """ Automatically spin a progress indicator. """ spinner = ProgressIndicator(self.io, fmt, interval, values) return spinner.auto(start_message, end_message) def add_style(self, name, fg=None, bg=None, options=None): """ Adds a new style """ style = Style(name) if fg is not None: style.fg(fg) if bg is not None: style.bg(bg) if options is not None: if "bold" in options: style.bold() if "underline" in options: style.underlined() self._io.output.formatter.add_style(style) self._io.error_output.formatter.add_style(style) def overwrite(self, text, size=None): """ Overwrites the current line. It will not add a new line so use line('') if necessary. """ self._io.overwrite(text, size=size) PK!uh%cleo/commands/completions/__init__.py# -*- coding: utf-8 -*- PK!IL&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} PK!Ė66$cleo/commands/completions_command.py# -*- coding: utf-8 -*- import os import hashlib import posixpath import re import subprocess 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): # type: () -> int shell = self.argument("shell") if not shell: shell = self.get_shell_type() if shell not in self.SUPPORTED_SHELLS: raise ValueError( "[shell] argument must be one of {}".format( ", ".join(self.SUPPORTED_SHELLS) ) ) self.line(self.render(shell)) return 0 def render(self, shell): # type: (str) -> str return getattr(self, "render_{}".format(shell))() def render_bash(self): # type: () -> str template = TEMPLATES["bash"] script_path = posixpath.realpath(self._args.script_name) script_name = os.path.basename(script_path) aliases = [script_name, script_path] aliases += self.option("alias") function = self._generate_function_name(script_name, script_path) commands = [] global_options = set() options_descriptions = {} commands_options = {} for option in self.application.config.options.values(): options_descriptions["--" + option.long_name] = self.io.remove_format( option.description ) global_options.add("--" + option.long_name) for command in self.application.commands: command_config = command.config if not command_config.is_enabled() or command_config.is_hidden(): continue command_options = [] commands.append(command_config.name) options = command_config.options for name in sorted(options.keys()): option = options[name] name = "--" + option.long_name description = option.description command_options.append(name) options_descriptions[name] = description commands_options[command_config.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 = posixpath.realpath(self._args.script_name) script_name = os.path.basename(script_path) aliases = [script_path] aliases += self.option("alias") function = self._generate_function_name(script_name, script_path) global_options = set() commands_descriptions = [] options_descriptions = {} commands_options_descriptions = {} commands_options = {} for option in self.application.config.options.values(): options_descriptions["--" + option.long_name] = self.io.remove_format( option.description ) global_options.add("--" + option.long_name) for command in self.application.commands: command_config = command.config if not command_config.is_enabled() or command_config.is_hidden(): continue command_options = [] commands_options_descriptions[command_config.name] = {} command_description = self._io.remove_format(command_config.description) commands_descriptions.append( self._zsh_describe(command_config.name, command_description) ) options = command_config.options for name in sorted(options.keys()): option = options[name] name = "--" + option.long_name description = self.io.remove_format(option.description) command_options.append(name) options_descriptions[name] = description commands_options_descriptions[command_config.name][name] = description commands_options[command_config.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 = posixpath.realpath(self._args.script_name) script_name = os.path.basename(script_path) aliases = [script_name] aliases += self.option("alias") function = self._generate_function_name(script_name, script_path) global_options = set() commands_descriptions = {} options_descriptions = {} commands_options_descriptions = {} commands_options = {} for option in self.application.config.options.values(): options_descriptions["--" + option.long_name] = self.io.remove_format( option.description ) global_options.add("--" + option.long_name) for command in self.application.commands: command_config = command.config if not command_config.is_enabled() or command_config.is_hidden(): continue command_options = [] commands_options_descriptions[command_config.name] = {} commands_descriptions[command_config.name] = self._io.remove_format( command_config.description ) options = command_config.options for name in sorted(options.keys()): option = options[name] name = "--" + option.long_name description = self._io.remove_format(option.description) command_options.append(name) options_descriptions[name] = description commands_options_descriptions[command_config.name][name] = description commands_options[command_config.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 _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 PK!H(&22cleo/config/__init__.pyfrom .application_config import ApplicationConfig PK!*4ff!cleo/config/application_config.pyfrom clikit.config import DefaultApplicationConfig from cleo.io import ConsoleIO class ApplicationConfig(DefaultApplicationConfig): """ Cleo's default application configuration. """ def configure(self): # type: () -> None super(ApplicationConfig, self).configure() @property def io_class(self): return ConsoleIO PK!? ProgressBar """ Create a new progress bar """ return ProgressBar(self, max) def ask(self, question, default=None): question = Question(question, default) return self.ask_question(question) def ask_hidden(self, question): question = Question(question) question.hide() 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. """ answer = question.ask(self) return answer def write(self, string, flags=0): super(IOMixin, self).write(string, flags) self._last_message = string def error(self, string, flags=0): super(IOMixin, self).error(string, flags) self._last_message = string def write_line(self, string, flags=0): super(IOMixin, self).write_line(string, flags) self._last_message = string def error_line(self, string, flags=0): super(IOMixin, self).error_line(string, flags) self._last_message = string def overwrite(self, message, size=None): self._do_overwrite(message, size) def overwrite_error(self, message, size=None): self._do_overwrite(message, size, True) def _do_overwrite(self, message, size=None, stderr=False): output = self.output if stderr: output = self.error_output # since overwrite is supposed to overwrite last message... if size is None: # removing possible formatting of lastMessage with strip_tags if stderr: last_message = self._last_message_err else: last_message = self._last_message size = len(output.remove_format(last_message)) # ...let's fill its length with backspaces output.write("\x08" * size) # write the new message output.write(message) fill = size - len(output.remove_format(message)) if fill > 0: # whitespace whatever has left output.write(" " * fill) # move the cursor back output.write("\x08" * fill) if stderr: self._last_message_err = message else: self._last_message = message PK!emcleo/parser.pyimport re import os from collections import namedtuple from clikit.api.args.format import Argument from clikit.api.args.format import Option _argument = namedtuple("argument", "name flags description default") _option = namedtuple("option", "long_name short_name flags description default") 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 ValueError("Console command signature is empty.") expression = expression.replace(os.linesep, "") matches = re.match(r"[^\s]+", expression) if not matches: raise ValueError("Unable to determine command name from signature.") name = matches.group(0) parsed["name"] = name tokens = re.findall(r"\{\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 = "" validator = None if " : " in token: token, description = tuple(token.split(" : ", 2)) token = token.strip() description = description.strip() # Checking validator: matches = re.match(r"(.*)\((.*?)\)", token) if matches: token = matches.group(1).strip() validator = matches.group(2).strip() if token.endswith("?*"): return _argument( token.rstrip("?*"), Argument.MULTI_VALUED, description, None ) elif token.endswith("*"): return _argument( token.rstrip("*"), Argument.MULTI_VALUED & Argument.REQUIRED, description, None, ) elif token.endswith("?"): return _argument(token.rstrip("?"), Argument.OPTIONAL, description, None) matches = re.match(r"(.+)=(.+)", token) if matches: return _argument( matches.group(1), Argument.OPTIONAL, description, matches.group(2) ) return _argument(token, Argument.REQUIRED, description, None) @classmethod def _parse_option(cls, token): """ Parse an option expression. :param token: The option expression :type token: str :rtype: InputOption """ description = "" validator = None if " : " in token: token, description = tuple(token.split(" : ", 2)) token = token.strip() description = description.strip() # Checking validator: matches = re.match(r"(.*)\((.*?)\)", token) if matches: token = matches.group(1).strip() validator = matches.group(2).strip() shortcut = None matches = re.split(r"\s*\|\s*", token, 2) if len(matches) > 1: shortcut = matches[0].lstrip("-") token = matches[1] else: token = token.lstrip("-") default = None mode = Option.NO_VALUE if token.endswith("=*"): mode = Option.MULTI_VALUED token = token.rstrip("=*") elif token.endswith("=?*"): mode = Option.OPTIONAL_VALUE & Option.MULTI_VALUED token = token.rstrip("=?*") elif token.endswith("=?"): mode = Option.OPTIONAL_VALUE token = token.rstrip("=?") elif token.endswith("="): mode = Option.REQUIRED_VALUE token = token.rstrip("=") matches = re.match(r"(.+)(=[?*]*)(.+)", token) if matches: token = matches.group(1) operator = matches.group(2) default = matches.group(3) if operator == "=*": mode = Option.REQUIRED_VALUE & Option.MULTI_VALUED elif operator == "=?*": mode = Option.OPTIONAL_VALUE & Option.MULTI_VALUED elif operator == "=?": mode = Option.OPTIONAL_VALUE elif operator == "=": mode = Option.REQUIRED_VALUE return _option(token, shortcut, mode, description, default) PK!Gpcleo/testers/__init__.py# -*- coding: utf-8 -*- from .application_tester import ApplicationTester from .command_tester import CommandTester __all__ = ["ApplicationTester", "CommandTester"] PK!I#<<"cleo/testers/application_tester.pyfrom clikit.args import StringArgs from cleo import Application from cleo.io import BufferedIO class ApplicationTester(object): """ Eases the testing of console applications. """ def __init__(self, application): # type: (Application) -> None self._application = application self._io = BufferedIO() self._status_code = 0 @property def io(self): # type: () -> BufferedIO return self._io @property def status_code(self): # type: () -> int return self._status_code def execute(self, args, **options): # type: (str, ...) -> int """ Executes the command Available options: * interactive: Sets the input interactive flag * verbosity: Sets the output verbosity flag """ args = StringArgs(args) if "inputs" in options: self._io.set_input(options["inputs"]) if "interactive" in options: self._io.set_interactive(options["interactive"]) if "verbosity" in options: self._io.set_verbosity(options["verbosity"]) self._status_code = self._application.run( args, self._io.input.stream, self._io.output.stream, self._io.error_output.stream, ) return self._status_code PK!:N*n44cleo/testers/command_tester.pyfrom clikit.api.command import Command from clikit.args import StringArgs from clikit.formatter import AnsiFormatter from cleo.commands import BaseCommand from cleo.io import BufferedIO class CommandTester(object): """ Eases the testing of console commands. """ def __init__(self, command): # type: (BaseCommand) -> None """ Constructor """ self._command = command self._io = BufferedIO() self._inputs = [] self._status_code = None if self._command.application: for style in self._command.application.config.style_set.styles.values(): self._io.output.formatter.add_style(style) self._io.error_output.formatter.add_style(style) @property def io(self): # type: () -> BufferedIO return self._io @property def status_code(self): # type: () -> int return self._status_code def execute(self, args="", **options): # type: (str, ...) -> int """ Executes the command Available options: * interactive: Sets the input interactive flag * decorated: Sets the output decorated flag * verbosity: Sets the output verbosity flag """ args = StringArgs(args) if "inputs" in options: self._io.set_input(options["inputs"]) if "interactive" in options: self._io.set_interactive(options["interactive"]) if "verbosity" in options: self._io.set_verbosity(options["verbosity"]) if "decorated" in options and options["decorated"]: self._io.set_formatter(AnsiFormatter()) command = Command(self._command.config, self._command.application) self._status_code = command.run(args, self._io) return self._status_code PK!>/N%%cleo-0.7.0.dist-info/LICENSECopyright (c) 2013 Sébastien Eustace Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.PK!HWYcleo-0.7.0.dist-info/WHEEL A н#Z;/"b&F]xzwC;dhfCSTֻ0*Ri.4œh6-]{H, JPK!H}>cleo-0.7.0.dist-info/METADATA[r?Oȩ"CNUeG+;km,i-oΞTAp Es7{̭3H/>7|I&g3NN wݓ?{3~*96g ѹh??9Ўώ 9B%nc7ɶaDҨ3R8;34]P\}z̓&x+P}y&o:4dMy: 2ɽ}H@sO&* Lagn[0_ȜiPH4o X#RXV|V/ W"u=˩~ d ETLțV-{_6uW 7ni^ih7L9 $@@|2t0hUҩ+I&T-KW͹nܛB3u@2_9/kµ3曞c][C0:wIybȝhaD  Ij ki[p=duٻ͕e:'<+[ VҮ XmbEyƍ\4NJx_M!)Et& , {V)ӓIdAJ=iK=f}2Ou??ԿT 6ߒ\H;)ay(-y_Lbh|+2U)+ၬ]~]dY.3_"@؛ %D?Y}3fSeI%4CA%R<]q5퓈WGwn}墩C %B󛟫/H"qu s%w 9Y/ʻ[l*Z+*s2ѪwqE!u#$ZFh7_6~!PEq^f RJ]uDoE6S|n1δ8ʩ xۗ%r~uѳӶ+N,sO0H6z*(9iƗXR8o|3uny 9Jmeiʨntʝ{iO+nee7{T"9'7u}Mvoʁ2?}W׃W/}O%h,37֬ma`WWeyEżueԌ)3o}7yy{& IsIB g{ި<\p6hxS*]6Ân1' 2]GF 4Y0s ЕeC1y9U-Cʑ)GXĵ |LR뎸|_gS6}eɳmq{Y='q%J 'a}W¥ *)"T-dyL; tSQ@] [ERڲeBaɦF1- #ZI,"bƐըpV.zI&WMfQpk5w\ҕyE$ m霟v'$1^-MvScLY;M"]rfފlVy[mKJ3{G2Gi=gb1mP{4ЀE2ܽ/}BɤkF?My&V1Ś .yWHs)0SH/[oIǀf2QyZJ7.͒צ|J`^S+{V Id?En:!9/>lێSn9:ӶLZLB}#lk>0pIE{5` xԄ˒d>. ?2iаU:s LwW0+?IYqaئ08-V8I~,;w\dY|ʅI#Sz"δOxqpf+OVa1?6Fۡ7HrtTH+Rv~URKy\t<&h(a'rfKjE[ ]VE)Os;gU4~i+*"JtWyYϤNo:*]?}Nl&YˊMO'#$!xSChw[ Rsj袷q *+Cj!;\W^@Ge)Wvmҹӟxl th :QL Bqxҵ1Or1e1UVg} g1k|XF0 褫M8Vy<]ߛXLd/2XKʷ؆t2N'r-*)&ĭg]w#C`('MBW@~,J ?rUl6G6h JGLf7\?5 FnJ=:@i\ Tƾ=놄VT 5~;Ie;XE7Ӧ?GpԜ2.X@0CAֈX: ;[LLwz92:bf+Ji\ʺ3ۅ5Bk)4f<`H92&8Wd!UsL42g`NـfQāv.Rjzpv-|vT\Xvj~˗~EiYBl 0dT,ڹvD5#u*r!mGar/4tݱɧ1%߸߇lDjƵG=UbU1d\g Ut_25mjLŬSqi<ܯ d ܉dEG3?D{6TMGwEO ѧj(c4˓? 7%p}z9F~99oj5_X\ouZY}$=n^2:K0ag_@ ~_x.oN/sfY|1Ŭ ԏğ߽c*Z}zѯ>PK!HLIu))cleo-0.7.0.dist-info/RECORD}ǒH< aX p!VylPhFዪ?3NaZ0KME?תZ~Ôf2O$uEȩɽk.ȏ?'>E0C+Z8Ć3aWz1I4}+q8D}_8A>X+S!(n[9q8O!5Uh"1ݺdzvɅ]f]X°/)w|Ȓ e22T#hJÏ ] 5?>etީS8M;YY69sCNP17$dO3iRo2Gᮐ %y11դNe4zf>mDf*E:r 09Gs:=pr2[iR/e[2CX!u#AM U@g@%EU2l7QI$V,=z tY`1{:.],K4 A5mOH7^H1gC5 Lnw$0:.a GstdThܚs(W5f"F_L(hhn0u#kT;Y6G8#q[D Z`MPg4h4pj-ԫ$D /N%%cleo-0.7.0.dist-info/LICENSEPK!HWYacleo-0.7.0.dist-info/WHEELPK!H}>cleo-0.7.0.dist-info/METADATAPK!HLIu))cleo-0.7.0.dist-info/RECORDPKn;