A Commandline Tool With Python Cmd And Argparse
The problem
Develop a commandline tool with several commands and input parameters for each command.
The solution
With Python we can use Cmd module to quickly setup a commandline utility and Argparse module to hande input parameters with related help messages and errors.
#!/usr/bin/env python3
import argparse
import ast
from cmd import Cmd
import os
try:
import readline
except ImportError:
readline = None
import sys
class NameValuePairType(object):
def __init__(self):
pass
def __call__(self, value):
pair = value.split("=")
if not (2 == len(pair)):
raise argparse.ArgumentTypeError("invalid name=value pair")
try:
name = pair[0]
value = ast.literal_eval(pair[1])
except Exception as e:
raise argparse.ArgumentTypeError("invalid value: " + pair[1])
return [name, value]
class CommandLineTool(Cmd):
_historyFile = os.path.expanduser('~/.cmdtool_history')
_historySize = 10000
intro = 'Welcome to the CommandLineTool shell. Type help or ? to list commands.\n'
prompt = '(CLI) '
def __init__(self, completekey='tab', stdin=None, stdout=None):
Cmd.__init__(self, completekey=completekey, stdin=stdin, stdout=stdout)
self._defaults = self.CommandLineToolDefaults()
def preloop(self):
self._loadHistory()
def postloop(self):
self._storeHistory()
def _loadHistory(self):
if readline and os.path.exists(self._historyFile):
readline.read_history_file(self._historyFile)
def _storeHistory(self):
if readline:
readline.set_history_length(self._historySize)
readline.write_history_file(self._historyFile)
############################################################################
# Exported Commands #
############################################################################
def do_foo(self, args):
"""
A useful command: foo
"""
parser = argparse.ArgumentParser(prog="foo")
parser.add_argument('--parameter-a',
dest='parameterA',
default=self._defaults.PARAMETER_A,
help="Parameter A Help"
)
parser.add_argument('--parameter-b',
type=int,
dest='parameterB',
default=self._defaults.PARAMETER_B,
help="Parameter B Help"
)
parser.add_argument('--parameter-pair',
required=True,
dest='parametersPairs',
type=NameValuePairType(),
action='append',
help='the parameters list, in the form of name=value pairs, if value is a string it needs to be enclosed in double quotes and watchout for spaces'
)
try:
arguments = self._getCommandArguments(parser, args)
print(arguments.parameterA)
print(arguments.parameterB)
print(arguments.parametersPairs)
return 0
except Exception as e:
# cannot avoid doing this if we do not want to halt the interpreter
# when in loop() mode
return None
def do_quit(self, args):
"""
Quits the program.
"""
print("bye!")
raise SystemExit
############################################################################
# Inner class to hold default values and constants #
############################################################################
class CommandLineToolDefaults(object):
PARAMETER_A = "aDefaultValue"
PARAMETER_B = 42
############################################################################
# Internal commodity Routines #
############################################################################
def _getCommandArguments(self, parser, args):
if args:
argsList = args.split(" ")
else:
argsList = None
try:
arguments = parser.parse_args(argsList)
return arguments
except SystemExit:
pass
raise Exception
def main():
prompt = CommandLineTool()
if len(sys.argv) > 1:
rc = prompt.onecmd(' '.join(sys.argv[1:]))
if rc == 0:
sys.exit(0)
else:
sys.exit(1)
else:
try:
prompt.cmdloop()
finally:
prompt._storeHistory()
if __name__ == '__main__':
main()
Written on November 23, 2018