outerbounds 0.3.55rc3__py3-none-any.whl → 0.3.133__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- outerbounds/_vendor/PyYAML.LICENSE +20 -0
- outerbounds/_vendor/__init__.py +0 -0
- outerbounds/_vendor/_yaml/__init__.py +34 -0
- outerbounds/_vendor/click/__init__.py +73 -0
- outerbounds/_vendor/click/_compat.py +626 -0
- outerbounds/_vendor/click/_termui_impl.py +717 -0
- outerbounds/_vendor/click/_textwrap.py +49 -0
- outerbounds/_vendor/click/_winconsole.py +279 -0
- outerbounds/_vendor/click/core.py +2998 -0
- outerbounds/_vendor/click/decorators.py +497 -0
- outerbounds/_vendor/click/exceptions.py +287 -0
- outerbounds/_vendor/click/formatting.py +301 -0
- outerbounds/_vendor/click/globals.py +68 -0
- outerbounds/_vendor/click/parser.py +529 -0
- outerbounds/_vendor/click/py.typed +0 -0
- outerbounds/_vendor/click/shell_completion.py +580 -0
- outerbounds/_vendor/click/termui.py +787 -0
- outerbounds/_vendor/click/testing.py +479 -0
- outerbounds/_vendor/click/types.py +1073 -0
- outerbounds/_vendor/click/utils.py +580 -0
- outerbounds/_vendor/click.LICENSE +28 -0
- outerbounds/_vendor/vendor_any.txt +2 -0
- outerbounds/_vendor/yaml/__init__.py +471 -0
- outerbounds/_vendor/yaml/_yaml.cpython-311-darwin.so +0 -0
- outerbounds/_vendor/yaml/composer.py +146 -0
- outerbounds/_vendor/yaml/constructor.py +862 -0
- outerbounds/_vendor/yaml/cyaml.py +177 -0
- outerbounds/_vendor/yaml/dumper.py +138 -0
- outerbounds/_vendor/yaml/emitter.py +1239 -0
- outerbounds/_vendor/yaml/error.py +94 -0
- outerbounds/_vendor/yaml/events.py +104 -0
- outerbounds/_vendor/yaml/loader.py +62 -0
- outerbounds/_vendor/yaml/nodes.py +51 -0
- outerbounds/_vendor/yaml/parser.py +629 -0
- outerbounds/_vendor/yaml/reader.py +208 -0
- outerbounds/_vendor/yaml/representer.py +378 -0
- outerbounds/_vendor/yaml/resolver.py +245 -0
- outerbounds/_vendor/yaml/scanner.py +1555 -0
- outerbounds/_vendor/yaml/serializer.py +127 -0
- outerbounds/_vendor/yaml/tokens.py +129 -0
- outerbounds/command_groups/apps_cli.py +450 -0
- outerbounds/command_groups/cli.py +9 -5
- outerbounds/command_groups/local_setup_cli.py +249 -33
- outerbounds/command_groups/perimeters_cli.py +231 -33
- outerbounds/command_groups/tutorials_cli.py +111 -0
- outerbounds/command_groups/workstations_cli.py +88 -15
- outerbounds/utils/kubeconfig.py +2 -2
- outerbounds/utils/metaflowconfig.py +111 -21
- outerbounds/utils/schema.py +8 -2
- outerbounds/utils/utils.py +19 -0
- outerbounds/vendor.py +159 -0
- {outerbounds-0.3.55rc3.dist-info → outerbounds-0.3.133.dist-info}/METADATA +17 -6
- outerbounds-0.3.133.dist-info/RECORD +59 -0
- {outerbounds-0.3.55rc3.dist-info → outerbounds-0.3.133.dist-info}/WHEEL +1 -1
- outerbounds-0.3.55rc3.dist-info/RECORD +0 -15
- {outerbounds-0.3.55rc3.dist-info → outerbounds-0.3.133.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,287 @@
|
|
1
|
+
import os
|
2
|
+
import typing as t
|
3
|
+
from gettext import gettext as _
|
4
|
+
from gettext import ngettext
|
5
|
+
|
6
|
+
from ._compat import get_text_stderr
|
7
|
+
from .utils import echo
|
8
|
+
|
9
|
+
if t.TYPE_CHECKING:
|
10
|
+
from .core import Context
|
11
|
+
from .core import Parameter
|
12
|
+
|
13
|
+
|
14
|
+
def _join_param_hints(
|
15
|
+
param_hint: t.Optional[t.Union[t.Sequence[str], str]]
|
16
|
+
) -> t.Optional[str]:
|
17
|
+
if param_hint is not None and not isinstance(param_hint, str):
|
18
|
+
return " / ".join(repr(x) for x in param_hint)
|
19
|
+
|
20
|
+
return param_hint
|
21
|
+
|
22
|
+
|
23
|
+
class ClickException(Exception):
|
24
|
+
"""An exception that Click can handle and show to the user."""
|
25
|
+
|
26
|
+
#: The exit code for this exception.
|
27
|
+
exit_code = 1
|
28
|
+
|
29
|
+
def __init__(self, message: str) -> None:
|
30
|
+
super().__init__(message)
|
31
|
+
self.message = message
|
32
|
+
|
33
|
+
def format_message(self) -> str:
|
34
|
+
return self.message
|
35
|
+
|
36
|
+
def __str__(self) -> str:
|
37
|
+
return self.message
|
38
|
+
|
39
|
+
def show(self, file: t.Optional[t.IO] = None) -> None:
|
40
|
+
if file is None:
|
41
|
+
file = get_text_stderr()
|
42
|
+
|
43
|
+
echo(_("Error: {message}").format(message=self.format_message()), file=file)
|
44
|
+
|
45
|
+
|
46
|
+
class UsageError(ClickException):
|
47
|
+
"""An internal exception that signals a usage error. This typically
|
48
|
+
aborts any further handling.
|
49
|
+
|
50
|
+
:param message: the error message to display.
|
51
|
+
:param ctx: optionally the context that caused this error. Click will
|
52
|
+
fill in the context automatically in some situations.
|
53
|
+
"""
|
54
|
+
|
55
|
+
exit_code = 2
|
56
|
+
|
57
|
+
def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None:
|
58
|
+
super().__init__(message)
|
59
|
+
self.ctx = ctx
|
60
|
+
self.cmd = self.ctx.command if self.ctx else None
|
61
|
+
|
62
|
+
def show(self, file: t.Optional[t.IO] = None) -> None:
|
63
|
+
if file is None:
|
64
|
+
file = get_text_stderr()
|
65
|
+
color = None
|
66
|
+
hint = ""
|
67
|
+
if (
|
68
|
+
self.ctx is not None
|
69
|
+
and self.ctx.command.get_help_option(self.ctx) is not None
|
70
|
+
):
|
71
|
+
hint = _("Try '{command} {option}' for help.").format(
|
72
|
+
command=self.ctx.command_path, option=self.ctx.help_option_names[0]
|
73
|
+
)
|
74
|
+
hint = f"{hint}\n"
|
75
|
+
if self.ctx is not None:
|
76
|
+
color = self.ctx.color
|
77
|
+
echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
|
78
|
+
echo(
|
79
|
+
_("Error: {message}").format(message=self.format_message()),
|
80
|
+
file=file,
|
81
|
+
color=color,
|
82
|
+
)
|
83
|
+
|
84
|
+
|
85
|
+
class BadParameter(UsageError):
|
86
|
+
"""An exception that formats out a standardized error message for a
|
87
|
+
bad parameter. This is useful when thrown from a callback or type as
|
88
|
+
Click will attach contextual information to it (for instance, which
|
89
|
+
parameter it is).
|
90
|
+
|
91
|
+
.. versionadded:: 2.0
|
92
|
+
|
93
|
+
:param param: the parameter object that caused this error. This can
|
94
|
+
be left out, and Click will attach this info itself
|
95
|
+
if possible.
|
96
|
+
:param param_hint: a string that shows up as parameter name. This
|
97
|
+
can be used as alternative to `param` in cases
|
98
|
+
where custom validation should happen. If it is
|
99
|
+
a string it's used as such, if it's a list then
|
100
|
+
each item is quoted and separated.
|
101
|
+
"""
|
102
|
+
|
103
|
+
def __init__(
|
104
|
+
self,
|
105
|
+
message: str,
|
106
|
+
ctx: t.Optional["Context"] = None,
|
107
|
+
param: t.Optional["Parameter"] = None,
|
108
|
+
param_hint: t.Optional[str] = None,
|
109
|
+
) -> None:
|
110
|
+
super().__init__(message, ctx)
|
111
|
+
self.param = param
|
112
|
+
self.param_hint = param_hint
|
113
|
+
|
114
|
+
def format_message(self) -> str:
|
115
|
+
if self.param_hint is not None:
|
116
|
+
param_hint = self.param_hint
|
117
|
+
elif self.param is not None:
|
118
|
+
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
119
|
+
else:
|
120
|
+
return _("Invalid value: {message}").format(message=self.message)
|
121
|
+
|
122
|
+
return _("Invalid value for {param_hint}: {message}").format(
|
123
|
+
param_hint=_join_param_hints(param_hint), message=self.message
|
124
|
+
)
|
125
|
+
|
126
|
+
|
127
|
+
class MissingParameter(BadParameter):
|
128
|
+
"""Raised if click required an option or argument but it was not
|
129
|
+
provided when invoking the script.
|
130
|
+
|
131
|
+
.. versionadded:: 4.0
|
132
|
+
|
133
|
+
:param param_type: a string that indicates the type of the parameter.
|
134
|
+
The default is to inherit the parameter type from
|
135
|
+
the given `param`. Valid values are ``'parameter'``,
|
136
|
+
``'option'`` or ``'argument'``.
|
137
|
+
"""
|
138
|
+
|
139
|
+
def __init__(
|
140
|
+
self,
|
141
|
+
message: t.Optional[str] = None,
|
142
|
+
ctx: t.Optional["Context"] = None,
|
143
|
+
param: t.Optional["Parameter"] = None,
|
144
|
+
param_hint: t.Optional[str] = None,
|
145
|
+
param_type: t.Optional[str] = None,
|
146
|
+
) -> None:
|
147
|
+
super().__init__(message or "", ctx, param, param_hint)
|
148
|
+
self.param_type = param_type
|
149
|
+
|
150
|
+
def format_message(self) -> str:
|
151
|
+
if self.param_hint is not None:
|
152
|
+
param_hint: t.Optional[str] = self.param_hint
|
153
|
+
elif self.param is not None:
|
154
|
+
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
155
|
+
else:
|
156
|
+
param_hint = None
|
157
|
+
|
158
|
+
param_hint = _join_param_hints(param_hint)
|
159
|
+
param_hint = f" {param_hint}" if param_hint else ""
|
160
|
+
|
161
|
+
param_type = self.param_type
|
162
|
+
if param_type is None and self.param is not None:
|
163
|
+
param_type = self.param.param_type_name
|
164
|
+
|
165
|
+
msg = self.message
|
166
|
+
if self.param is not None:
|
167
|
+
msg_extra = self.param.type.get_missing_message(self.param)
|
168
|
+
if msg_extra:
|
169
|
+
if msg:
|
170
|
+
msg += f". {msg_extra}"
|
171
|
+
else:
|
172
|
+
msg = msg_extra
|
173
|
+
|
174
|
+
msg = f" {msg}" if msg else ""
|
175
|
+
|
176
|
+
# Translate param_type for known types.
|
177
|
+
if param_type == "argument":
|
178
|
+
missing = _("Missing argument")
|
179
|
+
elif param_type == "option":
|
180
|
+
missing = _("Missing option")
|
181
|
+
elif param_type == "parameter":
|
182
|
+
missing = _("Missing parameter")
|
183
|
+
else:
|
184
|
+
missing = _("Missing {param_type}").format(param_type=param_type)
|
185
|
+
|
186
|
+
return f"{missing}{param_hint}.{msg}"
|
187
|
+
|
188
|
+
def __str__(self) -> str:
|
189
|
+
if not self.message:
|
190
|
+
param_name = self.param.name if self.param else None
|
191
|
+
return _("Missing parameter: {param_name}").format(param_name=param_name)
|
192
|
+
else:
|
193
|
+
return self.message
|
194
|
+
|
195
|
+
|
196
|
+
class NoSuchOption(UsageError):
|
197
|
+
"""Raised if click attempted to handle an option that does not
|
198
|
+
exist.
|
199
|
+
|
200
|
+
.. versionadded:: 4.0
|
201
|
+
"""
|
202
|
+
|
203
|
+
def __init__(
|
204
|
+
self,
|
205
|
+
option_name: str,
|
206
|
+
message: t.Optional[str] = None,
|
207
|
+
possibilities: t.Optional[t.Sequence[str]] = None,
|
208
|
+
ctx: t.Optional["Context"] = None,
|
209
|
+
) -> None:
|
210
|
+
if message is None:
|
211
|
+
message = _("No such option: {name}").format(name=option_name)
|
212
|
+
|
213
|
+
super().__init__(message, ctx)
|
214
|
+
self.option_name = option_name
|
215
|
+
self.possibilities = possibilities
|
216
|
+
|
217
|
+
def format_message(self) -> str:
|
218
|
+
if not self.possibilities:
|
219
|
+
return self.message
|
220
|
+
|
221
|
+
possibility_str = ", ".join(sorted(self.possibilities))
|
222
|
+
suggest = ngettext(
|
223
|
+
"Did you mean {possibility}?",
|
224
|
+
"(Possible options: {possibilities})",
|
225
|
+
len(self.possibilities),
|
226
|
+
).format(possibility=possibility_str, possibilities=possibility_str)
|
227
|
+
return f"{self.message} {suggest}"
|
228
|
+
|
229
|
+
|
230
|
+
class BadOptionUsage(UsageError):
|
231
|
+
"""Raised if an option is generally supplied but the use of the option
|
232
|
+
was incorrect. This is for instance raised if the number of arguments
|
233
|
+
for an option is not correct.
|
234
|
+
|
235
|
+
.. versionadded:: 4.0
|
236
|
+
|
237
|
+
:param option_name: the name of the option being used incorrectly.
|
238
|
+
"""
|
239
|
+
|
240
|
+
def __init__(
|
241
|
+
self, option_name: str, message: str, ctx: t.Optional["Context"] = None
|
242
|
+
) -> None:
|
243
|
+
super().__init__(message, ctx)
|
244
|
+
self.option_name = option_name
|
245
|
+
|
246
|
+
|
247
|
+
class BadArgumentUsage(UsageError):
|
248
|
+
"""Raised if an argument is generally supplied but the use of the argument
|
249
|
+
was incorrect. This is for instance raised if the number of values
|
250
|
+
for an argument is not correct.
|
251
|
+
|
252
|
+
.. versionadded:: 6.0
|
253
|
+
"""
|
254
|
+
|
255
|
+
|
256
|
+
class FileError(ClickException):
|
257
|
+
"""Raised if a file cannot be opened."""
|
258
|
+
|
259
|
+
def __init__(self, filename: str, hint: t.Optional[str] = None) -> None:
|
260
|
+
if hint is None:
|
261
|
+
hint = _("unknown error")
|
262
|
+
|
263
|
+
super().__init__(hint)
|
264
|
+
self.ui_filename = os.fsdecode(filename)
|
265
|
+
self.filename = filename
|
266
|
+
|
267
|
+
def format_message(self) -> str:
|
268
|
+
return _("Could not open file {filename!r}: {message}").format(
|
269
|
+
filename=self.ui_filename, message=self.message
|
270
|
+
)
|
271
|
+
|
272
|
+
|
273
|
+
class Abort(RuntimeError):
|
274
|
+
"""An internal signalling exception that signals Click to abort."""
|
275
|
+
|
276
|
+
|
277
|
+
class Exit(RuntimeError):
|
278
|
+
"""An exception that indicates that the application should exit with some
|
279
|
+
status code.
|
280
|
+
|
281
|
+
:param code: the status code to exit with.
|
282
|
+
"""
|
283
|
+
|
284
|
+
__slots__ = ("exit_code",)
|
285
|
+
|
286
|
+
def __init__(self, code: int = 0) -> None:
|
287
|
+
self.exit_code = code
|
@@ -0,0 +1,301 @@
|
|
1
|
+
import typing as t
|
2
|
+
from contextlib import contextmanager
|
3
|
+
from gettext import gettext as _
|
4
|
+
|
5
|
+
from ._compat import term_len
|
6
|
+
from .parser import split_opt
|
7
|
+
|
8
|
+
# Can force a width. This is used by the test system
|
9
|
+
FORCED_WIDTH: t.Optional[int] = None
|
10
|
+
|
11
|
+
|
12
|
+
def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]:
|
13
|
+
widths: t.Dict[int, int] = {}
|
14
|
+
|
15
|
+
for row in rows:
|
16
|
+
for idx, col in enumerate(row):
|
17
|
+
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
18
|
+
|
19
|
+
return tuple(y for x, y in sorted(widths.items()))
|
20
|
+
|
21
|
+
|
22
|
+
def iter_rows(
|
23
|
+
rows: t.Iterable[t.Tuple[str, str]], col_count: int
|
24
|
+
) -> t.Iterator[t.Tuple[str, ...]]:
|
25
|
+
for row in rows:
|
26
|
+
yield row + ("",) * (col_count - len(row))
|
27
|
+
|
28
|
+
|
29
|
+
def wrap_text(
|
30
|
+
text: str,
|
31
|
+
width: int = 78,
|
32
|
+
initial_indent: str = "",
|
33
|
+
subsequent_indent: str = "",
|
34
|
+
preserve_paragraphs: bool = False,
|
35
|
+
) -> str:
|
36
|
+
"""A helper function that intelligently wraps text. By default, it
|
37
|
+
assumes that it operates on a single paragraph of text but if the
|
38
|
+
`preserve_paragraphs` parameter is provided it will intelligently
|
39
|
+
handle paragraphs (defined by two empty lines).
|
40
|
+
|
41
|
+
If paragraphs are handled, a paragraph can be prefixed with an empty
|
42
|
+
line containing the ``\\b`` character (``\\x08``) to indicate that
|
43
|
+
no rewrapping should happen in that block.
|
44
|
+
|
45
|
+
:param text: the text that should be rewrapped.
|
46
|
+
:param width: the maximum width for the text.
|
47
|
+
:param initial_indent: the initial indent that should be placed on the
|
48
|
+
first line as a string.
|
49
|
+
:param subsequent_indent: the indent string that should be placed on
|
50
|
+
each consecutive line.
|
51
|
+
:param preserve_paragraphs: if this flag is set then the wrapping will
|
52
|
+
intelligently handle paragraphs.
|
53
|
+
"""
|
54
|
+
from ._textwrap import TextWrapper
|
55
|
+
|
56
|
+
text = text.expandtabs()
|
57
|
+
wrapper = TextWrapper(
|
58
|
+
width,
|
59
|
+
initial_indent=initial_indent,
|
60
|
+
subsequent_indent=subsequent_indent,
|
61
|
+
replace_whitespace=False,
|
62
|
+
)
|
63
|
+
if not preserve_paragraphs:
|
64
|
+
return wrapper.fill(text)
|
65
|
+
|
66
|
+
p: t.List[t.Tuple[int, bool, str]] = []
|
67
|
+
buf: t.List[str] = []
|
68
|
+
indent = None
|
69
|
+
|
70
|
+
def _flush_par() -> None:
|
71
|
+
if not buf:
|
72
|
+
return
|
73
|
+
if buf[0].strip() == "\b":
|
74
|
+
p.append((indent or 0, True, "\n".join(buf[1:])))
|
75
|
+
else:
|
76
|
+
p.append((indent or 0, False, " ".join(buf)))
|
77
|
+
del buf[:]
|
78
|
+
|
79
|
+
for line in text.splitlines():
|
80
|
+
if not line:
|
81
|
+
_flush_par()
|
82
|
+
indent = None
|
83
|
+
else:
|
84
|
+
if indent is None:
|
85
|
+
orig_len = term_len(line)
|
86
|
+
line = line.lstrip()
|
87
|
+
indent = orig_len - term_len(line)
|
88
|
+
buf.append(line)
|
89
|
+
_flush_par()
|
90
|
+
|
91
|
+
rv = []
|
92
|
+
for indent, raw, text in p:
|
93
|
+
with wrapper.extra_indent(" " * indent):
|
94
|
+
if raw:
|
95
|
+
rv.append(wrapper.indent_only(text))
|
96
|
+
else:
|
97
|
+
rv.append(wrapper.fill(text))
|
98
|
+
|
99
|
+
return "\n\n".join(rv)
|
100
|
+
|
101
|
+
|
102
|
+
class HelpFormatter:
|
103
|
+
"""This class helps with formatting text-based help pages. It's
|
104
|
+
usually just needed for very special internal cases, but it's also
|
105
|
+
exposed so that developers can write their own fancy outputs.
|
106
|
+
|
107
|
+
At present, it always writes into memory.
|
108
|
+
|
109
|
+
:param indent_increment: the additional increment for each level.
|
110
|
+
:param width: the width for the text. This defaults to the terminal
|
111
|
+
width clamped to a maximum of 78.
|
112
|
+
"""
|
113
|
+
|
114
|
+
def __init__(
|
115
|
+
self,
|
116
|
+
indent_increment: int = 2,
|
117
|
+
width: t.Optional[int] = None,
|
118
|
+
max_width: t.Optional[int] = None,
|
119
|
+
) -> None:
|
120
|
+
import shutil
|
121
|
+
|
122
|
+
self.indent_increment = indent_increment
|
123
|
+
if max_width is None:
|
124
|
+
max_width = 80
|
125
|
+
if width is None:
|
126
|
+
width = FORCED_WIDTH
|
127
|
+
if width is None:
|
128
|
+
width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
|
129
|
+
self.width = width
|
130
|
+
self.current_indent = 0
|
131
|
+
self.buffer: t.List[str] = []
|
132
|
+
|
133
|
+
def write(self, string: str) -> None:
|
134
|
+
"""Writes a unicode string into the internal buffer."""
|
135
|
+
self.buffer.append(string)
|
136
|
+
|
137
|
+
def indent(self) -> None:
|
138
|
+
"""Increases the indentation."""
|
139
|
+
self.current_indent += self.indent_increment
|
140
|
+
|
141
|
+
def dedent(self) -> None:
|
142
|
+
"""Decreases the indentation."""
|
143
|
+
self.current_indent -= self.indent_increment
|
144
|
+
|
145
|
+
def write_usage(
|
146
|
+
self, prog: str, args: str = "", prefix: t.Optional[str] = None
|
147
|
+
) -> None:
|
148
|
+
"""Writes a usage line into the buffer.
|
149
|
+
|
150
|
+
:param prog: the program name.
|
151
|
+
:param args: whitespace separated list of arguments.
|
152
|
+
:param prefix: The prefix for the first line. Defaults to
|
153
|
+
``"Usage: "``.
|
154
|
+
"""
|
155
|
+
if prefix is None:
|
156
|
+
prefix = f"{_('Usage:')} "
|
157
|
+
|
158
|
+
usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
|
159
|
+
text_width = self.width - self.current_indent
|
160
|
+
|
161
|
+
if text_width >= (term_len(usage_prefix) + 20):
|
162
|
+
# The arguments will fit to the right of the prefix.
|
163
|
+
indent = " " * term_len(usage_prefix)
|
164
|
+
self.write(
|
165
|
+
wrap_text(
|
166
|
+
args,
|
167
|
+
text_width,
|
168
|
+
initial_indent=usage_prefix,
|
169
|
+
subsequent_indent=indent,
|
170
|
+
)
|
171
|
+
)
|
172
|
+
else:
|
173
|
+
# The prefix is too long, put the arguments on the next line.
|
174
|
+
self.write(usage_prefix)
|
175
|
+
self.write("\n")
|
176
|
+
indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
|
177
|
+
self.write(
|
178
|
+
wrap_text(
|
179
|
+
args, text_width, initial_indent=indent, subsequent_indent=indent
|
180
|
+
)
|
181
|
+
)
|
182
|
+
|
183
|
+
self.write("\n")
|
184
|
+
|
185
|
+
def write_heading(self, heading: str) -> None:
|
186
|
+
"""Writes a heading into the buffer."""
|
187
|
+
self.write(f"{'':>{self.current_indent}}{heading}:\n")
|
188
|
+
|
189
|
+
def write_paragraph(self) -> None:
|
190
|
+
"""Writes a paragraph into the buffer."""
|
191
|
+
if self.buffer:
|
192
|
+
self.write("\n")
|
193
|
+
|
194
|
+
def write_text(self, text: str) -> None:
|
195
|
+
"""Writes re-indented text into the buffer. This rewraps and
|
196
|
+
preserves paragraphs.
|
197
|
+
"""
|
198
|
+
indent = " " * self.current_indent
|
199
|
+
self.write(
|
200
|
+
wrap_text(
|
201
|
+
text,
|
202
|
+
self.width,
|
203
|
+
initial_indent=indent,
|
204
|
+
subsequent_indent=indent,
|
205
|
+
preserve_paragraphs=True,
|
206
|
+
)
|
207
|
+
)
|
208
|
+
self.write("\n")
|
209
|
+
|
210
|
+
def write_dl(
|
211
|
+
self,
|
212
|
+
rows: t.Sequence[t.Tuple[str, str]],
|
213
|
+
col_max: int = 30,
|
214
|
+
col_spacing: int = 2,
|
215
|
+
) -> None:
|
216
|
+
"""Writes a definition list into the buffer. This is how options
|
217
|
+
and commands are usually formatted.
|
218
|
+
|
219
|
+
:param rows: a list of two item tuples for the terms and values.
|
220
|
+
:param col_max: the maximum width of the first column.
|
221
|
+
:param col_spacing: the number of spaces between the first and
|
222
|
+
second column.
|
223
|
+
"""
|
224
|
+
rows = list(rows)
|
225
|
+
widths = measure_table(rows)
|
226
|
+
if len(widths) != 2:
|
227
|
+
raise TypeError("Expected two columns for definition list")
|
228
|
+
|
229
|
+
first_col = min(widths[0], col_max) + col_spacing
|
230
|
+
|
231
|
+
for first, second in iter_rows(rows, len(widths)):
|
232
|
+
self.write(f"{'':>{self.current_indent}}{first}")
|
233
|
+
if not second:
|
234
|
+
self.write("\n")
|
235
|
+
continue
|
236
|
+
if term_len(first) <= first_col - col_spacing:
|
237
|
+
self.write(" " * (first_col - term_len(first)))
|
238
|
+
else:
|
239
|
+
self.write("\n")
|
240
|
+
self.write(" " * (first_col + self.current_indent))
|
241
|
+
|
242
|
+
text_width = max(self.width - first_col - 2, 10)
|
243
|
+
wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
|
244
|
+
lines = wrapped_text.splitlines()
|
245
|
+
|
246
|
+
if lines:
|
247
|
+
self.write(f"{lines[0]}\n")
|
248
|
+
|
249
|
+
for line in lines[1:]:
|
250
|
+
self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
|
251
|
+
else:
|
252
|
+
self.write("\n")
|
253
|
+
|
254
|
+
@contextmanager
|
255
|
+
def section(self, name: str) -> t.Iterator[None]:
|
256
|
+
"""Helpful context manager that writes a paragraph, a heading,
|
257
|
+
and the indents.
|
258
|
+
|
259
|
+
:param name: the section name that is written as heading.
|
260
|
+
"""
|
261
|
+
self.write_paragraph()
|
262
|
+
self.write_heading(name)
|
263
|
+
self.indent()
|
264
|
+
try:
|
265
|
+
yield
|
266
|
+
finally:
|
267
|
+
self.dedent()
|
268
|
+
|
269
|
+
@contextmanager
|
270
|
+
def indentation(self) -> t.Iterator[None]:
|
271
|
+
"""A context manager that increases the indentation."""
|
272
|
+
self.indent()
|
273
|
+
try:
|
274
|
+
yield
|
275
|
+
finally:
|
276
|
+
self.dedent()
|
277
|
+
|
278
|
+
def getvalue(self) -> str:
|
279
|
+
"""Returns the buffer contents."""
|
280
|
+
return "".join(self.buffer)
|
281
|
+
|
282
|
+
|
283
|
+
def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]:
|
284
|
+
"""Given a list of option strings this joins them in the most appropriate
|
285
|
+
way and returns them in the form ``(formatted_string,
|
286
|
+
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
287
|
+
indicates if any of the option prefixes was a slash.
|
288
|
+
"""
|
289
|
+
rv = []
|
290
|
+
any_prefix_is_slash = False
|
291
|
+
|
292
|
+
for opt in options:
|
293
|
+
prefix = split_opt(opt)[0]
|
294
|
+
|
295
|
+
if prefix == "/":
|
296
|
+
any_prefix_is_slash = True
|
297
|
+
|
298
|
+
rv.append((len(prefix), opt))
|
299
|
+
|
300
|
+
rv.sort(key=lambda x: x[0])
|
301
|
+
return ", ".join(x[1] for x in rv), any_prefix_is_slash
|
@@ -0,0 +1,68 @@
|
|
1
|
+
import typing as t
|
2
|
+
from threading import local
|
3
|
+
|
4
|
+
if t.TYPE_CHECKING:
|
5
|
+
import typing_extensions as te
|
6
|
+
from .core import Context
|
7
|
+
|
8
|
+
_local = local()
|
9
|
+
|
10
|
+
|
11
|
+
@t.overload
|
12
|
+
def get_current_context(silent: "te.Literal[False]" = False) -> "Context":
|
13
|
+
...
|
14
|
+
|
15
|
+
|
16
|
+
@t.overload
|
17
|
+
def get_current_context(silent: bool = ...) -> t.Optional["Context"]:
|
18
|
+
...
|
19
|
+
|
20
|
+
|
21
|
+
def get_current_context(silent: bool = False) -> t.Optional["Context"]:
|
22
|
+
"""Returns the current click context. This can be used as a way to
|
23
|
+
access the current context object from anywhere. This is a more implicit
|
24
|
+
alternative to the :func:`pass_context` decorator. This function is
|
25
|
+
primarily useful for helpers such as :func:`echo` which might be
|
26
|
+
interested in changing its behavior based on the current context.
|
27
|
+
|
28
|
+
To push the current context, :meth:`Context.scope` can be used.
|
29
|
+
|
30
|
+
.. versionadded:: 5.0
|
31
|
+
|
32
|
+
:param silent: if set to `True` the return value is `None` if no context
|
33
|
+
is available. The default behavior is to raise a
|
34
|
+
:exc:`RuntimeError`.
|
35
|
+
"""
|
36
|
+
try:
|
37
|
+
return t.cast("Context", _local.stack[-1])
|
38
|
+
except (AttributeError, IndexError) as e:
|
39
|
+
if not silent:
|
40
|
+
raise RuntimeError("There is no active click context.") from e
|
41
|
+
|
42
|
+
return None
|
43
|
+
|
44
|
+
|
45
|
+
def push_context(ctx: "Context") -> None:
|
46
|
+
"""Pushes a new context to the current stack."""
|
47
|
+
_local.__dict__.setdefault("stack", []).append(ctx)
|
48
|
+
|
49
|
+
|
50
|
+
def pop_context() -> None:
|
51
|
+
"""Removes the top level from the stack."""
|
52
|
+
_local.stack.pop()
|
53
|
+
|
54
|
+
|
55
|
+
def resolve_color_default(color: t.Optional[bool] = None) -> t.Optional[bool]:
|
56
|
+
"""Internal helper to get the default value of the color flag. If a
|
57
|
+
value is passed it's returned unchanged, otherwise it's looked up from
|
58
|
+
the current context.
|
59
|
+
"""
|
60
|
+
if color is not None:
|
61
|
+
return color
|
62
|
+
|
63
|
+
ctx = get_current_context(silent=True)
|
64
|
+
|
65
|
+
if ctx is not None:
|
66
|
+
return ctx.color
|
67
|
+
|
68
|
+
return None
|