did not expected this thread has gone this long, i guess it is controversial! Anyways, i have made a sophisticated python based cmd line processing API and has been using it conveniently. It has been bothering me a lot to do a lot of coding in every one of python 50+ something python tool with each has its own parameters. Now with this well documented and works like a magic (almost), but problems I trashed and has been mixing around the words "switches, parameters and arguments." Will have to work on it. Any consumer of this API will almost instantly call this function to process its parameters with few declarations. Posted the whole thing below.
There might be some weird convoluted code relating to first two arg-s in the cmdline sys.argv[1] and sys.argv[2], by convention, almost all of the tools I maintain is in the form:
<script_name> <mgmt_ip> <server_location> --<param1> <value2....N> --<param2> <param3>... etc.,
Some cool features
- All param-s can be in any order and makes no difference except first two. Static order has been real pain.
- All starts with --<param1> and can have optional values following.
- Optional values can be either pre-defined set or any value with pre-defined list lenth.
etc.,
Code: Select all
# Prepares, processes command line arguments.
# The pSysArgv is the all values of sys.argv as it is. This is what the
# users of particular enters in the command line.
# The pSupportedArgv is the tuple list of supported arguments in dictionary format passed
# on by scripts and therefore its contents are specific to a particular script's implementation.
# However following universal convention are used throughout to enable uniform processing
# for any or almost any type of switches and arguments.
# Dictionary key holds the name of the argument in the naming convention: "--<argName>"
# Dictionary value holds the tuple list object, serving as an initial indicator implying how many
# values the arg needs (refer to examples below):
# Current implementation divides the tuple values as two types:
# 1. (<value1>, <value2>) - implies literal processing, which means the supportedArgs lists out the
# actual values that user can enter in the command line for that particular switches and no other
# allowed value. Example is if particular scripts --drive switches accepts only C: D: E: drive
# letters and any other drive letters are not allowed.
# 2. ('__range__', <list>) - implies that there is no restrictions on the values provided by user
# for particular switch, however the <list> contains the range of number of allowed values for that
# particular switch. This is useful in case, it is not known what value user will be putting at the
# command line or there too many possibilities for particular variable.
# For example, if --ip switches requires one value which is IPv4 address but the range of valid
# IPv4 is too many to listed out as literal however --ip will accept only one value, in this case
# supported switch is declared as: ('__range__', 1)
# if --ip requires anywhere between 1 to 5 IP address following, then it should be declared as
# ('__range__', 1,2,3,4,5)
#
# pSupportedArgs typical examples are below but not limited to:
# -------------------------------------------------------------------------------------
# Arg name Initial indicator of value count, tuple list type Valid user input
# -------------------------------------------------------------------------------------
# '--timeout': (1,), (needs one and only one value for argument)
# -> user input --timeout 100
# '--timeout': () (needs no value) -> user input --timeout
# '--timeout': (1,2,3)(needs somewhere between 1 to 3 values, inclusive)
# -> user input --timeout 1
# -> user input --timeout 1 100 2
# -> user input --timeout 1 100
# !!! This is currently unimplementable, needs to look!!! Currently it is not supported.
# '--timeout': [1:CONFIG_MAX_ARG_VALUES] (needs somewhere between 1 to CONFIG_MAX_ARG_VALUES
# number of values, which effectively implies "greater than 1")
# -> user input --timeout 1
# -> user input --timeout 1 100 2
# -> user input --timeout 1 100
# !!! This is currently unimplementable, needs to look!!! Currently it is not supported.
# '--timeout': [1:CONFIG_MAX_ARG_VALUES] (needs somewhere between 1 to CONFIG_MAX_ARG_VALUES
# number of values, which effectively implies "greater than 1")
# -> user input --timeout 1
# -> user input --timeout 1 100 2
# -> user input --timeout 1 100
# -> user input --timeout 1 2 3 4 5 6 7
# -> user input --timeout 1 2 3 4 5 6 7 10 11
# -------------------------------------------------------------------------------------
# Once the arguments are processes and validated, the values of the dictionary will be populated
# with the user supplied values from the command line. If user has not provided the argument, its
# corresponding dictionary value remain unchanged, tuple list type, implying that the caller script
# will not process it.
#
# input:
# - pSysArgv - sys.argv() as it is.
# - pSupportedArgs - dictionary type consisting of tuple list of argument that scripts supports.
# - key holds the name of the argument.
# - tuple list type holding indicator of how many values are needed for the argument.
# - helpStringArr - if user input --help argument, use this string to display the help message.
# output:
# - pSupportedArgs' values are populated with the validated command line argument values as list type.
# - if particular argument is not provided by user, value remains unchanged as list tuple type.
# - EXIT_NO_ERR if --help is supplied.
# EXIT_ERR on any error condition.
def prepArgs(pSysArgv, pSupportedArgs, helpStringArr):
debug = 0
idx = 0 # holds the index of supported argument being processed during the loop.
RANGE_CHECK = 1 # if this value is assigned, each switch's child parameter will be checked against the range specified in th$
VALUE_CHECK = 2 # if this value is assigned, literal values in the supportedArgs will be checked against.
argCheckFlag = VALUE_CHECK
values = None
firstSwitchCheck = 0
if debug:
printDbg("pSysArgv: ")
printSeq(pSysArgv, 4)
printDbg("pSupportedArgs: ")
printSeq(pSupportedArgs, 4)
# Iterate through each command line args-.
for i in range(0, len(pSysArgv)):
argCheckFlag = VALUE_CHECK
printDbg("------------------------------------------------ ---", debug)
#printDbg(pSysArgc[i]: " + str(pSysArgv[i]), debug)
pSysArgv_i = pSysArgv[i]
printVars([i, firstSwitchCheck, pSysArgv[i]])
if re.search("--", pSysArgv[i]):
printDbg("Processing " + str(pSysArgv[i]), debug)
printDbg("From : " + str(pSysArgv), debug)
# If this is help, then display help and quit.
if pSysArgv[i] == '--help':
printHelp(helpStringArr)
return EXIT_NO_ERR
# Make sure it is supported arg.
if not pSysArgv[i] in pSupportedArgs.keys():
printErr(pSysArgv[i] + " is not supported arguments.")
printErr("Supported args: ")
printSeq(pSupportedArgs, 4)
return EXIT_ERR
# Get the index of matching arg from supportedArg dictionary,
idx = pSupportedArgs.keys().index(pSysArgv[i])
printDbg("idx (index of " + str(pSysArgv[i]) + " in pSupportedArgs.keys(): " + str(idx), debug)
if not type(pSupportedArgs.values()[idx]) == tuple:
printErr("Wrong type for supported Arg values: ")
print type(pSupportedArgs.values()[idx])
return EXIT_ERR
# If value is none then this specific parameters takes no values,
# If not none, process further.
if len(pSupportedArgs.values()[idx]) == 0:
printDbg("No values are needed for " + str(pSysArgv[i]), debug)
pSupportedArgs[pSysArgv[i]] = 1
else:
# sysArgvCurrToNextDashDash will extract first two letters of all arguments from
# sysArgv's members ranging from i+1 (since i-th one we know already contains --)
# This is used in finding a next occurrence of argument starting with -- so that
# all values between current argument and next occurrence will be assumed for values
# for current arg.
sysArgvCurrToNextDashDash = [j[0:2] for j in pSysArgv[i+1:]]
sysArgvCurrToNext = [j[:] for j in pSysArgv[i+1:]]
printDbg("sysArgvCurrToNextDashDash: " + str(sysArgvCurrToNextDashDash), debug)
if '--' in sysArgvCurrToNextDashDash:
# Once it is found (the next occurrence), the idxEnd will hold the index of sysArgvCurrToNextDashDash
# Note that idxEnd is not the index from the beginning of all arguments, the idxEnd is relative to
# current iteration of sysArgv whereas idxEndAbs will find the absolute index value from all the way
# to the beginning of sysArgv.
# If no argument with -- is found as next occurrence, we assume all the args between current one to the
# end of sysargv are considered values.
idxEnd = sysArgvCurrToNextDashDash.index('--')
idxEndAbs = idxEnd + i + 1
printDbg("Further occurrence of -- found, taking its index: " + str(idxEnd), debug)
else:
idxEnd = len(sysArgvCurrToNextDashDash)
idxEndAbs = len(pSysArgv)
printDbg("No further occurrence of -- found, taking index of last member of sysArgvCurrToNextDashDash", debug)
printDbg("idxEnd: " + str(idxEnd), debug)
printDbg("idxEndAbs: " + str(idxEndAbs), debug)
currParamSuppValues = pSupportedArgs.values()[idx]
printVar(currParamSuppValues, debug)
if currParamSuppValues[0] == '__range__':
printDbg("argCheckFlag: Range check flag is set.", debug)
currParamSuppValues = currParamSuppValues[1:]
argCheckFlag = RANGE_CHECK
else:
printDbg("argCheckFlag: not changed.")
printVar(currParamSuppValues, debug)
pSupportedArgs.values()[idx] = currParamSuppValues
# Check to see if the values are in the accepted range of values. This check is only done with non-integer values.
if len(sysArgvCurrToNext[:idxEnd]) == 0 and len(currParamSuppValues) != 0:
printErr("1. " + pSysArgv[i] + " accepts range of values: ")
printErr(str(currParamSuppValues))
return EXIT_ERR
for m in sysArgvCurrToNext[:idxEnd]:
if argCheckFlag == VALUE_CHECK:
printDbg("Literal value check...", debug)
if not m in currParamSuppValues:
# Special parameters where user input does not have to match the pre-defined values for specific param-s.
print "Checking if ", m, " in the range..."
printErr(pSysArgv[i] + ":" + str(m) + " is not in the accepted range of values: ")
print currParamSuppValues
return EXIT_ERR
elif argCheckFlag == RANGE_CHECK:
printDbg("Range check...", debug)
if not (idxEnd) in currParamSuppValues:
printErr("2. " + pSysArgv[i] + " accepts range of value: ")
printErr(str(currParamSuppValues))
return EXIT_ERR
else:
if debug:
printDbg("Range check OK: idxEnd" + str(idxEnd) + " is within ", debug)
printN(currParamSuppValues)
else:
printErr("Unsupported values for argCheckFlag: " + str(argCheckFlag))
return EXIT_ERR
values = pSysArgv[i+1:idxEndAbs]
printDbg("values/pSysArgv[" + str(i+1) + ":" + str(idxEndAbs) + "]: ", debug)
print values
pSupportedArgs[pSysArgv[i]] = values
if debug:
printDbg("pSupportedArgs at the end of this loop: ", debug)
print pSupportedArgs
else:
printDbg("Not starting with --, doing add'l check.")
if i == 0:
printDbg("1. Skipping " + str(pSysArgv[i]), debug)
elif firstSwitchCheck == 1:
printDbg("2. Skipping " + str(pSysArgv[i]), debug)
elif validateIp(pSysArgv[i]):
printDbg("3. Skipping " + str(pSysArgv[i]), debug)
elif validateBladeLoc(pSysArgv[i]):
printDbg("4. Skipping " + str(pSysArgv[i]), debug)
else:
printDbg("Previous switch check.")
if validateIp(pSysArgv[i-1]) or validateBladeLoc(pSysArgv[i-1]):
printErr("Expected switch (starting with --) after blade location or UCSM IP: ")
return EXIT_ERR
firstSwitchCheck += 1
if debug:
printDbg("Returning pSupportedArgs: ")
printSeq(pSupportedArgs, 4)
return pSupportedArgs
Consumer of this API is only few declarative lines, with few use cases below:
Code: Select all
...
helpParamStr = ""
PARAM_HELP = '--help'
PARAM_IP_SRC = '--ip-src'
PARAM_FILENAME = '--filename'
helpParamList = {\
PARAM_HELP : 'Display help',\
PARAM_IP_SRC : 'IP address of the server from which to copy the file from',\
PARAM_FILENAME : 'Name of the file to copy',
}
...
# Assign cmd line param-s, process and validate the params.
t1 = tuple([1])
argsSupported = {\
PARAM_HELP: (), PARAM_FILENAME: ('__range__', 1), PARAM_IP_SRC: ('__range__', 1) }
argsSupported = prepArgs(sys.argv, argsSupported, helpStringArr)
if argsSupported == EXIT_NO_ERR:
quit(0)
if argsSupported == EXIT_ERR:
printErr("Error in input, use --help for proper input.")
quit(1)
fileToCopy = argsSupported[PARAM_FILENAME][0]
option3 = argsSupported[PARAM_IP_SRC][0]
if validateIp(option3):
fileCopySrcIp = option3
printDbg("tftp-server: " + str(option3))
...
Another example:
Code: Select all
PARAM_HELP = '--help'
PARAM_SPNAME = '--sp'
helpParamList = {\
PARAM_HELP : 'display help',\
PARAM_SPNAME : 'service profile name to be associated with.',
}
# Construct supported parameter list.
t1 = tuple([1])
argsSupported = {\
PARAM_HELP: (), PARAM_SPNAME: ('__range__', 1) }
argsSupported = prepArgs(sys.argv, argsSupported, helpStringArr)
if argsSupported == EXIT_NO_ERR:
quit(0)
if argsSupported == EXIT_ERR:
printErr("Error in input, use --help for proper input.")
quit(1)
spNewName = argsSupported[PARAM_SPNAME][0]
Last example, this one used slightly pre-mod version of API some other place however it is similar:
Code: Select all
PARAM_LIST_DISKS = '--disks'
PARAM_LIST_DURATION = '--time'
PARAM_HELP = '--help'
PARAM_INFO = '--info'
helpParamList = {\
PARAM_LIST_DISKS: 'List of disks to be tested with benchmark.',\
PARAM_LIST_DURATION: 'duration in minutes to run each benchmark iteration.',\
PARAM_INFO: 'User provided information regarding benchmark test.',\
PARAM_HELP: 'Display help information.'
}
t1 = tuple([1])
configRangeDurationInt = range(0,30)
configRangeDurationStr = []
for i in range(0, len(configRangeDurationInt)):
configRangeDurationStr.append(str(configRangeDurationInt[i]))
CONFIG_RANGE_DURATION = tuple(configRangeDurationStr)
argsSupported = {\
PARAM_LIST_DISKS: ("C:","D:","E:","F:","G:","H:","I:","K:","L:","M:","N:","O:","P:","R:"), \
PARAM_HELP: t1,\
PARAM_LIST_DURATION: (CONFIG_RANGE_DURATION),\
PARAM_INFO: tuple(["-"])
}
argsSupported = prepArgs(sys.argv, argsSupported, helpStringArr)
if argsSupported == EXIT_NO_ERR:
quit(0)
if argsSupported == EXIT_ERR:
printErr("Error processing arguments.")
quit(1)
if type(argsSupported[PARAM_LIST_DURATION]) != tuple:
print "PARAM_LIST_DURATION was specified from command line."
CONFIG_TEST_DURATION_MIN = int(argsSupported[PARAM_LIST_DURATION][0])
if type(argsSupported[PARAM_INFO]) != tuple:
print "CONFIG_TEST_INFO was specified from command line."
CONFIG_TEST_INFO = argsSupported[PARAM_INFO][0]
if type(argsSupported[PARAM_LIST_DISKS]) != tuple:
print "PARAM_LIST_DISKS was specified from command line."
listPatternOutDisks = []
for i in argsSupported[PARAM_LIST_DISKS]:
listPatternOutDisks.append("\t"+i+"\n")