diff --git a/src/cleo/commands/completions_command.py b/src/cleo/commands/completions_command.py index 6a9df744..80f27947 100644 --- a/src/cleo/commands/completions_command.py +++ b/src/cleo/commands/completions_command.py @@ -14,6 +14,7 @@ from cleo._compat import shell_quote from cleo.commands.command import Command from cleo.commands.completions.templates import TEMPLATES +from cleo.exceptions import CleoRuntimeError if TYPE_CHECKING: @@ -137,10 +138,32 @@ def render(self, shell: str) -> str: raise RuntimeError(f"Unrecognized shell: {shell}") + @staticmethod + def _get_prog_name_from_stack() -> str: + package_name = "" + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + if not package_name: + raise CleoRuntimeError("Can not determine package name") + + return package_name + def _get_script_name_and_path(self) -> tuple[str, str]: - # FIXME: when generating completions via `python -m script completions`, - # we incorrectly infer `script_name` as `__main__.py` - script_name = self._io.input.script_name or inspect.stack()[-1][1] + script_name = self._io.input.script_name or self._get_prog_name_from_stack() script_path = posixpath.realpath(script_name) script_name = os.path.basename(script_path) @@ -215,6 +238,7 @@ def sanitize(s: str) -> str: self._zsh_describe(f"--{opt.name}", sanitize(opt.description)) for opt in sorted(cmd.definition.options, key=lambda o: o.name) ) + cmds_opts += [ f" ({command_name})", f" opts+=({options})", @@ -258,11 +282,12 @@ def sanitize(s: str) -> str: f"complete -c {script_name} -f -n '__fish{function}_no_subcommand' " f"-a {command_name} -d '{sanitize(cmd.description)}'" ) + cmds_opts += [ - f"# {command_name}", + f"# {cmd.name}", *[ f"complete -c {script_name} -A " - f"-n '__fish_seen_subcommand_from {sanitize(command_name)}' " + f"""-n '__fish_seen_subcommand_from "{cmd.name}"' """ f"-l {opt.name} -d '{sanitize(opt.description)}'" for opt in sorted(cmd.definition.options, key=lambda o: o.name) ],