PK!\ clikit/__init__.pyfrom .api.config.application_config import ApplicationConfig from .console_application import ConsoleApplication from .config.default_application_config import DefaultApplicationConfig __version__ = "0.2.2" PK!clikit/adapter/__init__.pyPK!lˋ!clikit/adapter/style_converter.pyfrom pastel.style import Style as PastelStyle from clikit.api.formatter import Style class StyleConverter(object): """ Converts a TTY Style instance to a Pastel Style instance. """ @classmethod def convert(cls, style): # type: (Style) -> PastelStyle options = [] if style.is_bold(): options.append("bold") if style.is_underlined(): options.append("underscore") if style.is_blinking(): options.append("blink") if style.is_inverse(): options.append("reverse") if style.is_hidden(): options.append("conceal") return PastelStyle(style.foreground_color, style.background_color, options) PK!clikit/api/__init__.pyPK!j %%"clikit/api/application/__init__.pyfrom .application import Application PK!0r%clikit/api/application/application.pyfrom clikit.api.args.format.args_format import ArgsFormat from clikit.api.args.raw_args import RawArgs from clikit.api.command.command_collection import CommandCollection from clikit.api.config.application_config import ApplicationConfig from clikit.api.io import InputStream from clikit.api.io import OutputStream from clikit.api.resolver.resolved_command import ResolvedCommand class Application: """ A console application. """ @property def config(self): # type: () -> ApplicationConfig raise NotImplementedError() @property def global_args_format(self): # type: () -> ArgsFormat raise NotImplementedError() def get_command(self, name): # type: (str) -> Command raise NotImplementedError() @property def commands(self): # type: () -> CommandCollection raise NotImplementedError() def has_command(self, name): # type: (str) -> bool raise NotImplementedError() def has_commands(self): # type: () -> bool raise NotImplementedError() @property def named_commands(self): # type: () -> CommandCollection raise NotImplementedError() def has_named_commands(self): # type: () -> bool raise NotImplementedError() @property def default_commands(self): # type: () -> CommandCollection raise NotImplementedError() def has_default_commands(self): # type: () -> bool raise NotImplementedError() def resolve_command(self, args): # type: (RawArgs) -> ResolvedCommand raise NotImplementedError() def run( self, args=None, input_stream=None, output_stream=None, error_stream=None ): # type: (RawArgs, InputStream, OutputStream, OutputStream) -> int raise NotImplementedError() PK!=OYYclikit/api/args/__init__.pyfrom .args import Args from .args_parser import ArgsParser from .raw_args import RawArgs PK!3>lMclikit/api/args/args.pyfrom typing import Any from typing import Dict from typing import Optional from typing import Union from .format.args_format import ArgsFormat from .raw_args import RawArgs class Args(object): """ The parsed console arguments. """ def __init__(self, fmt, raw_args=None): # type: (ArgsFormat, RawArgs) -> None self._fmt = fmt self._raw_args = raw_args self._options = {} self._arguments = {} @property def format(self): # type: () -> ArgsFormat return self._fmt @property def raw_args(self): # type: () -> RawArgs return self._raw_args @property def script_name(self): # type: () -> Optional[str] if self._raw_args: return self._raw_args.script_name @property def command_names(self): return self._fmt.get_command_names() @property def command_options(self): return self._fmt.get_command_options() def option(self, name): option = self._fmt.get_option(name) if option.long_name in self._options: return self._options[option.long_name] if option.accepts_value(): return option.default return False def options(self, include_defaults=True): options = self._options.copy() if include_defaults: for option in self._fmt.get_options().values(): name = option.long_name if not name in options: default = False if option.accepts_value(): default = option.default options[name] = default return options def set_option(self, name, value=True): option = self._fmt.get_option(name) if option.is_multi_valued(): if not isinstance(value, list): value = [value] for i, v in enumerate(value): value[i] = option.parse(v) elif option.accepts_value(): value = option.parse(value) elif value is False: if option.long_name in self._options: del self._options[option.long_name] return self else: value = True self._options[option.long_name] = value return self def is_option_set(self, name): # type: (str) -> bool return name in self._options def is_option_defined(self, name): # type: (str) -> bool return self._fmt.has_option(name) def argument(self, name): # type: (Union[str, int]) -> Any argument = self._fmt.get_argument(name) if argument.name in self._arguments: return self._arguments[name] return argument.default def arguments(self, include_defaults=True): # type: (bool) -> Dict[str, Any] arguments = {} for argument in self._fmt.get_arguments().values(): name = argument.name if name in self._arguments: arguments[name] = self._arguments[name] elif include_defaults: arguments[name] = argument.default return arguments def set_argument(self, name, value): # type: (Union[str, int], Any) -> Args argument = self._fmt.get_argument(name) if argument.is_multi_valued(): if not isinstance(value, list): value = [value] for i, v in enumerate(value): value[i] = argument.parse(v) else: value = argument.parse(value) self._arguments[argument.name] = value return self def is_argument_set(self, name): # type: (Union[str, int]) -> bool return name in self._arguments def is_argument_defined(self, name): # type: (Union[str, int]) -> bool return self._fmt.has_argument(name) PK!.ɂ^^clikit/api/args/args_parser.pyfrom .args import Args from .format.args_format import ArgsFormat from .raw_args import RawArgs class ArgsParser(object): """ Parses raw console arguments and returns the parsed arguments. """ def parse( self, args, fmt, lenient=False ): # type: (RawArgs, ArgsFormat, bool) -> Args raise NotImplementedError() PK!hclikit/api/args/exceptions.pyclass CannotAddOptionException(RuntimeError): @classmethod def already_exists(cls, name): return cls( 'An option named "{}{}" exists already.'.format( "--" if len(name) > 1 else "-", name ) ) class NoSuchOptionException(RuntimeError): def __init__(self, name): message = 'The "{}{}" option does not exist.'.format( "--" if len(name) > 1 else "-", name ) super(NoSuchOptionException, self).__init__(message) class CannotAddArgumentException(RuntimeError): @classmethod def already_exists(cls, name): return cls('An argument named "{}" exists already.'.format(name)) @classmethod def cannot_add_after_multi_valued(cls): return cls("Cannot add an argument after a multi-valued argument.") @classmethod def cannot_add_required_after_optional(cls): return cls("Cannot add a required argument after an optional one.") class NoSuchArgumentException(RuntimeError): def __init__(self, name): if isinstance(name, int): message = "The argument at position {} does not exist.".format(name) else: message = 'The "{}" argument does not exist.'.format(name) super(NoSuchArgumentException, self).__init__(message) class CannotParseArgsException(RuntimeError): @classmethod def too_many_arguments(cls): return cls("Too many arguments.") @classmethod def option_does_not_accept_value(cls, name): if len(name) > 1: name = "--" + name else: name = "--" + name return cls('The "{}" option does not accept a value.'.format(name)) @classmethod def option_requires_value(cls, name): if len(name) > 1: name = "--" + name else: name = "--" + name return cls('The "{}" option requires a value.'.format(name)) PK!lQ"clikit/api/args/format/__init__.pyfrom .args_format import ArgsFormat from .args_format_builder import ArgsFormatBuilder from .argument import Argument from .command_name import CommandName from .command_option import CommandOption from .option import Option PK!1)clikit/api/args/format/abstract_option.pyimport re from typing import Optional from clikit.utils._compat import basestring class AbstractOption(object): """ Base class for command line options. """ PREFER_LONG_NAME = 1 PREFER_SHORT_NAME = 2 def __init__( self, long_name, short_name=None, flags=0, description=None ): # type: (str, Optional[str], int, Optional[str]) -> None long_name = self._remove_double_dash_prefix(long_name) short_name = self._remove_dash_prefix(short_name) self._validate_flags(flags) self._validate_long_name(long_name) self._validate_short_name(short_name, flags) self._long_name = long_name self._short_name = short_name self._description = description flags = self._add_default_flags(flags) self._flags = flags @property def long_name(self): # type: () -> str return self._long_name @property def short_name(self): # type: () -> Optional[str] return self._short_name @property def flags(self): # type: () -> int return self._flags @property def description(self): # type: () -> Optional[str] return self._description def is_long_name_preferred(self): # type: () -> bool return bool(self._flags & self.PREFER_LONG_NAME) def is_short_name_preferred(self): # type: () -> bool return bool(self._flags & self.PREFER_SHORT_NAME) def _remove_double_dash_prefix(self, string): # type: (str) -> str if not isinstance(string, basestring): return string if string.startswith("--"): string = string[2:] return string def _remove_dash_prefix(self, string): # type: (Optional[str]) -> Optional[str] if string is None: return string if not isinstance(string, basestring): return string if string.startswith("-"): string = string[1:] return string def _validate_flags(self, flags): # type: (int) -> None if flags & self.PREFER_SHORT_NAME and flags & self.PREFER_LONG_NAME: raise ValueError( "The option flags PREFER_SHORT_NAME and PREFER_LONG_NAME cannot be combined." ) def _validate_long_name(self, long_name): # type: (Optional[str]) -> None if long_name is None: raise ValueError("The long option name must not be null.") if not isinstance(long_name, basestring): raise ValueError( "The long option name must be a string. Got: {}".format(type(long_name)) ) if not long_name: raise ValueError("The long option name must not be empty.") if len(long_name) < 2: raise ValueError( 'The long option name must contain more than one character. Got: "{}"'.format( len(long_name) ) ) if not long_name[:1].isalpha(): raise ValueError("The long option name must start with a letter") if not re.match(r"^[a-zA-Z0-9\-]+$", long_name): raise ValueError( "The long option name must contain letters, digits and hyphens only." ) def _validate_short_name( self, short_name, flags ): # type: (Optional[str], int) -> None if short_name is None: if flags & self.PREFER_SHORT_NAME: raise ValueError( "The short option name must be given if the option flag PREFER_SHORT_NAME is selected." ) return if not isinstance(short_name, basestring): raise ValueError( "The short option name must be a string. Got: {}".format( type(short_name) ) ) if not short_name: raise ValueError("The short option name must not be empty.") if not re.match(r"^[a-zA-Z]$", short_name): raise ValueError("The short option name must be exactly one letter.") def _add_default_flags(self, flags): # type: (int) -> int if not flags & (self.PREFER_LONG_NAME | self.PREFER_SHORT_NAME): flags |= ( self.PREFER_SHORT_NAME if self._short_name else self.PREFER_LONG_NAME ) return flags PK!v` %clikit/api/args/format/args_format.pyfrom typing import Any from typing import Dict from typing import List from typing import Optional from typing import Union from ..exceptions import NoSuchArgumentException from ..exceptions import NoSuchOptionException from .argument import Argument from .option import Option from .command_name import CommandName from .command_option import CommandOption class ArgsFormat(object): """ The format used to parse a RawArgs instance. """ def __init__( self, elements=None, base_format=None ): # type: (Optional[Union[List[Any], ArgsFormatBuilder]], Optional[ArgsFormat]) from .args_format_builder import ArgsFormatBuilder if elements is None: elements = [] if isinstance(elements, ArgsFormatBuilder): builder = elements else: builder = self._create_builder_for_elements(elements) if base_format is None: base_format = builder.base_format self._base_format = base_format self._command_names = builder.get_command_names(False) self._command_options = {} self._command_options_by_short_name = {} self._arguments = builder.get_arguments(False) self._options = builder.get_options(False) self._options_by_short_name = {} self._has_multi_valued_arg = builder.has_multi_valued_argument(False) self._hash_optional_arg = builder.has_optional_argument(False) for option in self._options.values(): if option.short_name: self._options_by_short_name[option.short_name] = option for command_option in builder.get_command_options(): self._command_options[command_option.long_name] = command_option if command_option.short_name: self._command_options_by_short_name[ command_option.short_name ] = command_option for long_alias in command_option.long_aliases: self._command_options[long_alias] = command_option for short_alias in command_option.short_aliases: self._command_options_by_short_name[short_alias] = command_option @property def base_format(self): # type: () -> ArgsFormat return self._base_format def has_command_names(self, include_base=True): # type: (bool) -> bool if self._command_names: return True if include_base and self._base_format: return self._base_format.has_command_names() return False def get_command_names(self, include_base=True): # type: (bool) -> List[CommandName] command_names = self._command_names if include_base and self._base_format: command_names = self._base_format.get_command_names() + command_names return command_names def has_command_option(self, name, include_base=True): # type: (str, bool) -> bool if name in self._command_options or name in self._command_options_by_short_name: return True if include_base and self._base_format: return self._base_format.has_command_option(name) return False def has_command_options(self, include_base=True): # type: (bool) -> bool if self._command_options: return True if include_base and self._base_format: return self._base_format.has_command_options() return False def get_command_option( self, name, include_base=True ): # type: (str, bool) -> CommandOption if name in self._command_options: return self._command_options[name] if name in self._command_options_by_short_name: return self._command_options_by_short_name[name] if include_base and self._base_format: return self._base_format.get_command_option(name) raise NoSuchOptionException(name) def get_command_options( self, include_base=True ): # type: (bool) -> List[CommandOption] command_options = list(self._command_options.values()) if include_base and self._base_format: command_options += self._base_format.get_command_options() return command_options def has_argument( self, name, include_base=True ): # type: (Union[str, int], bool) -> bool arguments = self.get_arguments(include_base) if isinstance(name, int): return name < len(arguments) return name in arguments def has_multi_valued_argument(self, include_base=True): # type: (bool) -> bool if self._has_multi_valued_arg: return True if include_base and self._base_format: return self._base_format.has_multi_valued_argument() return False def has_optional_argument(self, include_base=True): # type: (bool) -> bool if self._hash_optional_arg: return True if include_base and self._base_format: return self._base_format.has_optional_argument() return False def has_required_argument(self, include_base=True): # type: (bool) -> bool if not self._hash_optional_arg and self._arguments: return True if include_base and self._base_format: return self._base_format.has_required_argument() return False def has_arguments(self, include_base=True): # type: (bool) -> bool if self._arguments: return True if include_base and self._base_format: return self._base_format.has_arguments() return False def get_argument( self, name, include_base=True ): # type: (Union[str, int], bool) -> Argument if isinstance(name, int): arguments = list(self.get_arguments(include_base).values()) if name >= len(arguments): raise NoSuchArgumentException(name) else: arguments = self.get_arguments(include_base) if name not in arguments: raise NoSuchArgumentException(name) return arguments[name] def get_arguments(self, include_base=True): # type: (bool) -> Dict[str, Argument] arguments = self._arguments.copy() if include_base and self._base_format: base_arguments = self._base_format.get_arguments() base_arguments.update(arguments) arguments = base_arguments return arguments def has_option(self, name, include_base=True): # type: (str, bool) -> bool if name in self._options or name in self._options_by_short_name: return True if include_base and self._base_format: return self._base_format.has_option(name) return False def has_options(self, include_base=True): # type: (bool) -> bool if self._options: return True if include_base and self._base_format: return self._base_format.has_options() return False def get_option(self, name, include_base=True): # type: (str, bool) -> Option if name in self._options: return self._options[name] if name in self._options_by_short_name: return self._options_by_short_name[name] if include_base and self._base_format: return self._base_format.get_option(name) raise NoSuchOptionException(name) def get_options(self, include_base=True): # type: (bool) -> Dict[str, Option] options = self._options.copy() if include_base and self._base_format: options.update(self._base_format.get_options()) return options def _create_builder_for_elements( self, elements, base_format=None ): # type: (List[Any], Optional[ArgsFormat]) -> ArgsFormatBuilder from .args_format_builder import ArgsFormatBuilder builder = ArgsFormatBuilder(base_format) for element in elements: if isinstance(element, CommandName): builder.add_command_name(element) elif isinstance(element, CommandOption): builder.add_command_option(element) elif isinstance(element, Option): builder.add_option(element) elif isinstance(element, Argument): builder.add_argument(element) return builder PK!r#,,,,-clikit/api/args/format/args_format_builder.pyfrom typing import Dict from typing import Iterable from typing import List from typing import Optional from typing import Tuple from typing import Union from clikit.utils._compat import OrderedDict from ..exceptions import CannotAddArgumentException from ..exceptions import CannotAddOptionException from ..exceptions import NoSuchArgumentException from ..exceptions import NoSuchOptionException from .args_format import ArgsFormat from .argument import Argument from .option import Option from .command_name import CommandName from .command_option import CommandOption class ArgsFormatBuilder(object): """ A builder for ArgsFormat instances. """ def __init__(self, base_format=None): # type: (Optional[ArgsFormat]) -> None self._base_format = base_format self._command_names = [] self._command_options = OrderedDict() self._command_options_by_short_name = OrderedDict() self._arguments = OrderedDict() self._options = OrderedDict() self._options_by_short_name = OrderedDict() self._has_multi_valued_arg = False self._hash_optional_arg = False @property def base_format(self): # type: () -> Optional[ArgsFormat] return self._base_format def set_command_names( self, *command_names ): # type: (Tuple[CommandName]) -> ArgsFormatBuilder self._command_names = [] self.add_command_names(*command_names) return self def add_command_names( self, *command_names ): # type: (Tuple[CommandName]) -> ArgsFormatBuilder for command_name in command_names: self.add_command_name(command_name) return self def add_command_name( self, command_name ): # type: (CommandName) -> ArgsFormatBuilder self._command_names.append(command_name) return self def has_command_names(self, include_base=True): # type: (bool) -> bool if self._command_names: return True if include_base and self._base_format: return self._base_format.has_command_names() return False def get_command_names(self, include_base=True): # type: (bool) -> List[CommandName] command_names = self._command_names if include_base and self._base_format: command_names = self._base_format.get_command_names() + command_names return command_names def set_command_options( self, *command_options ): # type: (Tuple[CommandOption]) -> ArgsFormatBuilder self._command_options = {} self._command_options_by_short_name = {} self.add_command_options(*command_options) def add_command_options( self, *command_options ): # type: (Tuple[CommandOption]) -> ArgsFormatBuilder for command_option in command_options: self.add_command_option(command_option) def add_command_option( self, command_option ): # type: (CommandOption) -> ArgsFormatBuilder long_name = command_option.long_name short_name = command_option.short_name long_aliases = command_option.long_aliases short_aliases = command_option.short_aliases if self.has_option(long_name) or self.has_command_option(long_name): raise CannotAddOptionException.already_exists(long_name) for long_alias in long_aliases: if self.has_option(long_alias) or self.has_command_option(long_alias): raise CannotAddOptionException.already_exists(long_alias) if self.has_option(short_name) or self.has_command_option(short_name): raise CannotAddOptionException.already_exists(short_name) for short_alias in short_aliases: if self.has_option(short_alias) or self.has_command_option(short_alias): raise CannotAddOptionException.already_exists(short_alias) self._command_options[long_name] = command_option if short_name: self._command_options_by_short_name[short_name] = command_option for long_alias in long_aliases: self._command_options[long_alias] = command_option for short_alias in short_aliases: self._command_options_by_short_name[short_alias] = command_option return self def has_command_option(self, name, include_base=True): # type: (str, bool) -> bool if name in self._command_options or name in self._command_options_by_short_name: return True if include_base and self._base_format: return self._base_format.has_command_option(name) return False def has_command_options(self, include_base=True): # type: (bool) -> bool if self._command_options: return True if include_base and self._base_format: return self._base_format.has_command_options() return False def get_command_option( self, name, include_base=True ): # type: (str, bool) -> CommandOption if name in self._command_options: return self._command_options[name] if name in self._command_options_by_short_name: return self._command_options_by_short_name[name] if include_base and self._base_format: return self._base_format.get_command_option(name) raise NoSuchOptionException(name) def get_command_options( self, include_base=True ): # type: (bool) -> Iterable[CommandOption] command_options = list(self._command_options.values()) if include_base and self._base_format: command_options += self._base_format.get_command_options() return command_options def set_arguments( self, *arguments ): # type: (Iterable[Argument]) -> ArgsFormatBuilder self._arguments = {} self._has_multi_valued_arg = False self._hash_optional_arg = False self.add_arguments(*arguments) return self def add_arguments( self, *arguments ): # type: (Iterable[Argument]) -> ArgsFormatBuilder for argument in arguments: self.add_argument(argument) return self def add_argument(self, argument): # type: (Argument) -> ArgsFormatBuilder name = argument.name if self.has_argument(name): raise CannotAddArgumentException.already_exists(name) if self.has_multi_valued_argument(): raise CannotAddArgumentException.cannot_add_after_multi_valued() if argument.is_required() and self.has_optional_argument(): raise CannotAddArgumentException.cannot_add_required_after_optional() if argument.is_multi_valued(): self._has_multi_valued_arg = True if argument.is_optional(): self._hash_optional_arg = True self._arguments[name] = argument return self def has_argument( self, name, include_base=True ): # type: (Union[str, int], bool) -> bool arguments = self.get_arguments(include_base) if isinstance(name, int): return name < len(arguments) return name in arguments def has_multi_valued_argument(self, include_base=True): # type: (bool) -> bool if self._has_multi_valued_arg: return True if include_base and self._base_format: return self._base_format.has_multi_valued_argument() return False def has_optional_argument(self, include_base=True): # type: (bool) -> bool if self._hash_optional_arg: return True if include_base and self._base_format: return self._base_format.has_optional_argument() return False def has_required_argument(self, include_base=True): # type: (bool) -> bool if not self._hash_optional_arg and self._arguments: return True if include_base and self._base_format: return self._base_format.has_required_argument() return False def has_arguments(self, include_base=True): # type: (bool) -> bool if self._arguments: return True if include_base and self._base_format: return self._base_format.has_arguments() return False def get_argument( self, name, include_base=True ): # type: (Union[str, int], bool) -> Argument if isinstance(name, int): arguments = list(self.get_arguments(include_base).values()) if name >= len(arguments): raise NoSuchArgumentException(name) else: arguments = self.get_arguments(include_base) if name not in arguments: raise NoSuchArgumentException(name) return arguments[name] def get_arguments(self, include_base=True): # type: (bool) -> Dict[str, Argument] arguments = self._arguments.copy() if include_base and self._base_format: arguments.update(self._base_format.get_arguments()) return arguments def set_options(self, *options): # type: (Iterable[Option]) -> ArgsFormatBuilder self._options = {} self._options_by_short_name = {} self.add_options(*options) def add_options(self, *options): # type: (Iterable[Option]) -> ArgsFormatBuilder for option in options: self.add_option(option) def add_option(self, option): # type: (Option) -> ArgsFormatBuilder long_name = option.long_name short_name = option.short_name if self.has_option(long_name) or self.has_command_option(long_name): raise CannotAddOptionException.already_exists(long_name) if self.has_option(short_name) or self.has_command_option(short_name): raise CannotAddOptionException.already_exists(short_name) self._options[long_name] = option if short_name: self._options_by_short_name[short_name] = option return self def has_option(self, name, include_base=True): # type: (str, bool) -> bool if name in self._options or name in self._options_by_short_name: return True if include_base and self._base_format: return self._base_format.has_option(name) return False def has_options(self, include_base=True): # type: (bool) -> bool if self._options: return True if include_base and self._base_format: return self._base_format.has_options() return False def get_option(self, name, include_base=True): # type: (str, bool) -> Option if name in self._options: return self._options[name] if name in self._options_by_short_name: return self._options_by_short_name[name] if include_base and self._base_format: return self._base_format.get_option(name) raise NoSuchOptionException(name) def get_options(self, include_base=True): # type: (bool) -> Dict[str, Option] options = self._options.copy() if include_base and self._base_format: options.update(self._base_format.get_options()) return options @property def format(self): # type: () -> ArgsFormat return ArgsFormat(self, self._base_format) PK!P None if not isinstance(name, basestring): raise ValueError( "The argument name must be a string. Got: {}".format(type(name)) ) if not name: raise ValueError("The argument name must not be empty.") if not name[:1].isalpha(): raise ValueError("The argument name must start with a letter") if not re.match(r"^[a-zA-Z0-9\-]+$", name): raise ValueError( "The argument name must contain letters, digits and hyphens only." ) if description is not None: if not isinstance(description, basestring): raise ValueError( "The argument description must be a string. Got: {}".format( type(description) ) ) if not description: raise ValueError("The argument description must not be empty.") self._validate_flags(flags) flags = self._add_default_flags(flags) self._name = name self._flags = flags self._description = description if self.is_multi_valued(): self._default = [] else: self._default = None if self.is_optional() or default is not None: self.set_default(default) @property def name(self): # type: () -> str return self._name @property def flags(self): # type: () -> int return self._flags @property def description(self): # type: () -> Optional[str] return self._description @property def default(self): # type: () -> Any return self._default def is_required(self): # type: () -> bool return bool(self.REQUIRED & self._flags) def is_optional(self): # type: () -> bool return bool(self.OPTIONAL & self._flags) def is_multi_valued(self): # type: () -> bool return bool(self.MULTI_VALUED & self._flags) def set_default(self, default=None): # type: (Any) -> None if self.is_required(): raise ValueError("Required arguments do not accept default values.") if self.is_multi_valued(): if default is None: default = [] elif not isinstance(default, list): raise ValueError( "The default value of a multi-valued argument must be a list. " "Got: {}".format(type(default)) ) self._default = default def parse(self, value): # type: (Any) -> Any nullable = bool(self._flags & self.NULLABLE) if self._flags & self.BOOLEAN: return parse_boolean(value, nullable) elif self._flags & self.INTEGER: return parse_int(value, nullable) elif self._flags & self.FLOAT: return parse_float(value, nullable) return parse_string(value, nullable) def _validate_flags(self, flags): # type: (int) -> None if flags & self.REQUIRED and flags & self.OPTIONAL: raise ValueError( "The argument flags REQUIRED and OPTIONAL cannot be combined." ) if flags & self.STRING: if flags & self.BOOLEAN: raise ValueError( "The argument flags STRING and BOOLEAN cannot be combined." ) if flags & self.INTEGER: raise ValueError( "The argument flags STRING and INTEGER cannot be combined." ) if flags & self.FLOAT: raise ValueError( "The argument flags STRING and FLOAT cannot be combined." ) elif flags & self.BOOLEAN: if flags & self.INTEGER: raise ValueError( "The argument flags BOOLEAN and INTEGER cannot be combined." ) if flags & self.FLOAT: raise ValueError( "The argument flags BOOLEAN and FLOAT cannot be combined." ) elif flags & self.INTEGER: if flags & self.FLOAT: raise ValueError( "The argument flags INTEGER and FLOAT cannot be combined." ) def _add_default_flags(self, flags): # type: (int) -> int if not flags & (self.REQUIRED | self.OPTIONAL): flags |= self.OPTIONAL if not flags & (self.STRING | self.BOOLEAN | self.INTEGER | self.FLOAT): flags |= self.STRING return flags PK!Gz+<<&clikit/api/args/format/command_name.pyfrom typing import List from typing import Optional class CommandName(object): """ A command name in the console arguments. """ def __init__( self, string, aliases=None ): # type: (str, Optional[List[str]]) -> None if aliases is None: aliases = [] self._string = string self._aliases = aliases @property def string(self): # type: () -> str return self._string @property def aliases(self): # type: () -> List[str] return self._aliases def match(self, string): # type: (str) -> bool return self._string == string or string in self._aliases def __str__(self): # type: () -> str return self._string def __repr__(self): # type: () -> str return 'CommandName("{}")'.format(self._string) PK!o(clikit/api/args/format/command_option.pyimport re from typing import List from typing import Optional from .abstract_option import AbstractOption class CommandOption(AbstractOption): """ A command option in the console arguments. """ def __init__( self, long_name, short_name=None, aliases=None, flags=0, description=None ): # type: (str, Optional[str], Optional[List[str]], int, Optional[str]) -> None super(CommandOption, self).__init__(long_name, short_name, flags, description) if aliases is None: aliases = [] self._long_aliases = [] self._short_aliases = [] for alias in aliases: alias = self._remove_dash_prefix(alias) if len(alias) == 1: self._validate_short_alias(alias) self._short_aliases.append(alias) else: self._validate_long_alias(alias) self._long_aliases.append(alias) @property def long_aliases(self): # type: () -> List[str] return self._long_aliases @property def short_aliases(self): # type: () -> List[str] return self._short_aliases def _validate_long_alias(self, alias): # type: (str) -> None if not alias[:1].isalpha(): raise ValueError("A long option alias must start with a letter.") if not re.match("^[a-zA-Z0-9\-]+$", alias): raise ValueError( "A long option alias must contain letters, digits and hyphens only." ) def _validate_short_alias(self, alias): # type: (str) -> None if not re.match("^[a-zA-Z]$", alias): raise ValueError( 'A short option alias must be exactly one letter. Got: "{}"'.format( alias ) ) PK!^v clikit/api/args/format/option.pyfrom typing import Any from typing import Optional from clikit.utils.string import parse_boolean from clikit.utils.string import parse_float from clikit.utils.string import parse_int from clikit.utils.string import parse_string from .abstract_option import AbstractOption class Option(AbstractOption): """ An input option """ NO_VALUE = 4 REQUIRED_VALUE = 8 OPTIONAL_VALUE = 16 MULTI_VALUED = 32 STRING = 128 BOOLEAN = 256 INTEGER = 512 FLOAT = 1024 NULLABLE = 2048 def __init__( self, long_name, short_name=None, flags=0, description=None, default=None, value_name="...", ): # type: (str, Optional[str], int, Optional[str], Any, str) -> None self._validate_flags(flags) super(Option, self).__init__(long_name, short_name, flags, description) self._value_name = value_name if self.is_multi_valued(): self._default = [] else: self._default = None if self.accepts_value() or default is not None: self.set_default(default) @property def default(self): # type: () -> Any return self._default @property def value_name(self): # type: () -> str return self._value_name def accepts_value(self): # type: () -> bool return not bool(self.NO_VALUE & self._flags) def parse(self, value): # type: (Any) -> Any nullable = bool(self._flags & self.NULLABLE) if self._flags & self.BOOLEAN: return parse_boolean(value, nullable) elif self._flags & self.INTEGER: return parse_int(value, nullable) elif self._flags & self.FLOAT: return parse_float(value, nullable) return parse_string(value, nullable) def is_value_required(self): # type: () -> bool return bool(self.REQUIRED_VALUE & self._flags) def is_value_optional(self): # type: () -> bool return bool(self.OPTIONAL_VALUE & self._flags) def is_multi_valued(self): # type: () -> bool return bool(self.MULTI_VALUED & self._flags) def set_default(self, default=None): # type: (Any) -> None if not self.accepts_value(): raise ValueError( "Cannot set a default value when using the flag VALUE_NONE." ) if self.is_multi_valued(): if default is None: default = [] elif not isinstance(default, list): raise ValueError( "The default value of a multi-valued option must be a list. " "Got: {}".format(type(default)) ) self._default = default def _validate_flags(self, flags): # type: (int) -> None super(Option, self)._validate_flags(flags) if flags & self.NO_VALUE: if flags & self.REQUIRED_VALUE: raise ValueError( "The option flags VALUE_NONE and VALUE_REQUIRED cannot be combined." ) if flags & self.OPTIONAL_VALUE: raise ValueError( "The option flags VALUE_NONE and VALUE_OPTIONAL cannot be combined." ) if flags & self.MULTI_VALUED: raise ValueError( "The option flags VALUE_NONE and MULTI_VALUED cannot be combined." ) if flags & self.OPTIONAL_VALUE and flags & self.MULTI_VALUED: raise ValueError( "The option flags VALUE_OPTIONAL and MULTI_VALUED cannot be combined." ) if flags & self.STRING: if flags & self.BOOLEAN: raise ValueError( "The option flags STRING and BOOLEAN cannot be combined." ) if flags & self.INTEGER: raise ValueError( "The option flags STRING and INTEGER cannot be combined." ) if flags & self.FLOAT: raise ValueError( "The option flags STRING and FLOAT cannot be combined." ) elif flags & self.BOOLEAN: if flags & self.INTEGER: raise ValueError( "The option flags BOOLEAN and INTEGER cannot be combined." ) if flags & self.FLOAT: raise ValueError( "The option flags BOOLEAN and FLOAT cannot be combined." ) elif flags & self.INTEGER: if flags & self.FLOAT: raise ValueError( "The option flags INTEGER and FLOAT cannot be combined." ) def _add_default_flags(self, flags): # type: (int) -> int flags = super(Option, self)._add_default_flags(flags) if not flags & ( self.NO_VALUE | self.REQUIRED_VALUE | self.OPTIONAL_VALUE | self.MULTI_VALUED ): flags |= self.NO_VALUE if not flags & (self.STRING | self.BOOLEAN | self.INTEGER | self.FLOAT): flags |= self.STRING if flags & self.MULTI_VALUED and not flags & self.REQUIRED_VALUE: flags |= self.REQUIRED_VALUE return flags PK![clikit/api/args/raw_args.pyfrom typing import List from typing import Optional class RawArgs(object): """ The unparsed console arguments. """ @property def script_name(self): # type: () -> Optional[str] raise NotImplementedError() @property def tokens(self): # type: () -> List[str] raise NotImplementedError() def has_token(self, token): # type: (str) -> bool raise NotImplementedError() def to_string(self, script_name=True): # type: (bool) -> str raise NotImplementedError() PK!#\wOOclikit/api/command/__init__.pyfrom .command import Command from .command_collection import CommandCollection PK!Eclikit/api/command/command.pyfrom typing import List from typing import Optional from clikit.api.args.args import Args from clikit.api.args.raw_args import RawArgs from clikit.api.args.format.args_format import ArgsFormat from clikit.api.config.command_config import CommandConfig from clikit.api.event import ConsoleEvents from clikit.api.event import PreHandleEvent from clikit.api.io import IO class Command(object): """ A console command. """ def __init__( self, config, application=None, parent_command=None ): # type: (CommandConfig, Optional[Application], Optional[Command]) -> None from .command_collection import CommandCollection if not config.name: raise RuntimeError("The name of the command config must be set.") self._name = config.name self._short_name = None self._aliases = config.aliases self._config = config self._application = application self._parent_command = parent_command self._sub_commands = CommandCollection() self._named_sub_commands = CommandCollection() self._default_sub_commands = CommandCollection() self._args_format = config.build_args_format(self.base_format) self._dispatcher = application.config.dispatcher if application else None for sub_config in config.sub_command_configs: self.add_sub_command(sub_config) @property def name(self): # type: () -> str return self._name @property def short_name(self): # type: () -> str return self._short_name @property def aliases(self): # type: () -> List[str] return self._aliases def has_aliases(self): # type: () -> bool return len(self._aliases) > 0 @property def config(self): # type: () -> CommandConfig return self._config @property def args_format(self): # type: () -> ArgsFormat return self._args_format @property def application(self): # type: () -> Application return self._application @property def parent_command(self): # type: () -> Command return self._parent_command @property def sub_commands(self): # type: () -> CommandCollection return self._sub_commands def get_sub_command(self, name): # type: (str) -> Command return self._sub_commands.get(name) def has_sub_commands(self): # type: () -> bool return len(self._sub_commands) > 0 @property def named_sub_commands(self): # type: () -> CommandCollection return self._named_sub_commands def get_named_sub_command(self, name): # type: (str) -> Command return self._named_sub_commands.get(name) def has_named_sub_commands(self): # type: () -> bool return len(self._named_sub_commands) > 0 @property def default_sub_commands(self): # type: () -> CommandCollection return self._default_sub_commands def get_default_sub_command(self, name): # type: (str) -> Command return self._default_sub_commands.get(name) def has_default_sub_commands(self): # type: () -> bool return len(self._default_sub_commands) > 0 def parse(self, args, lenient=None): # type: (RawArgs, Optional[bool]) -> Args if lenient is None: lenient = self._config.is_lenient_args_parsing_enabled() return self._config.args_parser.parse(args, self._args_format, lenient) def run(self, args, io): # type: (RawArgs, IO) -> int return self.handle(self.parse(args), io) def handle(self, args, io): # type: (Args, IO) -> int status_code = self._do_handle(args, io) # Any empty value is considered a success if not status_code: return 0 # Anything else is normalized to a valid error status code return min(max(int(status_code), 1), 255) @property def base_format(self): # type: () -> Optional[ArgsFormat] if self._parent_command: return self._parent_command.args_format if self._application: return self._application.global_args_format return def add_sub_command(self, config): # type: (CommandConfig) -> None if not config.is_enabled(): return command = self.__class__(config, self._application, self) # TODO: Validate command self._sub_commands.add(command) if config.is_default(): self._default_sub_commands.add(command) if not config.is_anonymous(): self._named_sub_commands.add(command) def _do_handle(self, args, io): # type: (Args, IO) -> Optional[int] if self._dispatcher and self._dispatcher.has_listeners( ConsoleEvents.PRE_HANDLE.value ): event = PreHandleEvent(args, io, self) self._dispatcher.dispatch(ConsoleEvents.PRE_HANDLE.value, event) if event.is_handled(): return event.status_code handler = self._config.handler handler_method = self._config.handler_method return getattr(handler, handler_method)(args, io, self) PK!jx(clikit/api/command/command_collection.pyfrom typing import List from clikit.utils._compat import OrderedDict from .command import Command from .exceptions import NoSuchCommandException class CommandCollection(object): """ A collection of named commands. """ def __init__(self, commands=None): # type: (List[Command]) -> None if commands is None: commands = [] self._commands = OrderedDict() self._short_name_index = OrderedDict() self._alias_index = OrderedDict() for command in commands: self.add(command) def add(self, command): # type: (Command) -> CommandCollection name = command.name self._commands[name] = command short_name = command.short_name if short_name: self._short_name_index[short_name] = name for alias in command.aliases: self._alias_index[alias] = name return self def get(self, name): # type: (str) -> Command if name in self._commands: return self._commands[name] if name in self._short_name_index: return self._commands[self._short_name_index[name]] if name in self._alias_index: return self._commands[self._alias_index[name]] raise NoSuchCommandException(name) def is_empty(self): # type: () -> bool return not self._commands def get_names(self, include_aliases=False): # type: (bool) -> List[str] names = list(self._commands.keys()) if include_aliases: names += list(self._alias_index.keys()) names.sort() return names def __contains__(self, name): return ( name in self._commands or name in self._short_name_index or name in self._alias_index ) def __iter__(self): return iter(self._commands.values()) def __len__(self): return len(self._commands) PK!Waff clikit/api/command/exceptions.pyclass NoSuchCommandException(RuntimeError): def __init__(self, name): # type: (str) -> None message = 'The command "{}" does not exist.'.format(name) super(NoSuchCommandException, self).__init__(message) class CannotAddCommandException(RuntimeError): @classmethod def name_exists(cls, name): return cls('A command named "{}" already exists.'.format(name)) @classmethod def option_exists(cls, name): return cls('A option named "{}" already exists.'.format(name)) @classmethod def name_empty(cls): return cls("The command name must be set.") PK!7 \\clikit/api/config/__init__.pyfrom .application_config import ApplicationConfig from .command_config import CommandConfig PK!!'clikit/api/config/application_config.pyimport re from contextlib import contextmanager from typing import Callable from typing import List from typing import Optional from clikit.api.event import EventDispatcher from clikit.api.formatter import Style from clikit.api.formatter import StyleSet from clikit.api.command.exceptions import NoSuchCommandException from .command_config import CommandConfig from .config import Config class ApplicationConfig(Config): """ The configuration of a console application. """ def __init__( self, name=None, version=None ): # type: (Optional[str], Optional[str]) -> None self._name = name self._version = version self._display_name = None self._help = None self._command_configs = [] # type: List[CommandConfig] self._catch_exceptions = True self._terminate_after_run = True self._command_resolver = None self._io_factory = None self._debug = False self._style_set = None self._dispatcher = None self._pre_resolve_hooks = [] # type: List[Callable] super(ApplicationConfig, self).__init__() @property def name(self): # type: () -> Optional[str] return self._name def set_name(self, name): # type: (Optional[str]) -> ApplicationConfig self._name = name return self @property def display_name(self): # type: () -> str """ Returns the application name as it is displayed in the help. """ if self._display_name is not None: return self._display_name return self.default_display_name def set_display_name( self, display_name ): # type: (Optional[str]) -> ApplicationConfig self._display_name = display_name return self @property def version(self): # type: () -> Optional[str] return self._version def set_version(self, version): # type: (Optional[str]) -> ApplicationConfig self._version = version return self @property def help(self): # type: () -> Optional[str] return self._help def set_help(self, help): # type: (Optional[str]) -> ApplicationConfig self._help = help return self @property def dispatcher(self): # type: () -> EventDispatcher return self._dispatcher def set_event_dispatcher( self, dispatcher ): # type: (EventDispatcher) -> ApplicationConfig self._dispatcher = dispatcher return self def add_event_listener( self, event_name, listener, priority=0 ): # type: (str, Callable, int) -> ApplicationConfig if self._dispatcher is None: self._dispatcher = EventDispatcher() self._dispatcher.add_listener(event_name, listener, priority) return self def is_exception_caught(self): # type: () -> bool return self._catch_exceptions def set_catch_exceptions( self, catch_exceptions ): # type: (bool) -> ApplicationConfig self._catch_exceptions = catch_exceptions return self def is_terminated_after_run(self): # type: () -> bool return self._terminate_after_run def set_terminate_after_run( self, terminate_after_run ): # type: (bool) -> ApplicationConfig self._terminate_after_run = terminate_after_run return self @property def command_resolver(self): # type: () -> CommandResolver if self._command_resolver is None: self._command_resolver = self.default_command_resolver return self._command_resolver def set_command_resolver( self, command_resolver ): # type: (CommandResolver) -> ApplicationConfig self._command_resolver = command_resolver return self @property def io_factory(self): # type: () -> Optional[Callable] return self._io_factory def set_io_factory( self, io_factory ): # type: (Optional[Callable]) -> ApplicationConfig self._io_factory = io_factory return self def is_debug(self): # type: () -> bool return self._debug def debug(self, debug=True): # type: (bool) -> ApplicationConfig self._debug = debug return self @property def style_set(self): # type: () -> StyleSet if self._style_set is None: self._style_set = self.default_style_set return self._style_set def set_style_set(self, style_set): # type: (StyleSet) -> ApplicationConfig self._style_set = style_set return self def add_style(self, style): # type: (Style) -> ApplicationConfig self.style_set.add(style) return self def add_styles(self, styles): # type: (List[Style]) -> ApplicationConfig for style in styles: self.add_style(style) return self def remove_style(self, tag): # type: (str) -> ApplicationConfig self.style_set.remove(tag) @contextmanager def command(self, name): # type: (str) -> CommandConfig command_config = CommandConfig(name) self.add_command_config(command_config) yield command_config def create_command(self, name): # type: (str) -> CommandConfig command_config = CommandConfig(name) self.add_command_config(command_config) return command_config @contextmanager def edit_command(self, name): # type: (str) -> CommandConfig command_config = self.get_command_config(name) yield command_config def add_command_config( self, command_config ): # type: (CommandConfig) -> ApplicationConfig self._command_configs.append(command_config) return self def add_command_configs( self, command_configs ): # type: (List[CommandConfig]) -> ApplicationConfig for command_config in command_configs: self.add_command_config(command_config) return self def get_command_config(self, name): # type: (str) -> CommandConfig for command_config in self._command_configs: if command_config.name == name: return command_config raise NoSuchCommandException(name) @property def command_configs(self): # type: () -> List[CommandConfig] return self._command_configs def has_command_config(self, name): # type: (str) -> bool for command_config in self._command_configs: if command_config.name == name: return True raise False def has_command_configs(self): # type: () -> bool return len(self._command_configs) > 0 @property def default_display_name(self): # type: () -> Optional[str] if self._name is None: return return re.sub(r"[\s\-_]+", " ", self._name).title() @property def default_style_set(self): # type: () -> StyleSet raise NotImplementedError() @property def default_command_resolver(self): raise NotImplementedError() PK!`App#clikit/api/config/command_config.pyfrom contextlib import contextmanager from typing import Any from typing import List from typing import Optional from clikit.api.args.args_parser import ArgsParser from clikit.api.args.format.args_format import ArgsFormat from clikit.api.args.format.args_format_builder import ArgsFormatBuilder from clikit.api.args.format.command_name import CommandName from clikit.api.command.exceptions import NoSuchCommandException from .config import Config class CommandConfig(Config): """ The configuration of a console command. """ def __init__(self, name=None): # type: (Optional[str]) -> None super(CommandConfig, self).__init__() self._name = name self._aliases = [] self._description = "" self._help = None self._enabled = True self._hidden = False self._process_title = None self._default = None self._anonymous = None self._sub_command_configs = [] # type: List[CommandConfig] self._parent_config = None # type: Optional[CommandConfig] @property def name(self): # type: () -> Optional[str] return self._name def set_name(self, name): # type: (Optional[str]) -> CommandConfig self._name = name return self @property def aliases(self): # type: () -> List[str] return self._aliases def add_alias(self, alias): # type: (str) -> CommandConfig self._aliases.append(alias) return self def add_aliases(self, aliases): # type: (List[str]) -> CommandConfig for alias in aliases: self.add_alias(alias) return self def set_aliases(self, aliases): # type: (List[str]) -> CommandConfig self._aliases = [] return self.add_aliases(aliases) @property def description(self): # type: () -> str return self._description def set_description(self, description): # type: (str) -> CommandConfig self._description = description return self @property def help(self): # type: () -> Optional[str] return self._help def set_help(self, help): # type: (Optional[str]) -> CommandConfig self._help = help return self def is_enabled(self): # type: () -> bool return self._enabled def enable(self): # type: () -> CommandConfig self._enabled = True return self def disable(self): # type: () -> CommandConfig self._enabled = False return self def is_hidden(self): # type: () -> bool return self._hidden def hide(self, hidden=True): # type: (bool) -> CommandConfig self._hidden = hidden return self @property def process_title(self): # type: () -> Optional[str] return self._process_title def set_process_title( self, process_title ): # type: (Optional[str]) -> CommandConfig self._process_title = process_title return self def default(self, default=True): # type: (bool) -> CommandConfig """ Marks the command as the default command. """ self._default = default self._anonymous = False return self def anonymous(self): # type: () -> CommandConfig self._default = True self._anonymous = True return self def is_default(self): # type: () -> bool return self._default def is_anonymous(self): # type: () -> bool return self._anonymous @property def parent_config(self): # type: () -> Optional[CommandConfig] return self._parent_config def set_parent_config( self, parent_config ): # type: (Optional[CommandConfig]) -> CommandConfig self._parent_config = parent_config return self def is_sub_command_config(self): # type: () -> bool return self._parent_config is not None def build_args_format( self, base_format=None ): # type: (Optional[ArgsFormat]) -> ArgsFormat builder = ArgsFormatBuilder(base_format) if not self._anonymous: builder.add_command_name(CommandName(self.name, self.aliases)) builder.add_options(*self.options.values()) builder.add_arguments(*self.arguments.values()) return builder.format @contextmanager def sub_command(self, name): # type: (str) -> CommandConfig sub_command_config = CommandConfig(name) self.add_sub_command_config(sub_command_config) yield sub_command_config def create_sub_command(self, name): # type: (str) -> CommandConfig sub_command_config = CommandConfig(name) self.add_sub_command_config(sub_command_config) return sub_command_config @contextmanager def edit_sub_command(self, name): # type: (str) -> CommandConfig sub_command_config = self.get_sub_command_config(name) yield sub_command_config def add_sub_command_config( self, sub_command_config ): # type: (CommandConfig) -> CommandConfig self._sub_command_configs.append(sub_command_config) return self def add_sub_command_configs( self, sub_command_configs ): # type: (List[CommandConfig]) -> CommandConfig for sub_command_config in sub_command_configs: self.add_sub_command_config(sub_command_config) return self def get_sub_command_config(self, name): # type: (str) -> CommandConfig for sub_command_config in self._sub_command_configs: if sub_command_config.name == name: return sub_command_config raise NoSuchCommandException(name) @property def sub_command_configs(self): # type: () -> List[CommandConfig] return self._sub_command_configs def has_sub_command_config(self, name): # type: (str) -> bool for sub_command_config in self._sub_command_configs: if sub_command_config.name == name: return True return False def has_sub_command_configs(self): # type: () -> bool return len(self._sub_command_configs) > 0 @property def default_helper_set(self): # type: () -> HelperSet if self._parent_config: return self._parent_config.default_helper_set return super(CommandConfig, self).default_helper_set @property def default_args_parser(self): # type: () -> ArgsParser if self._parent_config: return self._parent_config.default_args_parser return super(CommandConfig, self).default_args_parser @property def default_lenient_args_parsing(self): # type: () -> bool if self._parent_config: return self._parent_config.default_lenient_args_parsing return super(CommandConfig, self).default_lenient_args_parsing @property def default_handler(self): # type: () -> Any if self._parent_config: return self._parent_config.default_handler return super(CommandConfig, self).default_handler @property def default_handler_method(self): # type: () -> str if self._parent_config: return self._parent_config.default_handler_method return super(CommandConfig, self).default_handler_method PK!.J4clikit/api/config/config.pyfrom typing import Any from typing import Dict from typing import Optional from clikit.api.args.args_parser import ArgsParser from clikit.api.args.format.args_format_builder import ArgsFormatBuilder from clikit.api.args.format.argument import Argument from clikit.api.args.format.option import Option from clikit.args.default_args_parser import DefaultArgsParser class Config(object): """ Implements methods shared by all configurations. """ def __init__(self): self._format_builder = ArgsFormatBuilder() self._helper_set = None self._args_parser = None self._lenient_args_parsing = None self._handler = None self._handler_method = None self.configure() @property def arguments(self): # type: () -> Dict[str, Argument] return self._format_builder.get_arguments() def add_argument( self, name, flags=0, description=None, default=None ): # type: (str, int, Optional[str], Any) -> Config argument = Argument(name, flags, description, default) self._format_builder.add_argument(argument) return self @property def options(self): # type: () -> Dict[str, Option] return self._format_builder.get_options() def add_option( self, long_name, short_name=None, flags=0, description=None, default=None, value_name="...", ): # type: (str, Optional[str], int, Optional[str], Any, str) -> Config option = Option(long_name, short_name, flags, description, default, value_name) self._format_builder.add_option(option) return self @property def helper_set(self): # type: () -> HelperSet if self._helper_set is None: return self.default_helper_set return self._helper_set def set_helper_set(self, helper_set): # type: (HelperSet) -> Config self._helper_set = helper_set return self @property def args_parser(self): # type: () -> ArgsParser if self._args_parser is None: return self.default_args_parser return self._args_parser def set_args_parser(self, args_parser): # type: (ArgsParser) -> Config self._args_parser = args_parser return self def is_lenient_args_parsing_enabled(self): # type: () -> bool if self._lenient_args_parsing is None: return self.default_lenient_args_parsing return self._lenient_args_parsing def enable_lenient_args_parsing(self): # type: () -> Config self._lenient_args_parsing = True return self def disable_lenient_args_parsing(self): # type: () -> Config self._lenient_args_parsing = False return self @property def handler(self): # type: () -> Any if self._handler is None: return self.default_handler if callable(self._handler): return self._handler() return self._handler def set_handler(self, handler): # type: (Any) -> Config self._handler = handler return self @property def handler_method(self): # type: () -> str if self._handler_method is None: return self.default_handler_method return self._handler_method def set_handler_method(self, handler_method): # type: (str) -> Config self._handler_method = handler_method return self @property def default_helper_set(self): # type: () -> HelperSet return HelperSet() @property def default_args_parser(self): # type: () -> ArgsParser return DefaultArgsParser() @property def default_lenient_args_parsing(self): # type: () -> bool return False @property def default_handler(self): # type: () -> Any return lambda: None @property def default_handler_method(self): # type: () -> Any return "handle" def configure(self): # type: () -> None """ Adds the default configuration. Should be overridden in subclasses """ PK!+[`clikit/api/event/__init__.pyfrom .config_event import ConfigEvent from .console_events import ConsoleEvents from .event import Event from .event_dispatcher import EventDispatcher from .pre_handle_event import PreHandleEvent from .pre_resolve_event import PreResolveEvent PK!}1 clikit/api/event/config_event.pyfrom .event import Event class ConfigEvent(Event): """ Dispatched after the configuration is built. Use this event to add custom configuration to the application. """ def __init__(self, config): # type: (ApplicationConfig) -> None self._config = config @property def config(self): # type: () -> ApplicationConfig return self._config PK!ˏoŠ"clikit/api/event/console_events.pyfrom enum import Enum class ConsoleEvents(Enum): PRE_RESOLVE = "pre-resolve" PRE_HANDLE = "pre-handle" CONFIG = "config" PK!2$gHHclikit/api/event/event.pyclass Event(object): """ Event """ def __init__(self): # type: () -> None self._propagation_stopped = False def is_propagation_stopped(self): # type: () -> bool return self._propagation_stopped def stop_propagation(self): # type: () -> None self._propagation_stopped = True PK!5oQ $clikit/api/event/event_dispatcher.pyfrom typing import Callable from typing import Dict from typing import List from typing import Optional from typing import Union from .event import Event class EventDispatcher(object): def __init__(self): # type: () -> None self._listeners = {} self._sorted = {} def dispatch(self, event_name, event=None): # type: (str, Event) -> Event if event is None: event = Event() listeners = self.get_listeners(event_name) if listeners: self._do_dispatch(listeners, event_name, event) return event def get_listeners( self, event_name=None ): # type: (str) -> Union[List[Callable], Dict[str, Callable]] if event_name is not None: if event_name not in self._listeners: return [] if event_name not in self._sorted: self._sort_listeners(event_name) return self._sorted[event_name] for event_name, event_listeners in self._listeners.items(): if event_name not in self._sorted: self._sort_listeners(event_name) return self._sorted def get_listener_priority( self, event_name, listener ): # type: (str, Callable) -> Optional[int] if event_name not in self._listeners: return for priority, listeners in self._listeners[event_name].items(): for v in listeners: if v == listener: return priority def has_listeners(self, event_name=None): # type: (Optional[str]) -> bool if event_name is not None: if event_name not in self._listeners: return False return len(self._listeners[event_name]) > 0 for event_listeners in self._listeners.values(): if event_listeners: return True return False def add_listener( self, event_name, listener, priority=0 ): # type: (str, Callable, int) -> None if event_name not in self._listeners: self._listeners[event_name] = {} if priority not in self._listeners[event_name]: self._listeners[event_name][priority] = [] self._listeners[event_name][priority].append(listener) if event_name in self._sorted: del self._sorted[event_name] def _do_dispatch( self, listeners, event_name, event ): # type: (List[Callable], str, Event) -> None for listener in listeners: if event.is_propagation_stopped(): break listener(event, event_name, self) def _sort_listeners(self, event_name): # type: (str) -> None """ Sorts the internal list of listeners for the given event by priority. """ self._sorted[event_name] = [] for priority, listeners in sorted( self._listeners[event_name].items(), key=lambda t: -t[0] ): for listener in listeners: self._sorted[event_name].append(listener) PK!ۣ$clikit/api/event/pre_handle_event.pyfrom clikit.api.args import Args from clikit.api.io import IO from .event import Event class PreHandleEvent(Event): """ Dispatched before a command is handled. Add a listener for this event to execute custom logic before or instead of the default handler. """ def __init__(self, args, io, command): # type: (Args, IO, Command) -> None super(PreHandleEvent, self).__init__() self._args = args self._io = io self._command = command self._handled = False self._status_code = 0 @property def args(self): # type: () -> Args return self._args @property def io(self): # type: () -> IO return self._io @property def command(self): # type: () -> Command return self._command @property def status_code(self): # type: () -> int return self._status_code def is_handled(self): # type: () -> bool return self._handled def handled(self, handled): # type: (bool) -> None self._handled = handled def set_status_code(self, status_code): # type: (int) -> None self._status_code = status_code PK!0i%clikit/api/event/pre_resolve_event.pyfrom clikit.api.args import RawArgs from clikit.api.resolver import ResolvedCommand from .event import Event class PreResolveEvent(Event): """ Dispatched before the console arguments are resolved to a command. Add a listener for this event to customize the command used for the given console arguments. """ def __init__(self, raw_args, application): # type: (RawArgs, Application) -> None super(PreResolveEvent, self).__init__() self._raw_args = raw_args self._application = application self._resolved_command = None @property def raw_args(self): # type: () -> RawArgs return self._raw_args @property def application(self): # type: () -> Application return self._application @property def resolved_command(self): # type: () -> ResolvedCommand return self._resolved_command def set_resolved_command( self, resolved_command=None ): # type: (ResolvedCommand) -> None self._resolved_command = resolved_command PK!ԇ UZZ clikit/api/formatter/__init__.pyfrom .formatter import Formatter from .style import Style from .style_set import StyleSet PK!VDD!clikit/api/formatter/formatter.pyclass Formatter(object): """ Formats strings. """ def format(self, string, style=None): # type: (str, Style) -> str """ Formats the given string. """ raise NotImplementedError() def remove_format(self, string): # type: (str) -> str """ Removes the format tags from the given string. """ raise NotImplementedError() def force_ansi(self): # type: () -> bool raise NotImplementedError() def add_style(self, style): # type: (Style) -> None raise NotImplementedError() PK!c= = clikit/api/formatter/style.pyfrom typing import Optional class Style(object): """ A formatter style. """ _colors = {"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"} def __init__(self, tag=None): # type: (Optional[str]) -> None self._tag = tag self._fg_color = None self._bg_color = None self._bold = False self._underlined = False self._blinking = False self._inverse = False self._hidden = False @property def tag(self): # type: () -> Optional[str] return self._tag @property def foreground_color(self): # type: () -> str return self._fg_color @property def background_color(self): # type: () -> str return self._bg_color def fg(self, color): # type: (str) -> Style """ Sets the foreground color. """ self._fg_color = color return self def bg(self, color): # type: (str) -> Style """ Sets the background color. """ self._bg_color = color return self def bold(self, bold=True): # type: (bool) -> Style """ Sets or unsets the font weight to bold. """ self._bold = bold return self def underlined(self, underlined=True): # type: (bool) -> Style """ Enables or disables underlining. """ self._underlined = underlined return self def blinking(self, blinking=True): # type: (bool) -> Style """ Enables or disables blinking. """ self._blinking = blinking return self def inverse(self, inverse=True): # type: (bool) -> Style """ Enables or disables inverse colors. """ self._inverse = inverse return self def hidden(self, hidden=True): # type: (bool) -> Style """ Hides or shows the text. """ self._hidden = hidden return self def is_bold(self): # type: () -> bool return self._bold def is_underlined(self): # type: () -> bool return self._underlined def is_blinking(self): # type: () -> bool return self._blinking def is_inverse(self): # type: () -> bool return self._inverse def is_hidden(self): # type: () -> bool return self._hidden PK!iA!clikit/api/formatter/style_set.pyfrom typing import Dict from typing import List from typing import Optional from .style import Style class StyleSet(object): """ A set of styles used by the formatter. """ def __init__(self, styles=None): # type: (Optional[List[Style]]) -> None if styles is None: styles = [] self._styles = {} for style in styles: self.add(style) @property def styles(self): # type: () -> (Dict[str, Style]) return self._styles def add(self, style): # type: (Style) -> None if not style.tag: raise ValueError("The tag of a style added to the style set must be set.") self._styles[style.tag] = style def replace(self, styles): # type: (Optional[List[Style]]) -> None self._styles = {} for style in styles: self.add(style) def remove(self, tag): # type: (str) -> None if tag in self._styles: del self._styles[tag] PK!vûclikit/api/io/__init__.pyfrom .input import Input from .input_stream import InputStream from .io import IO from .io_exception import IOException from .output import Output from .output_stream import OutputStream PK!nclikit/api/io/flags.py# Flag: Always write data NORMAL = 0 # Flag: Only write if the verbosity is "verbose" or greater. VERBOSE = 1 # Flag: Only write if the verbosity is "very verbose" or greater. VERY_VERBOSE = 2 # Flag: Only write if the verbosity is "debug". DEBUG = 4 PK!KKclikit/api/io/input.pyfrom typing import Optional from .input_stream import InputStream class Input(object): """ The console input. This class wraps an input stream and adds convenience functionality for reading that stream. """ def __init__(self, stream): # type: (InputStream) -> None self._stream = stream self._interactive = True def read(self, length, default=None): # type: (int, Optional[str]) -> str """ Reads the given amount of characters from the input stream. :raises: IOException """ if not self._interactive: return default return self._stream.read(length) def read_line( self, length=None, default=None ): # type: (Optional[int], Optional[str]) -> str """ Reads a line from the input stream. :raises: IOException """ if not self._interactive: return default return self._stream.read_line(length=length) def close(self): # type: () -> None """ Closes the input. """ self._stream.close() def is_closed(self): # type: () -> bool """ Returns whether the input is closed. """ return self._stream.is_closed() def set_stream(self, stream): # type: (InputStream) -> None """ Sets the underlying stream. """ self._stream = stream @property def stream(self): # type: () -> InputStream return self._stream def set_interactive(self, interactive): # type: (bool) -> None """ Enables or disables interaction with the user. """ self._interactive = interactive def is_interactive(self): # type: () -> bool """ Returns whether the user may be asked for input. """ return self._interactive PK!u5clikit/api/io/input_stream.pyfrom typing import Optional class InputStream(object): """ The console input stream. """ def read(self, length): # type: (int) -> str """ Reads the given amount of characters from the stream. """ raise NotImplementedError() def read_line(self, length=None): # type: (Optional[int]) -> str """ Reads a line from the stream. """ raise NotImplementedError() def close(self): # type: () -> None """ Closes the stream """ raise NotImplementedError() def is_closed(self): # type: () -> bool """ Returns whether the stream is closed or not """ raise NotImplementedError() PK!T%bbclikit/api/io/io.pyfrom typing import Optional from clikit.api.formatter import Formatter from .input import Input from .output import Output class IO(Formatter): """ Provides methods to access the console input and output. """ def __init__( self, input, output, error_output ): # type: (Input, Output, Output) -> None self._input = input self._output = output self._error_output = error_output self._terminal_dimensions = None @property def input(self): # type: () -> Input return self._input @property def output(self): # type: () -> Output return self._output @property def error_output(self): # type: () -> Output return self._error_output def read(self, length, default=None): # type: (int, Optional[str]) -> str """ Reads the given amount of characters from the standard input. :raises: IOException """ return self._input.read(length, default=default) def read_line( self, length=None, default=None ): # type: (Optional[int], Optional[str]) -> str """ Reads a line from the standard input. :raises: IOException """ return self._input.read_line(length=length, default=default) def write(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the standard output. The string is formatted before it is written to the output. """ self._output.write(string, flags=flags) def write_line(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a line to the standard output. The string is formatted before it is written to the output. """ self._output.write_line(string, flags=flags) def write_raw(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the standard output without formatting. """ self._output.write_raw(string, flags=flags) def write_line_raw(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a line to the standard output without formatting. """ self._output.write_raw(string, flags=flags) def error(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the error output. The string is formatted before it is written to the output. """ self._error_output.write(string, flags=flags) def error_line(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a line to the error output. The string is formatted before it is written to the output. """ self._error_output.write_line(string, flags=flags) def error_raw(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the error output without formatting. """ self._error_output.write_raw(string, flags=flags) def error_line_raw(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a line to the error output without formatting. """ self._error_output.write_raw(string, flags=flags) def flush(self): # type: () -> None """ Flushes the outputs and forces all pending text to be written out. """ self._output.flush() self._error_output.flush() def close(self): # type: () -> None """ Closes the input and the outputs. """ self._input.close() self._output.close() self._error_output.close() def set_interactive(self, interactive): # type: (bool) -> None """ Enables or disables interaction with the user. """ self._input.set_interactive(interactive) def is_interactive(self): # type: () -> bool """ Returns whether the user may be asked for input. """ return self._input.is_interactive() def set_verbosity(self, verbosity): # type: (int) -> None """ Sets the verbosity of the output. """ self._output.set_verbosity(verbosity) self._error_output.set_verbosity(verbosity) def is_verbose(self): # type: () -> bool """ Returns whether the verbosity is VERBOSE or greater. """ return self._output.is_verbose() def is_very_verbose(self): # type: () -> bool """ Returns whether the verbosity is VERY_VERBOSE or greater. """ return self._output.is_very_verbose() def is_debug(self): # type: () -> bool """ Returns whether the verbosity is DEBUG. """ return self._output.is_debug() @property def verbosity(self): # type: () -> int return self._output.verbosity def set_quiet(self, quiet): # type: (bool) -> None """ Sets whether all output should be suppressed. """ self._output.set_quiet(quiet) self._error_output.set_quiet(quiet) def is_quiet(self): # type: () -> bool """ Returns whether all output is suppressed. """ return self._output.is_quiet() def set_terminal_dimensions(self, dimensions): # type: (Rectangle) -> None """ Sets the dimensions of the terminal. """ self._terminal_dimensions = dimensions @property def terminal_dimensions(self): # type: () -> Rectangle if not self._terminal_dimensions: self._terminal_dimensions = self.get_default_terminal_dimensions() return self._terminal_dimensions def get_default_terminal_dimensions(self): # type: () -> Rectangle """ Returns the default terminal dimensions. """ from clikit.ui.rectangle import Rectangle return Rectangle(80, 20) def set_formatter(self, formatter): # type: (Formatter) -> None """ Sets the output formatter. """ self._output.set_formatter(formatter) self._error_output.set_formatter(formatter) @property def formatter(self): # type: () -> Formatter """ Returns the output formatter. """ return self._output.formatter def format(self, string, style=None): # type: (str, Style) -> str """ Formats the given string. """ return self._output.formatter.format(string, style=style) def remove_format(self, string): # type: (str) -> str """ Removes the format tags from the given string. """ return self._output.formatter.remove_format(string) PK!=\\clikit/api/io/io_exception.pyclass IOException(RuntimeError): """ Thrown if an error happens during I/O. """ PK!iUssclikit/api/io/output.pyimport os from typing import Optional from clikit.api.formatter import Formatter from clikit.formatter import NullFormatter from clikit.utils._compat import to_str from .flags import DEBUG from .flags import NORMAL from .flags import VERBOSE from .flags import VERY_VERBOSE from .output_stream import OutputStream class Output(Formatter): """ The console output. This class wraps an output stream and adds convenience functionality for writing that stream. """ def __init__( self, stream, formatter=None ): # type: (OutputStream, Optional[Formatter]) -> None self._stream = stream if formatter is None: formatter = NullFormatter() self._formatter = formatter self._quiet = False self._format_output = self._stream.supports_ansi() or formatter.force_ansi() self._verbosity = 0 def write(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the output stream. The string is formatted before it is written to the output stream. """ if self._may_write(flags): if self._format_output: formatted = self.format(string) else: formatted = self.remove_format(string) self._stream.write(to_str(formatted)) def write_line(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a line of text to the output stream. The string is formatted before it is written to the output stream. """ if self._may_write(flags): string = string.rstrip("\n") if self._format_output: formatted = self.format(string) else: formatted = self.remove_format(string) self._stream.write(to_str(formatted + "\n")) def write_raw(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the output stream without formatting. """ if self._may_write(flags): self._stream.write(to_str(string)) def write_line_raw(self, string, flags=None): # type: (str, Optional[int]) -> None """ Writes a string to the output stream without formatting. """ if self._may_write(flags): self._stream.write(to_str(string.rstrip("\n") + "\n")) def flush(self): # type: () -> None """ Forces all pending text to be written out. """ self._stream.flush() def close(self): # type: () -> None """ Closes the output. """ self._stream.close() def is_closed(self): # type: () -> bool """ Returns whether the output is closed. """ return self._stream.is_closed() def set_stream(self, stream): # type: (OutputStream) -> None """ Sets the underlying stream. """ self._stream = stream self._format_output = ( self._stream.supports_ansi() or self._formatter.force_ansi() ) @property def stream(self): # type: () -> OutputStream """ Returns the underlying stream. """ return self._stream def set_formatter(self, formatter): # type: (Formatter) -> None """ Sets the underlying formatter. """ self._formatter = formatter self._format_output = self._stream.supports_ansi() or formatter.force_ansi() @property def formatter(self): # type: () -> Formatter """ Returns the underlying formatter. """ return self._formatter def set_verbosity(self, verbosity): # type: (int) -> None """ Sets the verbosity level of the output. """ if verbosity not in {NORMAL, VERBOSE, VERY_VERBOSE, DEBUG}: raise ValueError( "The verbosity must be one of NORMAL, VERBOSE, VERY_VERBOSE or DEBUG." ) self._verbosity = verbosity @property def verbosity(self): # type: () -> int """ Returns the current verbosity level. """ return self._verbosity def is_verbose(self): # type: () -> bool """ Returns whether the verbosity is VERBOSE or greater. """ return self._verbosity >= VERBOSE def is_very_verbose(self): # type: () -> bool """ Returns whether the verbosity is VERY_VERBOSE or greater. """ return self._verbosity >= VERY_VERBOSE def is_debug(self): # type: () -> bool """ Returns whether the verbosity is DEBUG. """ return self._verbosity == DEBUG def set_quiet(self, quiet): # type: (bool) -> None """ Sets whether all output should be suppressed. """ self._quiet = quiet def is_quiet(self): # type: () -> bool """ Returns whether all output is suppressed. """ return self._quiet def format(self, string, style=None): # type: (str, Style) -> str """ Formats the given string. """ return self._formatter.format(string, style) def remove_format(self, string): # type: (str) -> str """ Removes the format tags from the given string. """ return self._formatter.remove_format(string) def supports_ansi(self): # type: () -> bool return self._format_output def _may_write(self, flags): # type: (int) -> bool """ Returns whether an output may be written for the given flags. """ if flags is None: flags = 0 if self._quiet: return False if flags & VERBOSE: return self._verbosity >= VERBOSE if flags & VERY_VERBOSE: return self._verbosity >= VERY_VERBOSE if flags & DEBUG: return self._verbosity >= DEBUG return True PK![T6YYclikit/api/io/output_stream.pyclass OutputStream(object): """ The console output stream. """ def write(self, string): # type: (str) -> None """ Writes a string to the stream. """ raise NotImplementedError() def flush(self): # type: () -> None """ Flushes the stream and forces all pending text to be written out. """ raise NotImplementedError() def supports_ansi(self): # type: () -> bool """ Returns whether the stream supports ANSI format codes. """ raise NotImplementedError() def close(self): # type: () -> None """ Closes the stream. """ raise NotImplementedError() def is_closed(self): # type: () -> bool """ Returns whether the stream is closed. """ raise NotImplementedError() PK!Ձb\\clikit/api/resolver/__init__.pyfrom .command_resolver import CommandResolver from .resolved_command import ResolvedCommand PK!nXX'clikit/api/resolver/command_resolver.pyfrom clikit.api.args import RawArgs from .resolved_command import ResolvedCommand class CommandResolver(object): """ Returns the command to execute for the given console arguments. """ def resolve( self, args, application ): # type: (RawArgs, Application) -> ResolvedCommand raise NotImplementedError() PK!~x!clikit/api/resolver/exceptions.pyfrom clikit.utils.command import find_similar_command_names class CannotResolveCommandException(RuntimeError): @classmethod def name_not_found(cls, name, commands): message = 'The command "{}" is not defined.'.format(name) suggested_names = find_similar_command_names(name, commands) if suggested_names: if len(suggested_names) == 1: message += "\n\nDid you mean this?\n " else: message += "\n\nDid you mean one of these?\n " message += "\n ".join(suggested_names) return cls(message) @classmethod def no_default_command(cls): return cls("No default command is defined.") PK!1E'clikit/api/resolver/resolved_command.pyfrom clikit.api.args import Args from clikit.api.command import Command class ResolvedCommand(object): """ A resolved command. """ def __init__(self, command, args): # type: (Command, Args) -> None self._command = command self._args = args @property def command(self): # type: () -> Command return self._command @property def args(self): # type: () -> Args return self._args PK!@wwclikit/args/__init__.pyfrom .argv_args import ArgvArgs from .default_args_parser import DefaultArgsParser from .string_args import StringArgs PK!jSclikit/args/argv_args.pyimport sys from typing import List from typing import Optional from clikit.api.args.raw_args import RawArgs class ArgvArgs(RawArgs): """ Console arguments passed via sys.argv. """ def __init__(self, argv=None): # type: (Optional[List[str]]) -> None if argv is None: argv = list(sys.argv) argv = argv[:] self._script_name = argv.pop(0) self._tokens = argv @property def script_name(self): # type: () -> str return self._script_name @property def tokens(self): # type: () -> List[str] return self._tokens def has_token(self, token): # type: (str) -> bool return token in self._tokens def to_string(self, script_name=True): # type: (bool) -> str string = " ".join(self._tokens) if script_name: string = self._script_name.lstrip() + " " + string return string PK!SJ--"clikit/args/default_args_parser.pyfrom typing import Dict from typing import List from typing import Optional from clikit.api.args.args import Args from clikit.api.args.args_parser import ArgsParser from clikit.api.args.format import ArgsFormat from clikit.api.args.format import Argument from clikit.api.args.format import CommandName from clikit.api.args.exceptions import CannotParseArgsException from clikit.api.args.exceptions import NoSuchOptionException from clikit.api.args.raw_args import RawArgs from clikit.utils._compat import OrderedDict class DefaultArgsParser(ArgsParser): """ Default parser for RawArgs instances. """ def __init__(self): # type: () -> None self._arguments = OrderedDict() self._options = OrderedDict() def parse( self, args, fmt, lenient=False ): # type: (RawArgs, ArgsFormat, bool) -> Args self._arguments = OrderedDict() arguments = OrderedDict() command_names = OrderedDict() i = 1 for j, command_name in enumerate(fmt.get_command_names()): arg_name = "cmd{}{}".format(j + 1, i) while fmt.has_argument(arg_name): i += 1 arg_name = "cmd{}{}".format(j + 1, i) arguments[arg_name] = Argument(arg_name, Argument.REQUIRED) command_names[arg_name] = command_name arguments.update(fmt.get_arguments()) _fmt = ArgsFormat( fmt.get_command_names() + list(arguments.values()) + list(fmt.get_options().values()) ) try: self._parse(args, _fmt, lenient) except (CannotParseArgsException, NoSuchOptionException): if not lenient: raise self._insert_missing_command_names(arguments, command_names, lenient) # Validate missing_arguments = [ arg.name for arg in _fmt.get_arguments().values() if arg.name not in self._arguments and arg.is_required() ] if missing_arguments and not lenient: raise CannotParseArgsException( 'Not enough arguments (missing: "{}").'.format( ", ".join(missing_arguments) ) ) parsed_args = Args(fmt, args) for name, value in self._arguments.items(): if fmt.has_argument(name): parsed_args.set_argument(name, value) for name, value in self._options.items(): if fmt.has_option(name): parsed_args.set_option(name, value) return parsed_args def _parse( self, raw_args, fmt, lenient ): # type: (RawArgs, ArgsFormat, bool) -> None tokens = raw_args.tokens[:] parse_options = True while True: try: token = tokens.pop(0) except IndexError: break if parse_options and token == "": self._parse_argument(token, fmt, lenient) elif parse_options and token == "--": parse_options = False elif parse_options and token.find("--") == 0: self._parse_long_option(token, tokens, fmt, lenient) elif parse_options and token[0] == "-" and token != "-": self._parse_short_option(token, tokens, fmt, lenient) else: self._parse_argument(token, fmt, lenient) def _insert_missing_command_names( self, arguments, command_names, lenient=False ): # type: (Dict[str, Argument], Dict[str, CommandName], bool) -> None fixed_values = {} actual_values = self._flatten(self._arguments.values()) command_names_iterator = iter(command_names.values()) actual_values_iterator = iter(actual_values) arguments_iterator = iter(arguments.values()) actual_value, command_name = self._skip_command_names( actual_values_iterator, command_names_iterator, arguments_iterator ) _, argument = self._copy_argument_values( command_names_iterator, command_name, arguments_iterator, None, fixed_values, lenient, ) self._copy_argument_values( actual_values_iterator, actual_value, arguments_iterator, argument, fixed_values, lenient, ) for name, value in fixed_values.items(): self._arguments[name] = value def _skip_command_names(self, actual_values, command_names, arguments): arg = next(actual_values, None) command_name = next(command_names, None) while arg and command_name and command_name.match(arg): arg = next(actual_values, None) command_name = next(command_names, None) next(arguments, None) return arg, command_name def _copy_argument_values( self, actual_values, value, arguments, argument, fixed_values, lenient ): if value is None: value = next(actual_values, None) if argument is None: argument = next(arguments, None) while value is not None: if argument is None: if lenient: return raise CannotParseArgsException.too_many_arguments() name = argument.name # Append the value to multi-valued arguments if argument.is_multi_valued(): if name not in fixed_values: fixed_values[name] = [] fixed_values[name].append(value) # The multi-valued argument is the last one, so we don't # need to advance the array pointer anymore. else: fixed_values[name] = value argument = next(arguments, None) value = next(actual_values, None) return value, argument def _flatten(self, arguments, result=None): if result is None: result = [] for value in arguments: if isinstance(value, list): self._flatten(value, result) else: result.append(value) return result def _parse_argument( self, token, fmt, lenient ): # type: (str, ArgsFormat, bool) -> None c = len(self._arguments) # if input is expecting another argument, add it if fmt.has_argument(c): arg = fmt.get_argument(c) if arg.is_multi_valued(): if arg.name not in self._arguments: self._arguments[arg.name] = [] self._arguments[arg.name].append(token) else: self._arguments[arg.name] = token elif fmt.has_argument(c - 1) and fmt.get_argument(c - 1).is_multi_valued(): arg = fmt.get_argument(c - 1) if arg.name not in self._arguments: self._arguments[arg.name] = [] self._arguments[arg.name].append(token) # unexpected argument else: if not lenient: raise CannotParseArgsException.too_many_arguments() def _parse_long_option( self, token, tokens, fmt, lenient ): # type: (str, List[str], ArgsFormat, bool) -> None name = token[2:] pos = name.find("=") if pos != -1: self._add_long_option(name[:pos], name[pos + 1 :], tokens, fmt, lenient) else: if fmt.has_option(name) and fmt.get_option(name).accepts_value(): try: value = tokens.pop(0) except IndexError: value = None if value and value.startswith("-"): tokens.insert(0, value) value = None self._add_long_option(name, value, tokens, fmt, lenient) else: self._add_long_option(name, None, tokens, fmt, lenient) def _parse_short_option( self, token, tokens, fmt, lenient ): # type: (str, List[str], ArgsFormat, bool) -> None name = token[1:] if len(name) > 1: if fmt.has_option(name[0]) and fmt.get_option(name[0]).accepts_value(): # an option with a value (with no space) self._add_short_option(name[0], name[1:], tokens, fmt, lenient) else: self._parse_short_option_set(name, tokens, fmt, lenient) else: if fmt.has_option(name[0]) and fmt.get_option(name[0]).accepts_value(): try: value = tokens.pop(0) except IndexError: value = None if value and value.startswith("-"): tokens.insert(0, value) value = None self._add_short_option(name, value, tokens, fmt, lenient) else: self._add_short_option(name, None, tokens, fmt, lenient) def _parse_short_option_set( self, name, tokens, fmt, lenient ): # type: (str, List[str], ArgsFormat, bool) -> None l = len(name) for i in range(0, l): if not fmt.has_option(name[i]): raise NoSuchOptionException(name[i]) option = fmt.get_option(name[i]) if option.accepts_value(): self._add_long_option( option.long_name, None if l - 1 == i else name[i + 1 :], tokens, fmt, lenient, ) break else: self._add_long_option(option.long_name, None, tokens, fmt, lenient) def _add_long_option( self, name, value, tokens, fmt, lenient ): # type: (str, Optional[str], List[str], ArgsFormat, bool) -> None if not fmt.has_option(name): raise NoSuchOptionException(name) option = fmt.get_option(name) if value is False: value = None if value is not None and not option.accepts_value(): raise CannotParseArgsException.option_does_not_accept_value(name) if value is None and option.accepts_value() and len(tokens): # if option accepts an optional or mandatory argument # let's see if there is one provided try: nxt = tokens.pop(0) except IndexError: nxt = None if nxt and len(nxt) >= 1 and nxt[0] != "-": value = nxt elif not nxt: value = "" else: tokens.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 CannotParseArgsException.option_requires_value(name) if not option.is_multi_valued(): value = option.default if option.is_value_optional() else True if option.is_multi_valued(): if name not in self._options: self._options[name] = [] self._options[name].append(value) else: self._options[name] = value def _add_short_option( self, name, value, tokens, fmt, lenient ): # type: (str, Optional[str], List[str], ArgsFormat, bool) -> None if not fmt.has_option(name): raise NoSuchOptionException(name) self._add_long_option( fmt.get_option(name).long_name, value, tokens, fmt, lenient ) PK!clikit/args/inputs/__init__.pyPK!$clikit/args/string_args.pyfrom typing import List from typing import Optional from clikit.api.args import RawArgs from .token_parser import TokenParser class StringArgs(RawArgs): """ Console arguments passed as a string. """ def __init__(self, string): # type: (str) -> None parser = TokenParser() self._tokens = parser.parse(string) @property def script_name(self): # type: () -> Optional[str] return @property def tokens(self): # type: () -> List[str] return self._tokens def has_token(self, token): # type: (str) -> bool return token in self._tokens def to_string(self, script_name=True): # type: (bool) -> str return " ".join(self._tokens) PK! clikit/args/token_parser.pyfrom typing import List from typing import Optional class TokenParser(object): """ Parses tokens from a string passed to StringArgs. """ def __init__(self): # type: () -> None self._string = "" # type: str self._cursor = 0 # type: int self._current = None # type: Optional[str] self._next_ = None # type: Optional[str] def parse(self, string): # type: (str) -> List[str] self._string = string self._cursor = 0 self._current = None if len(string) > 0: self._current = string[0] self._next_ = None if len(string) > 1: self._next_ = string[1] tokens = self._parse() return tokens def _parse(self): # type: () -> List[str] tokens = [] while self._is_valid(): if self._current.isspace(): # Skip spaces self._next() continue if self._is_valid(): tokens.append(self._parse_token()) return tokens def _is_valid(self): # type: () -> bool return self._current is not None def _next(self): # type: () -> None """ Advances the cursor to the next position. """ if not self._is_valid(): return self._cursor += 1 self._current = self._next_ if self._cursor + 1 < len(self._string): self._next_ = self._string[self._cursor + 1] else: self._next_ = None def _parse_token(self): # type: () -> str token = "" while self._is_valid(): if self._current.isspace(): self._next() break if self._current == "\\": token += self._parse_escape_sequence() elif self._current in ["'", '"']: token += self._parse_quoted_string() else: token += self._current self._next() return token def _parse_quoted_string(self): # type: () -> str string = "" delimiter = self._current # Skip first delimiter self._next() while self._is_valid(): if self._current == delimiter: # Skip last delimiter self._next() break if self._current == "\\": string += self._parse_escape_sequence() elif self._current == '"': string += '"{}"'.format(self._parse_quoted_string()) elif self._current == "'": string += "'{}'".format(self._parse_quoted_string()) else: string += self._current self._next() return string def _parse_escape_sequence(self): # type: () -> str if self._next_ in ['"', "'"]: sequence = self._next_ else: sequence = "\\" + self._next_ self._next() self._next() return sequence PK!V-G~AAclikit/config/__init__.pyfrom .default_application_config import DefaultApplicationConfig PK!!H<<+clikit/config/default_application_config.pyfrom clikit.api.config.application_config import ApplicationConfig from clikit.api.args.raw_args import RawArgs from clikit.api.args.format.argument import Argument from clikit.api.args.format.option import Option from clikit.api.event import ConsoleEvents from clikit.api.event import EventDispatcher from clikit.api.event import PreHandleEvent from clikit.api.event import PreResolveEvent from clikit.api.io import IO from clikit.api.io import Input from clikit.api.io import InputStream from clikit.api.io import Output from clikit.api.io import OutputStream from clikit.api.io.flags import DEBUG from clikit.api.io.flags import VERBOSE from clikit.api.io.flags import VERY_VERBOSE from clikit.api.resolver import ResolvedCommand from clikit.formatter import AnsiFormatter from clikit.formatter import DefaultStyleSet from clikit.formatter import PlainFormatter from clikit.handler.help import HelpTextHandler from clikit.io import ConsoleIO from clikit.io.input_stream import StandardInputStream from clikit.io.output_stream import ErrorOutputStream from clikit.io.output_stream import StandardOutputStream from clikit.resolver.default_resolver import DefaultResolver from clikit.ui.components import NameVersion class DefaultApplicationConfig(ApplicationConfig): """ The default application configuration. """ def configure(self): self.set_io_factory(self.create_io) self.add_event_listener( ConsoleEvents.PRE_RESOLVE.value, self.resolve_help_command ) self.add_event_listener(ConsoleEvents.PRE_HANDLE.value, self.print_version) self.add_option("help", "h", Option.NO_VALUE, "Display this help message") self.add_option("quiet", "q", Option.NO_VALUE, "Do not output any message") self.add_option( "verbose", "v", Option.OPTIONAL_VALUE, "Increase the verbosity of messages: " '"-v" for normal output, ' '"-vv" for more verbose output ' 'and "-vvv" for debug', ) self.add_option( "version", "V", Option.NO_VALUE, "Display this application version" ) self.add_option("ansi", None, Option.NO_VALUE, "Force ANSI output") self.add_option("no-ansi", None, Option.NO_VALUE, "Disable ANSI output") self.add_option( "no-interaction", "n", Option.NO_VALUE, "Do not ask any interactive question", ) with self.command("help") as c: c.default() c.set_description("Display the manual of a command") c.add_argument("command", Argument.OPTIONAL, "The command name") c.set_handler(HelpTextHandler()) def create_io( self, application, args, input_stream=None, output_stream=None, error_stream=None, ): # type: (Application, RawArgs, InputStream, OutputStream, OutputStream) -> IO if input_stream is None: input_stream = StandardInputStream() if output_stream is None: output_stream = StandardOutputStream() if error_stream is None: error_stream = ErrorOutputStream() style_set = application.config.style_set if args.has_token("--no-ansi"): output_formatter = error_formatter = PlainFormatter(style_set) elif args.has_token("--ansi"): output_formatter = error_formatter = AnsiFormatter(style_set) else: if output_stream.supports_ansi(): output_formatter = AnsiFormatter(style_set) else: output_formatter = PlainFormatter(style_set) if error_stream.supports_ansi(): error_formatter = AnsiFormatter(style_set) else: error_formatter = PlainFormatter(style_set) io = self.io_class( Input(input_stream), Output(output_stream, output_formatter), Output(error_stream, error_formatter), ) if args.has_token("-vvv") or self.is_debug(): io.set_verbosity(DEBUG) elif args.has_token("-vv"): io.set_verbosity(VERY_VERBOSE) elif args.has_token("-v"): io.set_verbosity(VERBOSE) if args.has_token("--quiet") or args.has_token("-q"): io.set_quiet(True) if args.has_token("--no-interaction") or args.has_token("-n"): io.set_interactive(False) return io @property def io_class(self): # type: () -> IO.__class__ return ConsoleIO @property def default_style_set(self): # type: () -> DefaultStyleSet return DefaultStyleSet() @property def default_command_resolver(self): # type: () -> DefaultResolver return DefaultResolver() def resolve_help_command( self, event, event_name, dispatcher ): # type: (PreResolveEvent, str, EventDispatcher) -> None args = event.raw_args application = event.application if args.has_token("-h") or args.has_token("--help"): command = application.get_command("help") # Enable lenient parsing parsed_args = command.parse(args, True) event.set_resolved_command(ResolvedCommand(command, parsed_args)) event.stop_propagation() def print_version( self, event, event_name, dispatcher ): # type: (PreHandleEvent, str, EventDispatcher) -> None if event.args.is_option_set("version"): version = NameVersion(event.command.application.config) version.render(event.io) event.handled(True) PK!G3Mclikit/console_application.pyimport sys from .api.application import Application as BaseApplication from .api.args.raw_args import RawArgs from .api.args.format.args_format import ArgsFormat from .api.command import Command from .api.command import CommandCollection from .api.command.exceptions import CannotAddCommandException from .api.config.application_config import ApplicationConfig from .api.config.command_config import CommandConfig from .api.event import ConfigEvent from .api.event import ConsoleEvents from .api.event import PreResolveEvent from .api.io import IO from .api.io import InputStream from .api.io import OutputStream from .api.io.flags import VERY_VERBOSE from .api.resolver.resolved_command import ResolvedCommand from .args.argv_args import ArgvArgs from .io import ConsoleIO from .ui.components.exception_trace import ExceptionTrace class ConsoleApplication(BaseApplication): """ A console application """ def __init__(self, config): # type: (ApplicationConfig) -> None self._preliminary_io = ConsoleIO() # Enable trace output for exceptions thrown during boot self._preliminary_io.set_verbosity(VERY_VERBOSE) self._dispatcher = None try: dispatcher = config.dispatcher if dispatcher and dispatcher.has_listeners(ConsoleEvents.CONFIG.value): dispatcher.dispatch(ConsoleEvents.CONFIG.value, ConfigEvent(config)) self._config = config self._dispatcher = config.dispatcher self._commands = CommandCollection() self._named_commands = CommandCollection() self._default_commands = CommandCollection() self._global_args_format = ArgsFormat( list(config.arguments.values()) + list(config.options.values()) ) for command_config in config.command_configs: self.add_command(command_config) except Exception as e: if not config.is_exception_caught(): raise # Render the trace to the preliminary IO trace = ExceptionTrace(e) trace.render(self._preliminary_io) # Ignore is_terminated_after_run() setting. This is a fatal error. sys.exit(self.exception_to_exit_code(e)) @property def config(self): # type: () -> ApplicationConfig return self._config @property def global_args_format(self): # type: () -> ArgsFormat return self._global_args_format def get_command(self, name): # type: (str) -> Command return self._commands.get(name) @property def commands(self): # type: () -> CommandCollection return self._commands def has_command(self, name): # type: (str) -> bool return name in self._commands def has_commands(self): # type: () -> bool return not self._commands.is_empty() @property def named_commands(self): # type: () -> CommandCollection return self._named_commands def has_named_commands(self): # type: () -> bool return not self._named_commands.is_empty() @property def default_commands(self): # type: () -> CommandCollection return self._default_commands def has_default_commands(self): # type: () -> bool return not self._default_commands.is_empty() def resolve_command(self, args): # type: (RawArgs) -> ResolvedCommand if self._dispatcher and self._dispatcher.has_listeners( ConsoleEvents.PRE_RESOLVE.value ): event = PreResolveEvent(args, self) self._dispatcher.dispatch(ConsoleEvents.PRE_RESOLVE.value, event) resolved_command = event.resolved_command if resolved_command: return resolved_command return self._config.command_resolver.resolve(args, self) def run( self, args=None, input_stream=None, output_stream=None, error_stream=None ): # type: (RawArgs, InputStream, OutputStream, OutputStream) -> int # Render errors to the preliminary IO until the final IO is created io = self._preliminary_io try: if args is None: args = ArgvArgs() io_factory = self._config.io_factory io = io_factory( self, args, input_stream, output_stream, error_stream ) # type: IO resolved_command = self.resolve_command(args) command = resolved_command.command parsed_args = resolved_command.args status_code = command.handle(parsed_args, io) except Exception as e: if not self._config.is_exception_caught(): raise trace = ExceptionTrace(e) trace.render(io) status_code = self.exception_to_exit_code(e) if self._config.is_terminated_after_run(): sys.exit(status_code) return status_code def exception_to_exit_code(self, e): # type: (Exception) -> int if not hasattr(e, "code"): return 1 return min(max(e.code, 1), 255) def add_command(self, config): # type: (CommandConfig) -> None if not config.is_enabled(): return self._validate_command_name(config.name) command = Command(config, self) self._commands.add(command) if config.is_default(): self._default_commands.add(command) if not config.is_anonymous(): self._named_commands.add(command) def _validate_command_name(self, name): # type: (Optional[str]) -> None if not name: raise CannotAddCommandException.name_empty() if name in self._commands: raise CannotAddCommandException.name_exists(name) PK! Rclikit/formatter/__init__.pyfrom .ansi_formatter import AnsiFormatter from .default_style_set import DefaultStyleSet from .null_formatter import NullFormatter from .plain_formatter import PlainFormatter PK!8 "clikit/formatter/ansi_formatter.pyfrom typing import Optional from pastel import Pastel from clikit.adapter.style_converter import StyleConverter from clikit.api.formatter import Formatter from clikit.api.formatter import Style from clikit.api.formatter import StyleSet from .default_style_set import DefaultStyleSet class AnsiFormatter(Formatter): """ A formatter that replaces style tags by ANSI format codes. """ def __init__(self, style_set=None): # type: (StyleSet) -> None self._formatter = Pastel(True) if style_set is None: style_set = DefaultStyleSet() for tag, style in style_set.styles.items(): pastel_style = StyleConverter.convert(style) self._formatter.add_style( tag, pastel_style.foreground, pastel_style.background, pastel_style.options, ) def format(self, string, style=None): # type: (str, Optional[Style]) -> str if style is not None: self._formatter._style_stack.push(StyleConverter.convert(style)) formatted = self._formatter.colorize(string) if style is not None: self._formatter._style_stack.pop() return formatted def remove_format(self, string): # type: (str) -> str with self._formatter.colorized(False): return self._formatter.colorize(string) def force_ansi(self): # type: () -> bool return True def add_style(self, style): # type: (Style) -> None pastel_style = StyleConverter.convert(style) self._formatter.add_style( style.tag, pastel_style.foreground, pastel_style.background, pastel_style.options, ) PK!ESQQ%clikit/formatter/default_style_set.pyfrom clikit.api.formatter import Style from clikit.api.formatter import StyleSet class DefaultStyleSet(StyleSet): """ The Default TTY style set """ def __init__(self): # type: () -> None styles = [ Style("info").fg("green"), Style("comment").fg("cyan"), Style("question").fg("blue"), Style("error").fg("red"), Style("b").bold(), Style("u").underlined(), Style("c1").fg("cyan"), Style("c2").fg("yellow"), ] super(DefaultStyleSet, self).__init__(styles) PK!`yH"clikit/formatter/null_formatter.pyfrom typing import Optional from clikit.api.formatter import Formatter class NullFormatter(Formatter): """ A formatter that returns all text unchanged. """ def format(self, string, style=None): # type: (str, Optional[Style]) -> str return string def remove_format(self, string): # type: (str) -> str return string def force_ansi(self): # type: () -> bool return False def add_style(self, style): # type: (Style) -> None pass PK!VM¸#clikit/formatter/plain_formatter.pyfrom typing import Optional from pastel import Pastel from clikit.adapter.style_converter import StyleConverter from clikit.api.formatter import Formatter from clikit.api.formatter import Style from clikit.api.formatter import StyleSet from .default_style_set import DefaultStyleSet class PlainFormatter(Formatter): """ A formatter that removes all format tags. """ def __init__(self, style_set=None): # type: (StyleSet) -> None self._formatter = Pastel(False) if style_set is None: style_set = DefaultStyleSet() for tag, style in style_set.styles.items(): pastel_style = StyleConverter.convert(style) self._formatter.add_style( tag, pastel_style.foreground, pastel_style.background, pastel_style.options, ) def format(self, string, style=None): # type: (str, Optional[Style]) -> str return self._formatter.colorize(string) def remove_format(self, string): # type: (str) -> str return self._formatter.colorize(string) def force_ansi(self): # type: () -> bool return False def add_style(self, style): # type: (Style) -> None pastel_style = StyleConverter.convert(style) self._formatter.add_style( style.tag, pastel_style.foreground, pastel_style.background, pastel_style.options, ) PK!clikit/handler/__init__.pyPK!o"clikit/handler/callback_handler.pyfrom typing import Callable from clikit.api.args import Args from clikit.api.io import IO class CallbackHandler: """ Delegates command handling to a callable. """ def __init__(self, callback): # type: (Callable) -> None self._calllback = callback def handle(self, args, io, _): # type: (Args, IO, ...) -> int return self._calllback(args, io) PK!4//clikit/handler/help/__init__.pyfrom .help_text_handler import HelpTextHandler PK!#clikit/handler/help/help_handler.pyfrom clikit.api.args.args import Args from clikit.api.io import IO class HelpHandler: """ Handler for the "help" command. """ def handle(self, args, io): # type: (Args, IO) -> int print(args) return 0 PK!ڦ(clikit/handler/help/help_text_handler.pyfrom clikit.api.args import Args from clikit.api.command import Command from clikit.api.io import IO from clikit.ui.help import ApplicationHelp from clikit.ui.help import CommandHelp class HelpTextHandler: """ Displays help as text format. """ def handle(self, args, io, command): # type: (Args, IO, Command) -> int application = command.application if args.is_argument_set("command"): the_command = application.get_command(args.argument("command")) usage = CommandHelp(the_command) else: usage = ApplicationHelp(application) usage.render(io) return 0 PK! h_bbclikit/io/__init__.pyfrom .buffered_io import BufferedIO from .console_io import ConsoleIO from .null_io import NullIO PK!Fclikit/io/buffered_io.pyfrom typing import Optional from clikit.api.formatter import Formatter from clikit.api.io import IO from clikit.api.io import Input from clikit.api.io import Output from clikit.formatter import PlainFormatter from .input_stream import StringInputStream from .output_stream import BufferedOutputStream class BufferedIO(IO): """ An I/O that reads from and writes to a buffer. """ def __init__( self, input_data="", formatter=None ): # type: (str, Optional[Formatter]) -> None if formatter is None: formatter = PlainFormatter() input = Input(StringInputStream(input_data)) output = Output(BufferedOutputStream(), formatter) error_output = Output(BufferedOutputStream(), formatter) super(BufferedIO, self).__init__(input, output, error_output) def set_input(self, data): # type: (str) -> None self.input.stream.set(data) def append_input(self, data): # type: (str) -> None self.input.stream.append(data) def clear_input(self): # type: () -> None self.input.stream.clear() def fetch_output(self): # type: () -> str return self.output.stream.fetch() def clear_output(self): # type: () -> None self.output.stream.clear() def fetch_error(self): # type: () -> str return self.error_output.stream.fetch() def clear_error(self): # type: () -> None self.error_output.stream.clear() PK! None if input is None: input_stream = StandardInputStream() input = Input(input_stream) if output is None: output_stream = StandardOutputStream() if output_stream.supports_ansi(): formatter = AnsiFormatter() else: formatter = PlainFormatter() output = Output(output_stream, formatter) if error_output is None: error_stream = ErrorOutputStream() if error_stream.supports_ansi(): formatter = AnsiFormatter() else: formatter = PlainFormatter() error_output = Output(error_stream, formatter) super(ConsoleIO, self).__init__(input, output, error_output) def get_default_terminal_dimensions(self): # type: () -> Rectangle terminal = Terminal() return Rectangle(terminal.width, terminal.height) PK!q_"clikit/io/input_stream/__init__.pyfrom .null_input_stream import NullInputStream from .standard_input_stream import StandardInputStream from .stream_input_stream import StreamInputStream from .string_input_stream import StringInputStream PK!+clikit/io/input_stream/null_input_stream.pyfrom typing import Optional from clikit.api.io.input_stream import InputStream class NullInputStream(InputStream): """ An input stream that returns nothing. """ def read(self, length): # type: (int) -> str """ Reads the given amount of characters from the stream. """ return "" def read_line(self, length=None): # type: (Optional[int]) -> str """ Reads a line from the stream. """ return "" def close(self): # type: () -> None """ Closes the stream """ def is_closed(self): # type: () -> bool """ Returns whether the stream is closed or not """ return False PK!%yp:!!/clikit/io/input_stream/standard_input_stream.pyimport sys from .stream_input_stream import StreamInputStream class StandardInputStream(StreamInputStream): """ An input stream that reads from the standard input. """ def __init__(self): # type: () -> None super(StandardInputStream, self).__init__(sys.stdin) PK!Ex˕-clikit/io/input_stream/stream_input_stream.pyimport io from typing import Optional from clikit.api.io.input_stream import InputStream class StreamInputStream(InputStream): """ An input stream that reads from a stream. """ def __init__(self, stream): # type: (io.TextIOWrapper) -> None self._stream = stream if hasattr("stream", "seekable") and stream.seekable(): stream.seek(0) def read(self, length): # type: (int) -> str """ Reads the given amount of characters from the stream. """ if self.is_closed(): raise io.UnsupportedOperation("Cannot read from a closed input.") try: data = self._stream.read(length) except EOFError: return "" if data: return data return "" def read_line(self, length=None): # type: (Optional[int]) -> str """ Reads a line from the stream. """ if self.is_closed(): raise io.UnsupportedOperation("Cannot read from a closed input.") try: return self._stream.readline(length) or "" except EOFError: return "" def close(self): # type: () -> None """ Closes the stream """ self._stream.close() def is_closed(self): # type: () -> bool """ Returns whether the stream is closed or not """ return self._stream.closed PK!1oo-clikit/io/input_stream/string_input_stream.pyfrom io import BytesIO from io import SEEK_END from clikit.utils._compat import encode from .stream_input_stream import StreamInputStream class StringInputStream(StreamInputStream): """ An input stream that reads from a string. """ def __init__(self, string=""): # type: (str) -> None self._stream = BytesIO() super(StringInputStream, self).__init__(self._stream) self.set(string) def clear(self): # type: () -> None self._stream.truncate(0) self._stream.seek(0) def set(self, string): # type: (str) -> None self.clear() self._stream.write(encode(string)) self._stream.seek(0) def append(self, string): # type: (str) -> None pos = self._stream.tell() self._stream.seek(0, SEEK_END) self._stream.write(encode(string)) self._stream.seek(pos) PK!T;clikit/io/null_io.pyfrom clikit.api.io import IO from clikit.api.io import Input from clikit.api.io import Output from .input_stream import NullInputStream from .output_stream import NullOutputStream class NullIO(IO): """ An I/O that does nothing. """ def __init__(self): # type: () -> None input = Input(NullInputStream()) output = Output(NullOutputStream()) error_output = Output(NullOutputStream()) super(NullIO, self).__init__(input, output, error_output) PK!|f  #clikit/io/output_stream/__init__.pyfrom .buffered_output_stream import BufferedOutputStream from .error_output_stream import ErrorOutputStream from .null_output_stream import NullOutputStream from .standard_output_stream import StandardOutputStream from .stream_output_stream import StreamOutputStream PK!5Z7##1clikit/io/output_stream/buffered_output_stream.pyfrom clikit.api.io import OutputStream from clikit.utils._compat import decode class BufferedOutputStream(OutputStream): """ An output stream that writes to a buffer. """ def __init__(self): # type: () -> None self._buffer = "" self._closed = False def fetch(self): # type: () -> str return self._buffer def clear(self): # type: () -> None self._buffer = "" def write(self, string): # type: (str) -> None """ Writes a string to the stream. """ if self._closed: raise IOError("Cannot read from a closed input.") self._buffer += decode(string) def flush(self): # type: () -> None """ Flushes the stream and forces all pending text to be written out. """ if self._closed: raise IOError("Cannot read from a closed input.") def supports_ansi(self): # type: () -> bool """ Returns whether the stream supports ANSI format codes. """ return False def close(self): # type: () -> None """ Closes the stream. """ self._closed = True def is_closed(self): # type: () -> bool """ Returns whether the stream is closed. """ return self._closed PK!.clikit/io/output_stream/error_output_stream.pyimport sys from .stream_output_stream import StreamOutputStream class ErrorOutputStream(StreamOutputStream): """ An output stream that writes to the error output. """ def __init__(self): # type: () -> None super(ErrorOutputStream, self).__init__(sys.stderr) PK!79-clikit/io/output_stream/null_output_stream.pyfrom clikit.api.io.output_stream import OutputStream class NullOutputStream(OutputStream): """ An output stream that ignores all output. """ def write(self, string): # type: (str) -> None """ Writes a string to the stream. """ def flush(self): # type: () -> None """ Flushes the stream and forces all pending text to be written out. """ def supports_ansi(self): # type: () -> bool """ Returns whether the stream supports ANSI format codes. """ return False def close(self): # type: () -> None """ Closes the stream. """ def is_closed(self): # type: () -> bool """ Returns whether the stream is closed. """ return False PK!((1clikit/io/output_stream/standard_output_stream.pyimport sys from .stream_output_stream import StreamOutputStream class StandardOutputStream(StreamOutputStream): """ An output stream that writes to the standard output. """ def __init__(self): # type: () -> None super(StandardOutputStream, self).__init__(sys.stdout) PK!r /clikit/io/output_stream/stream_output_stream.pyimport io import os import platform import sys from clikit.api.io.output_stream import OutputStream class StreamOutputStream(OutputStream): """ An output stream that writes to a stream. """ def __init__(self, stream): # type: (io.TextIOWrapper) -> None self._stream = stream def write(self, string): # type: (str) -> None """ Writes a string to the stream. """ if self.is_closed(): raise io.UnsupportedOperation("Cannot write to a closed input.") self._stream.write(string) self._stream.flush() def flush(self): # type: () -> None """ Flushes the stream and forces all pending text to be written out. """ if self.is_closed(): raise io.UnsupportedOperation("Cannot write to a closed input.") self._stream.flush() def supports_ansi(self): # type: () -> bool """ Returns whether the stream supports ANSI format codes. """ if platform.system().lower() == "windows": shell_supported = ( os.getenv("ANSICON") is not None or "ON" == os.getenv("ConEmuANSI") or "xterm" == os.getenv("Term") ) if shell_supported: return True if not hasattr(self._stream, "fileno"): return False # Checking for Windows version # If we have a compatible version # activate color support windows_version = sys.getwindowsversion() major, build = windows_version[0], windows_version[2] if (major, build) < (10, 14393): return False # Activate colors if possible import ctypes import ctypes.wintypes FILE_TYPE_CHAR = 0x0002 FILE_TYPE_REMOTE = 0x8000 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 kernel32 = ctypes.windll.kernel32 fileno = self._stream.fileno() if fileno == 1: h = kernel32.GetStdHandle(-11) elif fileno == 2: h = kernel32.GetStdHandle(-12) else: return False if h is None or h == ctypes.wintypes.HANDLE(-1): return False if (kernel32.GetFileType(h) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR: return False mode = ctypes.wintypes.DWORD() if not kernel32.GetConsoleMode(h, ctypes.byref(mode)): return False if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: kernel32.SetConsoleMode( h, mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) return True return False if not hasattr(self._stream, "fileno"): return False try: return os.isatty(self._stream.fileno()) except io.UnsupportedOperation: return False def close(self): # type: () -> None """ Closes the stream. """ self._stream.close() def is_closed(self): # type: () -> bool """ Returns whether the stream is closed. """ return self._stream.closed PK!Y..clikit/resolver/__init__.pyfrom .default_resolver import DefaultResolver PK!xA44#clikit/resolver/default_resolver.pyfrom typing import Iterator from typing import List from typing import Optional from clikit.api.args import RawArgs from clikit.api.command import Command from clikit.api.command import CommandCollection from clikit.api.resolver import CommandResolver from clikit.api.resolver import ResolvedCommand from clikit.api.resolver.exceptions import CannotResolveCommandException from .resolve_result import ResolveResult class DefaultResolver(CommandResolver): """ Parses the raw console arguments for the command to execute. """ def resolve( self, args, application ): # type: (RawArgs, Application) -> ResolvedCommand tokens = args.tokens named_commands = application.named_commands tokens = iter(tokens) arguments_to_test = self.get_arguments_to_test(tokens) options_to_test = self.get_options_to_test(tokens) result = self.process_arguments( args, named_commands, arguments_to_test, options_to_test ) if result: return self.create_resolved_command(result) # Try to find a command for the passed arguments and options. if arguments_to_test: raise CannotResolveCommandException.name_not_found( arguments_to_test[0], named_commands ) # If no arguments were passed, run the application's default command. result = self.process_default_commands(args, application.default_commands) if result: return self.create_resolved_command(result) raise CannotResolveCommandException.no_default_command() def process_arguments( self, args, named_commands, arguments_to_test, options_to_test ): # type: (RawArgs, CommandCollection, List[str], List[str]) -> Optional[ResolveResult] current_command = None # Parse the arguments for command names until we fail to find a # matching command for name in arguments_to_test: if name not in named_commands: break next_command = named_commands.get(name) current_command = next_command named_commands = current_command.named_sub_commands if not current_command: return return self.process_options(args, current_command, options_to_test) def process_options( self, args, current_command, options_to_test ): # type: (RawArgs, Command, List[str]) -> Optional[ResolveResult] for option in options_to_test: commands = current_command.named_sub_commands if option not in commands: continue next_command = commands.get(option) return self.process_default_sub_commands(args, current_command) def process_default_sub_commands( self, args, current_command ): # type: (RawArgs, Command) -> Optional[ResolveResult] result = self.process_default_commands( args, current_command.default_sub_commands ) if result: return result # No default commands, return the current command return ResolveResult(current_command, args) def process_default_commands( self, args, default_commands ): # type: (RawArgs, CommandCollection) -> Optional[ResolveResult] first_result = None for default_command in default_commands: resolved_command = ResolveResult(default_command, args) if resolved_command.is_parsable(): return resolved_command if not first_result: first_result = resolved_command # Return the first default command if one was found return first_result def get_arguments_to_test(self, tokens): # type: (Iterator[str]) -> List[str] arguments_to_test = [] token = next(tokens, None) while token: # "--" stops argument parsing if token == "--": break # Stop argument parsing when we reach the first option. # Command names must be passed before any option. The reason # is that we cannot determine whether an argument after an # option is the value of that option or an argument by itself # without getting the input definition of the corresponding # command first. # For example, in the command "server -f add" we don't know # whether "add" is the value of the "-f" option or an argument. # Hence we stop argument parsing after "-f" and assume that # "server" (or "server -f") is the command to execute. if token[:1] and token[0] == "-": break arguments_to_test.append(token) token = next(tokens, None) return arguments_to_test def get_options_to_test(self, tokens): # type: (Iterator[str]) -> List[str] options_to_test = [] token = next(tokens, None) while token: # "--" stops option parsing if token == "--": break if token[:1] and token[0] == "-": if token[:2] == "--" and len(token) > 2: options_to_test.append(token[2:]) elif len(token) == 2: options_to_test.append(token[1:]) token = next(tokens, None) return options_to_test def create_resolved_command( self, result ): # type: (ResolveResult) -> ResolvedCommand if not result.is_parsable(): raise result.parse_error return ResolvedCommand(result.command, result.parsed_args) PK!u#9rr!clikit/resolver/resolve_result.pyfrom clikit.api.args import Args from clikit.api.args import RawArgs from clikit.api.args.exceptions import CannotParseArgsException from clikit.api.command import Command class ResolveResult(object): """ An intermediate result created during resolving. """ def __init__(self, command, raw_args): # type: (Command, RawArgs) -> None self._command = command self._raw_args = raw_args self._parsed_args = None self._parse_error = None self._parsed = False @property def command(self): # type: () -> Command return self._command @property def raw_args(self): # type: () -> RawArgs return self._raw_args @property def parsed_args(self): # type: () -> Args if not self._parsed: self._parse() return self._parsed_args @property def parse_error(self): # type: () -> CannotParseArgsException if not self._parsed: self._parse() return self._parse_error def is_parsable(self): # type: () -> bool if not self._parsed: self._parse() return self.parse_error is None def _parse(self): # type: () -> None try: self._parsed_args = self._command.parse(self._raw_args) except CannotParseArgsException as e: self._parse_error = e self._parsed = True PK!+BBclikit/ui/__init__.pyfrom .component import Component from .rectangle import Rectangle PK!-,,clikit/ui/alignment/__init__.pyfrom .label_alignment import LabelAlignment PK!'3&clikit/ui/alignment/label_alignment.pyfrom typing import List from clikit.api.formatter import Formatter from clikit.ui.components import LabeledParagraph class LabelAlignment: """ Aligns labeled paragraphs. """ def __init__(self): # type: () -> None self._paragraphs = [] # type: List[LabeledParagraph] self._indentations = [] # type: List[int] self._text_offset = 0 def add(self, paragraph, indentation=0): # type: (LabeledParagraph, int) -> None if paragraph.is_aligned(): self._paragraphs.append(paragraph) self._indentations.append(indentation) def align(self, formatter, indentation=0): # type: (Formatter, int) -> None self._text_offset = 0 for i, paragraph in enumerate(self._paragraphs): label = formatter.remove_format(paragraph.label) text_offset = self._indentations[i] + len(label) + paragraph.padding self._text_offset = max(self._text_offset, text_offset) self._text_offset += indentation def set_text_offset(self, offset): # type: (int) -> None self._text_offset = offset @property def text_offset(self): # type: () -> int return self._text_offset PK!njclikit/ui/component.pyfrom clikit.api.io import IO class Component(object): """ A UI component that can be rendered on the I/O. """ def render(self, io, indentation=0): # type: (IO, int) -> None raise NotImplementedError() PK![ clikit/ui/components/__init__.pyfrom .border_util import BorderUtil from .cell_wrapper import CellWrapper from .choice_question import ChoiceQuestion from .confirmation_question import ConfirmationQuestion from .empty_line import EmptyLine from .exception_trace import ExceptionTrace from .labeled_paragraph import LabeledParagraph from .name_version import NameVersion from .paragraph import Paragraph from .progress_bar import ProgressBar from .progress_indicator import ProgressIndicator from .question import Question from .table import Table PK!^>#clikit/ui/components/border_util.pyfrom __future__ import unicode_literals import math from typing import List from clikit.api.formatter import Style from clikit.api.io import IO from clikit.ui.style.alignment import Alignment from clikit.ui.style.border_style import BorderStyle from clikit.utils.string import get_string_length class BorderUtil: """ Contains utility methods to draw borders and bordered cells. """ @classmethod def draw_top_border( cls, io, style, column_lengths, indentation=0 ): # type: (IO, BorderStyle, List[int], int) -> None cls.draw_border( io, column_lengths, indentation, style.line_ht_char, style.corner_tl_char, style.crossing_t_char, style.corner_tr_char, style.style, ) @classmethod def draw_middle_border( cls, io, style, column_lengths, indentation=0 ): # type: (IO, BorderStyle, List[int], int) -> None cls.draw_border( io, column_lengths, indentation, style.line_hc_char, style.crossing_l_char, style.crossing_c_char, style.crossing_r_char, style.style, ) @classmethod def draw_bottom_border( cls, io, style, column_lengths, indentation=0 ): # type: (IO, BorderStyle, List[int], int) -> None cls.draw_border( io, column_lengths, indentation, style.line_hb_char, style.corner_bl_char, style.crossing_b_char, style.corner_br_char, style.style, ) @classmethod def draw_row( cls, io, style, row, column_lengths, alignments, cell_format, padding_char, cell_style=None, indentation=0, ): # type: (IO, BorderStyle, List[str], List[int], List[int], str, str, Style, int) -> None total_lines = 0 # Split all cells into lines for col, cell in enumerate(row): row[col] = cell.split("\n") total_lines = max(total_lines, len(row[col])) nb_columns = len(row) border_vl_char = io.format(style.line_vl_char, style.style) border_vc_char = io.format(style.line_vc_char, style.style) border_vr_char = io.format(style.line_vr_char, style.style) for i in range(total_lines): line = " " * indentation line += border_vl_char for col, remaining_lines in enumerate(row): if remaining_lines: cell_line = remaining_lines.pop(0) else: cell_line = "" total_pad_length = column_lengths[col] - get_string_length( cell_line, io ) padding_left = "" padding_right = "" if total_pad_length >= 0: try: alignment = alignments[col] except IndexError: alignment = Alignment.LEFT if alignment == Alignment.LEFT: padding_right = padding_char * total_pad_length elif alignment == Alignment.RIGHT: padding_left = padding_char * total_pad_length else: left_pad_length = int(math.floor(total_pad_length / 2)) padding_left = padding_char * left_pad_length padding_right = padding_char * ( total_pad_length - left_pad_length ) line += io.format( cell_format.format(padding_left + cell_line + padding_right), cell_style, ) if col < nb_columns - 1: line += border_vc_char else: line += border_vr_char # Remove trailing space io.write(line.rstrip() + "\n") @classmethod def draw_border( cls, io, column_lengths, indentation, line_char, crossing_l_char, crossing_c_char, crossing_r_char, style=None, ): # type: (IO, List[int], int, str, str, str, str, Style) -> None line = " " * indentation line += crossing_l_char l = len(column_lengths) for i in range(l): line += line_char * column_lengths[i] if i < l - 1: line += crossing_c_char else: line += crossing_r_char line = line.rstrip() if line: io.write(io.format(line, style) + "\n") PK!NKK$clikit/ui/components/cell_wrapper.pyfrom __future__ import division import textwrap from typing import List from clikit.api.formatter import Formatter from clikit.utils.string import get_max_line_length from clikit.utils.string import get_max_word_length from clikit.utils.string import get_string_length class CellWrapper: """ Wraps cells to fit a given screen width with a given number of columns. """ def __init__(self): # type: () -> None self._cells = [] self._cell_lengths = [] self._wrapped_rows = [] self._nb_columns = 0 self._column_lengths = [] self._word_wraps = False self._word_cuts = False self._max_total_width = 0 self._total_width = 0 @property def cells(self): # type: () -> List[str] return self._cells @property def wrapped_rows(self): # type: () -> List[List[str]] return self._wrapped_rows @property def column_lengths(self): # type: () -> List[int] return self._column_lengths @property def nb_columns(self): # type: () -> int return self._nb_columns @property def max_total_width(self): # type: () -> int return self._max_total_width @property def total_width(self): # type: () -> int return self._total_width def add_cell(self, cell): # type: (str) -> CellWrapper self._cells.append(cell.rstrip()) return self def add_cells(self, cells): # type: (List[str]) -> CellWrapper for cell in cells: self.add_cell(cell) return self def get_estimated_nb_columns(self, max_total_width): # type: (int) -> int """ Returns an estimated number of columns for the given maximum width. """ row_width = 0 for i, cell in enumerate(self._cells): row_width += get_string_length(cell) if row_width > max_total_width: return i return len(self._cells) - 1 def has_word_wraps(self): # type: () -> bool return self._word_wraps def has_word_cuts(self): # type: () -> bool return self._word_cuts def fit( self, max_total_width, nb_columns, formatter ): # type: (int, int, Formatter) -> None self._reset_state(max_total_width, nb_columns) self._init_rows(formatter) # If the cells fit within the max width we're good if self._total_width <= max_total_width: return self._wrap_columns(formatter) def _reset_state(self, max_total_width, nb_columns): # type: (int, int) -> None self._wrapped_rows = [] self._nb_columns = nb_columns self._cell_lengths = [] self._column_lengths = [0] * nb_columns self._word_wraps = False self._word_cuts = False self._max_total_width = max_total_width self._total_width = 0 def _init_rows(self, formatter): # type: (Formatter) -> None col = 0 for i, cell in enumerate(self._cells): if col == 0: self._wrapped_rows.append([""] * self._nb_columns) self._cell_lengths.append([0] * self._nb_columns) self._wrapped_rows[-1][col] = cell self._cell_lengths[-1][col] = get_string_length(cell, formatter) self._column_lengths[col] = max( self._column_lengths[col], self._cell_lengths[-1][col] ) col = (col + 1) % self._nb_columns # Fill last row if col > 0: while col < self._nb_columns: self._wrapped_rows[-1][col] = "" self._cell_lengths[-1][col] = 0 col += 1 self._total_width = sum(self._column_lengths) def _wrap_columns(self, formatter): # type: (Formatter) -> None available_width = self._max_total_width long_column_lengths = self._column_lengths[:] # Filter "short" column, i.e. columns that are not wrapped # We distribute the available screen width by the number of columns # and decide that all columns that are shorter than their share are # "short". # This process is repeated until no more "short" columns are found. repeat = True while repeat: threshold = available_width / len(long_column_lengths) repeat = False for col, length in enumerate(long_column_lengths): if length is not None and length <= threshold: available_width -= length long_column_lengths[col] = None repeat = True # Calculate actual and available width actual_width = 0 last_adapted_col = 0 # "Long" columns, i.e. columns that need to be wrapped, are added to # the actual width for col, length in enumerate(long_column_lengths): if length is None: continue actual_width += length last_adapted_col = col # Fit columns into available width for col, length in enumerate(long_column_lengths): if length is None: continue # Keep ratios of column lengths and distribute them among the # available width self._column_lengths[col] = int( round((length / actual_width) * available_width) ) if col == last_adapted_col: # Fix rounding errors self._column_lengths[col] += self._max_total_width - sum( self._column_lengths ) self._wrap_column(col, self._column_lengths[col], formatter) # Recalculate the column length based on the actual wrapped length self._refresh_column_length(col) # Recalculate the actual width based on the changed length. actual_width = actual_width - length + self._column_lengths[col] self._total_width = sum(self._column_lengths) def _wrap_column( self, col, column_length, formatter ): # type: (int, List[int], Formatter) -> None for i, row in enumerate(self._wrapped_rows): cell = row[col] cell_length = self._cell_lengths[i][col] if cell_length > column_length: self._word_wraps = True if not self._word_cuts: min_length_without_cut = get_max_word_length(cell, formatter) if min_length_without_cut > column_length: self._word_cuts = True # TODO: use format aware wrapper wrapped_cell = "\n".join(textwrap.wrap(cell, column_length)) self._wrapped_rows[i][col] = wrapped_cell # Refresh cell length self._cell_lengths[i][col] = get_max_line_length( wrapped_cell, formatter ) def _refresh_column_length(self, col): # type: (int) -> None self._column_lengths[col] = 0 for i, row in enumerate(self._wrapped_rows): self._column_lengths[col] = max( self._column_lengths[col], self._cell_lengths[i][col] ) PK!Ob//'clikit/ui/components/choice_question.pyimport os import re from .question import Question class SelectChoiceValidator: def __init__(self, question): """ Constructor. """ self._question = question self._values = question.choices def validate(self, selected): """ Validate a choice. """ # Collapse all spaces. if isinstance(selected, int): selected = str(selected) selected_choices = selected.replace(" ", "") if self._question.supports_multiple_choices(): # Check for a separated comma values if not re.match("^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$", selected_choices): raise ValueError(self._question.error_message.format(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 ValueError( "The provided answer is ambiguous. Value should be one of {}.".format( " or ".join(str(r) for r in 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 ValueError(self._question.error_message.format(value)) multiselect_choices.append(result) if self._question.supports_multiple_choices(): return multiselect_choices return multiselect_choices[0] class ChoiceQuestion(Question): """ Multiple choice question. """ def __init__(self, question, choices, default=None): super(ChoiceQuestion, self).__init__(question, default) self._multi_select = False self._choices = choices self._validator = SelectChoiceValidator(self).validate self._autocomplete_values = choices self._prompt = " > " self._error_message = 'Value "{}" is invalid' @property def error_message(self): return self._error_message @property def choices(self): return self._choices def supports_multiple_choices(self): return self._multi_select def set_multi_select(self, multi_select): self._multi_select = multi_select def set_error_message(self, message): self._error_message = message def _write_prompt(self, io): """ Outputs the question prompt. """ message = self._question default = self._default if default is None: message = "{}: ".format(message) elif self._multi_select: choices = self._choices default = default.split(",") for i, value in enumerate(default): default[i] = choices[int(value.strip())] message = "{} [{}]:".format( message, ", ".join(default) ) else: choices = self._choices message = "{} [{}]:".format( message, choices[int(default)] ) if len(self._choices) > 1: width = max(*map(len, [str(k) for k, _ in enumerate(self._choices)])) else: width = 1 messages = [message] for key, value in enumerate(self._choices): messages.append(" [{:{}}] {}".format(key, width, value)) io.error_line("\n".join(messages)) message = self._prompt io.error(message) PK!T-clikit/ui/components/confirmation_question.pyimport re from .question import Question class ConfirmationQuestion(Question): """ Represents a yes/no question. """ def __init__(self, question, default=True, true_answer_regex="(?i)^y"): super(ConfirmationQuestion, self).__init__(question, default) self._true_answer_regex = true_answer_regex self._normalizer = self._get_default_normalizer def _write_prompt(self, io): message = self._question message = "{} (yes/no) [{}] ".format( message, "yes" if self._default else "no" ) io.error(message) 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!}fV"clikit/ui/components/empty_line.pyfrom clikit.api.io import IO from clikit.ui import Component class EmptyLine(Component): """ An empty line. """ def render(self, io, indentation=0): # type: (IO, int) -> None io.write("\n") PK!Ǡ)'clikit/ui/components/exception_trace.pyimport ast import inspect import keyword import math import sys import traceback from clikit.api.io import IO class ExceptionTrace(object): """ Renders the trace of an exception. """ THEME = { "comment": "", "keyword": "", "builtin": "", "literal": "", } AST_ELEMENTS = { "builtins": __builtins__.keys() if type(__builtins__) is dict else dir(__builtins__), "keywords": [ getattr(ast, cls) for cls in dir(ast) if keyword.iskeyword(cls.lower()) and inspect.isclass(getattr(ast, cls)) and issubclass(getattr(ast, cls), ast.AST) ], } def __init__(self, exception): # type: (Exception) -> None self._exception = exception self._exc_info = sys.exc_info() def render(self, io): # type: (IO) -> None if hasattr(self._exception, "__traceback__"): tb = self._exception.__traceback__ else: tb = self._exc_info[2] title = "\n[{}]\n{}".format( self._exception.__class__.__name__, str(self._exception) ) io.write_line(title) if io.is_verbose(): io.write_line("") self._render_traceback(io, tb) def _render_traceback(self, io, tb): # type: (IO, ...) -> None frames = [] while tb: frames.append(self._format_traceback_frame(io, tb)) tb = tb.tb_next io.write_line("Traceback (most recent call last):") io.write_line("".join(traceback.format_list(frames))) def _format_traceback_frame(self, io, tb): # type: (IO, ...) -> Tuple[Any] frame_info = inspect.getframeinfo(tb) filename = frame_info.filename lineno = frame_info.lineno function = frame_info.function line = frame_info.code_context[0] stripped_line = line.lstrip(" ") try: tree = ast.parse(stripped_line, mode="exec") formatted = self._format_tree(tree, stripped_line, io) formatted = (len(line) - len(stripped_line)) * " " + formatted except SyntaxError: formatted = line return ( io.format("{}".format(filename)), lineno, function, formatted, ) def _format_tree(self, tree, source, io): offset = 0 chunks = [] nodes = [n for n in ast.walk(tree)] displayed_nodes = [] for node in nodes: nodecls = node.__class__ nodename = nodecls.__name__ if "col_offset" not in dir(node): continue if nodecls in self.AST_ELEMENTS["keywords"]: displayed_nodes.append((node, nodename.lower(), "keyword")) elif nodecls == ast.Name and node.id in self.AST_ELEMENTS["builtins"]: displayed_nodes.append((node, node.id, "builtin")) elif nodecls == ast.Str: displayed_nodes.append((node, "'{}'".format(node.s), "literal")) elif nodecls == ast.Num: displayed_nodes.append((node, str(node.n), "literal")) displayed_nodes.sort(key=lambda elem: elem[0].col_offset) for dn in displayed_nodes: node = dn[0] s = dn[1] theme = dn[2] begin_col = node.col_offset src_chunk = source[offset:begin_col] chunks.append(src_chunk) chunks.append(io.format("{}{}".format(self.THEME[theme], s))) offset = begin_col + len(s) chunks.append(source[offset:]) return "".join(chunks) PK!Jb)clikit/ui/components/labeled_paragraph.pyfrom __future__ import unicode_literals import re import textwrap from clikit.api.io import IO from clikit.ui import Component class LabeledParagraph(Component): """ A paragraph with a label on its left. """ def __init__( self, label, text, padding=2, aligned=True ): # type: (str, str, int, bool) -> None self._label = label self._text = text self._padding = padding self._aligned = aligned self._alignment = None @property def label(self): # type: () -> str return self._label @property def text(self): # type: () -> str return self._text @property def padding(self): # type: () -> int return self._padding def is_aligned(self): # type: () -> bool return self._aligned def set_alignment(self, alignment): # type: (LabelAlignment) -> None self._alignment = alignment def render(self, io, indentation=0): # type: (IO, int) -> None line_prefix = " " * indentation visible_label = io.remove_format(self._label) style_tag_length = len(self._label) - len(visible_label) if self._aligned and self._alignment: text_offset = self._alignment.text_offset - indentation else: text_offset = 0 text_offset = max(text_offset, len(visible_label) + self._padding) text_prefix = " " * text_offset # 1 trailing space text_width = io.terminal_dimensions.width - 1 - text_offset - indentation text = re.sub( r"\n(?!\n)", "\n" + line_prefix + text_prefix, "\n".join(textwrap.wrap(self._text, text_width)), ) # Add the total length of the style tags ("", ...) label_width = text_offset + style_tag_length io.write( "{}{:<{}}{}".format( line_prefix, self._label, label_width, text.rstrip() ).rstrip() + "\n" ) PK!A]pp$clikit/ui/components/name_version.pyfrom clikit.api.io import IO from clikit.api.config import ApplicationConfig from clikit.ui import Component from .paragraph import Paragraph class NameVersion(Component): """ Renders the name and version of an application. """ def __init__(self, config): # type: (ApplicationConfig) -> None self._config = config def render(self, io, indentation=0): # type: (IO, int) -> None if self._config.display_name and self._config.version: paragraph = Paragraph( "{} version {}".format( self._config.display_name, self._config.version ) ) elif self._config.display_name: paragraph = Paragraph("{}".format(self._config.display_name)) else: paragraph = Paragraph("Console Tool") paragraph.render(io, indentation) PK!H!clikit/ui/components/paragraph.pyimport re import textwrap from clikit.api.io import IO from clikit.ui import Component class Paragraph(Component): """ A paragraph of text. The paragraph is wrapped into the dimensions of the output. """ def __init__(self, text): # type: (str) -> None self._text = text def render(self, io, indentation=0): # type: (IO, int) -> None line_prefix = " " * indentation text_width = io.terminal_dimensions.width - 1 - indentation text = re.sub( r"\n(?!\n)", "\n" + line_prefix, "\n".join(textwrap.wrap(self._text, text_width)), ) io.write(line_prefix + text.rstrip() + "\n") PK!,f))$clikit/ui/components/progress_bar.py# -*- coding: utf-8 -*- from __future__ import division import time import re import math from clikit.api.io import IO from clikit.api.io.flags import DEBUG from clikit.api.io.flags import VERBOSE from clikit.api.io.flags import VERY_VERBOSE from clikit.formatter import AnsiFormatter from clikit.utils.time import format_time 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, io, max=0): # type: (IO, int) -> None """ Constructor. """ self._io = io 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 isinstance(self._io.error_output.formatter, AnsiFormatter): # 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. """ 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. """ 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): """ Output the current progress string. """ if self._io.is_quiet(): return if self._format is None: self._set_real_format( self._internal_format or self._determine_best_format() ) self._overwrite( re.sub( r"(?i)%([a-z\-_]+)(?::([^%]+))?%", self._overwrite_callback, self._format, ) ) def _overwrite_callback(self, matches): if hasattr(self, "_formatter_{}".format(matches.group(1))): text = str(getattr(self, "_formatter_{}".format(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. """ self._max = max(0, mx) if self._max: self._step_width = len(str(self._max)) else: self._step_width = 4 def _overwrite(self, message): """ Overwrites a previous message to the output. """ 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 > len(self._io.remove_format(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._io.error("\x0D") elif self._step > 0: # move to new line self._io.error_line("") if self._format_line_count: self._io.error("\033[{}A".format(self._format_line_count)) self._io.error("\n".join(lines)) self._io.error_output.flush() self._last_messages_length = 0 for line in lines: length = len(self._io.remove_format(line)) if length > self._last_messages_length: self._last_messages_length = length def _determine_best_format(self): verbosity = self._io.verbosity if verbosity == VERBOSE: if self._max: return "verbose" return "verbose_nomax" elif verbosity == VERY_VERBOSE: if self._max: return "very_verbose" return "very_verbose_nomax" elif 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 - len(self._io.remove_format(self.progress_char)) ) display += self.progress_char + self.empty_bar_char * int(empty_bars) return display def _formatter_elapsed(self): return format_time(time.time() - self._start_time) def _formatter_remaining(self): if not self._max: raise RuntimeError( "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 format_time(remaining) def _formatter_estimated(self): if not self._max: raise RuntimeError( "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)) PK!UFPP*clikit/ui/components/progress_indicator.pyimport re import time import threading from contextlib import contextmanager from typing import List from typing import Optional from clikit.api.io import IO from clikit.formatter import AnsiFormatter from clikit.utils.time import format_time class ProgressIndicator(object): """ A process indicator. """ 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, io, fmt=None, interval=100, values=None ): # type: (IO, Optional[str], int, Optional[List[str]]) -> None self._io = io if fmt is None: fmt = self._determine_best_format() self._fmt = fmt if values is None: values = ["-", "\\", "|", "/"] if len(values) < 2: raise ValueError( "The progress indicator must have at least 2 indicator value characters." ) self._interval = interval self._values = values self._message = None self._update_time = None self._started = False self._current = 0 self._auto_running = None self._auto_thread = None self._start_time = None self._last_message_length = 0 @property def message(self): # type: () -> Optional[str] return self._message def set_message(self, message): # type: (Optional[str]) -> None self._message = message self._display() @property def current_value(self): # type: () -> str return self._values[self._current % len(self._values)] def start(self, message): # type: (str) -> None if self._started: raise RuntimeError("Progress indicator already started.") self._message = message self._started = True self._start_time = time.time() self._update_time = self._get_current_time_in_milliseconds() + self._interval self._current = 0 self._display() def advance(self): # type: () -> None if not self._started: raise RuntimeError("Progress indicator has not yet been started.") if not isinstance(self._io.error_output.formatter, AnsiFormatter): return current_time = self._get_current_time_in_milliseconds() if current_time < self._update_time: return self._update_time = current_time + self._interval self._current += 1 self._display() def finish(self, message, reset_indicator=False): # type: (str, bool) -> None if not self._started: raise RuntimeError("Progress indicator has not yet been started.") if self._auto_thread is not None: self._auto_running.set() self._auto_thread.join() self._message = message if reset_indicator: self._current = 0 self._display() self._io.error_line("") 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, KeyboardInterrupt): self._io.error_line("") 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._io.is_quiet(): return self._overwrite( re.sub( r"(?i){([a-z\-_]+)(?::([^}]+))?}", self._overwrite_callback, self._fmt ) ) def _overwrite_callback(self, matches): if hasattr(self, "_formatter_{}".format(matches.group(1))): text = str(getattr(self, "_formatter_{}".format(matches.group(1)))()) else: text = matches.group(0) return text def _overwrite(self, message): """ Overwrites a previous message to the output. """ if isinstance(self._io.error_output.formatter, AnsiFormatter): self._io.error("\x0D\x1B[2K") self._io.error(message) else: self._io.error_line(message) def _determine_best_format(self): decorated = self._io.error_output.supports_ansi() if self._io.is_very_verbose(): if decorated: return self.VERY_VERBOSE return self.VERY_VERBOSE_NO_ANSI elif self._io.is_verbose(): if decorated: return self.VERY_VERBOSE return self.VERBOSE_NO_ANSI if decorated: return self.NORMAL return self.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.message def _formatter_elapsed(self): return format_time(time.time() - self._start_time) PK!K  clikit/ui/components/question.pyimport getpass import os import subprocess from typing import Any from clikit.api.formatter import Style from clikit.api.io import IO from clikit.utils._compat import decode class Question(object): """ A question that will be asked in a Console. """ def __init__(self, question, default=None): # type: (str, Any) -> None self._question = question self._default = default self._attempts = None self._hidden = False self._hidden_fallback = True self._autocomplete_values = None self._validator = None self._normalizer = None self._error_message = None @property def question(self): # type: () -> str return self._question @property def default(self): # type: () -> Any return self._default @property def autocomplete_values(self): return self._autocomplete_values @property def max_attempts(self): return self._attempts def is_hidden(self): # type: () -> bool return self._hidden def hide(self, hidden=True): # type: (bool) -> None if hidden is True and self._autocomplete_values: raise RuntimeError("A hidden question cannot use the autocompleter.") self._hidden = hidden def set_autocomplete_values(self, autocomplete_values): if self.is_hidden(): raise RuntimeError("A hidden question cannot use the autocompleter.") self._autocomplete_values = autocomplete_values def set_max_attempts(self, attempts): self._attempts = attempts def set_validator(self, validator): self._validator = validator def ask(self, io): # type: (IO) -> str """ Asks the question to the user. """ if not io.is_interactive(): return self.default if not self._validator: return self._do_ask(io) interviewer = lambda: self._do_ask(io) return self._validate_attempts(interviewer, io) def _do_ask(self, io): # type: (IO) -> str """ Asks the question to the user. """ self._write_prompt(io) autocomplete = self._autocomplete_values if autocomplete is None or not self._has_stty_available(): ret = False if self.is_hidden(): try: ret = self._get_hidden_response(io) except RuntimeError: if not self._hidden_fallback: raise if not ret: ret = self._read_from_input(io) else: ret = self._autocomplete(io) if len(ret) <= 0: ret = self._default if self._normalizer: return self._normalizer(ret) return ret def _write_prompt(self, io): """ Outputs the question prompt. """ message = self._question io.error("{} ".format(message)) def _write_error(self, io, error): """ Outputs an error message. """ message = "{}".format(decode(str(error))) io.error_line(message) def _autocomplete(self, io): # type: (IO) -> str """ Autocomplete a question. """ autocomplete = self._autocomplete_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 read each keypress) and echo (we'll do echoing here instead) subprocess.check_output(["stty", "-icanon", "-echo"]) # Add highlighted text style style = Style("hl").fg("black").bg("white") io.error_output.formatter.add_style(style) # Read a keypress while True: c = io.read(1) # Backspace character if c == "\177": if num_matches == 0 and i != 0: i -= 1 # Move cursor backwards io.error("\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 += io.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 io.error(ret[i:]) i = len(ret) if c == "\n": io.error(c) break num_matches = 0 continue else: io.error(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 io.error("\033[K") if num_matches > 0 and ofs != -1: # Save cursor position io.error("\0337") # Write highlighted text io.error("" + matches[ofs][i:] + "") # Restore cursor position io.error("\0338") subprocess.call(["stty", "{}".format(decode(stty_mode))]) return ret def _get_hidden_response(self, io): # type: (IO) -> str """ Gets a hidden response from user. """ return getpass.getpass("", stream=io.error_output.stream) def _validate_attempts(self, interviewer, io): # type: (Callable, IO) -> str """ Validates an attempt. """ error = None attempts = self._attempts while attempts is None or attempts: if error is not None: self._write_error(io, error) try: return self._validator(interviewer()) except Exception as e: error = e if attempts is not None: attempts -= 1 raise error def _read_from_input(self, io): """ Read user input. """ ret = io.read_line(4096) if not ret: raise RuntimeError("Aborted") return decode(ret.strip()) def _has_stty_available(self): devnull = open(os.devnull, "w") try: exit_code = subprocess.call(["stty"], stdout=devnull, stderr=devnull) except Exception: exit_code = 2 return exit_code == 0 PK!9&ppclikit/ui/components/table.pyfrom typing import List from typing import Optional from clikit.api.formatter import Formatter from clikit.api.io import IO from clikit.ui import Component from clikit.ui.components import BorderUtil from clikit.ui.components import CellWrapper from clikit.ui.style import TableStyle from clikit.utils.string import get_string_length class Table(Component): """ A table of rows and columns. """ def __init__(self, style=None): # type: (Optional[TableStyle]) -> None self._header_row = [] self._rows = [] self._nb_columns = None if style is None: style = TableStyle.ascii() self._style = style def set_header_row(self, row): # type: (List[str]) -> Table if self._nb_columns is None: self._nb_columns = len(row) elif len(row) != self._nb_columns: raise ValueError( "Expected the header row to contain {} cells, but got {}.".format( self._nb_columns, len(row) ) ) self._header_row = row return self def add_row(self, row): # type: (List[str]) -> Table if self._nb_columns is None: self._nb_columns = len(row) elif len(row) != self._nb_columns: raise ValueError( "Expected the row to contain {} cells, but got {}.".format( self._nb_columns, len(row) ) ) self._rows.append(row) return self def add_rows(self, rows): # type: (List[List[str]]) -> Table for row in rows: self.add_row(row) return self def set_rows(self, rows): # type: (List[List[str]]) -> Table self._rows = [] for row in rows: self.add_row(row) return self def set_row(self, index, row): # type: (int, List[str]) -> Table if len(row) != self._nb_columns: raise ValueError( "Expected the row to contain {} cells, but got {}.".format( self._nb_columns, len(row) ) ) self._rows[index] = row return self def render(self, io, indentation=0): # type: (IO, int) -> None if not self._rows: return screen_width = io.terminal_dimensions.width excess_column_width = max( get_string_length(self._style.header_cell_format.format("")), get_string_length(self._style.cell_format.format("")), ) wrapper = self._get_cell_wrapper( io, screen_width, excess_column_width, indentation ) return self._render_rows( io, wrapper.wrapped_rows, wrapper.column_lengths, excess_column_width, indentation, ) def _get_cell_wrapper( self, formatter, screen_width, excess_column_width, indentation ): # type: (Formatter, int, int, int) -> CellWrapper border_style = self._style.border_style border_width = ( get_string_length(border_style.line_vl_char) + (self._nb_columns - 1) * get_string_length(border_style.line_vc_char) + get_string_length(border_style.line_vr_char) ) available_width = ( screen_width - indentation - border_width - self._nb_columns * excess_column_width ) wrapper = CellWrapper() for header_cell in self._header_row: wrapper.add_cell(header_cell) for row in self._rows: for cell in row: wrapper.add_cell(cell) wrapper.fit(available_width, self._nb_columns, formatter) return wrapper def _render_rows( self, io, rows, column_lengths, excess_column_length, indentation ): # type: (IO, List[List[str]], List[int], int, int) -> None alignments = self._style.get_column_alignments(len(column_lengths)) border_style = self._style.border_style border_column_lengths = [ length + excess_column_length for length in column_lengths ] BorderUtil.draw_top_border(io, border_style, border_column_lengths, indentation) if self._header_row: BorderUtil.draw_row( io, border_style, rows.pop(0), column_lengths, alignments, self._style.header_cell_format, self._style.padding_char, self._style.header_cell_style, indentation, ) BorderUtil.draw_middle_border( io, border_style, border_column_lengths, indentation ) for row in rows: BorderUtil.draw_row( io, border_style, row, column_lengths, alignments, self._style.cell_format, self._style.padding_char, self._style.cell_style, indentation, ) BorderUtil.draw_bottom_border( io, border_style, border_column_lengths, indentation ) PK!TTclikit/ui/help/__init__.pyfrom .application_help import ApplicationHelp from .command_help import CommandHelp PK!)l jjclikit/ui/help/abstract_help.py# -*- coding: utf-8 -*- from __future__ import unicode_literals import json from typing import Any from typing import Iterable from clikit.api.args.format import Argument from clikit.api.args.format import ArgsFormat from clikit.api.args.format import Option from clikit.api.io import IO from clikit.ui import Component from clikit.ui.components import EmptyLine from clikit.ui.components import LabeledParagraph from clikit.ui.components import Paragraph from clikit.ui.layout import BlockLayout class AbstractHelp(Component): """ Base class for rendering help pages. """ def render(self, io, indentation=0): # type: (IO, int) -> None layout = BlockLayout() self._render_help(layout) layout.render(io, indentation) def _render_help(self, layout): # type: (BlockLayout) -> None raise NotImplementedError() def _render_arguments( self, layout, arguments ): # type: (BlockLayout, Iterable[Argument]) -> None layout.add(Paragraph("ARGUMENTS")) with layout.block(): for argument in arguments: self._render_argument(layout, argument) layout.add(EmptyLine()) def _render_argument( self, layout, argument ): # type: (BlockLayout, Argument) -> None description = argument.description name = "<{}>".format(argument.name) default = argument.default if default is not None and (not isinstance(default, list) or len(default) > 0): description += " {}".format(self._format_value(default)) layout.add(LabeledParagraph(name, description)) def _render_options( self, layout, options ): # type: (BlockLayout, Iterable[Option]) -> None layout.add(Paragraph("OPTIONS")) with layout.block(): for option in options: self._render_option(layout, option) layout.add(EmptyLine()) def _render_global_options( self, layout, options ): # type: (BlockLayout, Iterable[Option]) -> None layout.add(Paragraph("GLOBAL OPTIONS")) with layout.block(): for option in options: self._render_option(layout, option) layout.add(EmptyLine()) def _render_option(self, layout, option): # type: (BlockLayout, Option) -> None description = option.description default = option.default alternative_name = None if option.is_long_name_preferred(): preferred_name = "--{}".format(option.long_name) if option.short_name: alternative_name = "-{}".format(option.short_name) else: preferred_name = "-{}".format(option.short_name) alternative_name = "--{}".format(option.long_name) name = "{}".format(preferred_name) if alternative_name: name += " ({})".format(alternative_name) if ( option.accepts_value() and default is not None and (not isinstance(default, list) or len(default) > 0) ): description += " (default: {})".format(self._format_value(default)) if option.is_multi_valued(): description += " (multiple values allowed)" layout.add(LabeledParagraph(name, description)) def _render_synopsis( self, layout, args_format, app_name, prefix="", last_optional=False ): # type: (BlockLayout, ArgsFormat, str, str, bool) -> None name_parts = [] argument_parts = [] name_parts.append("{}".format(app_name or "console")) for command_name in args_format.get_command_names(): name_parts.append("{}".format(command_name.string)) for command_option in args_format.get_command_options(): if command_option.is_long_name_preferred(): name_parts.append("--{}".format(command_option.long_name)) else: name_parts.append("-{}".format(command_option.short_name)) if last_optional: name_parts[-1] = "[{}]".format(name_parts[-1]) for option in args_format.get_options(False).values(): # \xC2\xA0 is a non-breaking space if option.is_value_required(): fmt = "{}\u00A0<{}>" elif option.is_value_optional(): fmt = "{}\u00A0[<{}>]" else: fmt = "{}" if option.is_long_name_preferred(): option_name = "--{}".format(option.long_name) else: option_name = "-{}".format(option.short_name) argument_parts.append( "[{}]".format(fmt.format(option_name, option.value_name)) ) for argument in args_format.get_arguments().values(): arg_name = argument.name argument_parts.append( ("<{}>" if argument.is_required() else "[<{}>]").format( arg_name + str(int(argument.is_multi_valued()) or "") ) ) if argument.is_multi_valued(): argument_parts.append("... [<{}N>]".format(arg_name)) args_opts = " ".join(argument_parts) name = " ".join(name_parts) layout.add(LabeledParagraph(prefix + name, args_opts, 1, False)) def _format_value(self, value): # type: (Any) -> str return json.dumps(value) PK!!_m2 2 "clikit/ui/help/application_help.pyfrom clikit.api.application import Application from clikit.api.args.format import ArgsFormat from clikit.api.args.format import ArgsFormatBuilder from clikit.api.args.format import Argument from clikit.api.command import Command from clikit.api.command import CommandCollection from clikit.ui.components import EmptyLine from clikit.ui.components import LabeledParagraph from clikit.ui.components import NameVersion from clikit.ui.components import Paragraph from clikit.ui.layout import BlockLayout from .abstract_help import AbstractHelp class ApplicationHelp(AbstractHelp): """ Renders the help of a console application. """ def __init__(self, application): # type: (Application) -> None self._application = application def _render_help(self, layout): # type: (BlockLayout) -> None help = self._application.config.help commands = self._application.named_commands global_args_format = self._application.global_args_format builder = ArgsFormatBuilder() builder.add_argument( Argument("command", Argument.REQUIRED, "The command to execute") ) builder.add_argument( Argument("arg", Argument.MULTI_VALUED, "The arguments of the command") ) builder.add_options(*global_args_format.get_options().values()) args_format = builder.format self._render_name(layout, self._application) self._render_usage(layout, self._application, args_format) self._render_arguments(layout, args_format.get_arguments().values()) if args_format.has_options(): self._render_global_options(layout, args_format.get_options().values()) if not commands.is_empty(): self._render_commands(layout, commands) if help: self._render_description(layout, help) def _render_name( self, layout, application ): # type: (BlockLayout, Application) -> None layout.add(NameVersion(application.config)) layout.add(EmptyLine()) def _render_usage( self, layout, application, args_format ): # type: (BlockLayout, Application, ArgsFormat) -> None app_name = application.config.name layout.add(Paragraph("USAGE")) with layout.block(): self._render_synopsis(layout, args_format, app_name) layout.add(EmptyLine()) def _render_commands( self, layout, commands ): # type: (BlockLayout, CommandCollection) -> None layout.add(Paragraph("AVAILABLE COMMANDS")) with layout.block(): for command in sorted(commands, key=lambda c: c.name): self._render_command(layout, command) layout.add(EmptyLine()) def _render_command(self, layout, command): # type: (BlockLayout, Command) -> None description = command.config.description name = "{}".format(command.name) layout.add(LabeledParagraph(name, description)) def _render_description(self, layout, help): # type: (BlockLayout, str) -> None help = help.format(script_name=self._application.config.name or "console") layout.add(Paragraph("DESCRIPTION")) with layout.block(): for paragraph in help.split("\n"): layout.add(Paragraph(paragraph)) layout.add(EmptyLine()) PK!2,,clikit/ui/help/command_help.pyfrom typing import Iterable from clikit.api.args.format import Argument from clikit.api.args.format import Option from clikit.api.command import Command from clikit.api.command import CommandCollection from clikit.ui.components import EmptyLine from clikit.ui.components import Paragraph from clikit.ui.layout import BlockLayout from .abstract_help import AbstractHelp class CommandHelp(AbstractHelp): """ Renders the help of a command. """ def __init__(self, command): # type: (Command) -> None self._command = command def _render_help(self, layout): # type: (BlockLayout) -> None help = self._command.config.help args_format = self._command.args_format sub_commands = self._command.named_sub_commands self._render_usage(layout, self._command) if args_format.has_arguments(): self._render_arguments(layout, args_format.get_arguments().values()) if not sub_commands.is_empty(): self._render_sub_commands(layout, sub_commands) if args_format.has_options(False): self._render_options(layout, args_format.get_options(False).values()) if args_format.base_format and args_format.base_format.has_options(): self._render_global_options( layout, args_format.base_format.get_options().values() ) if help: self._render_description(layout, help) def _render_usage(self, layout, command): # type: (BlockLayout, Command) -> None formats_to_print = [] # Start with the default commands if command.has_default_sub_commands(): # If the command has default commands, print them for sub_command in command.default_sub_commands: # The name of the sub command is only optional (i.e. printed # wrapped in brackets: "[sub]") if the command is not # anonymous name_optional = not sub_command.config.is_anonymous() formats_to_print.append((sub_command.args_format, name_optional)) else: # Otherwise print the command's usage itself formats_to_print.append((command.args_format, False)) # Add remaining sub-commands for sub_command in command.sub_commands: # Don't duplicate default commands if not sub_command.config.is_default(): formats_to_print.append((sub_command.args_format, False)) app_name = command.application.config.name prefix = " " if len(formats_to_print) > 1 else "" layout.add(Paragraph("USAGE")) with layout.block(): for vars in formats_to_print: self._render_synopsis(layout, vars[0], app_name, prefix, vars[1]) prefix = "or: " if command.has_aliases(): layout.add(EmptyLine()) self._render_aliases(layout, command.aliases) layout.add(EmptyLine()) def _render_aliases( self, layout, aliases ): # type: (BlockLayout, Iterable[str]) -> None layout.add(Paragraph("aliases: {}".format(", ".join(aliases)))) def _render_sub_commands( self, layout, sub_commands ): # type: (BlockLayout, CommandCollection) -> None layout.add(Paragraph("COMMANDS")) with layout.block(): for sub_command in sorted(sub_commands, key=lambda c: c.name): self._render_sub_command(layout, sub_command) def _render_sub_command( self, layout, command ): # type: (BlockLayout, Command) -> None config = command.config description = config.description help = config.help arguments = command.args_format.get_arguments(False) options = command.args_format.get_options(False) # TODO: option commands name = "{}".format(command.name) layout.add(Paragraph(name)) with layout.block(): if description: self._render_sub_command_description(layout, description) if help: self._render_sub_command_help(layout, help) if arguments: self._render_sub_command_arguments(layout, arguments.values()) if options: self._render_sub_command_options(layout, options.values()) if not description and not help and not arguments and not options: layout.add(EmptyLine()) def _render_sub_command_description( self, layout, description ): # type: (BlockLayout, str) -> None layout.add(Paragraph(description)) layout.add(EmptyLine()) def _render_sub_command_help( self, layout, help ): # type: (BlockLayout, str) -> None layout.add(Paragraph(help)) layout.add(EmptyLine()) def _render_sub_command_arguments( self, layout, arguments ): # type: (BlockLayout, Iterable[Argument]) -> None for argument in arguments: self._render_argument(layout, argument) layout.add(EmptyLine()) def _render_sub_command_options( self, layout, options ): # type: (BlockLayout, Iterable[Option]) -> None for option in options: self._render_option(layout, option) layout.add(EmptyLine()) def _render_description(self, layout, help): # type: (BlockLayout, str) -> None script_name = "console" application = self._command.application if application and application.config.name: script_name = application.config.name help = help.format(script_name=script_name, command_name=self._command.name) layout.add(Paragraph("DESCRIPTION")) with layout.block(): for paragraph in help.split("\n"): layout.add(Paragraph(paragraph)) layout.add(EmptyLine()) PK!Ϛ&&clikit/ui/layout/__init__.pyfrom .block_layout import BlockLayout PK!X4 clikit/ui/layout/block_layout.pyfrom contextlib import contextmanager from clikit.api.io import IO from clikit.ui import Component from clikit.ui.alignment import LabelAlignment from clikit.ui.components import LabeledParagraph class BlockLayout: """ Renders renderable objects in indented blocks. """ def __init__(self): # type: () -> None self._current_indentation = 0 self._elements = [] self._indentations = [] self._alignment = LabelAlignment() def add(self, element): # type: (Component) -> BlockLayout self._elements.append(element) self._indentations.append(self._current_indentation) if isinstance(element, LabeledParagraph): self._alignment.add(element, self._current_indentation) element.set_alignment(self._alignment) return self @contextmanager def block(self): # type: () -> BlockLayout self._current_indentation += 2 yield self self._current_indentation -= 2 def render(self, io, indentation=0): # type: (IO, int) -> None self._alignment.align(io, indentation) for i, element in enumerate(self._elements): element.render(io, self._indentations[i] + indentation) self._elements = [] PK!jYYclikit/ui/rectangle.pyfrom collections import namedtuple Rectangle = namedtuple("Rectangle", "width height") PK!}BsEEclikit/ui/style/__init__.pyfrom .alignment import Alignment from .table_style import TableStyle PK!}=nnclikit/ui/style/alignment.pyclass Alignment: """ Constants for text alignment. """ LEFT = 0 RIGHT = 1 CENTER = 2 PK!ʛ clikit/ui/style/border_style.py# -*- coding: utf-8 -*- from __future__ import unicode_literals from clikit.api.formatter import Style class BorderStyle: """ Defines the style of a border. """ _none = None _ascii = None _solid = None def __init__(self): # type: () -> None self.line_ht_char = "-" self.line_hc_char = "-" self.line_hb_char = "-" self.line_vl_char = "|" self.line_vc_char = "|" self.line_vr_char = "|" self.corner_tl_char = "+" self.corner_tr_char = "+" self.corner_bl_char = "+" self.corner_br_char = "+" self.crossing_c_char = "+" self.crossing_l_char = "+" self.crossing_t_char = "+" self.crossing_r_char = "+" self.crossing_b_char = "+" self.style = None # type: Style @classmethod def none(cls): # type: () -> BorderStyle if cls._none is None: style = cls() style.line_ht_char = "" style.line_hc_char = "" style.line_hb_char = "" style.line_vl_char = "" style.line_vc_char = " " style.line_vr_char = "" style.corner_tl_char = "" style.corner_tr_char = "" style.corner_bl_char = "" style.corner_br_char = "" style.crossing_c_char = "" style.crossing_l_char = "" style.crossing_t_char = "" style.crossing_r_char = "" style.crossing_b_char = "" cls._none = style return cls._none @classmethod def ascii(cls): # type: () -> BorderStyle if cls._ascii is None: style = cls() cls._ascii = style return cls._ascii @classmethod def solid(cls): # type: () -> BorderStyle if cls._solid is None: style = cls() style.line_ht_char = "─" style.line_hc_char = "─" style.line_hb_char = "─" style.line_vl_char = "│" style.line_vc_char = "│" style.line_vr_char = "│" style.corner_tl_char = "┌" style.corner_tr_char = "┐" style.corner_bl_char = "└" style.corner_br_char = "┘" style.crossing_c_char = "┼" style.crossing_l_char = "├" style.crossing_r_char = "┤" style.crossing_t_char = "┬" style.crossing_b_char = "┴" cls._solid = style return cls._solid PK!z¨clikit/ui/style/table_style.pyfrom __future__ import unicode_literals from .alignment import Alignment from .border_style import BorderStyle class TableStyle: """ Defines the style of a Table """ _borderless = None _ascii = None _solid = None def __init__(self): # type: () -> None self.padding_char = " " self.header_cell_format = "{}" self.cell_format = "{}" self.column_alignments = [] self.default_column_alignment = Alignment.LEFT self.border_style = None self.header_cell_style = None self.cell_style = None def get_column_alignments(self, nb_columns): # type: (int) -> List[int] default_alignments = [self.default_column_alignment] * nb_columns for i, alignment in enumerate(self.column_alignments): default_alignments[i] = alignment return default_alignments def set_column_alignment(self, col, alignment): # type: (int, int) -> TableStyle if col > len(self.column_alignments) - 1: diff = abs(len(self.column_alignments) - col) + 1 self.column_alignments += [self.default_column_alignment] * diff self.column_alignments[col] = alignment @classmethod def borderless(cls): # type: () -> TableStyle style = TableStyle() style.border_style = BorderStyle.none() style.border_style.line_hc_char = "=" style.border_style.line_vc_char = " " style.border_style.crossing_c_char = " " return style @classmethod def compact(cls): # type: () -> TableStyle style = TableStyle() style.border_style = BorderStyle.none() style.border_style.line_hc_char = "" style.border_style.line_vc_char = " " style.border_style.crossing_c_char = "" return style @classmethod def ascii(cls): # type: () -> TableStyle style = TableStyle() style.header_cell_format = " {} " style.cell_format = " {} " style.border_style = BorderStyle.ascii() return style @classmethod def solid(cls): # type: () -> TableStyle style = TableStyle() style.header_cell_format = " {} " style.cell_format = " {} " style.border_style = BorderStyle.solid() return style PK!clikit/utils/__init__.pyPK!%clikit/utils/_compat.pyimport sys try: # Python 2 long = long unicode = unicode basestring = basestring except NameError: # Python 3 long = int unicode = str basestring = str PY2 = sys.version_info[0] == 2 PY35 = sys.version_info >= (3, 5) PY36 = sys.version_info >= (3, 6) if not PY36: from collections import OrderedDict else: OrderedDict = dict WINDOWS = sys.platform == "win32" def decode(string, encodings=None): if not PY2 and not isinstance(string, bytes): return string if PY2 and isinstance(string, unicode): return string encodings = encodings or ["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 encodings = encodings or ["utf-8", "latin1", "ascii"] for encoding in encodings: try: return string.encode(encoding) except (UnicodeEncodeError, UnicodeDecodeError): pass return string.encode(encodings[0], errors="ignore") def to_str(string): if isinstance(string, str) or not isinstance(string, (unicode, bytes)): return string if PY2: method = "encode" else: method = "decode" encodings = ["utf-8", "latin1", "ascii"] for encoding in encodings: try: return getattr(string, method)(encoding) except (UnicodeEncodeError, UnicodeDecodeError): pass return getattr(string, method)(encodings[0], errors="ignore") PK! List[str] """ Finds names similar to a given command name. """ threshold = 1e3 distance_by_name = {} # Include aliases in the search actual_names = commands.get_names(True) for actual_name in actual_names: # Get Levenshtein distance between the input and each command name distance = levenshtein(name, actual_name) is_similar = distance <= len(name) / 3 is_sub_string = actual_name.find(name) != -1 if is_similar or is_sub_string: distance_by_name[actual_name] = distance # Only keep results with a distance below the threshold distance_by_name = { k: v for k, v in distance_by_name.items() if v < 2 * threshold } # Display results with shortest distance first suggested_names = [] for k, v in sorted(distance_by_name.items(), key=lambda _, v: v): if k not in suggested_names: suggested_names.append(k) return suggested_names PK!09._ clikit/utils/string.pyimport re from typing import Any from typing import Optional from ._compat import basestring def parse_string(value, nullable=True): # type: (Any, bool) -> Optional[str] if nullable and (value is None or value == "null"): return if value is None: return "null" if isinstance(value, bool): return str(value).lower() return str(value) def parse_boolean(value, nullable=True): # type: (Any, bool) -> Optional[bool] if nullable and (value is None or value == "null"): return if isinstance(value, bool): return value if isinstance(value, int): value = str(value) if isinstance(value, basestring): if not value: return False if value in {"false", "0", "no", "off"}: return False if value in {"true", "1", "yes", "on"}: return True raise ValueError('The value "{}" cannot be parsed as boolean.'.format(value)) def parse_int(value, nullable=True): # type: (Any, bool) -> Optional[int] if nullable and (value is None or value == "null"): return try: return int(value) except ValueError: raise ValueError('The value "{}" cannot be parsed as integer.'.format(value)) def parse_float(value, nullable=True): # type: (Any, bool) -> Optional[float] if nullable and (value is None or value == "null"): return try: return float(value) except ValueError: raise ValueError('The value "{}" cannot be parsed as float.'.format(value)) def get_string_length( string, formatter=None ): # type: (str, Optional[Formatter]) -> int if formatter is not None: string = formatter.remove_format(string) return len(string) def get_max_word_length( string, formatter=None ): # type: (str, Optional[Formatter]) -> int if formatter is not None: string = formatter.remove_format(string) max_length = 0 words = re.split("\s+", string) for word in words: max_length = max(max_length, get_string_length(word)) return max_length def get_max_line_length( string, formatter=None ): # type: (str, Optional[Formatter]) -> int if formatter is not None: string = formatter.remove_format(string) max_length = 0 words = re.split("\n", string) for word in words: max_length = max(max_length, get_string_length(word)) return max_length PK!e0 clikit/utils/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_output( shlex.split("tput cols"), stderr=subprocess.STDOUT ) ) rows = int( subprocess.check_output( shlex.split("tput lines"), stderr=subprocess.STDOUT ) ) 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]) PK!<clikit/utils/time.pyimport math _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 format_time(secs): # type: (int) -> str for fmt in _TIME_FORMATS: if secs > fmt[0]: continue if len(fmt) == 2: return fmt[1] return "{} {}".format(math.ceil(secs / fmt[2]), fmt[1]) PK!5&&clikit-0.2.2.dist-info/LICENSECopyright (c) 2018 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!HJnVYclikit-0.2.2.dist-info/WHEELA н#V;/"dF&[xjxdڠwsތ`hA3HQt[YֻbaPf^PK!H "//4clikit-0.2.2.dist-info/METADATATn0S6%nB;1uHL` 7!79MM'y$dmA|?>?>BR"A5)$/^ SJ$!=DU|¤@PPx[;sI4 Z9Py]29R!?Jm!saⅭ0v D.niQ$K!)fw7] M`T\`sg}Rp*[B"ѡdM,oiM Sygkyieb.SڊXk!n}9'r@:od 䑘*=ȇo4Wpꜷ+od 𪪸*TI-H%pO`_<[hos<ӁR@SWc88e?%wܽjwуQ]EM3rcyMS7u+FGLPٺB|D a Hw}`="EZA F0OUtT{ o" Wy+A״#qz\;>NA#ď'bX1)͇oVb*0G=Dw/[$aXx'<{J WSd/횐C05zQg[)h4p8CrގPDz]Ѻh%.{ݕ˘c&[sxӈ9FXj0'Qr$w0s!{~?t^0u$ka,cE|J88A-pli=ء_z`(R&A է@3&Sf&N] mi~ԋUIMX+~/H)GVg'+ӒT!$e;(u Rcf#uxK[[[0ߡ,*t+VJ*`.(^1 4ѧjŊYC莄c{F \⊇rDmGM~s.ݭ@ޚG!1@]ڹ W> k>sCΛo1Oڔ.vQG# &{蛯*@txut0rJ,uX2]e'Vo%W]&=ԡTz, $>=ow!P UQͽ\lL9PoZC8Ū`ϔgj*CZ C*R <˙KxK4.AY7ë{rqd-Q~Iw.'ȩv_NgSr2=/JbIq ke#R kN`[#@x76xQ/7€MFY)VI=z$ nmU @z+G8$/tY%%ߡ%EӖ~^ؾcWb#t <{ zts`m$YcdOpݪ.!IIT\%8_΢/} _W'nPu2AQps,9AO;!(X%Ɣy80X.s-ބ͌⠔0o/%r&bR;C $1 0ւ-/c~›.rqyL ٵ6 D!w}w2C>kxm~~ҧ=hPlf3qӆ֨nϣ;nZDdn3UXÂ^O*c>4'I?}6; |9P|4}o1U搩bOVni|_\!X!ǹX.dJ8R"A$s#8/TK^9ׇDzTŹ hG {8QhNW~_n2S\a" <SQrޗ<.ݦ>#J,>H_GDI)u#rc7ZZ5͞JN)Zw +Aޒiiu<\r6qp5#|>bqSgvx=Ǜj1%ӻ#.L'qY}Tip-Ԣ7|I΋\G}#vC|Wx쥳 MAOxtRv-ّtw(7ͱ=r7si(mC){{M3~+1}ʊgs&z F&L%JA:>rA`"n). qJX<B}WMp퀹c (>0^E^^ZDpb:&&{cUŶ=Ұ B;[xWhgS˼;ET&s5; i AaIT4̍p g\#2$uҴģ&a)Z9> kYo' H9~(/juZ}Gom}Lxhrx).g8n*[ 6B[3ueL{mFZʯc"y;A>9[׻E$k.}w&fUҨ瞯=  8^IU>U s)*De< p:5ܑ" |,s9qn=$)6쩈rr_$ۛRF3f̌G-Fï2;'ޢ;Q4*jӝ13X*'fGbWT=yJA5kT_PeE<#Hf&w-uf4iuMtūt3 wE9=N-Q_~͠`vq$ÊD7`$><!?~r^^&{Y=L,eN"rr4yl*`↨+{VfڛVz$μ@jrm}{A߁בJ'hH+;P\q(aHR_g+>2JIս&O%hkͣtX.v2 ܯ%ooH^%@Tg"dyz҆tH90%4Οh>џ0k-8&՘(W+pm䭩PK!\ clikit/__init__.pyPK!clikit/adapter/__init__.pyPK!lˋ!9clikit/adapter/style_converter.pyPK!Qclikit/api/__init__.pyPK!j %%"clikit/api/application/__init__.pyPK!0r%clikit/api/application/application.pyPK!=OYY clikit/api/args/__init__.pyPK!3>lM clikit/api/args/args.pyPK!.ɂ^^clikit/api/args/args_parser.pyPK!hzclikit/api/args/exceptions.pyPK!lQ"?%clikit/api/args/format/__init__.pyPK!1)`&clikit/api/args/format/abstract_option.pyPK!v` %7clikit/api/args/format/args_format.pyPK!r#,,,,-Xclikit/api/args/format/args_format_builder.pyPK!Pclikit/io/null_io.pyPK!|f  #Aclikit/io/output_stream/__init__.pyPK!5Z7##1aBclikit/io/output_stream/buffered_output_stream.pyPK!.Gclikit/io/output_stream/error_output_stream.pyPK!79->Iclikit/io/output_stream/null_output_stream.pyPK!((1Lclikit/io/output_stream/standard_output_stream.pyPK!r /Nclikit/io/output_stream/stream_output_stream.pyPK!Y..^[clikit/resolver/__init__.pyPK!xA44#[clikit/resolver/default_resolver.pyPK!u#9rr!:rclikit/resolver/resolve_result.pyPK!+BBwclikit/ui/__init__.pyPK!-,,`xclikit/ui/alignment/__init__.pyPK!'3&xclikit/ui/alignment/label_alignment.pyPK!nj}clikit/ui/component.pyPK![ ~clikit/ui/components/__init__.pyPK!^>##clikit/ui/components/border_util.pyPK!NKK$Lclikit/ui/components/cell_wrapper.pyPK!Ob//'ٰclikit/ui/components/choice_question.pyPK!T-Mclikit/ui/components/confirmation_question.pyPK!}fV"xclikit/ui/components/empty_line.pyPK!Ǡ)'clikit/ui/components/exception_trace.pyPK!Jb)clikit/ui/components/labeled_paragraph.pyPK!A]pp$clikit/ui/components/name_version.pyPK!H!kclikit/ui/components/paragraph.pyPK!,f))$Zclikit/ui/components/progress_bar.pyPK!UFPP* clikit/ui/components/progress_indicator.pyPK!K  5#clikit/ui/components/question.pyPK!9&ppAclikit/ui/components/table.pyPK!TTUclikit/ui/help/__init__.pyPK!)l jj8Vclikit/ui/help/abstract_help.pyPK!!_m2 2 "kclikit/ui/help/application_help.pyPK!2,,Qyclikit/ui/help/command_help.pyPK!Ϛ&&clikit/ui/layout/__init__.pyPK!X4 clikit/ui/layout/block_layout.pyPK!jYYEclikit/ui/rectangle.pyPK!}BsEEҖclikit/ui/style/__init__.pyPK!}=nnPclikit/ui/style/alignment.pyPK!ʛ clikit/ui/style/border_style.pyPK!z¨!clikit/ui/style/table_style.pyPK!\clikit/utils/__init__.pyPK!%clikit/utils/_compat.pyPK!