def parser(cls, epilog=None, help=None, prog=None): """ Args: epilog (str | None): Optional epilog help (str | None): Help to use (default: __doc__ of caller) prog (str | None): Name of the program (default: caller cmd_ function) Returns: (argparse.ArgumentParser): Parser with help auto-populated and well formatted """ if not help or not prog: caller = find_caller() if caller: if not help and caller.function: help = caller.function.__doc__ if not prog: prog = caller.function_name if prog and prog.startswith("cmd_"): prog = prog[4:] if prog and cls._prog and prog != cls._prog: prog = "%s %s" % (cls._prog, prog) return argparse.ArgumentParser( prog=prog or cls._prog, description=Cli.formatted_help(help), epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter)
def version(*args, **attrs): """Show the version and exit""" if "version" not in attrs: # Ensure 'version' is not None here, otherwise click gets runez version (instead of caller package's version) caller = find_caller(need_package=True) attrs["version"] = get_version(caller and caller.top_level, default="") return click.version_option(*args, **attrs)
def auto_import_siblings(skip=None, caller=None): """Auto-import all sibling submodules from caller. This is handy for click command groups for example. It allows to avoid having to have a module that simply lists all sub-commands, just to ensure they are added to `main`. - ./my_cli.py:: from runez.inspector import auto_import_siblings @click.group() def main(...): ... auto_import_siblings() - ./my_sub_command.py:: from .my_cli import main @main.command() # The auto-import will trigger importing this without further ado def foo(...): ... Args: skip (list | None): Do not auto-import specified modules package (runez.system._CallerInfo | None): Caller info (for testing purposes) Returns: (list): List of imported modules, if any """ if caller is None: caller = find_caller() if not caller or caller.is_main: raise ImportError( "Calling auto_import_siblings() from __main__ is not supported: %s" % caller) if not caller.package_name or not caller.folder: raise ImportError( "Could not determine caller's __package__ and __file__, can't auto-import: %s" % caller) import pkgutil imported = [] for loader, module_name, _ in pkgutil.walk_packages([caller.folder], prefix="%s." % caller.package_name): if _should_auto_import(module_name, skip): importlib.import_module(module_name) imported.append(module_name) return imported
def __exit__(self, *_): msg = self.function_name if not msg: msg = "%s()" % find_caller() logger = _R.rdefault(self.logger, LogManager.spec.default_logger) if callable(logger): elapsed = time.time() - self.start_time elapsed = represented_duration(elapsed) msg = _R.colored(msg, self.color) elapsed = _R.colored(elapsed, self.color) msg = self.fmt.format(function=msg, elapsed=elapsed) logger(msg)
def run_cmds(cls, prog=None): """To be called from one's main() Args: prog (str | None): The name of the program (default: sys.argv[0]) """ from runez.render import PrettyTable caller = find_caller() package = caller.package_name # Will fail if no caller could be found (intentional) available_commands = {} for name, func in caller.globals(prefix="cmd_"): name = name[4:].replace("_", "-") available_commands[name] = func if not prog: if package: prog = "python -m%s" % package if caller.is_main else package elif caller.basename in ("__init__.py", "__main__.py"): prog = short(caller.folder) epilog = PrettyTable(2) epilog.header[0].style = "bold" for cmd, func in available_commands.items(): epilog.add_row(" " + cmd, first_line(func.__doc__, default="")) epilog = "Available commands:\n%s" % epilog cls._prog = prog or package parser = cls.parser(epilog=epilog, help=caller.module_docstring, prog=prog) if cls.version and package: parser.add_argument(*cls.version, action="version", version=get_version(package), help="Show version and exit") if cls.color: parser.add_argument(*cls.color, action="store_true", help="Do not use colors (even if on tty)") if cls.debug: parser.add_argument(*cls.debug, action="store_true", help="Show debugging information") if cls.dryrun: parser.add_argument(*cls.dryrun, action="store_true", help="Perform a dryrun") parser.add_argument("command", choices=available_commands, metavar="command", help="Command to run") parser.add_argument("args", nargs=argparse.REMAINDER, help="Passed-through to command") args = parser.parse_args() if cls.console_format or hasattr(args, "debug") or hasattr( args, "dryrun"): LogManager.setup( debug=getattr(args, "debug", UNSET), dryrun=getattr(args, "dryrun", UNSET), console_format=cls.console_format, console_level=cls.console_level, default_logger=cls.default_logger, locations=cls.log_locations, ) color = getattr(args, "no_color", None) if color is not None: ColorManager.activate_colors(enable=not color) try: func = available_commands[args.command] with TempArgv(args.args): func() except KeyboardInterrupt: # pragma: no cover sys.stderr.write("\nAborted\n") sys.exit(1)
def __enter__(self): """We're used as a context""" self.key = str(find_caller()) self.start() return self