Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/humanfriendly/cli.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 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)) |
