Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/click/formatting.py: 19%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import collections.abc as cabc
4from contextlib import contextmanager
5from gettext import gettext as _
7from ._compat import term_len
8from .parser import _split_opt
10# Can force a width. This is used by the test system
11FORCED_WIDTH: int | None = None
14def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]:
15 widths: dict[int, int] = {}
17 for row in rows:
18 for idx, col in enumerate(row):
19 widths[idx] = max(widths.get(idx, 0), term_len(col))
21 return tuple(y for x, y in sorted(widths.items()))
24def iter_rows(
25 rows: cabc.Iterable[tuple[str, str]], col_count: int
26) -> cabc.Iterator[tuple[str, ...]]:
27 for row in rows:
28 yield row + ("",) * (col_count - len(row))
31def wrap_text(
32 text: str,
33 width: int = 78,
34 initial_indent: str = "",
35 subsequent_indent: str = "",
36 preserve_paragraphs: bool = False,
37) -> str:
38 """A helper function that intelligently wraps text. By default, it
39 assumes that it operates on a single paragraph of text but if the
40 `preserve_paragraphs` parameter is provided it will intelligently
41 handle paragraphs (defined by two empty lines).
43 If paragraphs are handled, a paragraph can be prefixed with an empty
44 line containing the ``\\b`` character (``\\x08``) to indicate that
45 no rewrapping should happen in that block.
47 :param text: the text that should be rewrapped.
48 :param width: the maximum width for the text.
49 :param initial_indent: the initial indent that should be placed on the
50 first line as a string.
51 :param subsequent_indent: the indent string that should be placed on
52 each consecutive line.
53 :param preserve_paragraphs: if this flag is set then the wrapping will
54 intelligently handle paragraphs.
56 .. versionchanged:: 8.4
57 Width is measured in visible characters. ANSI escape sequences in
58 ``text``, ``initial_indent``, or ``subsequent_indent`` no longer
59 count toward the width budget, so styled input wraps based on what
60 the user sees instead of raw byte length.
61 """
62 from ._textwrap import TextWrapper
64 text = text.expandtabs()
65 wrapper = TextWrapper(
66 width,
67 initial_indent=initial_indent,
68 subsequent_indent=subsequent_indent,
69 replace_whitespace=False,
70 )
71 if not preserve_paragraphs:
72 return wrapper.fill(text)
74 p: list[tuple[int, bool, str]] = []
75 buf: list[str] = []
76 indent = None
78 def _flush_par() -> None:
79 if not buf:
80 return
81 if buf[0].strip() == "\b":
82 p.append((indent or 0, True, "\n".join(buf[1:])))
83 else:
84 p.append((indent or 0, False, " ".join(buf)))
85 del buf[:]
87 for line in text.splitlines():
88 if not line:
89 _flush_par()
90 indent = None
91 else:
92 if indent is None:
93 orig_len = term_len(line)
94 line = line.lstrip()
95 indent = orig_len - term_len(line)
96 buf.append(line)
97 _flush_par()
99 rv = []
100 for indent, raw, text in p:
101 with wrapper.extra_indent(" " * indent):
102 if raw:
103 rv.append(wrapper.indent_only(text))
104 else:
105 rv.append(wrapper.fill(text))
107 return "\n\n".join(rv)
110class HelpFormatter:
111 """This class helps with formatting text-based help pages. It's
112 usually just needed for very special internal cases, but it's also
113 exposed so that developers can write their own fancy outputs.
115 At present, it always writes into memory.
117 :param indent_increment: the additional increment for each level.
118 :param width: the width for the text. This defaults to the terminal
119 width clamped to a maximum of 78.
120 """
122 def __init__(
123 self,
124 indent_increment: int = 2,
125 width: int | None = None,
126 max_width: int | None = None,
127 ) -> None:
128 self.indent_increment = indent_increment
129 if max_width is None:
130 max_width = 80
131 if width is None:
132 import shutil
134 width = FORCED_WIDTH
135 if width is None:
136 width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
137 self.width = width
138 self.current_indent: int = 0
139 self.buffer: list[str] = []
141 def write(self, string: str) -> None:
142 """Writes a unicode string into the internal buffer."""
143 self.buffer.append(string)
145 def indent(self) -> None:
146 """Increases the indentation."""
147 self.current_indent += self.indent_increment
149 def dedent(self) -> None:
150 """Decreases the indentation."""
151 self.current_indent -= self.indent_increment
153 def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None:
154 """Writes a usage line into the buffer.
156 :param prog: the program name.
157 :param args: whitespace separated list of arguments.
158 :param prefix: The prefix for the first line. Defaults to
159 ``"Usage: "``.
160 """
161 if prefix is None:
162 prefix = "{usage} ".format(usage=_("Usage:"))
164 usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
165 text_width = self.width - self.current_indent
167 if text_width >= (term_len(usage_prefix) + 20):
168 # The arguments will fit to the right of the prefix.
169 indent = " " * term_len(usage_prefix)
170 self.write(
171 wrap_text(
172 args,
173 text_width,
174 initial_indent=usage_prefix,
175 subsequent_indent=indent,
176 )
177 )
178 else:
179 # The prefix is too long, put the arguments on the next line.
180 self.write(usage_prefix)
181 self.write("\n")
182 indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
183 self.write(
184 wrap_text(
185 args, text_width, initial_indent=indent, subsequent_indent=indent
186 )
187 )
189 self.write("\n")
191 def write_heading(self, heading: str) -> None:
192 """Writes a heading into the buffer."""
193 self.write(f"{'':>{self.current_indent}}{heading}:\n")
195 def write_paragraph(self) -> None:
196 """Writes a paragraph into the buffer."""
197 if self.buffer:
198 self.write("\n")
200 def write_text(self, text: str) -> None:
201 """Writes re-indented text into the buffer. This rewraps and
202 preserves paragraphs.
203 """
204 indent = " " * self.current_indent
205 self.write(
206 wrap_text(
207 text,
208 self.width,
209 initial_indent=indent,
210 subsequent_indent=indent,
211 preserve_paragraphs=True,
212 )
213 )
214 self.write("\n")
216 def write_dl(
217 self,
218 rows: cabc.Sequence[tuple[str, str]],
219 col_max: int = 30,
220 col_spacing: int = 2,
221 ) -> None:
222 """Writes a definition list into the buffer. This is how options
223 and commands are usually formatted.
225 :param rows: a list of two item tuples for the terms and values.
226 :param col_max: the maximum width of the first column.
227 :param col_spacing: the number of spaces between the first and
228 second column.
229 """
230 rows = list(rows)
231 widths = measure_table(rows)
232 if len(widths) != 2:
233 raise TypeError("Expected two columns for definition list")
235 first_col = min(widths[0], col_max) + col_spacing
237 for first, second in iter_rows(rows, len(widths)):
238 self.write(f"{'':>{self.current_indent}}{first}")
239 if not second:
240 self.write("\n")
241 continue
242 if term_len(first) <= first_col - col_spacing:
243 self.write(" " * (first_col - term_len(first)))
244 else:
245 self.write("\n")
246 self.write(" " * (first_col + self.current_indent))
248 text_width = max(self.width - first_col - 2, 10)
249 wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
250 lines = wrapped_text.splitlines()
252 if lines:
253 self.write(f"{lines[0]}\n")
255 for line in lines[1:]:
256 self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
257 else:
258 self.write("\n")
260 @contextmanager
261 def section(self, name: str) -> cabc.Iterator[None]:
262 """Helpful context manager that writes a paragraph, a heading,
263 and the indents.
265 :param name: the section name that is written as heading.
266 """
267 self.write_paragraph()
268 self.write_heading(name)
269 self.indent()
270 try:
271 yield
272 finally:
273 self.dedent()
275 @contextmanager
276 def indentation(self) -> cabc.Iterator[None]:
277 """A context manager that increases the indentation."""
278 self.indent()
279 try:
280 yield
281 finally:
282 self.dedent()
284 def getvalue(self) -> str:
285 """Returns the buffer contents."""
286 return "".join(self.buffer)
289def join_options(options: cabc.Sequence[str]) -> tuple[str, bool]:
290 """Given a list of option strings this joins them in the most appropriate
291 way and returns them in the form ``(formatted_string,
292 any_prefix_is_slash)`` where the second item in the tuple is a flag that
293 indicates if any of the option prefixes was a slash.
294 """
295 rv = []
296 any_prefix_is_slash = False
298 for opt in options:
299 prefix = _split_opt(opt)[0]
301 if prefix == "/":
302 any_prefix_is_slash = True
304 rv.append((len(prefix), opt))
306 rv.sort(key=lambda x: x[0])
307 return ", ".join(x[1] for x in rv), any_prefix_is_slash