comparison env/lib/python3.9/site-packages/click/formatting.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 from contextlib import contextmanager
2
3 from ._compat import term_len
4 from .parser import split_opt
5 from .termui import get_terminal_size
6
7 # Can force a width. This is used by the test system
8 FORCED_WIDTH = None
9
10
11 def measure_table(rows):
12 widths = {}
13 for row in rows:
14 for idx, col in enumerate(row):
15 widths[idx] = max(widths.get(idx, 0), term_len(col))
16 return tuple(y for x, y in sorted(widths.items()))
17
18
19 def iter_rows(rows, col_count):
20 for row in rows:
21 row = tuple(row)
22 yield row + ("",) * (col_count - len(row))
23
24
25 def wrap_text(
26 text, width=78, initial_indent="", subsequent_indent="", preserve_paragraphs=False
27 ):
28 """A helper function that intelligently wraps text. By default, it
29 assumes that it operates on a single paragraph of text but if the
30 `preserve_paragraphs` parameter is provided it will intelligently
31 handle paragraphs (defined by two empty lines).
32
33 If paragraphs are handled, a paragraph can be prefixed with an empty
34 line containing the ``\\b`` character (``\\x08``) to indicate that
35 no rewrapping should happen in that block.
36
37 :param text: the text that should be rewrapped.
38 :param width: the maximum width for the text.
39 :param initial_indent: the initial indent that should be placed on the
40 first line as a string.
41 :param subsequent_indent: the indent string that should be placed on
42 each consecutive line.
43 :param preserve_paragraphs: if this flag is set then the wrapping will
44 intelligently handle paragraphs.
45 """
46 from ._textwrap import TextWrapper
47
48 text = text.expandtabs()
49 wrapper = TextWrapper(
50 width,
51 initial_indent=initial_indent,
52 subsequent_indent=subsequent_indent,
53 replace_whitespace=False,
54 )
55 if not preserve_paragraphs:
56 return wrapper.fill(text)
57
58 p = []
59 buf = []
60 indent = None
61
62 def _flush_par():
63 if not buf:
64 return
65 if buf[0].strip() == "\b":
66 p.append((indent or 0, True, "\n".join(buf[1:])))
67 else:
68 p.append((indent or 0, False, " ".join(buf)))
69 del buf[:]
70
71 for line in text.splitlines():
72 if not line:
73 _flush_par()
74 indent = None
75 else:
76 if indent is None:
77 orig_len = term_len(line)
78 line = line.lstrip()
79 indent = orig_len - term_len(line)
80 buf.append(line)
81 _flush_par()
82
83 rv = []
84 for indent, raw, text in p:
85 with wrapper.extra_indent(" " * indent):
86 if raw:
87 rv.append(wrapper.indent_only(text))
88 else:
89 rv.append(wrapper.fill(text))
90
91 return "\n\n".join(rv)
92
93
94 class HelpFormatter(object):
95 """This class helps with formatting text-based help pages. It's
96 usually just needed for very special internal cases, but it's also
97 exposed so that developers can write their own fancy outputs.
98
99 At present, it always writes into memory.
100
101 :param indent_increment: the additional increment for each level.
102 :param width: the width for the text. This defaults to the terminal
103 width clamped to a maximum of 78.
104 """
105
106 def __init__(self, indent_increment=2, width=None, max_width=None):
107 self.indent_increment = indent_increment
108 if max_width is None:
109 max_width = 80
110 if width is None:
111 width = FORCED_WIDTH
112 if width is None:
113 width = max(min(get_terminal_size()[0], max_width) - 2, 50)
114 self.width = width
115 self.current_indent = 0
116 self.buffer = []
117
118 def write(self, string):
119 """Writes a unicode string into the internal buffer."""
120 self.buffer.append(string)
121
122 def indent(self):
123 """Increases the indentation."""
124 self.current_indent += self.indent_increment
125
126 def dedent(self):
127 """Decreases the indentation."""
128 self.current_indent -= self.indent_increment
129
130 def write_usage(self, prog, args="", prefix="Usage: "):
131 """Writes a usage line into the buffer.
132
133 :param prog: the program name.
134 :param args: whitespace separated list of arguments.
135 :param prefix: the prefix for the first line.
136 """
137 usage_prefix = "{:>{w}}{} ".format(prefix, prog, w=self.current_indent)
138 text_width = self.width - self.current_indent
139
140 if text_width >= (term_len(usage_prefix) + 20):
141 # The arguments will fit to the right of the prefix.
142 indent = " " * term_len(usage_prefix)
143 self.write(
144 wrap_text(
145 args,
146 text_width,
147 initial_indent=usage_prefix,
148 subsequent_indent=indent,
149 )
150 )
151 else:
152 # The prefix is too long, put the arguments on the next line.
153 self.write(usage_prefix)
154 self.write("\n")
155 indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
156 self.write(
157 wrap_text(
158 args, text_width, initial_indent=indent, subsequent_indent=indent
159 )
160 )
161
162 self.write("\n")
163
164 def write_heading(self, heading):
165 """Writes a heading into the buffer."""
166 self.write("{:>{w}}{}:\n".format("", heading, w=self.current_indent))
167
168 def write_paragraph(self):
169 """Writes a paragraph into the buffer."""
170 if self.buffer:
171 self.write("\n")
172
173 def write_text(self, text):
174 """Writes re-indented text into the buffer. This rewraps and
175 preserves paragraphs.
176 """
177 text_width = max(self.width - self.current_indent, 11)
178 indent = " " * self.current_indent
179 self.write(
180 wrap_text(
181 text,
182 text_width,
183 initial_indent=indent,
184 subsequent_indent=indent,
185 preserve_paragraphs=True,
186 )
187 )
188 self.write("\n")
189
190 def write_dl(self, rows, col_max=30, col_spacing=2):
191 """Writes a definition list into the buffer. This is how options
192 and commands are usually formatted.
193
194 :param rows: a list of two item tuples for the terms and values.
195 :param col_max: the maximum width of the first column.
196 :param col_spacing: the number of spaces between the first and
197 second column.
198 """
199 rows = list(rows)
200 widths = measure_table(rows)
201 if len(widths) != 2:
202 raise TypeError("Expected two columns for definition list")
203
204 first_col = min(widths[0], col_max) + col_spacing
205
206 for first, second in iter_rows(rows, len(widths)):
207 self.write("{:>{w}}{}".format("", first, w=self.current_indent))
208 if not second:
209 self.write("\n")
210 continue
211 if term_len(first) <= first_col - col_spacing:
212 self.write(" " * (first_col - term_len(first)))
213 else:
214 self.write("\n")
215 self.write(" " * (first_col + self.current_indent))
216
217 text_width = max(self.width - first_col - 2, 10)
218 wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
219 lines = wrapped_text.splitlines()
220
221 if lines:
222 self.write("{}\n".format(lines[0]))
223
224 for line in lines[1:]:
225 self.write(
226 "{:>{w}}{}\n".format(
227 "", line, w=first_col + self.current_indent
228 )
229 )
230
231 if len(lines) > 1:
232 # separate long help from next option
233 self.write("\n")
234 else:
235 self.write("\n")
236
237 @contextmanager
238 def section(self, name):
239 """Helpful context manager that writes a paragraph, a heading,
240 and the indents.
241
242 :param name: the section name that is written as heading.
243 """
244 self.write_paragraph()
245 self.write_heading(name)
246 self.indent()
247 try:
248 yield
249 finally:
250 self.dedent()
251
252 @contextmanager
253 def indentation(self):
254 """A context manager that increases the indentation."""
255 self.indent()
256 try:
257 yield
258 finally:
259 self.dedent()
260
261 def getvalue(self):
262 """Returns the buffer contents."""
263 return "".join(self.buffer)
264
265
266 def join_options(options):
267 """Given a list of option strings this joins them in the most appropriate
268 way and returns them in the form ``(formatted_string,
269 any_prefix_is_slash)`` where the second item in the tuple is a flag that
270 indicates if any of the option prefixes was a slash.
271 """
272 rv = []
273 any_prefix_is_slash = False
274 for opt in options:
275 prefix = split_opt(opt)[0]
276 if prefix == "/":
277 any_prefix_is_slash = True
278 rv.append((len(prefix), opt))
279
280 rv.sort(key=lambda x: x[0])
281
282 rv = ", ".join(x[1] for x in rv)
283 return rv, any_prefix_is_slash