comparison env/lib/python3.9/site-packages/humanfriendly/cli.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 # Human friendly input/output in Python.
2 #
3 # Author: Peter Odding <peter@peterodding.com>
4 # Last Change: March 1, 2020
5 # URL: https://humanfriendly.readthedocs.io
6
7 """
8 Usage: humanfriendly [OPTIONS]
9
10 Human friendly input/output (text formatting) on the command
11 line based on the Python package with the same name.
12
13 Supported options:
14
15 -c, --run-command
16
17 Execute an external command (given as the positional arguments) and render
18 a spinner and timer while the command is running. The exit status of the
19 command is propagated.
20
21 --format-table
22
23 Read tabular data from standard input (each line is a row and each
24 whitespace separated field is a column), format the data as a table and
25 print the resulting table to standard output. See also the --delimiter
26 option.
27
28 -d, --delimiter=VALUE
29
30 Change the delimiter used by --format-table to VALUE (a string). By default
31 all whitespace is treated as a delimiter.
32
33 -l, --format-length=LENGTH
34
35 Convert a length count (given as the integer or float LENGTH) into a human
36 readable string and print that string to standard output.
37
38 -n, --format-number=VALUE
39
40 Format a number (given as the integer or floating point number VALUE) with
41 thousands separators and two decimal places (if needed) and print the
42 formatted number to standard output.
43
44 -s, --format-size=BYTES
45
46 Convert a byte count (given as the integer BYTES) into a human readable
47 string and print that string to standard output.
48
49 -b, --binary
50
51 Change the output of -s, --format-size to use binary multiples of bytes
52 (base-2) instead of the default decimal multiples of bytes (base-10).
53
54 -t, --format-timespan=SECONDS
55
56 Convert a number of seconds (given as the floating point number SECONDS)
57 into a human readable timespan and print that string to standard output.
58
59 --parse-length=VALUE
60
61 Parse a human readable length (given as the string VALUE) and print the
62 number of metres to standard output.
63
64 --parse-size=VALUE
65
66 Parse a human readable data size (given as the string VALUE) and print the
67 number of bytes to standard output.
68
69 --demo
70
71 Demonstrate changing the style and color of the terminal font using ANSI
72 escape sequences.
73
74 -h, --help
75
76 Show this message and exit.
77 """
78
79 # Standard library modules.
80 import functools
81 import getopt
82 import pipes
83 import subprocess
84 import sys
85
86 # Modules included in our package.
87 from humanfriendly import (
88 Timer,
89 format_length,
90 format_number,
91 format_size,
92 format_timespan,
93 parse_length,
94 parse_size,
95 )
96 from humanfriendly.tables import format_pretty_table, format_smart_table
97 from humanfriendly.terminal import (
98 ANSI_COLOR_CODES,
99 ANSI_TEXT_STYLES,
100 HIGHLIGHT_COLOR,
101 ansi_strip,
102 ansi_wrap,
103 enable_ansi_support,
104 find_terminal_size,
105 output,
106 usage,
107 warning,
108 )
109 from humanfriendly.terminal.spinners import Spinner
110
111 # Public identifiers that require documentation.
112 __all__ = (
113 'demonstrate_256_colors',
114 'demonstrate_ansi_formatting',
115 'main',
116 'print_formatted_length',
117 'print_formatted_number',
118 'print_formatted_size',
119 'print_formatted_table',
120 'print_formatted_timespan',
121 'print_parsed_length',
122 'print_parsed_size',
123 'run_command',
124 )
125
126
127 def main():
128 """Command line interface for the ``humanfriendly`` program."""
129 enable_ansi_support()
130 try:
131 options, arguments = getopt.getopt(sys.argv[1:], 'cd:l:n:s:bt:h', [
132 'run-command', 'format-table', 'delimiter=', 'format-length=',
133 'format-number=', 'format-size=', 'binary', 'format-timespan=',
134 'parse-length=', 'parse-size=', 'demo', 'help',
135 ])
136 except Exception as e:
137 warning("Error: %s", e)
138 sys.exit(1)
139 actions = []
140 delimiter = None
141 should_format_table = False
142 binary = any(o in ('-b', '--binary') for o, v in options)
143 for option, value in options:
144 if option in ('-d', '--delimiter'):
145 delimiter = value
146 elif option == '--parse-size':
147 actions.append(functools.partial(print_parsed_size, value))
148 elif option == '--parse-length':
149 actions.append(functools.partial(print_parsed_length, value))
150 elif option in ('-c', '--run-command'):
151 actions.append(functools.partial(run_command, arguments))
152 elif option in ('-l', '--format-length'):
153 actions.append(functools.partial(print_formatted_length, value))
154 elif option in ('-n', '--format-number'):
155 actions.append(functools.partial(print_formatted_number, value))
156 elif option in ('-s', '--format-size'):
157 actions.append(functools.partial(print_formatted_size, value, binary))
158 elif option == '--format-table':
159 should_format_table = True
160 elif option in ('-t', '--format-timespan'):
161 actions.append(functools.partial(print_formatted_timespan, value))
162 elif option == '--demo':
163 actions.append(demonstrate_ansi_formatting)
164 elif option in ('-h', '--help'):
165 usage(__doc__)
166 return
167 if should_format_table:
168 actions.append(functools.partial(print_formatted_table, delimiter))
169 if not actions:
170 usage(__doc__)
171 return
172 for partial in actions:
173 partial()
174
175
176 def run_command(command_line):
177 """Run an external command and show a spinner while the command is running."""
178 timer = Timer()
179 spinner_label = "Waiting for command: %s" % " ".join(map(pipes.quote, command_line))
180 with Spinner(label=spinner_label, timer=timer) as spinner:
181 process = subprocess.Popen(command_line)
182 while True:
183 spinner.step()
184 spinner.sleep()
185 if process.poll() is not None:
186 break
187 sys.exit(process.returncode)
188
189
190 def print_formatted_length(value):
191 """Print a human readable length."""
192 if '.' in value:
193 output(format_length(float(value)))
194 else:
195 output(format_length(int(value)))
196
197
198 def print_formatted_number(value):
199 """Print large numbers in a human readable format."""
200 output(format_number(float(value)))
201
202
203 def print_formatted_size(value, binary):
204 """Print a human readable size."""
205 output(format_size(int(value), binary=binary))
206
207
208 def print_formatted_table(delimiter):
209 """Read tabular data from standard input and print a table."""
210 data = []
211 for line in sys.stdin:
212 line = line.rstrip()
213 data.append(line.split(delimiter))
214 output(format_pretty_table(data))
215
216
217 def print_formatted_timespan(value):
218 """Print a human readable timespan."""
219 output(format_timespan(float(value)))
220
221
222 def print_parsed_length(value):
223 """Parse a human readable length and print the number of metres."""
224 output(parse_length(value))
225
226
227 def print_parsed_size(value):
228 """Parse a human readable data size and print the number of bytes."""
229 output(parse_size(value))
230
231
232 def demonstrate_ansi_formatting():
233 """Demonstrate the use of ANSI escape sequences."""
234 # First we demonstrate the supported text styles.
235 output('%s', ansi_wrap('Text styles:', bold=True))
236 styles = ['normal', 'bright']
237 styles.extend(ANSI_TEXT_STYLES.keys())
238 for style_name in sorted(styles):
239 options = dict(color=HIGHLIGHT_COLOR)
240 if style_name != 'normal':
241 options[style_name] = True
242 style_label = style_name.replace('_', ' ').capitalize()
243 output(' - %s', ansi_wrap(style_label, **options))
244 # Now we demonstrate named foreground and background colors.
245 for color_type, color_label in (('color', 'Foreground colors'),
246 ('background', 'Background colors')):
247 intensities = [
248 ('normal', dict()),
249 ('bright', dict(bright=True)),
250 ]
251 if color_type != 'background':
252 intensities.insert(0, ('faint', dict(faint=True)))
253 output('\n%s' % ansi_wrap('%s:' % color_label, bold=True))
254 output(format_smart_table([
255 [color_name] + [
256 ansi_wrap(
257 'XXXXXX' if color_type != 'background' else (' ' * 6),
258 **dict(list(kw.items()) + [(color_type, color_name)])
259 ) for label, kw in intensities
260 ] for color_name in sorted(ANSI_COLOR_CODES.keys())
261 ], column_names=['Color'] + [
262 label.capitalize() for label, kw in intensities
263 ]))
264 # Demonstrate support for 256 colors as well.
265 demonstrate_256_colors(0, 7, 'standard colors')
266 demonstrate_256_colors(8, 15, 'high-intensity colors')
267 demonstrate_256_colors(16, 231, '216 colors')
268 demonstrate_256_colors(232, 255, 'gray scale colors')
269
270
271 def demonstrate_256_colors(i, j, group=None):
272 """Demonstrate 256 color mode support."""
273 # Generate the label.
274 label = '256 color mode'
275 if group:
276 label += ' (%s)' % group
277 output('\n' + ansi_wrap('%s:' % label, bold=True))
278 # Generate a simple rendering of the colors in the requested range and
279 # check if it will fit on a single line (given the terminal's width).
280 single_line = ''.join(' ' + ansi_wrap(str(n), color=n) for n in range(i, j + 1))
281 lines, columns = find_terminal_size()
282 if columns >= len(ansi_strip(single_line)):
283 output(single_line)
284 else:
285 # Generate a more complex rendering of the colors that will nicely wrap
286 # over multiple lines without using too many lines.
287 width = len(str(j)) + 1
288 colors_per_line = int(columns / width)
289 colors = [ansi_wrap(str(n).rjust(width), color=n) for n in range(i, j + 1)]
290 blocks = [colors[n:n + colors_per_line] for n in range(0, len(colors), colors_per_line)]
291 output('\n'.join(''.join(b) for b in blocks))