Skip to content

Instantly share code, notes, and snippets.

@loicmolinari
Last active March 10, 2022 16:11
Show Gist options
  • Select an option

  • Save loicmolinari/be1488c067055c64a32b9b116b19f2b8 to your computer and use it in GitHub Desktop.

Select an option

Save loicmolinari/be1488c067055c64a32b9b116b19f2b8 to your computer and use it in GitHub Desktop.
Boilerplate Python script for a CLI that reads from an input file and writes to an output file with default files set to stdin and stdout and with standard option names. Provides basic support for parsing command-line parameters of different types (integers, reals, flags, positional parameters, etc).
#!/usr/bin/env python3
import os
import sys
from typing import Tuple, List, Iterator, IO, Callable, Any
# Terminal ECMA-48 SGR escape sequences.
ECMA48_BOLD: str = '\x1b[01m'
ECMA48_RESET: str = '\x1b[00m'
HAS_ECMA48_SGR: bool = not os.getenv('NO_COLOR') and (os.getenv('TERM') != 'dumb') \
and sys.stdout.isatty()
def usage(command: str) -> None:
command_str: str = (ECMA48_BOLD + 'Command' + ECMA48_RESET) if HAS_ECMA48_SGR else 'Command'
options_str: str = (ECMA48_BOLD + 'Options' + ECMA48_RESET) if HAS_ECMA48_SGR else 'Options'
print(f'Command description.\n'
f'\n'
f'{command_str}\n'
f' {command} [-h] [-|<file>] [-c|-o <file>] [-f] [-i <value>]\n'
f'\n'
f'{options_str}\n'
f' -h, --help Show this help.\n'
f' -, --stdin Read from stdin (default).\n'
f' -c, --stdout Write to stdout (default).\n'
f' -o, --output <file> Write to <file>.\n'
f' -f, --force Overwrite existing output file.\n'
f' -i, --integer <value> An integer value.\n'
f' -r, --real <value> A real value.')
def from_str(string: str, func: Callable, default: Any = 0) -> Any:
try:
return func(string)
except:
return default
def parse_command_line(args: List[str]) -> Tuple[str, str, bool, int, float, List[str]]:
input_filename: str = ''
output_filename: str = ''
force: bool = False
integer: int = 0
real: float = 0
positional: List[str] = []
args_iterator: Iterator[int] = iter(range(1, len(args)))
for i in args_iterator:
arg: str = args[i]
skip_next: bool = False
if arg in ('-h', '--help'):
usage(args[0])
sys.exit(0)
elif arg in ('-', '--stdin'):
input_filename = ''
elif arg in ('-c', '--stdout'):
output_filename = ''
elif arg in ('-o', '--output'):
output_filename = i + 1 < len(args) and args[i + 1] or ''
skip_next = True
elif arg in ('-f', '--force'):
force = True
elif arg in ('-i', '--integer'):
integer = i + 1 < len(args) and from_str(args[i + 1], int, integer)
skip_next = True
elif arg in ('-r', '--real'):
real = i + 1 < len(args) and from_str(args[i + 1], float, real)
skip_next = True
elif arg.startswith('-'):
print(f'Unknown option \'{arg}\'.')
sys.exit(1)
elif not input_filename:
input_filename = arg
else:
positional.append(arg)
skip_next and i + 1 < len(args) and next(args_iterator)
return input_filename, output_filename, force, integer, real, positional
def open_io(input_filename: str, output_filename: str, force: bool) -> Tuple[IO[str], IO[str]]:
input_file: IO[str]
output_file: IO[str]
if input_filename:
try:
input_file = open(input_filename, 'r')
except OSError:
print(f'Cannot open input file \'{input_filename}\'.')
sys.exit(1)
else:
input_file = sys.stdin
if output_filename:
if force or not os.path.exists(output_filename) or output_filename == '/dev/null':
try:
output_file = open(output_filename, 'w')
except OSError:
print(f'Cannot open output file \'{output_filename}\'.')
sys.exit(1)
else:
print(f'Output file \'{output_filename}\' exists, pass \'-f\' to overwrite.')
sys.exit(1)
else:
output_file = sys.stdout
return input_file, output_file
def close_io(input_file: IO[str], output_file: IO[str]) -> None:
if input_file != sys.stdin:
input_file.close()
if output_file != sys.stdout:
output_file.close()
def copy_io(input_file: IO[str], output_file: IO[str]) -> None:
output_file.write(input_file.read())
def main(args: List[str]) -> None:
# Command line parsing.
input_filename: str
output_filename: str
force: bool
integer: int
real: float
positional: List[str]
input_filename, output_filename, force, integer, real, positional = parse_command_line(args)
# Simply copy input to output.
input_file: IO[str]
output_file: IO[str]
input_file, output_file = open_io(input_filename, output_filename, force)
copy_io(input_file, output_file)
close_io(input_file, output_file)
if __name__ == '__main__':
sys.exit(main(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment