]>
Commit | Line | Data |
---|---|---|
1 | import typing as t | |
2 | from gettext import gettext as _ | |
3 | from gettext import ngettext | |
4 | ||
5 | from ._compat import get_text_stderr | |
6 | from .utils import echo | |
7 | from .utils import format_filename | |
8 | ||
9 | if t.TYPE_CHECKING: | |
10 | from .core import Command | |
11 | from .core import Context | |
12 | from .core import Parameter | |
13 | ||
14 | ||
15 | def _join_param_hints( | |
16 | param_hint: t.Optional[t.Union[t.Sequence[str], str]] | |
17 | ) -> t.Optional[str]: | |
18 | if param_hint is not None and not isinstance(param_hint, str): | |
19 | return " / ".join(repr(x) for x in param_hint) | |
20 | ||
21 | return param_hint | |
22 | ||
23 | ||
24 | class ClickException(Exception): | |
25 | """An exception that Click can handle and show to the user.""" | |
26 | ||
27 | #: The exit code for this exception. | |
28 | exit_code = 1 | |
29 | ||
30 | def __init__(self, message: str) -> None: | |
31 | super().__init__(message) | |
32 | self.message = message | |
33 | ||
34 | def format_message(self) -> str: | |
35 | return self.message | |
36 | ||
37 | def __str__(self) -> str: | |
38 | return self.message | |
39 | ||
40 | def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: | |
41 | if file is None: | |
42 | file = get_text_stderr() | |
43 | ||
44 | echo(_("Error: {message}").format(message=self.format_message()), file=file) | |
45 | ||
46 | ||
47 | class UsageError(ClickException): | |
48 | """An internal exception that signals a usage error. This typically | |
49 | aborts any further handling. | |
50 | ||
51 | :param message: the error message to display. | |
52 | :param ctx: optionally the context that caused this error. Click will | |
53 | fill in the context automatically in some situations. | |
54 | """ | |
55 | ||
56 | exit_code = 2 | |
57 | ||
58 | def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None: | |
59 | super().__init__(message) | |
60 | self.ctx = ctx | |
61 | self.cmd: t.Optional["Command"] = self.ctx.command if self.ctx else None | |
62 | ||
63 | def show(self, file: t.Optional[t.IO[t.Any]] = None) -> None: | |
64 | if file is None: | |
65 | file = get_text_stderr() | |
66 | color = None | |
67 | hint = "" | |
68 | if ( | |
69 | self.ctx is not None | |
70 | and self.ctx.command.get_help_option(self.ctx) is not None | |
71 | ): | |
72 | hint = _("Try '{command} {option}' for help.").format( | |
73 | command=self.ctx.command_path, option=self.ctx.help_option_names[0] | |
74 | ) | |
75 | hint = f"{hint}\n" | |
76 | if self.ctx is not None: | |
77 | color = self.ctx.color | |
78 | echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) | |
79 | echo( | |
80 | _("Error: {message}").format(message=self.format_message()), | |
81 | file=file, | |
82 | color=color, | |
83 | ) | |
84 | ||
85 | ||
86 | class BadParameter(UsageError): | |
87 | """An exception that formats out a standardized error message for a | |
88 | bad parameter. This is useful when thrown from a callback or type as | |
89 | Click will attach contextual information to it (for instance, which | |
90 | parameter it is). | |
91 | ||
92 | .. versionadded:: 2.0 | |
93 | ||
94 | :param param: the parameter object that caused this error. This can | |
95 | be left out, and Click will attach this info itself | |
96 | if possible. | |
97 | :param param_hint: a string that shows up as parameter name. This | |
98 | can be used as alternative to `param` in cases | |
99 | where custom validation should happen. If it is | |
100 | a string it's used as such, if it's a list then | |
101 | each item is quoted and separated. | |
102 | """ | |
103 | ||
104 | def __init__( | |
105 | self, | |
106 | message: str, | |
107 | ctx: t.Optional["Context"] = None, | |
108 | param: t.Optional["Parameter"] = None, | |
109 | param_hint: t.Optional[str] = None, | |
110 | ) -> None: | |
111 | super().__init__(message, ctx) | |
112 | self.param = param | |
113 | self.param_hint = param_hint | |
114 | ||
115 | def format_message(self) -> str: | |
116 | if self.param_hint is not None: | |
117 | param_hint = self.param_hint | |
118 | elif self.param is not None: | |
119 | param_hint = self.param.get_error_hint(self.ctx) # type: ignore | |
120 | else: | |
121 | return _("Invalid value: {message}").format(message=self.message) | |
122 | ||
123 | return _("Invalid value for {param_hint}: {message}").format( | |
124 | param_hint=_join_param_hints(param_hint), message=self.message | |
125 | ) | |
126 | ||
127 | ||
128 | class MissingParameter(BadParameter): | |
129 | """Raised if click required an option or argument but it was not | |
130 | provided when invoking the script. | |
131 | ||
132 | .. versionadded:: 4.0 | |
133 | ||
134 | :param param_type: a string that indicates the type of the parameter. | |
135 | The default is to inherit the parameter type from | |
136 | the given `param`. Valid values are ``'parameter'``, | |
137 | ``'option'`` or ``'argument'``. | |
138 | """ | |
139 | ||
140 | def __init__( | |
141 | self, | |
142 | message: t.Optional[str] = None, | |
143 | ctx: t.Optional["Context"] = None, | |
144 | param: t.Optional["Parameter"] = None, | |
145 | param_hint: t.Optional[str] = None, | |
146 | param_type: t.Optional[str] = None, | |
147 | ) -> None: | |
148 | super().__init__(message or "", ctx, param, param_hint) | |
149 | self.param_type = param_type | |
150 | ||
151 | def format_message(self) -> str: | |
152 | if self.param_hint is not None: | |
153 | param_hint: t.Optional[str] = self.param_hint | |
154 | elif self.param is not None: | |
155 | param_hint = self.param.get_error_hint(self.ctx) # type: ignore | |
156 | else: | |
157 | param_hint = None | |
158 | ||
159 | param_hint = _join_param_hints(param_hint) | |
160 | param_hint = f" {param_hint}" if param_hint else "" | |
161 | ||
162 | param_type = self.param_type | |
163 | if param_type is None and self.param is not None: | |
164 | param_type = self.param.param_type_name | |
165 | ||
166 | msg = self.message | |
167 | if self.param is not None: | |
168 | msg_extra = self.param.type.get_missing_message(self.param) | |
169 | if msg_extra: | |
170 | if msg: | |
171 | msg += f". {msg_extra}" | |
172 | else: | |
173 | msg = msg_extra | |
174 | ||
175 | msg = f" {msg}" if msg else "" | |
176 | ||
177 | # Translate param_type for known types. | |
178 | if param_type == "argument": | |
179 | missing = _("Missing argument") | |
180 | elif param_type == "option": | |
181 | missing = _("Missing option") | |
182 | elif param_type == "parameter": | |
183 | missing = _("Missing parameter") | |
184 | else: | |
185 | missing = _("Missing {param_type}").format(param_type=param_type) | |
186 | ||
187 | return f"{missing}{param_hint}.{msg}" | |
188 | ||
189 | def __str__(self) -> str: | |
190 | if not self.message: | |
191 | param_name = self.param.name if self.param else None | |
192 | return _("Missing parameter: {param_name}").format(param_name=param_name) | |
193 | else: | |
194 | return self.message | |
195 | ||
196 | ||
197 | class NoSuchOption(UsageError): | |
198 | """Raised if click attempted to handle an option that does not | |
199 | exist. | |
200 | ||
201 | .. versionadded:: 4.0 | |
202 | """ | |
203 | ||
204 | def __init__( | |
205 | self, | |
206 | option_name: str, | |
207 | message: t.Optional[str] = None, | |
208 | possibilities: t.Optional[t.Sequence[str]] = None, | |
209 | ctx: t.Optional["Context"] = None, | |
210 | ) -> None: | |
211 | if message is None: | |
212 | message = _("No such option: {name}").format(name=option_name) | |
213 | ||
214 | super().__init__(message, ctx) | |
215 | self.option_name = option_name | |
216 | self.possibilities = possibilities | |
217 | ||
218 | def format_message(self) -> str: | |
219 | if not self.possibilities: | |
220 | return self.message | |
221 | ||
222 | possibility_str = ", ".join(sorted(self.possibilities)) | |
223 | suggest = ngettext( | |
224 | "Did you mean {possibility}?", | |
225 | "(Possible options: {possibilities})", | |
226 | len(self.possibilities), | |
227 | ).format(possibility=possibility_str, possibilities=possibility_str) | |
228 | return f"{self.message} {suggest}" | |
229 | ||
230 | ||
231 | class BadOptionUsage(UsageError): | |
232 | """Raised if an option is generally supplied but the use of the option | |
233 | was incorrect. This is for instance raised if the number of arguments | |
234 | for an option is not correct. | |
235 | ||
236 | .. versionadded:: 4.0 | |
237 | ||
238 | :param option_name: the name of the option being used incorrectly. | |
239 | """ | |
240 | ||
241 | def __init__( | |
242 | self, option_name: str, message: str, ctx: t.Optional["Context"] = None | |
243 | ) -> None: | |
244 | super().__init__(message, ctx) | |
245 | self.option_name = option_name | |
246 | ||
247 | ||
248 | class BadArgumentUsage(UsageError): | |
249 | """Raised if an argument is generally supplied but the use of the argument | |
250 | was incorrect. This is for instance raised if the number of values | |
251 | for an argument is not correct. | |
252 | ||
253 | .. versionadded:: 6.0 | |
254 | """ | |
255 | ||
256 | ||
257 | class FileError(ClickException): | |
258 | """Raised if a file cannot be opened.""" | |
259 | ||
260 | def __init__(self, filename: str, hint: t.Optional[str] = None) -> None: | |
261 | if hint is None: | |
262 | hint = _("unknown error") | |
263 | ||
264 | super().__init__(hint) | |
265 | self.ui_filename: str = format_filename(filename) | |
266 | self.filename = filename | |
267 | ||
268 | def format_message(self) -> str: | |
269 | return _("Could not open file {filename!r}: {message}").format( | |
270 | filename=self.ui_filename, message=self.message | |
271 | ) | |
272 | ||
273 | ||
274 | class Abort(RuntimeError): | |
275 | """An internal signalling exception that signals Click to abort.""" | |
276 | ||
277 | ||
278 | class Exit(RuntimeError): | |
279 | """An exception that indicates that the application should exit with some | |
280 | status code. | |
281 | ||
282 | :param code: the status code to exit with. | |
283 | """ | |
284 | ||
285 | __slots__ = ("exit_code",) | |
286 | ||
287 | def __init__(self, code: int = 0) -> None: | |
288 | self.exit_code: int = code |