diff --git a/src/click/_termui_impl.py b/src/click/_termui_impl.py index 51fd9bf35..46915e579 100644 --- a/src/click/_termui_impl.py +++ b/src/click/_termui_impl.py @@ -174,7 +174,13 @@ def format_eta(self) -> str: hours = t % 24 t //= 24 if t > 0: - return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" + return "{d}{day_label} {h:02}:{m:02}:{s:02}".format( + d=t, + day_label=_("d"), + h=hours, + m=minutes, + s=seconds, + ) else: return f"{hours:02}:{minutes:02}:{seconds:02}" return "" diff --git a/src/click/_winconsole.py b/src/click/_winconsole.py index e56c7c6ae..7e5a31fe8 100644 --- a/src/click/_winconsole.py +++ b/src/click/_winconsole.py @@ -28,6 +28,7 @@ from ctypes.wintypes import HANDLE from ctypes.wintypes import LPCWSTR from ctypes.wintypes import LPWSTR +from gettext import gettext as _ from ._compat import _NonClosingTextIOWrapper @@ -152,7 +153,7 @@ def readinto(self, b: Buffer) -> int: # wait for KeyboardInterrupt time.sleep(0.1) if not rv: - raise OSError(f"Windows error: {GetLastError()}") + raise OSError(_("Windows error: {error}").format(error=GetLastError())) # noqa: UP032 if buffer[0] == EOF: return 0 @@ -169,7 +170,7 @@ def _get_error_message(errno: int) -> str: return "ERROR_SUCCESS" elif errno == ERROR_NOT_ENOUGH_MEMORY: return "ERROR_NOT_ENOUGH_MEMORY" - return f"Windows error {errno}" + return _("Windows error {errno}").format(errno=errno) # noqa: UP032 def write(self, b: Buffer) -> int: bytes_to_be_written = len(b) diff --git a/src/click/core.py b/src/click/core.py index 4745b533e..9753eb93f 100644 --- a/src/click/core.py +++ b/src/click/core.py @@ -1,3 +1,11 @@ +# Ask Ruff to accept the format method on strings, and not let pyupgrade +# always force f-strings. The latter are unfortunately not supported yet +# by Babel, a localisation library. +# +# Note: Using `# noqa: UP032` on lines has not worked, so a file +# setting. +# ruff: noqa: UP032 + from __future__ import annotations import collections.abc as cabc @@ -76,15 +84,15 @@ def _check_nested_chain( return if register: - message = ( - f"It is not possible to add the group {cmd_name!r} to another" - f" group {base_command.name!r} that is in chain mode." - ) + message = _( + "It is not possible to add the group {cmd_name!r} to another" + " group {base_cmd_name!r} that is in chain mode." + ).format(cmd_name=cmd_name, base_cmd_name=base_command.name) # noqa: UP032 else: - message = ( - f"Found the group {cmd_name!r} as subcommand to another group " - f" {base_command.name!r} that is in chain mode. This is not supported." - ) + message = _( + "Found the group {cmd_name!r} as subcommand to another group " + " {base_cmd_name!r} that is in chain mode. This is not supported." + ).format(cmd_name=cmd_name, base_cmd_name=base_command.name) # noqa: UP032 raise RuntimeError(message) @@ -986,8 +994,10 @@ def get_params(self, ctx: Context) -> list[Parameter]: for duplicate_opt in duplicate_opts: warnings.warn( ( - f"The parameter {duplicate_opt} is used more than once. " - "Remove its duplicate as parameters should be unique." + _( + "The parameter {param} is used more than once. " + "Remove its duplicate as parameters should be unique." + ).format(param=duplicate_opt) ), stacklevel=3, ) @@ -1077,9 +1087,9 @@ def get_short_help_str(self, limit: int = 45) -> str: if self.deprecated: deprecated_message = ( - f"(DEPRECATED: {self.deprecated})" + _("(DEPRECATED: {target})".format(target=self.deprecated)) if isinstance(self.deprecated, str) - else "(DEPRECATED)" + else _("(DEPRECATED)") ) text = _("{text} {deprecated_message}").format( text=text, deprecated_message=deprecated_message @@ -1114,9 +1124,9 @@ def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: if self.deprecated: deprecated_message = ( - f"(DEPRECATED: {self.deprecated})" + _("(DEPRECATED: {target})".format(target=self.deprecated)) if isinstance(self.deprecated, str) - else "(DEPRECATED)" + else _("(DEPRECATED)") ) text = _("{text} {deprecated_message}").format( text=text, deprecated_message=deprecated_message @@ -2123,8 +2133,10 @@ def __init__( if __debug__: if self.type.is_composite and nargs != self.type.arity: raise ValueError( - f"'nargs' must be {self.type.arity} (or None) for" - f" type {self.type!r}, but it was {nargs}." + _( + "'nargs' must be {arity} (or None) for" + " type {type!r}, but it was {nargs}." + ).format(arity=self.type.arity, type=self.type, nargs=nargs) ) # Skip no default or callable default. @@ -2158,14 +2170,21 @@ def __init__( if nargs > 1 and len(check_default) != nargs: subject = "item length" if multiple else "length" raise ValueError( - f"'default' {subject} must match nargs={nargs}." + _("'default' {subject} must match nargs={nargs}.").format( + subject=subject, nargs=nargs + ) ) if required and deprecated: raise ValueError( - f"The {self.param_type_name} '{self.human_readable_name}' " - "is deprecated and still required. A deprecated " - f"{self.param_type_name} cannot be required." + _( + "The {type_name} '{readable_name}' " + "is deprecated and still required. A deprecated " + "{type_name} cannot be required." + ).format( + type_name=self.param_type_name, + readable_name=self.human_readable_name, + ) ) def to_info_dict(self) -> dict[str, t.Any]: @@ -2571,9 +2590,9 @@ def __init__( if deprecated: deprecated_message = ( - f"(DEPRECATED: {deprecated})" + _("(DEPRECATED: {target})".format(target=deprecated)) if isinstance(deprecated, str) - else "(DEPRECATED)" + else _("(DEPRECATED)") ) help = help + deprecated_message if help is not None else deprecated_message @@ -2676,7 +2695,7 @@ def to_info_dict(self) -> dict[str, t.Any]: def get_error_hint(self, ctx: Context) -> str: result = super().get_error_hint(ctx) if self.show_envvar: - result += f" (env var: '{self.envvar}')" + result += _(" (env var: '{var}')").format(var=self.envvar) # noqa: UP032 return result def _parse_decls( @@ -2690,7 +2709,7 @@ def _parse_decls( for decl in decls: if decl.isidentifier(): if name is not None: - raise TypeError(f"Name '{name}' defined twice") + raise TypeError(_("Name '{name}' defined twice").format(name=name)) # noqa: UP032 name = decl else: split_char = ";" if decl[:1] == "/" else "/" @@ -2705,8 +2724,10 @@ def _parse_decls( secondary_opts.append(second.lstrip()) if first == second: raise ValueError( - f"Boolean option {decl!r} cannot use the" - " same flag for true/false." + _( + "Boolean option {decl!r} cannot use the" + " same flag for true/false." + ).format(decl=decl) ) else: possible_names.append(_split_opt(decl)) @@ -2722,14 +2743,18 @@ def _parse_decls( if not expose_value: return None, opts, secondary_opts raise TypeError( - f"Could not determine name for option with declarations {decls!r}" + _( + "Could not determine name for option with declarations {decls!r}" + ).format(decls=decls) ) if not opts and not secondary_opts: raise TypeError( - f"No options defined but a name was passed ({name})." - " Did you mean to declare an argument instead? Did" - f" you mean to pass '--{name}'?" + _( + "No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + " you mean to pass '--{name}'?" + ).format(name=name) ) return name, opts, secondary_opts @@ -3095,8 +3120,10 @@ def _parse_decls( name = name.replace("-", "_").lower() else: raise TypeError( - "Arguments take exactly one parameter declaration, got" - f" {len(decls)}: {decls}." + _( + "Arguments take exactly one parameter declaration, got" + " {length}: {decls}." + ).format(length=len(decls), decls=decls) ) return name, [arg], [] diff --git a/src/click/decorators.py b/src/click/decorators.py index 21f4c3422..3b00e6300 100644 --- a/src/click/decorators.py +++ b/src/click/decorators.py @@ -1,3 +1,11 @@ +# Ask Ruff to accept the format method on strings, and not let pyupgrade +# always force f-strings. The latter are unfortunately not supported yet +# by Babel, a localisation library. +# +# Note: Using `# noqa: UP032` on lines has not worked, so a file +# setting. +# ruff: noqa: UP032 + from __future__ import annotations import inspect @@ -85,10 +93,12 @@ def new_func(*args: P.args, **kwargs: P.kwargs) -> R: if obj is None: raise RuntimeError( - "Managed to invoke callback without a context" - f" object of type {object_type.__name__!r}" - " existing." - ) + _( + "Managed to invoke callback without a context" + " object of type {type!r}" + " existing." + ).format(type=object_type.__name__) # noqa: UP032 + ) # noqa: UP032 return ctx.invoke(f, obj, *args, **kwargs) @@ -121,12 +131,14 @@ def new_func(*args: P.args, **kwargs: P.kwargs) -> R: return update_wrapper(new_func, f) if doc_description is None: - doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + doc_description = _("the {key!r} key from :attr:`click.Context.meta`").format( + key=key + ) # noqa: UP032 - decorator.__doc__ = ( - f"Decorator that passes {doc_description} as the first argument" + decorator.__doc__ = _( + "Decorator that passes {description} as the first argument" " to the decorated function." - ) + ).format(description=doc_description) # noqa: UP032 return decorator @@ -498,13 +510,16 @@ def callback(ctx: Context, param: Parameter, value: bool) -> None: version = importlib.metadata.version(package_name) except importlib.metadata.PackageNotFoundError: raise RuntimeError( - f"{package_name!r} is not installed. Try passing" - " 'package_name' instead." + _( + "{name!r} is not installed. Try passing 'package_name' instead." + ).format(name=package_name) # noqa: UP032 ) from None if version is None: raise RuntimeError( - f"Could not determine the version for {package_name!r} automatically." + _("Could not determine the version for {name!r} automatically.").format( + name=package_name + ) # noqa: UP032 ) echo( diff --git a/src/click/formatting.py b/src/click/formatting.py index 9891f8809..b5d7bd8a2 100644 --- a/src/click/formatting.py +++ b/src/click/formatting.py @@ -153,7 +153,7 @@ def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> N ``"Usage: "``. """ if prefix is None: - prefix = f"{_('Usage:')} " + prefix = "{usage} ".format(usage=_("Usage:")) usage_prefix = f"{prefix:>{self.current_indent}}{prog} " text_width = self.width - self.current_indent diff --git a/src/click/parser.py b/src/click/parser.py index a8b7d2634..8a02d7845 100644 --- a/src/click/parser.py +++ b/src/click/parser.py @@ -18,6 +18,14 @@ Copyright 2002-2006 Python Software Foundation. All rights reserved. """ +# Ask Ruff to accept the format method on strings, and not let pyupgrade +# always force f-strings. The latter are unfortunately not supported yet +# by Babel, a localisation library. +# +# Note: Using `# noqa: UP032` on lines has not worked, so a file +# setting. +# ruff: noqa: UP032 + # This code uses parts of optparse written by Gregory P. Ward and # maintained by the Python Software Foundation. # Copyright 2001-2006 Gregory P. Ward @@ -142,7 +150,11 @@ def __init__( for opt in opts: prefix, value = _split_opt(opt) if not prefix: - raise ValueError(f"Invalid start character for option ({opt})") + raise ValueError( + _("Invalid start character for option ({option})").format( + option=opt + ) + ) # noqa: UP032 self.prefixes.add(prefix[0]) if len(prefix) == 1 and len(value) == 1: self._short_opts.append(opt) @@ -175,7 +187,7 @@ def process(self, value: t.Any, state: _ParsingState) -> None: elif self.action == "count": state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore else: - raise ValueError(f"unknown action '{self.action}'") + raise ValueError(_("unknown action '{action}'").format(action=self.action)) # noqa: UP032 state.order.append(self.obj) @@ -511,8 +523,10 @@ def __getattr__(name: str) -> object: "ParsingState", }: warnings.warn( - f"'parser.{name}' is deprecated and will be removed in Click 9.0." - " The old parser is available in 'optparse'.", + _( + "'parser.{name}' is deprecated and will be removed in Click 9.0." + " The old parser is available in 'optparse'." + ).format(name=name), # noqa: UP032 DeprecationWarning, stacklevel=2, ) @@ -522,8 +536,10 @@ def __getattr__(name: str) -> object: from .shell_completion import split_arg_string warnings.warn( - "Importing 'parser.split_arg_string' is deprecated, it will only be" - " available in 'shell_completion' in Click 9.0.", + _( + "Importing 'parser.split_arg_string' is deprecated, it will only be" + " available in 'shell_completion' in Click 9.0." + ), DeprecationWarning, stacklevel=2, ) diff --git a/src/click/termui.py b/src/click/termui.py index dcbb22216..a2d9dff9e 100644 --- a/src/click/termui.py +++ b/src/click/termui.py @@ -608,13 +608,13 @@ def style( try: bits.append(f"\033[{_interpret_color(fg)}m") except KeyError: - raise TypeError(f"Unknown color {fg!r}") from None + raise TypeError(_("Unknown color {colour!r}").format(colour=fg)) from None # noqa: UP032 if bg: try: bits.append(f"\033[{_interpret_color(bg, 10)}m") except KeyError: - raise TypeError(f"Unknown color {bg!r}") from None + raise TypeError(_("Unknown color {colour!r}").format(colour=bg)) from None # noqa: UP032 if bold is not None: bits.append(f"\033[{1 if bold else 22}m") diff --git a/src/click/types.py b/src/click/types.py index 684cb3b1e..a31729548 100644 --- a/src/click/types.py +++ b/src/click/types.py @@ -1,3 +1,11 @@ +# Ask Ruff to accept the format method on strings, and not let pyupgrade +# always force f-strings. The latter are unfortunately not supported yet +# by Babel, a localisation library. +# +# Note: Using `# noqa: UP032` on lines has not worked, so a file +# setting. +# ruff: noqa: UP032 + from __future__ import annotations import collections.abc as cabc @@ -372,7 +380,7 @@ def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str: ).format(value=value, choice=choices_str, choices=choices_str) def __repr__(self) -> str: - return f"Choice({list(self.choices)})" + return _("Choice({choices})").format(choices=list(self.choices)) # noqa: UP032 def shell_complete( self, ctx: Context, param: Parameter, incomplete: str @@ -809,7 +817,11 @@ def convert( return f except OSError as e: - self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx) + self.fail( + f"'{format_filename(value)}': {e.strerror}", + param, + ctx, + ) def shell_complete( self, ctx: Context, param: Parameter, incomplete: str @@ -1116,7 +1128,9 @@ def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType: try: if issubclass(ty, ParamType): raise AssertionError( - f"Attempted to use an uninstantiated parameter type ({ty})." + _( + "Attempted to use an uninstantiated parameter type ({type})." + ).format(type=ty) # noqa: UP032 ) except TypeError: # ty is an instance (correct), so issubclass fails. diff --git a/src/click/utils.py b/src/click/utils.py index ab2fe5889..356fb6a5e 100644 --- a/src/click/utils.py +++ b/src/click/utils.py @@ -1,3 +1,11 @@ +# Ask Ruff to accept the format method on strings, and not let pyupgrade +# always force f-strings. The latter are unfortunately not supported yet +# by Babel, a localisation library. +# +# Note: Using `# noqa: UP032` on lines has not worked, so a file +# setting. +# ruff: noqa: UP032 + from __future__ import annotations import collections.abc as cabc @@ -6,6 +14,7 @@ import sys import typing as t from functools import update_wrapper +from gettext import gettext as _ from types import ModuleType from types import TracebackType @@ -330,7 +339,7 @@ def get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryI """ opener = binary_streams.get(name) if opener is None: - raise TypeError(f"Unknown standard stream '{name}'") + raise TypeError(_("Unknown standard stream '{name}'").format(name=name)) # noqa: UP032 return opener() @@ -351,7 +360,7 @@ def get_text_stream( """ opener = text_streams.get(name) if opener is None: - raise TypeError(f"Unknown standard stream '{name}'") + raise TypeError(_("Unknown standard stream '{name}'").format(name=name)) # noqa: UP032 return opener(encoding, errors)