Exemple #1
0
    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)
Exemple #2
0
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)
Exemple #3
0
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
Exemple #4
0
    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)
Exemple #5
0
    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)
Exemple #6
0
 def __enter__(self):
     """We're used as a context"""
     self.key = str(find_caller())
     self.start()
     return self