Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/click/formatting.py: 20%

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

147 statements  

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4from contextlib import contextmanager 

5from gettext import gettext as _ 

6 

7from ._compat import term_len 

8from .parser import _split_opt 

9 

10# Can force a width. This is used by the test system 

11FORCED_WIDTH: int | None = None 

12 

13 

14def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]: 

15 widths: dict[int, int] = {} 

16 

17 for row in rows: 

18 for idx, col in enumerate(row): 

19 widths[idx] = max(widths.get(idx, 0), term_len(col)) 

20 

21 return tuple(y for x, y in sorted(widths.items())) 

22 

23 

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)) 

29 

30 

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). 

42 

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. 

46 

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. 

55 

56 .. versionchanged:: 8.4.0 

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 

63 

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) 

73 

74 p: list[tuple[int, bool, str]] = [] 

75 buf: list[str] = [] 

76 indent = None 

77 

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[:] 

86 

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() 

98 

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)) 

106 

107 return "\n\n".join(rv) 

108 

109 

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. 

114 

115 At present, it always writes into memory. 

116 

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 """ 

121 

122 indent_increment: int 

123 width: int 

124 current_indent: int 

125 buffer: list[str] 

126 

127 def __init__( 

128 self, 

129 indent_increment: int = 2, 

130 width: int | None = None, 

131 max_width: int | None = None, 

132 ) -> None: 

133 self.indent_increment = indent_increment 

134 if max_width is None: 

135 max_width = 80 

136 if width is None: 

137 import shutil 

138 

139 width = FORCED_WIDTH 

140 if width is None: 

141 width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) 

142 self.width = width 

143 self.current_indent = 0 

144 self.buffer = [] 

145 

146 def write(self, string: str) -> None: 

147 """Writes a unicode string into the internal buffer.""" 

148 self.buffer.append(string) 

149 

150 def indent(self) -> None: 

151 """Increases the indentation.""" 

152 self.current_indent += self.indent_increment 

153 

154 def dedent(self) -> None: 

155 """Decreases the indentation.""" 

156 self.current_indent -= self.indent_increment 

157 

158 def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None: 

159 """Writes a usage line into the buffer. 

160 

161 :param prog: the program name. 

162 :param args: whitespace separated list of arguments. 

163 :param prefix: The prefix for the first line. Defaults to 

164 ``"Usage: "``. 

165 """ 

166 if prefix is None: 

167 prefix = "{usage} ".format(usage=_("Usage:")) 

168 

169 usage_prefix = f"{prefix:>{self.current_indent}}{prog} " 

170 text_width = self.width - self.current_indent 

171 

172 if not args: 

173 # Without args, the prefix's trailing space and the wrap_text 

174 # call that would normally place args on the line are both 

175 # unnecessary. Emit just the prefix line. 

176 self.write(usage_prefix.rstrip(" ")) 

177 self.write("\n") 

178 return 

179 

180 if text_width >= (term_len(usage_prefix) + 20): 

181 # The arguments will fit to the right of the prefix. 

182 indent = " " * term_len(usage_prefix) 

183 self.write( 

184 wrap_text( 

185 args, 

186 text_width, 

187 initial_indent=usage_prefix, 

188 subsequent_indent=indent, 

189 ) 

190 ) 

191 else: 

192 # The prefix is too long, put the arguments on the next line. 

193 self.write(usage_prefix) 

194 self.write("\n") 

195 indent = " " * (max(self.current_indent, term_len(prefix)) + 4) 

196 self.write( 

197 wrap_text( 

198 args, text_width, initial_indent=indent, subsequent_indent=indent 

199 ) 

200 ) 

201 

202 self.write("\n") 

203 

204 def write_heading(self, heading: str) -> None: 

205 """Writes a heading into the buffer.""" 

206 self.write(f"{'':>{self.current_indent}}{heading}:\n") 

207 

208 def write_paragraph(self) -> None: 

209 """Writes a paragraph into the buffer.""" 

210 if self.buffer: 

211 self.write("\n") 

212 

213 def write_text(self, text: str) -> None: 

214 """Writes re-indented text into the buffer. This rewraps and 

215 preserves paragraphs. 

216 """ 

217 indent = " " * self.current_indent 

218 self.write( 

219 wrap_text( 

220 text, 

221 self.width, 

222 initial_indent=indent, 

223 subsequent_indent=indent, 

224 preserve_paragraphs=True, 

225 ) 

226 ) 

227 self.write("\n") 

228 

229 def write_dl( 

230 self, 

231 rows: cabc.Iterable[tuple[str, str]], 

232 col_max: int = 30, 

233 col_spacing: int = 2, 

234 ) -> None: 

235 """Writes a definition list into the buffer. This is how options 

236 and commands are usually formatted. 

237 

238 :param rows: a list of two item tuples for the terms and values. 

239 :param col_max: the maximum width of the first column. 

240 :param col_spacing: the number of spaces between the first and 

241 second column. 

242 """ 

243 rows = list(rows) 

244 widths = measure_table(rows) 

245 if len(widths) != 2: 

246 raise TypeError("Expected two columns for definition list") 

247 

248 first_col = min(widths[0], col_max) + col_spacing 

249 

250 for first, second in iter_rows(rows, len(widths)): 

251 self.write(f"{'':>{self.current_indent}}{first}") 

252 if not second: 

253 self.write("\n") 

254 continue 

255 if term_len(first) <= first_col - col_spacing: 

256 self.write(" " * (first_col - term_len(first))) 

257 else: 

258 self.write("\n") 

259 self.write(" " * (first_col + self.current_indent)) 

260 

261 text_width = max(self.width - first_col - 2, 10) 

262 wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) 

263 lines = wrapped_text.splitlines() 

264 

265 if lines: 

266 self.write(f"{lines[0]}\n") 

267 

268 for line in lines[1:]: 

269 self.write(f"{'':>{first_col + self.current_indent}}{line}\n") 

270 else: 

271 self.write("\n") 

272 

273 @contextmanager 

274 def section(self, name: str) -> cabc.Generator[None]: 

275 """Helpful context manager that writes a paragraph, a heading, 

276 and the indents. 

277 

278 :param name: the section name that is written as heading. 

279 """ 

280 self.write_paragraph() 

281 self.write_heading(name) 

282 self.indent() 

283 try: 

284 yield 

285 finally: 

286 self.dedent() 

287 

288 @contextmanager 

289 def indentation(self) -> cabc.Generator[None]: 

290 """A context manager that increases the indentation.""" 

291 self.indent() 

292 try: 

293 yield 

294 finally: 

295 self.dedent() 

296 

297 def getvalue(self) -> str: 

298 """Returns the buffer contents.""" 

299 return "".join(self.buffer) 

300 

301 

302def join_options(options: cabc.Iterable[str]) -> tuple[str, bool]: 

303 """Given a list of option strings this joins them in the most appropriate 

304 way and returns them in the form ``(formatted_string, 

305 any_prefix_is_slash)`` where the second item in the tuple is a flag that 

306 indicates if any of the option prefixes was a slash. 

307 """ 

308 rv = [] 

309 any_prefix_is_slash = False 

310 

311 for opt in options: 

312 prefix = _split_opt(opt)[0] 

313 

314 if prefix == "/": 

315 any_prefix_is_slash = True 

316 

317 rv.append((len(prefix), opt)) 

318 

319 rv.sort(key=lambda x: x[0]) 

320 return ", ".join(x[1] for x in rv), any_prefix_is_slash