| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- # Human friendly input/output in Python.
- #
- # Author: Peter Odding <peter@peterodding.com>
- # Last Change: March 1, 2020
- # URL: https://humanfriendly.readthedocs.io
- """
- Usage: humanfriendly [OPTIONS]
- Human friendly input/output (text formatting) on the command
- line based on the Python package with the same name.
- Supported options:
- -c, --run-command
- Execute an external command (given as the positional arguments) and render
- a spinner and timer while the command is running. The exit status of the
- command is propagated.
- --format-table
- Read tabular data from standard input (each line is a row and each
- whitespace separated field is a column), format the data as a table and
- print the resulting table to standard output. See also the --delimiter
- option.
- -d, --delimiter=VALUE
- Change the delimiter used by --format-table to VALUE (a string). By default
- all whitespace is treated as a delimiter.
- -l, --format-length=LENGTH
- Convert a length count (given as the integer or float LENGTH) into a human
- readable string and print that string to standard output.
- -n, --format-number=VALUE
- Format a number (given as the integer or floating point number VALUE) with
- thousands separators and two decimal places (if needed) and print the
- formatted number to standard output.
- -s, --format-size=BYTES
- Convert a byte count (given as the integer BYTES) into a human readable
- string and print that string to standard output.
- -b, --binary
- Change the output of -s, --format-size to use binary multiples of bytes
- (base-2) instead of the default decimal multiples of bytes (base-10).
- -t, --format-timespan=SECONDS
- Convert a number of seconds (given as the floating point number SECONDS)
- into a human readable timespan and print that string to standard output.
- --parse-length=VALUE
- Parse a human readable length (given as the string VALUE) and print the
- number of metres to standard output.
- --parse-size=VALUE
- Parse a human readable data size (given as the string VALUE) and print the
- number of bytes to standard output.
- --demo
- Demonstrate changing the style and color of the terminal font using ANSI
- escape sequences.
- -h, --help
- Show this message and exit.
- """
- # Standard library modules.
- import functools
- import getopt
- import pipes
- import subprocess
- import sys
- # Modules included in our package.
- from humanfriendly import (
- Timer,
- format_length,
- format_number,
- format_size,
- format_timespan,
- parse_length,
- parse_size,
- )
- from humanfriendly.tables import format_pretty_table, format_smart_table
- from humanfriendly.terminal import (
- ANSI_COLOR_CODES,
- ANSI_TEXT_STYLES,
- HIGHLIGHT_COLOR,
- ansi_strip,
- ansi_wrap,
- enable_ansi_support,
- find_terminal_size,
- output,
- usage,
- warning,
- )
- from humanfriendly.terminal.spinners import Spinner
- # Public identifiers that require documentation.
- __all__ = (
- 'demonstrate_256_colors',
- 'demonstrate_ansi_formatting',
- 'main',
- 'print_formatted_length',
- 'print_formatted_number',
- 'print_formatted_size',
- 'print_formatted_table',
- 'print_formatted_timespan',
- 'print_parsed_length',
- 'print_parsed_size',
- 'run_command',
- )
- def main():
- """Command line interface for the ``humanfriendly`` program."""
- enable_ansi_support()
- try:
- options, arguments = getopt.getopt(sys.argv[1:], 'cd:l:n:s:bt:h', [
- 'run-command', 'format-table', 'delimiter=', 'format-length=',
- 'format-number=', 'format-size=', 'binary', 'format-timespan=',
- 'parse-length=', 'parse-size=', 'demo', 'help',
- ])
- except Exception as e:
- warning("Error: %s", e)
- sys.exit(1)
- actions = []
- delimiter = None
- should_format_table = False
- binary = any(o in ('-b', '--binary') for o, v in options)
- for option, value in options:
- if option in ('-d', '--delimiter'):
- delimiter = value
- elif option == '--parse-size':
- actions.append(functools.partial(print_parsed_size, value))
- elif option == '--parse-length':
- actions.append(functools.partial(print_parsed_length, value))
- elif option in ('-c', '--run-command'):
- actions.append(functools.partial(run_command, arguments))
- elif option in ('-l', '--format-length'):
- actions.append(functools.partial(print_formatted_length, value))
- elif option in ('-n', '--format-number'):
- actions.append(functools.partial(print_formatted_number, value))
- elif option in ('-s', '--format-size'):
- actions.append(functools.partial(print_formatted_size, value, binary))
- elif option == '--format-table':
- should_format_table = True
- elif option in ('-t', '--format-timespan'):
- actions.append(functools.partial(print_formatted_timespan, value))
- elif option == '--demo':
- actions.append(demonstrate_ansi_formatting)
- elif option in ('-h', '--help'):
- usage(__doc__)
- return
- if should_format_table:
- actions.append(functools.partial(print_formatted_table, delimiter))
- if not actions:
- usage(__doc__)
- return
- for partial in actions:
- partial()
- def run_command(command_line):
- """Run an external command and show a spinner while the command is running."""
- timer = Timer()
- spinner_label = "Waiting for command: %s" % " ".join(map(pipes.quote, command_line))
- with Spinner(label=spinner_label, timer=timer) as spinner:
- process = subprocess.Popen(command_line)
- while True:
- spinner.step()
- spinner.sleep()
- if process.poll() is not None:
- break
- sys.exit(process.returncode)
- def print_formatted_length(value):
- """Print a human readable length."""
- if '.' in value:
- output(format_length(float(value)))
- else:
- output(format_length(int(value)))
- def print_formatted_number(value):
- """Print large numbers in a human readable format."""
- output(format_number(float(value)))
- def print_formatted_size(value, binary):
- """Print a human readable size."""
- output(format_size(int(value), binary=binary))
- def print_formatted_table(delimiter):
- """Read tabular data from standard input and print a table."""
- data = []
- for line in sys.stdin:
- line = line.rstrip()
- data.append(line.split(delimiter))
- output(format_pretty_table(data))
- def print_formatted_timespan(value):
- """Print a human readable timespan."""
- output(format_timespan(float(value)))
- def print_parsed_length(value):
- """Parse a human readable length and print the number of metres."""
- output(parse_length(value))
- def print_parsed_size(value):
- """Parse a human readable data size and print the number of bytes."""
- output(parse_size(value))
- def demonstrate_ansi_formatting():
- """Demonstrate the use of ANSI escape sequences."""
- # First we demonstrate the supported text styles.
- output('%s', ansi_wrap('Text styles:', bold=True))
- styles = ['normal', 'bright']
- styles.extend(ANSI_TEXT_STYLES.keys())
- for style_name in sorted(styles):
- options = dict(color=HIGHLIGHT_COLOR)
- if style_name != 'normal':
- options[style_name] = True
- style_label = style_name.replace('_', ' ').capitalize()
- output(' - %s', ansi_wrap(style_label, **options))
- # Now we demonstrate named foreground and background colors.
- for color_type, color_label in (('color', 'Foreground colors'),
- ('background', 'Background colors')):
- intensities = [
- ('normal', dict()),
- ('bright', dict(bright=True)),
- ]
- if color_type != 'background':
- intensities.insert(0, ('faint', dict(faint=True)))
- output('\n%s' % ansi_wrap('%s:' % color_label, bold=True))
- output(format_smart_table([
- [color_name] + [
- ansi_wrap(
- 'XXXXXX' if color_type != 'background' else (' ' * 6),
- **dict(list(kw.items()) + [(color_type, color_name)])
- ) for label, kw in intensities
- ] for color_name in sorted(ANSI_COLOR_CODES.keys())
- ], column_names=['Color'] + [
- label.capitalize() for label, kw in intensities
- ]))
- # Demonstrate support for 256 colors as well.
- demonstrate_256_colors(0, 7, 'standard colors')
- demonstrate_256_colors(8, 15, 'high-intensity colors')
- demonstrate_256_colors(16, 231, '216 colors')
- demonstrate_256_colors(232, 255, 'gray scale colors')
- def demonstrate_256_colors(i, j, group=None):
- """Demonstrate 256 color mode support."""
- # Generate the label.
- label = '256 color mode'
- if group:
- label += ' (%s)' % group
- output('\n' + ansi_wrap('%s:' % label, bold=True))
- # Generate a simple rendering of the colors in the requested range and
- # check if it will fit on a single line (given the terminal's width).
- single_line = ''.join(' ' + ansi_wrap(str(n), color=n) for n in range(i, j + 1))
- lines, columns = find_terminal_size()
- if columns >= len(ansi_strip(single_line)):
- output(single_line)
- else:
- # Generate a more complex rendering of the colors that will nicely wrap
- # over multiple lines without using too many lines.
- width = len(str(j)) + 1
- colors_per_line = int(columns / width)
- colors = [ansi_wrap(str(n).rjust(width), color=n) for n in range(i, j + 1)]
- blocks = [colors[n:n + colors_per_line] for n in range(0, len(colors), colors_per_line)]
- output('\n'.join(''.join(b) for b in blocks))
|