123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436 |
- import inspect
- import types
- import typing as t
- from functools import update_wrapper
- from gettext import gettext as _
- from .core import Argument
- from .core import Command
- from .core import Context
- from .core import Group
- from .core import Option
- from .core import Parameter
- from .globals import get_current_context
- from .utils import echo
- F = t.TypeVar("F", bound=t.Callable[..., t.Any])
- FC = t.TypeVar("FC", t.Callable[..., t.Any], Command)
- def pass_context(f: F) -> F:
- """Marks a callback as wanting to receive the current context
- object as first argument.
- """
- def new_func(*args, **kwargs): # type: ignore
- return f(get_current_context(), *args, **kwargs)
- return update_wrapper(t.cast(F, new_func), f)
- def pass_obj(f: F) -> F:
- """Similar to :func:`pass_context`, but only pass the object on the
- context onwards (:attr:`Context.obj`). This is useful if that object
- represents the state of a nested system.
- """
- def new_func(*args, **kwargs): # type: ignore
- return f(get_current_context().obj, *args, **kwargs)
- return update_wrapper(t.cast(F, new_func), f)
- def make_pass_decorator(
- object_type: t.Type, ensure: bool = False
- ) -> "t.Callable[[F], F]":
- """Given an object type this creates a decorator that will work
- similar to :func:`pass_obj` but instead of passing the object of the
- current context, it will find the innermost context of type
- :func:`object_type`.
- This generates a decorator that works roughly like this::
- from functools import update_wrapper
- def decorator(f):
- @pass_context
- def new_func(ctx, *args, **kwargs):
- obj = ctx.find_object(object_type)
- return ctx.invoke(f, obj, *args, **kwargs)
- return update_wrapper(new_func, f)
- return decorator
- :param object_type: the type of the object to pass.
- :param ensure: if set to `True`, a new object will be created and
- remembered on the context if it's not there yet.
- """
- def decorator(f: F) -> F:
- def new_func(*args, **kwargs): # type: ignore
- ctx = get_current_context()
- if ensure:
- obj = ctx.ensure_object(object_type)
- else:
- obj = ctx.find_object(object_type)
- if obj is None:
- raise RuntimeError(
- "Managed to invoke callback without a context"
- f" object of type {object_type.__name__!r}"
- " existing."
- )
- return ctx.invoke(f, obj, *args, **kwargs)
- return update_wrapper(t.cast(F, new_func), f)
- return decorator
- def pass_meta_key(
- key: str, *, doc_description: t.Optional[str] = None
- ) -> "t.Callable[[F], F]":
- """Create a decorator that passes a key from
- :attr:`click.Context.meta` as the first argument to the decorated
- function.
- :param key: Key in ``Context.meta`` to pass.
- :param doc_description: Description of the object being passed,
- inserted into the decorator's docstring. Defaults to "the 'key'
- key from Context.meta".
- .. versionadded:: 8.0
- """
- def decorator(f: F) -> F:
- def new_func(*args, **kwargs): # type: ignore
- ctx = get_current_context()
- obj = ctx.meta[key]
- return ctx.invoke(f, obj, *args, **kwargs)
- return update_wrapper(t.cast(F, new_func), f)
- if doc_description is None:
- doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
- decorator.__doc__ = (
- f"Decorator that passes {doc_description} as the first argument"
- " to the decorated function."
- )
- return decorator
- def _make_command(
- f: F,
- name: t.Optional[str],
- attrs: t.MutableMapping[str, t.Any],
- cls: t.Type[Command],
- ) -> Command:
- if isinstance(f, Command):
- raise TypeError("Attempted to convert a callback into a command twice.")
- try:
- params = f.__click_params__ # type: ignore
- params.reverse()
- del f.__click_params__ # type: ignore
- except AttributeError:
- params = []
- help = attrs.get("help")
- if help is None:
- help = inspect.getdoc(f)
- else:
- help = inspect.cleandoc(help)
- attrs["help"] = help
- return cls(
- name=name or f.__name__.lower().replace("_", "-"),
- callback=f,
- params=params,
- **attrs,
- )
- def command(
- name: t.Optional[str] = None,
- cls: t.Optional[t.Type[Command]] = None,
- **attrs: t.Any,
- ) -> t.Callable[[F], Command]:
- r"""Creates a new :class:`Command` and uses the decorated function as
- callback. This will also automatically attach all decorated
- :func:`option`\s and :func:`argument`\s as parameters to the command.
- The name of the command defaults to the name of the function with
- underscores replaced by dashes. If you want to change that, you can
- pass the intended name as the first argument.
- All keyword arguments are forwarded to the underlying command class.
- Once decorated the function turns into a :class:`Command` instance
- that can be invoked as a command line utility or be attached to a
- command :class:`Group`.
- :param name: the name of the command. This defaults to the function
- name with underscores replaced by dashes.
- :param cls: the command class to instantiate. This defaults to
- :class:`Command`.
- """
- if cls is None:
- cls = Command
- def decorator(f: t.Callable[..., t.Any]) -> Command:
- cmd = _make_command(f, name, attrs, cls) # type: ignore
- cmd.__doc__ = f.__doc__
- return cmd
- return decorator
- def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]:
- """Creates a new :class:`Group` with a function as callback. This
- works otherwise the same as :func:`command` just that the `cls`
- parameter is set to :class:`Group`.
- """
- attrs.setdefault("cls", Group)
- return t.cast(Group, command(name, **attrs))
- def _param_memo(f: FC, param: Parameter) -> None:
- if isinstance(f, Command):
- f.params.append(param)
- else:
- if not hasattr(f, "__click_params__"):
- f.__click_params__ = [] # type: ignore
- f.__click_params__.append(param) # type: ignore
- def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
- """Attaches an argument to the command. All positional arguments are
- passed as parameter declarations to :class:`Argument`; all keyword
- arguments are forwarded unchanged (except ``cls``).
- This is equivalent to creating an :class:`Argument` instance manually
- and attaching it to the :attr:`Command.params` list.
- :param cls: the argument class to instantiate. This defaults to
- :class:`Argument`.
- """
- def decorator(f: FC) -> FC:
- ArgumentClass = attrs.pop("cls", Argument)
- _param_memo(f, ArgumentClass(param_decls, **attrs))
- return f
- return decorator
- def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
- """Attaches an option to the command. All positional arguments are
- passed as parameter declarations to :class:`Option`; all keyword
- arguments are forwarded unchanged (except ``cls``).
- This is equivalent to creating an :class:`Option` instance manually
- and attaching it to the :attr:`Command.params` list.
- :param cls: the option class to instantiate. This defaults to
- :class:`Option`.
- """
- def decorator(f: FC) -> FC:
- # Issue 926, copy attrs, so pre-defined options can re-use the same cls=
- option_attrs = attrs.copy()
- if "help" in option_attrs:
- option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
- OptionClass = option_attrs.pop("cls", Option)
- _param_memo(f, OptionClass(param_decls, **option_attrs))
- return f
- return decorator
- def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
- """Add a ``--yes`` option which shows a prompt before continuing if
- not passed. If the prompt is declined, the program will exit.
- :param param_decls: One or more option names. Defaults to the single
- value ``"--yes"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- """
- def callback(ctx: Context, param: Parameter, value: bool) -> None:
- if not value:
- ctx.abort()
- if not param_decls:
- param_decls = ("--yes",)
- kwargs.setdefault("is_flag", True)
- kwargs.setdefault("callback", callback)
- kwargs.setdefault("expose_value", False)
- kwargs.setdefault("prompt", "Do you want to continue?")
- kwargs.setdefault("help", "Confirm the action without prompting.")
- return option(*param_decls, **kwargs)
- def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
- """Add a ``--password`` option which prompts for a password, hiding
- input and asking to enter the value again for confirmation.
- :param param_decls: One or more option names. Defaults to the single
- value ``"--password"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- """
- if not param_decls:
- param_decls = ("--password",)
- kwargs.setdefault("prompt", True)
- kwargs.setdefault("confirmation_prompt", True)
- kwargs.setdefault("hide_input", True)
- return option(*param_decls, **kwargs)
- def version_option(
- version: t.Optional[str] = None,
- *param_decls: str,
- package_name: t.Optional[str] = None,
- prog_name: t.Optional[str] = None,
- message: t.Optional[str] = None,
- **kwargs: t.Any,
- ) -> t.Callable[[FC], FC]:
- """Add a ``--version`` option which immediately prints the version
- number and exits the program.
- If ``version`` is not provided, Click will try to detect it using
- :func:`importlib.metadata.version` to get the version for the
- ``package_name``. On Python < 3.8, the ``importlib_metadata``
- backport must be installed.
- If ``package_name`` is not provided, Click will try to detect it by
- inspecting the stack frames. This will be used to detect the
- version, so it must match the name of the installed package.
- :param version: The version number to show. If not provided, Click
- will try to detect it.
- :param param_decls: One or more option names. Defaults to the single
- value ``"--version"``.
- :param package_name: The package name to detect the version from. If
- not provided, Click will try to detect it.
- :param prog_name: The name of the CLI to show in the message. If not
- provided, it will be detected from the command.
- :param message: The message to show. The values ``%(prog)s``,
- ``%(package)s``, and ``%(version)s`` are available. Defaults to
- ``"%(prog)s, version %(version)s"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- :raise RuntimeError: ``version`` could not be detected.
- .. versionchanged:: 8.0
- Add the ``package_name`` parameter, and the ``%(package)s``
- value for messages.
- .. versionchanged:: 8.0
- Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
- version is detected based on the package name, not the entry
- point name. The Python package name must match the installed
- package name, or be passed with ``package_name=``.
- """
- if message is None:
- message = _("%(prog)s, version %(version)s")
- if version is None and package_name is None:
- 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]
- def callback(ctx: Context, param: Parameter, value: bool) -> None:
- if not value or ctx.resilient_parsing:
- return
- nonlocal prog_name
- nonlocal version
- if prog_name is None:
- prog_name = ctx.find_root().info_name
- if version is None and package_name is not None:
- metadata: t.Optional[types.ModuleType]
- try:
- from importlib import metadata # type: ignore
- except ImportError:
- # Python < 3.8
- import importlib_metadata as metadata # type: ignore
- try:
- version = metadata.version(package_name) # type: ignore
- except metadata.PackageNotFoundError: # type: ignore
- raise RuntimeError(
- f"{package_name!r} is not installed. Try passing"
- " 'package_name' instead."
- ) from None
- if version is None:
- raise RuntimeError(
- f"Could not determine the version for {package_name!r} automatically."
- )
- echo(
- t.cast(str, message)
- % {"prog": prog_name, "package": package_name, "version": version},
- color=ctx.color,
- )
- ctx.exit()
- if not param_decls:
- param_decls = ("--version",)
- kwargs.setdefault("is_flag", True)
- kwargs.setdefault("expose_value", False)
- kwargs.setdefault("is_eager", True)
- kwargs.setdefault("help", _("Show the version and exit."))
- kwargs["callback"] = callback
- return option(*param_decls, **kwargs)
- def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
- """Add a ``--help`` option which immediately prints the help page
- and exits the program.
- This is usually unnecessary, as the ``--help`` option is added to
- each command automatically unless ``add_help_option=False`` is
- passed.
- :param param_decls: One or more option names. Defaults to the single
- value ``"--help"``.
- :param kwargs: Extra arguments are passed to :func:`option`.
- """
- def callback(ctx: Context, param: Parameter, value: bool) -> None:
- if not value or ctx.resilient_parsing:
- return
- echo(ctx.get_help(), color=ctx.color)
- ctx.exit()
- if not param_decls:
- param_decls = ("--help",)
- kwargs.setdefault("is_flag", True)
- kwargs.setdefault("expose_value", False)
- kwargs.setdefault("is_eager", True)
- kwargs.setdefault("help", _("Show this message and exit."))
- kwargs["callback"] = callback
- return option(*param_decls, **kwargs)
|