././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1686832855.3919046 click-repl-0.3.0/0000775000175000017500000000000014442603327012234 5ustar00asifasif././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686807160.0 click-repl-0.3.0/LICENSE0000664000175000017500000000216514442521170013240 0ustar00asifasifCopyright (c) 2014-2015 Markus Unterwaditzer & contributors. Copyright (c) 2016-2026 Asif Saif Uddin & contributors. 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/MANIFEST.in0000664000175000017500000000002014442520014013751 0ustar00asifasifinclude LICENSE ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1686832855.3919046 click-repl-0.3.0/PKG-INFO0000664000175000017500000000637314442603327013342 0ustar00asifasifMetadata-Version: 2.1 Name: click-repl Version: 0.3.0 Summary: REPL plugin for Click Home-page: https://github.com/untitaker/click-repl Author: Markus Unterwaditzer Author-email: markus@unterwaditzer.net License: MIT Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.6 Description-Content-Type: text/markdown Provides-Extra: testing License-File: LICENSE click-repl === [![Tests](https://github.com/click-contrib/click-repl/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/click-contrib/click-repl/actions/workflows/tests.yml) [![License](https://img.shields.io/pypi/l/click-repl?label=License)](https://github.com/click-contrib/click-repl/LICENSE) ![Python - version](https://img.shields.io/badge/python-3%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue) [![PyPi - version](https://img.shields.io/badge/pypi-v0.2.0-blue)](https://pypi.org/project/click-repl/) ![wheels](https://img.shields.io/piwheels/v/click-repl?label=wheel) ![PyPI - Status](https://img.shields.io/pypi/status/click) ![PyPI - Downloads](https://img.shields.io/pypi/dm/click-repl) Installation === Installation is done via pip: ``` pip install click-repl ``` Usage === In your [click](http://click.pocoo.org/) app: ```py import click from click_repl import register_repl @click.group() def cli(): pass @cli.command() def hello(): click.echo("Hello world!") register_repl(cli) cli() ``` In the shell: ``` $ my_app repl > hello Hello world! > ^C $ echo hello | my_app repl Hello world! ``` **Features not shown:** - Tab-completion. - The parent context is reused, which means `ctx.obj` persists between subcommands. If you're keeping caches on that object (like I do), using the app's repl instead of the shell is a huge performance win. - `!` - prefix executes shell commands. You can use the internal `:help` command to explain usage. Advanced Usage === For more flexibility over how your REPL works you can use the `repl` function directly instead of `register_repl`. For example, in your app: ```py import click from click_repl import repl from prompt_toolkit.history import FileHistory @click.group() def cli(): pass @cli.command() def myrepl(): prompt_kwargs = { 'history': FileHistory('/etc/myrepl/myrepl-history'), } repl(click.get_current_context(), prompt_kwargs=prompt_kwargs) cli() ``` And then your custom `myrepl` command will be available on your CLI, which will start a REPL which has its history stored in `/etc/myrepl/myrepl-history` and persist between sessions. Any arguments that can be passed to the [`python-prompt-toolkit`](https://github.com/prompt-toolkit/python-prompt-toolkit) [Prompt](http://python-prompt-toolkit.readthedocs.io/en/stable/pages/reference.html?prompt_toolkit.shortcuts.Prompt#prompt_toolkit.shortcuts.Prompt) class can be passed in the `prompt_kwargs` argument and will be used when instantiating your `Prompt`. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686807269.0 click-repl-0.3.0/README.md0000664000175000017500000000501214442521345013510 0ustar00asifasifclick-repl === [![Tests](https://github.com/click-contrib/click-repl/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/click-contrib/click-repl/actions/workflows/tests.yml) [![License](https://img.shields.io/pypi/l/click-repl?label=License)](https://github.com/click-contrib/click-repl/LICENSE) ![Python - version](https://img.shields.io/badge/python-3%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue) [![PyPi - version](https://img.shields.io/badge/pypi-v0.2.0-blue)](https://pypi.org/project/click-repl/) ![wheels](https://img.shields.io/piwheels/v/click-repl?label=wheel) ![PyPI - Status](https://img.shields.io/pypi/status/click) ![PyPI - Downloads](https://img.shields.io/pypi/dm/click-repl) Installation === Installation is done via pip: ``` pip install click-repl ``` Usage === In your [click](http://click.pocoo.org/) app: ```py import click from click_repl import register_repl @click.group() def cli(): pass @cli.command() def hello(): click.echo("Hello world!") register_repl(cli) cli() ``` In the shell: ``` $ my_app repl > hello Hello world! > ^C $ echo hello | my_app repl Hello world! ``` **Features not shown:** - Tab-completion. - The parent context is reused, which means `ctx.obj` persists between subcommands. If you're keeping caches on that object (like I do), using the app's repl instead of the shell is a huge performance win. - `!` - prefix executes shell commands. You can use the internal `:help` command to explain usage. Advanced Usage === For more flexibility over how your REPL works you can use the `repl` function directly instead of `register_repl`. For example, in your app: ```py import click from click_repl import repl from prompt_toolkit.history import FileHistory @click.group() def cli(): pass @cli.command() def myrepl(): prompt_kwargs = { 'history': FileHistory('/etc/myrepl/myrepl-history'), } repl(click.get_current_context(), prompt_kwargs=prompt_kwargs) cli() ``` And then your custom `myrepl` command will be available on your CLI, which will start a REPL which has its history stored in `/etc/myrepl/myrepl-history` and persist between sessions. Any arguments that can be passed to the [`python-prompt-toolkit`](https://github.com/prompt-toolkit/python-prompt-toolkit) [Prompt](http://python-prompt-toolkit.readthedocs.io/en/stable/pages/reference.html?prompt_toolkit.shortcuts.Prompt#prompt_toolkit.shortcuts.Prompt) class can be passed in the `prompt_kwargs` argument and will be used when instantiating your `Prompt`. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1686832855.3919046 click-repl-0.3.0/click_repl/0000775000175000017500000000000014442603327014343 5ustar00asifasif././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686808160.0 click-repl-0.3.0/click_repl/__init__.py0000664000175000017500000000100214442523140016437 0ustar00asifasiffrom ._completer import ClickCompleter as ClickCompleter # noqa: F401 from ._repl import register_repl as register_repl # noqa: F401 from ._repl import repl as repl # noqa: F401 from .exceptions import CommandLineParserError as CommandLineParserError # noqa: F401 from .exceptions import ExitReplException as ExitReplException # noqa: F401 from .exceptions import ( # noqa: F401 InternalCommandException as InternalCommandException, ) from .utils import exit as exit # noqa: F401 __version__ = "0.3.0" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/click_repl/_completer.py0000664000175000017500000002304014442520014017034 0ustar00asifasiffrom __future__ import unicode_literals import os from glob import iglob import click from prompt_toolkit.completion import Completion, Completer from .utils import _resolve_context, split_arg_string __all__ = ["ClickCompleter"] IS_WINDOWS = os.name == "nt" # Handle backwards compatibility between Click<=7.0 and >=8.0 try: import click.shell_completion HAS_CLICK_V8 = True AUTO_COMPLETION_PARAM = "shell_complete" except (ImportError, ModuleNotFoundError): import click._bashcomplete # type: ignore[import] HAS_CLICK_V8 = False AUTO_COMPLETION_PARAM = "autocompletion" def text_type(text): return "{}".format(text) class ClickCompleter(Completer): __slots__ = ("cli", "ctx", "parsed_args", "parsed_ctx", "ctx_command") def __init__(self, cli, ctx): self.cli = cli self.ctx = ctx self.parsed_args = [] self.parsed_ctx = ctx self.ctx_command = ctx.command def _get_completion_from_autocompletion_functions( self, param, autocomplete_ctx, args, incomplete, ): param_choices = [] if HAS_CLICK_V8: autocompletions = param.shell_complete(autocomplete_ctx, incomplete) else: autocompletions = param.autocompletion( # type: ignore[attr-defined] autocomplete_ctx, args, incomplete ) for autocomplete in autocompletions: if isinstance(autocomplete, tuple): param_choices.append( Completion( text_type(autocomplete[0]), -len(incomplete), display_meta=autocomplete[1], ) ) elif HAS_CLICK_V8 and isinstance( autocomplete, click.shell_completion.CompletionItem ): param_choices.append( Completion(text_type(autocomplete.value), -len(incomplete)) ) else: param_choices.append( Completion(text_type(autocomplete), -len(incomplete)) ) return param_choices def _get_completion_from_choices_click_le_7(self, param, incomplete): if not getattr(param.type, "case_sensitive", True): incomplete = incomplete.lower() return [ Completion( text_type(choice), -len(incomplete), display=text_type(repr(choice) if " " in choice else choice), ) for choice in param.type.choices # type: ignore[attr-defined] if choice.lower().startswith(incomplete) ] else: return [ Completion( text_type(choice), -len(incomplete), display=text_type(repr(choice) if " " in choice else choice), ) for choice in param.type.choices # type: ignore[attr-defined] if choice.startswith(incomplete) ] def _get_completion_for_Path_types(self, param, args, incomplete): if "*" in incomplete: return [] choices = [] _incomplete = os.path.expandvars(incomplete) search_pattern = _incomplete.strip("'\"\t\n\r\v ").replace("\\\\", "\\") + "*" quote = "" if " " in _incomplete: for i in incomplete: if i in ("'", '"'): quote = i break for path in iglob(search_pattern): if " " in path: if quote: path = quote + path else: if IS_WINDOWS: path = repr(path).replace("\\\\", "\\") else: if IS_WINDOWS: path = path.replace("\\", "\\\\") choices.append( Completion( text_type(path), -len(incomplete), display=text_type(os.path.basename(path.strip("'\""))), ) ) return choices def _get_completion_for_Boolean_type(self, param, incomplete): return [ Completion( text_type(k), -len(incomplete), display_meta=text_type("/".join(v)) ) for k, v in { "true": ("1", "true", "t", "yes", "y", "on"), "false": ("0", "false", "f", "no", "n", "off"), }.items() if any(i.startswith(incomplete) for i in v) ] def _get_completion_from_params(self, autocomplete_ctx, args, param, incomplete): choices = [] param_type = param.type # shell_complete method for click.Choice is intorduced in click-v8 if not HAS_CLICK_V8 and isinstance(param_type, click.Choice): choices.extend( self._get_completion_from_choices_click_le_7(param, incomplete) ) elif isinstance(param_type, click.types.BoolParamType): choices.extend(self._get_completion_for_Boolean_type(param, incomplete)) elif isinstance(param_type, (click.Path, click.File)): choices.extend(self._get_completion_for_Path_types(param, args, incomplete)) elif getattr(param, AUTO_COMPLETION_PARAM, None) is not None: choices.extend( self._get_completion_from_autocompletion_functions( param, autocomplete_ctx, args, incomplete, ) ) return choices def _get_completion_for_cmd_args( self, ctx_command, incomplete, autocomplete_ctx, args, ): choices = [] param_called = False for param in ctx_command.params: if isinstance(param.type, click.types.UnprocessedParamType): return [] elif getattr(param, "hidden", False): continue elif isinstance(param, click.Option): for option in param.opts + param.secondary_opts: # We want to make sure if this parameter was called # If we are inside a parameter that was called, we want to show only # relevant choices if option in args[param.nargs * -1 :]: # noqa: E203 param_called = True break elif option.startswith(incomplete): choices.append( Completion( text_type(option), -len(incomplete), display_meta=text_type(param.help or ""), ) ) if param_called: choices = self._get_completion_from_params( autocomplete_ctx, args, param, incomplete ) elif isinstance(param, click.Argument): choices.extend( self._get_completion_from_params( autocomplete_ctx, args, param, incomplete ) ) return choices def get_completions(self, document, complete_event=None): # Code analogous to click._bashcomplete.do_complete args = split_arg_string(document.text_before_cursor, posix=False) choices = [] cursor_within_command = ( document.text_before_cursor.rstrip() == document.text_before_cursor ) if document.text_before_cursor.startswith(("!", ":")): return if args and cursor_within_command: # We've entered some text and no space, give completions for the # current word. incomplete = args.pop() else: # We've not entered anything, either at all or for the current # command, so give all relevant completions for this context. incomplete = "" if self.parsed_args != args: self.parsed_args = args self.parsed_ctx = _resolve_context(args, self.ctx) self.ctx_command = self.parsed_ctx.command if getattr(self.ctx_command, "hidden", False): return try: choices.extend( self._get_completion_for_cmd_args( self.ctx_command, incomplete, self.parsed_ctx, args ) ) if isinstance(self.ctx_command, click.MultiCommand): incomplete_lower = incomplete.lower() for name in self.ctx_command.list_commands(self.parsed_ctx): command = self.ctx_command.get_command(self.parsed_ctx, name) if getattr(command, "hidden", False): continue elif name.lower().startswith(incomplete_lower): choices.append( Completion( text_type(name), -len(incomplete), display_meta=getattr(command, "short_help", ""), ) ) except Exception as e: click.echo("{}: {}".format(type(e).__name__, str(e))) # If we are inside a parameter that was called, we want to show only # relevant choices # if param_called: # choices = param_choices for item in choices: yield item ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/click_repl/_repl.py0000664000175000017500000001064114442520014016007 0ustar00asifasiffrom __future__ import with_statement import click import sys from prompt_toolkit import PromptSession from prompt_toolkit.history import InMemoryHistory from ._completer import ClickCompleter from .exceptions import ClickExit # type: ignore[attr-defined] from .exceptions import CommandLineParserError, ExitReplException, InvalidGroupFormat from .utils import _execute_internal_and_sys_cmds __all__ = ["bootstrap_prompt", "register_repl", "repl"] def bootstrap_prompt( group, prompt_kwargs, ctx=None, ): """ Bootstrap prompt_toolkit kwargs or use user defined values. :param group: click Group :param prompt_kwargs: The user specified prompt kwargs. """ defaults = { "history": InMemoryHistory(), "completer": ClickCompleter(group, ctx=ctx), "message": "> ", } defaults.update(prompt_kwargs) return defaults def repl( old_ctx, prompt_kwargs={}, allow_system_commands=True, allow_internal_commands=True ): """ Start an interactive shell. All subcommands are available in it. :param old_ctx: The current Click context. :param prompt_kwargs: Parameters passed to :py:func:`prompt_toolkit.PromptSession`. If stdin is not a TTY, no prompt will be printed, but only commands read from stdin. """ group_ctx = old_ctx # Switching to the parent context that has a Group as its command # as a Group acts as a CLI for all of its subcommands if old_ctx.parent is not None and not isinstance(old_ctx.command, click.Group): group_ctx = old_ctx.parent group = group_ctx.command # An Optional click.Argument in the CLI Group, that has no value # will consume the first word from the REPL input, causing issues in # executing the command # So, if there's an empty Optional Argument for param in group.params: if ( isinstance(param, click.Argument) and group_ctx.params[param.name] is None and not param.required ): raise InvalidGroupFormat( f"{type(group).__name__} '{group.name}' requires value for " f"an optional argument '{param.name}' in REPL mode" ) isatty = sys.stdin.isatty() # Delete the REPL command from those available, as we don't want to allow # nesting REPLs (note: pass `None` to `pop` as we don't want to error if # REPL command already not present for some reason). repl_command_name = old_ctx.command.name if isinstance(group_ctx.command, click.CommandCollection): available_commands = { cmd_name: cmd_obj for source in group_ctx.command.sources for cmd_name, cmd_obj in source.commands.items() } else: available_commands = group_ctx.command.commands original_command = available_commands.pop(repl_command_name, None) if isatty: prompt_kwargs = bootstrap_prompt(group, prompt_kwargs, group_ctx) session = PromptSession(**prompt_kwargs) def get_command(): return session.prompt() else: get_command = sys.stdin.readline while True: try: command = get_command() except KeyboardInterrupt: continue except EOFError: break if not command: if isatty: continue else: break try: args = _execute_internal_and_sys_cmds( command, allow_internal_commands, allow_system_commands ) if args is None: continue except CommandLineParserError: continue except ExitReplException: break try: # The group command will dispatch based on args. old_protected_args = group_ctx.protected_args try: group_ctx.protected_args = args group.invoke(group_ctx) finally: group_ctx.protected_args = old_protected_args except click.ClickException as e: e.show() except (ClickExit, SystemExit): pass except ExitReplException: break if original_command is not None: available_commands[repl_command_name] = original_command def register_repl(group, name="repl"): """Register :func:`repl()` as sub-command *name* of *group*.""" group.command(name=name)(click.pass_context(repl)) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/click_repl/exceptions.py0000664000175000017500000000067514442520014017075 0ustar00asifasifclass InternalCommandException(Exception): pass class ExitReplException(InternalCommandException): pass class CommandLineParserError(Exception): pass class InvalidGroupFormat(Exception): pass # Handle click.exceptions.Exit introduced in Click 7.0 try: from click.exceptions import Exit as ClickExit except (ImportError, ModuleNotFoundError): class ClickExit(RuntimeError): # type: ignore[no-redef] pass ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/click_repl/utils.py0000664000175000017500000001374714442520014016060 0ustar00asifasifimport click import os import shlex import sys from collections import defaultdict from .exceptions import CommandLineParserError, ExitReplException __all__ = [ "_execute_internal_and_sys_cmds", "_exit_internal", "_get_registered_target", "_help_internal", "_resolve_context", "_register_internal_command", "dispatch_repl_commands", "handle_internal_commands", "split_arg_string", "exit", ] # Abstract datatypes in collections module are moved to collections.abc # module in Python 3.3 if sys.version_info >= (3, 3): from collections.abc import Iterable, Mapping # noqa: F811 else: from collections import Iterable, Mapping def _resolve_context(args, ctx=None): """Produce the context hierarchy starting with the command and traversing the complete arguments. This only follows the commands, it doesn't trigger input prompts or callbacks. :param args: List of complete args before the incomplete value. :param cli_ctx: `click.Context` object of the CLI group """ while args: command = ctx.command if isinstance(command, click.MultiCommand): if not command.chain: name, cmd, args = command.resolve_command(ctx, args) if cmd is None: return ctx ctx = cmd.make_context(name, args, parent=ctx, resilient_parsing=True) args = ctx.protected_args + ctx.args else: while args: name, cmd, args = command.resolve_command(ctx, args) if cmd is None: return ctx sub_ctx = cmd.make_context( name, args, parent=ctx, allow_extra_args=True, allow_interspersed_args=False, resilient_parsing=True, ) args = sub_ctx.args ctx = sub_ctx args = [*sub_ctx.protected_args, *sub_ctx.args] else: break return ctx _internal_commands = {} def split_arg_string(string, posix=True): """Split an argument string as with :func:`shlex.split`, but don't fail if the string is incomplete. Ignores a missing closing quote or incomplete escape sequence and uses the partial token as-is. .. code-block:: python split_arg_string("example 'my file") ["example", "my file"] split_arg_string("example my\\") ["example", "my"] :param string: String to split. """ lex = shlex.shlex(string, posix=posix) lex.whitespace_split = True lex.commenters = "" out = [] try: for token in lex: out.append(token) except ValueError: # Raised when end-of-string is reached in an invalid state. Use # the partial token as-is. The quote or escape character is in # lex.state, not lex.token. out.append(lex.token) return out def _register_internal_command(names, target, description=None): if not hasattr(target, "__call__"): raise ValueError("Internal command must be a callable") if isinstance(names, str): names = [names] elif isinstance(names, Mapping) or not isinstance(names, Iterable): raise ValueError( '"names" must be a string, or an iterable object, but got "{}"'.format( type(names).__name__ ) ) for name in names: _internal_commands[name] = (target, description) def _get_registered_target(name, default=None): target_info = _internal_commands.get(name) if target_info: return target_info[0] return default def _exit_internal(): raise ExitReplException() def _help_internal(): formatter = click.HelpFormatter() formatter.write_heading("REPL help") formatter.indent() with formatter.section("External Commands"): formatter.write_text('prefix external commands with "!"') with formatter.section("Internal Commands"): formatter.write_text('prefix internal commands with ":"') info_table = defaultdict(list) for mnemonic, target_info in _internal_commands.items(): info_table[target_info[1]].append(mnemonic) formatter.write_dl( # type: ignore[arg-type] ( # type: ignore[arg-type] ", ".join(map(":{}".format, sorted(mnemonics))), description, ) for description, mnemonics in info_table.items() ) val = formatter.getvalue() # type: str return val _register_internal_command(["q", "quit", "exit"], _exit_internal, "exits the repl") _register_internal_command( ["?", "h", "help"], _help_internal, "displays general help information" ) def _execute_internal_and_sys_cmds( command, allow_internal_commands=True, allow_system_commands=True, ): """ Executes internal, system, and all the other registered click commands from the input """ if allow_system_commands and dispatch_repl_commands(command): return None if allow_internal_commands: result = handle_internal_commands(command) if isinstance(result, str): click.echo(result) return None try: return split_arg_string(command) except ValueError as e: raise CommandLineParserError("{}".format(e)) def exit(): """Exit the repl""" _exit_internal() def dispatch_repl_commands(command): """ Execute system commands entered in the repl. System commands are all commands starting with "!". """ if command.startswith("!"): os.system(command[1:]) return True return False def handle_internal_commands(command): """ Run repl-internal commands. Repl-internal commands are all commands starting with ":". """ if command.startswith(":"): target = _get_registered_target(command[1:], default=None) if target: return target() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1686832855.3919046 click-repl-0.3.0/click_repl.egg-info/0000775000175000017500000000000014442603327016035 5ustar00asifasif././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686832855.0 click-repl-0.3.0/click_repl.egg-info/PKG-INFO0000664000175000017500000000637314442603327017143 0ustar00asifasifMetadata-Version: 2.1 Name: click-repl Version: 0.3.0 Summary: REPL plugin for Click Home-page: https://github.com/untitaker/click-repl Author: Markus Unterwaditzer Author-email: markus@unterwaditzer.net License: MIT Platform: UNKNOWN Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.6 Description-Content-Type: text/markdown Provides-Extra: testing License-File: LICENSE click-repl === [![Tests](https://github.com/click-contrib/click-repl/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/click-contrib/click-repl/actions/workflows/tests.yml) [![License](https://img.shields.io/pypi/l/click-repl?label=License)](https://github.com/click-contrib/click-repl/LICENSE) ![Python - version](https://img.shields.io/badge/python-3%20%7C%203.7%20%7C%203.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue) [![PyPi - version](https://img.shields.io/badge/pypi-v0.2.0-blue)](https://pypi.org/project/click-repl/) ![wheels](https://img.shields.io/piwheels/v/click-repl?label=wheel) ![PyPI - Status](https://img.shields.io/pypi/status/click) ![PyPI - Downloads](https://img.shields.io/pypi/dm/click-repl) Installation === Installation is done via pip: ``` pip install click-repl ``` Usage === In your [click](http://click.pocoo.org/) app: ```py import click from click_repl import register_repl @click.group() def cli(): pass @cli.command() def hello(): click.echo("Hello world!") register_repl(cli) cli() ``` In the shell: ``` $ my_app repl > hello Hello world! > ^C $ echo hello | my_app repl Hello world! ``` **Features not shown:** - Tab-completion. - The parent context is reused, which means `ctx.obj` persists between subcommands. If you're keeping caches on that object (like I do), using the app's repl instead of the shell is a huge performance win. - `!` - prefix executes shell commands. You can use the internal `:help` command to explain usage. Advanced Usage === For more flexibility over how your REPL works you can use the `repl` function directly instead of `register_repl`. For example, in your app: ```py import click from click_repl import repl from prompt_toolkit.history import FileHistory @click.group() def cli(): pass @cli.command() def myrepl(): prompt_kwargs = { 'history': FileHistory('/etc/myrepl/myrepl-history'), } repl(click.get_current_context(), prompt_kwargs=prompt_kwargs) cli() ``` And then your custom `myrepl` command will be available on your CLI, which will start a REPL which has its history stored in `/etc/myrepl/myrepl-history` and persist between sessions. Any arguments that can be passed to the [`python-prompt-toolkit`](https://github.com/prompt-toolkit/python-prompt-toolkit) [Prompt](http://python-prompt-toolkit.readthedocs.io/en/stable/pages/reference.html?prompt_toolkit.shortcuts.Prompt#prompt_toolkit.shortcuts.Prompt) class can be passed in the `prompt_kwargs` argument and will be used when instantiating your `Prompt`. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686832855.0 click-repl-0.3.0/click_repl.egg-info/SOURCES.txt0000664000175000017500000000057214442603327017725 0ustar00asifasifLICENSE MANIFEST.in README.md pyproject.toml setup.cfg setup.py click_repl/__init__.py click_repl/_completer.py click_repl/_repl.py click_repl/exceptions.py click_repl/utils.py click_repl.egg-info/PKG-INFO click_repl.egg-info/SOURCES.txt click_repl.egg-info/dependency_links.txt click_repl.egg-info/not-zip-safe click_repl.egg-info/requires.txt click_repl.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686832855.0 click-repl-0.3.0/click_repl.egg-info/dependency_links.txt0000664000175000017500000000000114442603327022103 0ustar00asifasif ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686808462.0 click-repl-0.3.0/click_repl.egg-info/not-zip-safe0000664000175000017500000000000114442523616020265 0ustar00asifasif ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686832855.0 click-repl-0.3.0/click_repl.egg-info/requires.txt0000664000175000017500000000013014442603327020427 0ustar00asifasifclick>=7.0 prompt_toolkit>=3.0.36 [testing] pytest-cov>=4.0.0 pytest>=7.2.1 tox>=4.4.3 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686832855.0 click-repl-0.3.0/click_repl.egg-info/top_level.txt0000664000175000017500000000001314442603327020561 0ustar00asifasifclick_repl ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/pyproject.toml0000664000175000017500000000027014442520014015136 0ustar00asifasif[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.pytest.ini_options] addopts = [ "--cov=click_repl" ] testpaths = [ "tests", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1686832855.3919046 click-repl-0.3.0/setup.cfg0000664000175000017500000000203014442603327014050 0ustar00asifasif[metadata] name = click-repl version = attr: click_repl.__version__ description = REPL plugin for Click description-file = README.md long_description_content_type = text/markdown long_description = file: README.md url = https://github.com/untitaker/click-repl author = Markus Unterwaditzer author_email = markus@unterwaditzer.net license = MIT classifiers = Programming Language :: Python :: 3 Programming Language :: Python :: 3 :: Only Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 [options] packages = click_repl install_requires = click>=7.0 prompt_toolkit>=3.0.36 python_requires = >=3.6 zip_safe = no [options.extras_require] testing = pytest>=7.2.1 pytest-cov>=4.0.0 tox>=4.4.3 [flake8] ignore = E203, E266, W503, E402, E731, C901 max-line-length = 90 max-complexity = 18 select = B,C,E,F,W,T4,B9 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1686806540.0 click-repl-0.3.0/setup.py0000664000175000017500000000013614442520014013735 0ustar00asifasif#!/usr/bin/env python3 from setuptools import setup if __name__ == '__main__': setup()