コード例 #1
0
ファイル: flow.py プロジェクト: zpyoung/click-project
def get_flow_params(cmd, flow_default=None, flowfrom_default=None, flowafter_default=None):
    deps = get_flow_commands_to_run(cmd)
    return [
        AutomaticOption(
            ["--flow/--no-flow"],
            group='flow',
            default=flow_default,
            help="Trigger the dependency flow ({})".format(", ".join(deps))
        ),
        AutomaticOption(
            ["--flow-from"],
            group='flow',
            default=flowfrom_default,
            type=click.Choice(deps),
            help="Trigger the dependency flow from the given step"
            " (ignored if --flow-after is given)",
        ),
        AutomaticOption(
            ["--flow-after"],
            group='flow',
            default=flowafter_default,
            type=click.Choice(deps),
            help="Trigger the dependency flow after the given step (overrides --flow-from)",
        ),
    ]
コード例 #2
0
def in_project(command):
    from click_project.overloads import AutomaticOption
    options = [
        AutomaticOption(['--in-project/--no-in-project'], group='working directory', is_flag=True,
                        help="Run the command in the project directory"),
        AutomaticOption(['--cwd'], group='working directory',
                        help="Run the command in this directory. It can be used with --in-project to change to a"
                             " directory relative to the project directory")
    ]
    callback = command.callback

    @functools.wraps(callback)
    def launcher(*args, **kwargs):
        in_project = kwargs["in_project"]
        cwd = kwargs["cwd"]
        del kwargs["in_project"]
        del kwargs["cwd"]

        def run_in_cwd():
            if cwd:
                with cd(cwd):
                    return callback(*args, **kwargs)
            else:
                return callback(*args, **kwargs)

        if in_project:
            config.require_project()
            with cd(config.project):
                res = run_in_cwd()
        else:
            res = run_in_cwd()
        return res
    command.callback = launcher
    command.params.extend(options)
    return command
コード例 #3
0
 def _get_command(self, path, parent=None):
     module = self.source.load_plugin(path)
     if path not in dir(module):
         raise BadCustomCommandError(
             f"The file {module.__file__} must contain a command or a group named {path}"
         )
     cmd = getattr(module, path)
     cmd.customcommand_path = module.__file__
     cmd.params.append(
         AutomaticOption(
             ["--edit-customcommand"], is_flag=True, expose_value=False,
             help="Edit this command",
             callback=lambda ctx, param, value: edit_custom_command(cmd.customcommand_path) if value is True else None
         )
     )
     return cmd
コード例 #4
0
    def _get_command(self, path, parent=None):
        name = path.replace("@", ".")
        cmdhelp = "external command"
        command_name = name
        paths = self.cmddirs
        command_path = os.path.abspath(
            which(command_name, os.pathsep.join(paths)))
        options = []
        arguments = []
        flags = []
        remaining_args = False
        ignore_unknown_options = False
        try:
            process = subprocess.Popen([command_path, "--help"],
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE)
            out, err = process.communicate()
            if process.returncode == 0:
                out = out.decode("utf-8")
                cmdhelp_lines = out.splitlines() + ['']
                try:
                    index_desc = cmdhelp_lines.index('') + 1
                except ValueError:
                    index_desc = 0
                try:
                    metadata_desc = cmdhelp_lines.index('--')
                except ValueError:
                    metadata_desc = -1
                    # then, we don't bother parsing the arguments. Let the
                    # remaining arguments and unknown options pass through
                    ignore_unknown_options = True
                    remaining_args = "Remaining arguments"
                cmdhelp = "\n".join(cmdhelp_lines[index_desc:metadata_desc])
                cmdhelp = cmdhelp.strip()
                metadata_out = out[metadata_desc:]
                ignore_unknown_options = False
                for l in metadata_out.splitlines():
                    if l.startswith("O:"):
                        m = re.match(
                            "^O:(?P<name>[^:]+):(?P<type>[^:]+):(?P<help>[^:]+)(:(?P<default>[^:]+))?$",
                            l)
                        if m is None:
                            raise click.UsageError(
                                "Expected format in {} is O:name:type:help[:defautl],"
                                " got {}".format(path, l))
                        options.append(m.groupdict())
                    if l.startswith("F:"):
                        m = re.match(
                            "^F:(?P<name>[^:]+):(?P<help>[^:]+)(:(?P<default>[^:]+))?$",
                            l)
                        if m is None:
                            raise click.UsageError(
                                "Expected format in {} is F:name:help[:defautl],"
                                " got {}".format(path, l))
                        flags.append(m.groupdict())
                    if l.startswith("A:"):
                        m = re.match(
                            "^A:(?P<name>[^:]+):(?P<type>[^:]+):(?P<help>[^:]+)(:(?P<nargs>[^:]+))?$",
                            l)
                        if m is None:
                            raise click.UsageError(
                                "Expected format in {} is A:name:type:help[:nargs],"
                                " got {}".format(path, l))
                        arguments.append(m.groupdict())
                    m = re.match("^N:(?P<help>[^:]+)$", l)
                    if m is not None:
                        remaining_args = m.group("help")
                    m = re.match("^M:(?P<meta>.+)$", l)
                    if m is not None:
                        meta = m.group("meta")
                        if "I" in meta:
                            ignore_unknown_options = True
                cmdflowdepends = re.search('flowdepends: (.+)', out)
                if cmdflowdepends:
                    cmdflowdepends = cmdflowdepends.group(1).split(', ')
                else:
                    cmdflowdepends = []
            else:
                cmdflowdepends = []
                cmdhelp = "No help found... (the command is most likely broken)"
            process.wait()
        except Exception as e:
            LOGGER.warning("When loading command {}: {}".format(name, e))
            from click_project.overloads import on_command_loading_error
            on_command_loading_error()
            raise

        from click_project.decorators import command, argument, option, flag

        def external_command(**kwargs):
            from click_project.lib import call
            ctx = click.get_current_context()
            config.merge_settings()
            args = ([command_path] + list(ctx.params.get("args", [])))

            def value_to_string(value):
                return (" ".join(map(quote, value)) if type(value) is tuple
                        else str(value) if value else "")

            env = {("CLK___" + key).upper(): (value_to_string(value))
                   for key, value in kwargs.items()}
            env[("CLK___PATH").upper()] = (ctx.command_path.replace(
                " ", "_").upper())
            if "args" in ctx.params:
                env[("CLK___ARGS").upper()] = " ".join(
                    map(quote, ctx.params["args"]))

            while ctx:
                env.update({
                    (ctx.command_path.replace(" ", "_") + "__" + key).upper():
                    (value_to_string(value))
                    for key, value in ctx.params.items()
                })
                ctx = ctx.parent

            for path, parameters in config.get_settings2("parameters").items():
                env[(f"CLK_P_" + path.replace("-", "__").replace(
                    ".", "_")).upper()] = " ".join(map(quote, parameters))

            env[("CLK___CMD_OPTIND").upper()] = (str(
                len(
                    config.commandline_profile.get_settings("parameters")
                    [path])))
            env[("CLK___CMD_ARGS").upper()] = (" ".join(
                quote(a) for a in config.commandline_profile.get_settings(
                    "parameters")[path]))
            env[("CLK___OPTIND").upper()] = (str(len(args[1:])))
            env[("CLK___ALL").upper()] = (" ".join(quote(a) for a in args[1:]))
            with updated_env(**env):
                call(
                    args,
                    internal=True,
                )

        types = {
            "int": int,
            "float": float,
            "str": str,
        }

        def get_type(t):
            if t.startswith("["):
                t = click.Choice(json.loads(t))
            elif "." in t:
                t = t.split(".")
                m = importlib.import_module(".".join(t[:-1]))
                t = getattr(m, t[-1])
            elif t.startswith("date("):
                format = re.match("date\((?P<format>.+)\)", t).group("format")
                from click_project.lib import parsedatetime
                t = lambda value: parsedatetime(value)[0].strftime(format)
            else:
                t = types[t]
            return t

        if remaining_args:
            external_command = argument('args', nargs=-1,
                                        help=remaining_args)(external_command)
        for o in options:
            if "type" in o:
                t = get_type(o["type"])
            external_command = option(
                *(o["name"].split(",")),
                help=o["help"],
                type=t or str,
                default=o.get("default"),
            )(external_command)
        for a in reversed(arguments):
            if "type" in a:
                t = get_type(a["type"])
            external_command = argument(
                a["name"],
                help=a["help"],
                type=t or str,
                nargs=int(a["nargs"] or "1"),
            )(external_command)
        for f in flags:
            external_command = flag(
                *(f["name"].split(",")),
                help=f["help"],
                default=f["default"] == "True",
            )(external_command)

        external_command = command(
            name=name,
            ignore_unknown_options=ignore_unknown_options,
            help=cmdhelp,
            short_help=cmdhelp.splitlines()[0] if cmdhelp else "",
            handle_dry_run=True,
            flowdepends=cmdflowdepends)(external_command)
        external_command.params.append(
            AutomaticOption(["--edit-customcommand"],
                            help="Edit the external command",
                            expose_value=False,
                            is_flag=True,
                            callback=lambda ctx, param, value:
                            edit_external_command(command_path)
                            if value is True else None))
        external_command.customcommand_path = command_path
        return external_command
コード例 #5
0
    def _get_command(self, path, parent=None):
        name = path.split(".")[-1]
        commands_to_run = config.get_settings('alias')[path]["commands"]
        cmdhelp = config.get_settings('alias')[path]["documentation"]
        cmdhelp = cmdhelp or "Alias for: {}".format(' , '.join(
            ' '.join(quote(arg) for arg in cmd) for cmd in commands_to_run))
        short_help = cmdhelp.splitlines()[0]
        if len(cmdhelp) > 55:
            short_help = cmdhelp[:52] + '...'
        deps = []

        for cmd in commands_to_run:

            cmdctx = get_ctx(cmd, resilient_parsing=True)
            # capture the flow of the aliased command only if it is not called
            # with an explicit flow
            if (not cmdctx.params.get("flow")
                    and not cmdctx.params.get("flow_from")
                    and not cmdctx.params.get("flow_after")):
                deps += get_flow_commands_to_run(cmdctx.command.path)
        c = get_ctx(commands_to_run[-1])
        kind = None

        def create_cls(cls):
            return cls(name=name,
                       help=cmdhelp,
                       short_help=short_help,
                       ignore_unknown_options=c is not None
                       and c.ignore_unknown_options)

        if c is not None:
            if isinstance(c.command, Group):
                cls = create_cls(group)
                kind = "group"
            elif isinstance(c.command, Command):
                cls = create_cls(command)
                kind = "command"
            elif isinstance(c.command, config.main_command.__class__):
                cls = click.group(cls=config.main_command.__class__,
                                  name=name,
                                  help=cmdhelp,
                                  short_help=short_help)
                kind = config.main_command.path
            else:
                raise NotImplementedError()
        elif commands_to_run[-1][0] == config.main_command.path:
            cls = click.group(cls=config.main_command.__class__,
                              name=name,
                              help=cmdhelp,
                              short_help=short_help)
            del commands_to_run[-1][0]
            c = get_ctx(commands_to_run[-1])
            kind = config.main_command.path
        else:
            cls = create_cls(command)

        def alias_command(ctx, *args, **kwargs):
            if "config" in kwargs:
                del kwargs["config"]
            commands = list(commands_to_run)

            for command_ in commands[:-1]:
                LOGGER.debug("Running command: {}".format(" ".join(
                    quote(c) for c in command_)))
                run(command_)
            arguments = ctx.command.complete_arguments[:]
            arguments = clean_flow_arguments(arguments)
            whole_command = commands[-1] + arguments
            original_command_ctx = get_ctx(whole_command, side_effects=True)
            cur_ctx = original_command_ctx
            ctxs = []
            # if the resolution of the context brought too many commands, we
            # must not call the call back of the children of the original_command
            while cur_ctx and ctx.command.original_command != cur_ctx.command:
                cur_ctx = cur_ctx.parent

            while cur_ctx:
                ctxs.insert(0, cur_ctx)
                cur_ctx = cur_ctx.parent
            LOGGER.develop("Running command: {}".format(" ".join(
                quote(c) for c in commands[-1])))

            def run_callback(_ctx):
                LOGGER.develop(
                    "Running callback of {} with args {}, params {}".format(
                        _ctx.command.path,
                        config.commandline_profile.get_settings("parameters")[
                            _ctx.command.path],
                        _ctx.params,
                    ))
                with _ctx:
                    old_resilient_parsing = _ctx.resilient_parsing
                    _ctx.resilient_parsing = ctx.resilient_parsing
                    _ctx.command.callback(**_ctx.params)
                    _ctx.resilient_parsing = old_resilient_parsing

            for cur_ctx in ctxs:
                run_callback(cur_ctx)

        alias_command = pass_context(alias_command)
        alias_command = cls(alias_command)
        alias_command.params.append(
            AutomaticOption(
                ["--edit-alias"],
                help="Edit the alias",
                expose_value=False,
                is_flag=True,
                callback=lambda ctx, param, value: edit_alias_command(path)
                if value is True else None))
        if deps:
            alias_command.clickproject_flowdepends = deps

        alias_command.commands_to_run = commands_to_run
        if c is not None:
            alias_command.original_command = c.command
            if kind == "group":
                if c.command.default_cmd_name is not None:
                    alias_command.set_default_command(
                        c.command.default_cmd_name)
            elif kind == "command":
                alias_command.handle_dry_run = c.command.handle_dry_run
            alias_param_names = list(
                map(lambda c: c.name, alias_command.params))

            def was_given(param):
                return not (
                    # catched the default value only because it was not
                    # given to the command line
                    param.name in c.click_project_default_catch or
                    # not given for sure
                    c.params.get(param.name) is None)

            alias_command.params = [
                param for param in c.command.params
                if param.name not in alias_param_names and param.name not in
                ("flow", "flow_from", "flow_after") and (
                    # options may be given several times
                    isinstance(param, click.Option) or (
                        # it is an argument then!
                        not was_given(param) or
                        # may be given, but may be given again
                        param.multiple or
                        # may be given, but may be given again
                        param.nargs == -1))
            ] + alias_command.params
            # any option required with nargs=-1 that was already given should be
            # no more required
            for param in alias_command.params:
                if param.nargs == -1 and param.required and was_given(param):
                    param.required = False
        return alias_command