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)", ), ]
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
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
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
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