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

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

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

133 

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

140 

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

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

143 self.buffer.append(string) 

144 

145 def indent(self) -> None: 

146 """Increases the indentation.""" 

147 self.current_indent += self.indent_increment 

148 

149 def dedent(self) -> None: 

150 """Decreases the indentation.""" 

151 self.current_indent -= self.indent_increment 

152 

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

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

155 

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

163 

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

165 text_width = self.width - self.current_indent 

166 

167 if not args: 

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

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

170 # unnecessary. Emit just the prefix line. 

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

172 self.write("\n") 

173 return 

174 

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

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

177 indent = " " * term_len(usage_prefix) 

178 self.write( 

179 wrap_text( 

180 args, 

181 text_width, 

182 initial_indent=usage_prefix, 

183 subsequent_indent=indent, 

184 ) 

185 ) 

186 else: 

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

188 self.write(usage_prefix) 

189 self.write("\n") 

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

191 self.write( 

192 wrap_text( 

193 args, text_width, initial_indent=indent, subsequent_indent=indent 

194 ) 

195 ) 

196 

197 self.write("\n") 

198 

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

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

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

202 

203 def write_paragraph(self) -> None: 

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

205 if self.buffer: 

206 self.write("\n") 

207 

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

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

210 preserves paragraphs. 

211 """ 

212 indent = " " * self.current_indent 

213 self.write( 

214 wrap_text( 

215 text, 

216 self.width, 

217 initial_indent=indent, 

218 subsequent_indent=indent, 

219 preserve_paragraphs=True, 

220 ) 

221 ) 

222 self.write("\n") 

223 

224 def write_dl( 

225 self, 

226 rows: cabc.Sequence[tuple[str, str]], 

227 col_max: int = 30, 

228 col_spacing: int = 2, 

229 ) -> None: 

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

231 and commands are usually formatted. 

232 

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

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

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

236 second column. 

237 """ 

238 rows = list(rows) 

239 widths = measure_table(rows) 

240 if len(widths) != 2: 

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

242 

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

244 

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

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

247 if not second: 

248 self.write("\n") 

249 continue 

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

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

252 else: 

253 self.write("\n") 

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

255 

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

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

258 lines = wrapped_text.splitlines() 

259 

260 if lines: 

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

262 

263 for line in lines[1:]: 

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

265 else: 

266 self.write("\n") 

267 

268 @contextmanager 

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

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

271 and the indents. 

272 

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

274 """ 

275 self.write_paragraph() 

276 self.write_heading(name) 

277 self.indent() 

278 try: 

279 yield 

280 finally: 

281 self.dedent() 

282 

283 @contextmanager 

284 def indentation(self) -> cabc.Iterator[None]: 

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

286 self.indent() 

287 try: 

288 yield 

289 finally: 

290 self.dedent() 

291 

292 def getvalue(self) -> str: 

293 """Returns the buffer contents.""" 

294 return "".join(self.buffer) 

295 

296 

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

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

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

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

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

302 """ 

303 rv = [] 

304 any_prefix_is_slash = False 

305 

306 for opt in options: 

307 prefix = _split_opt(opt)[0] 

308 

309 if prefix == "/": 

310 any_prefix_is_slash = True 

311 

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

313 

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

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