Skip to content

Instantly share code, notes, and snippets.

@polyvertex
Created February 20, 2015 09:18
Show Gist options
  • Save polyvertex/f87967100496038ed6ba to your computer and use it in GitHub Desktop.
Save polyvertex/f87967100496038ed6ba to your computer and use it in GitHub Desktop.
A small command line arguments parser in Python
def getopts(args=None, opts=[]):
"""
A small command line arguments parser.
args is the list of the arguments to parse. sys.argv[1:] by default.
opts is a list of definitions of the options to interpret.
Each element in the list is a string defining one option and its properties.
Definition format is as follows:
{name}[,name2[,...]][(properties)][={value_type}]
Where {name} is the name of the option that will directly be used by the
caller of the script like in "-name" or "--name". You can optionally declare
a comma-separated list of one or several additional names as long as none of
them collide with any other.
(properties) is an optional field to specify some properties to your option.
It is a string of one or several letters in no particular order and enclosed
by parentheses. Each letter represents a property.
Properties:
* "r": means this option is required and a ValueError exception will be
raised if it is missing
* "m": indicates an option that can be specified several times
{value_type} is a single letter that indicates two things:
* this option requires parameter
* the expected type of this parameter:
"s" for a string, "u" for a digit-only string casted to an integer,
"i" for an integer and "f" for a float.
Example:
getopts(opts=[
"help,h",
"dir,d(r)=s",
"verbose,v(rm)",
"count,loops,c=u",
"my-long-option-name"])
"""
if args == None:
args = sys.argv[1:]
opts_dict = {} # name: def_dict
opts_names = {} # secondary name: name
opts_required = [] # names of every options marked as 'required'
opts_out = {}
# interpret options definitions
for opt_def in opts:
# "help,h(rm)[={s|i|f}]"
m = re.match(r"""^
(?P<names>[0-9a-zA-Z][0-9a-zA-Z\,\-]*)
(?:\( (?P<props>[rm]+) \))?
(?:\= (?P<valtype>[suif]) )?
$""", opt_def, re.VERBOSE | re.A)
if not m:
raise ValueError(
"getopts: malformed definition \"" + opt_def + "\"")
mdict = m.groupdict("")
# expand properties
mdict['names'] = " ".join(mdict['names'].split(",")).split()
mdict['required'] = "r" in mdict['props']
mdict['multi'] = "m" in mdict['props']
del mdict['props']
name = mdict['names'][0]
# 'names' contains the primary name and optional secondary names
# ensure none of them has been used by an other option already
if not set(mdict['names']).isdisjoint(opts_names.keys()):
raise ValueError("getopts: option defined twice")
# keep option's definition in opts_dict pointed by its primary name
opts_dict[name] = mdict
# also, ensure we can easily reference an option by any of its names
for n in mdict['names']:
opts_names[n] = name
# if this option is required, keep track of that somewhere to be able
# to easily check later
if mdict['required']:
opts_required.append(name)
# populate the output dictionary so the caller doesn't have to check
# first if a key exists
if mdict['valtype']:
opts_out[name] = None
else:
opts_out[name] = 0 if mdict['multi'] else False
# read args
idx = 0
while idx < len(args):
m = re.match(
r"^\-\-?([0-9a-zA-Z][0-9a-zA-Z\-]*)(?:\=(.+))?$",
args[idx], re.A)
if not m or m.group(1) not in opts_names:
idx += 1
else:
opt_info = opts_dict[opts_names[m.group(1)]]
opt_name = opt_info['names'][0]
try:
opt_value = m.group(2)
except IndexError:
opt_value = None
args.pop(idx)
if opt_name in opts_required:
opts_required.remove(opt_name)
if not opt_info['valtype']:
if opt_value is not None:
raise ValueError("Option " + opt_name +
" has an unexpected parameter: " + opt_value)
if not opt_info['multi']:
opts_out[opt_name] = True
else:
try:
opts_out[opt_name] += 1
except KeyError:
opts_out[opt_name] = 1
else:
if opt_value is None:
try:
opt_value = args.pop(idx)
except IndexError:
raise ValueError(
"Option " + opt_name + " requires a parameter")
if opt_info['valtype'] == "s":
pass
elif opt_info['valtype'] == "u":
m = re.match(r"^[0-9]+$", opt_value, re.A)
if not m:
raise ValueError(
"Not an unsigned integer: " + opt_value)
opt_value = int(opt_value)
elif opt_info['valtype'] == "i":
try:
opt_value = int(opt_value)
except ValueError:
raise ValueError("Not an integer: " + opt_value)
elif opt_info['valtype'] == "f":
try:
opt_value = float(opt_value)
except ValueError:
raise ValueError("Not an integer: " + opt_value)
else:
raise Exception("getopt: new valtype '" + \
opt_info['valtype'] + "' not handled here!")
if not opt_info['multi']:
opts_out[opt_name] = opt_value
else:
try:
opts_out[opt_name].append(opt_value)
except KeyError:
opts_out[opt_name] = [opt_value]
# ensure we've got every required parameters
if len(opts_required) > 0:
raise ValueError("Missing parameter(s): " + ", ".join(opts_required))
return (opts_out, args)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment