Simple command line argument parser for Python
list_things.py:
from quickparse import QuickParse
def list_things(a_list, quickparse):
if quickparse.numeric:
if isinstance(quickparse.numeric, tuple):
print(', '.join(map(str, a_list[:quickparse.numeric[-1]])))
else:
print(', '.join(map(str, a_list[:quickparse.numeric])))
else:
print("How many items? Give a numeric value like '-3'")
commands_config = {
'ls': list_things,
'': lambda: print("Command is missing, use 'ls'"),
}
things = ['apple', 'banana', 'blueberry', 'orange', 'pear', 'pineapple']
QuickParse(commands_config).execute(things)Run it:
$ python list_things.py ls -5
apple, banana, blueberry, orange, pearThe way it works:
commands_configtells QuickParse to look forlsas a command and calllist_thingson it - when no commands show help- QuickParse parses arguments as normal while
lsis privileged as a command - QuickParse finds
-5so it adds asquickparse.numeric = 5(quickparsebeing theQuickParseinstance that otherwise would come asquickparse = QuickParse(commands_config)) - QuickParse sees
list_thingsbeing associated tols, soquickparse.execute(things)calls it, passing on the arguments ofexecute(..)- one positional argument in this case - since
list_thingsexpects a named argumentquickparse, QuickParse makes sure it passes on the reference to its own instance ofquickparse - if there are multiple numeric flags are given all are passed down with
quickparse.numericin a tuple
GNU Argument Syntax: https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
$ my_cmd -12$ my_cmd +12$ my_cmd -listBy default it becomes -l -i -s -t, but adding QuickParse(options_config = [ ('-list', ) ]) will stop unpacking.
$ my_cmd +list$ my_cmd -lis equivalent to
$ my_cmd --listif adding QuickParse(options_config = [ ('-l', '--list') ])
Defining a random sample from git looks like this:
commands_config = {
'': do_show_help,
'commit': do_commit,
'log': do_log,
'stash': {
'': do_stash,
'list': do_stash_list,
}
}
options_config = [
('-a', '--all'),
]
QuickParse(commands_config, options_config).execute()Commands are called according to commands_config.
That is $ git log -3 calls do_log
do_log may look like this:
def do_log(quickparse):
print(get_log_entries()[:quickparse.numeric])If there is a named argument in do_log's signature called quickparse, the instance coming from QuickParse(commands_config, options_config) is passed down holding all the results of parsing.
Parsing happens by using the defaults and applying what options_config adds to it.
| Argument Format | Example | Remarks |
|---|---|---|
-<number> |
$ my_cmd -12 |
(default) |
+<number> |
$ my_cmd +12 |
(default) |
-<single_letter> |
$ my_cmd -x |
(default) |
+<single_letter> |
$ my_cmd +x |
(default) |
-<single_letter><value> |
$ my_cmd -nFoo |
unpacking is the default: -n -F -ooptions_config needs a type entry saying it expects a value (other than bool) |
+<single_letter><value> |
$ my_cmd +nFoo |
unpacking is the default: +n +F +ooptions_config needs a type entry saying it expects a value (other than bool) |
-<single_letter>=<value> |
$ my_cmd -n=Foo |
(default) |
+<single_letter>=<value> |
$ my_cmd +n=Foo |
(default) |
-<single_letter> <value> |
$ my_cmd -n Foo |
options_config needs a type entry saying it expects a value (other than bool) |
+<single_letter> <value> |
$ my_cmd +n Foo |
options_config needs a type entry saying it expects a value (other than bool) |
-<letters> |
$ my_cmd -abc |
unpacking is the default: -a -b -c if in options_config it's taken as -abc |
+<letters> |
$ my_cmd +abc |
unpacking is the default: +a +b +c if in options_config it's taken as +abc |
-<letters>=<value> |
$ my_cmd -name=Foo |
(default) |
+<letters>=<value> |
$ my_cmd +name=Foo |
(default) |
--<letters> |
$ my_cmd --list |
(default) |
--<letters>=<value> |
$ my_cmd --message=Bar |
(default) |
--<letters> <value> |
$ my_cmd --message Bar |
options_config needs a type entry saying it expects a value (other than bool) |
-- |
$ my_cmd -- --param-anyway |
parameters delimiter (default) |
<letters> means [a-zA-Z] and '-'s not in the first place
- '-a' is not defined to expect a value
- the '*' part has only letters, not '-' or '='
It can mean (default):
-s -w -i -n -g
or
-s wing / -s=wing
To acheve the latter make the parser aware that '-s' expects a str value:
options_config = [
('-s', str),
]Add type explicitly in options_config.
For just getting as it is add str.
Use build-in types like int or float, or create a callable that raises exceptions.
Using bool is a special case: parser will not expect a value but explicitly adds an error if one provided.
--option=
Some commands support '-' as empty value like curl -C - -O http://domanin.com/
To avoid ambiguities this syntax is not supported.
Use --option= instead.
options_test.py:
from quickparse import QuickParse
options_config = [
('-u', '--utc', '--universal'),
('-l', '--long'),
('-n', '--name', str),
]
quickparse = QuickParse(options_config=options_config)
print(f'quickparse.options: {quickparse.options}')
print(f'quickparse.errors: {quickparse.errors}')Run it:
$ python options_test.py
quickparse.options: {}
quickparse.errors: {}
$ python options_test.py -u
quickparse.options: {'-u': True, '--utc': True, '--universal': True}
quickparse.errors: {}
$ python options_test.py -ul
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True}
quickparse.errors: {}
$ python options_test.py -uln
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '-n': True, '--name': True}
quickparse.errors: {'-n': {'type': 1, 'message': "No value got for '-n/--name' - validator: str"}, '--name': {'type': 1, 'message': "No value got for '-n/--name' - validator: str"}}
$ python options_test.py -ul -nthe_name
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '-n': 'the_name', '--name': 'the_name'}
quickparse.errors: {}
$ python options_test.py -ul -n the_name
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '-n': 'the_name', '--name': 'the_name'}
quickparse.errors: {}
$ python options_test.py -ul -n=the_name
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '-n': 'the_name', '--name': 'the_name'}
quickparse.errors: {}
$ python options_test.py -ul --name the_name
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '--name': 'the_name', '-n': 'the_name'}
quickparse.errors: {}
$ python options_test.py -ul --name=the_name
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '--name': 'the_name', '-n': 'the_name'}
quickparse.errors: {}quickparse_test_args.py (committed in the repo):
from pprint import pformat
from quickparse import QuickParse
def do_show_help():
print("Executing 'do_show_help'...")
def do_commit():
print("Executing 'do_commit'...")
def do_log(quickparse):
print("Executing 'do_log'...")
def do_stash():
print("Executing 'do_stash'...")
def do_stash_list():
print("Executing 'do_stash_list'...")
commands_config = {
'': do_show_help,
'commit': do_commit,
'log': do_log,
'stash': {
'': do_stash,
'list': do_stash_list,
}
}
options_config = [
('-m', '--message', str),
('-p', '--patch'),
]
quickparse = QuickParse(commands_config, options_config)
print(f'Commands:\n{pformat(quickparse.commands)}')
print(f'Parameters:\n{pformat(quickparse.parameters)}')
print(f'Options:\n{pformat(quickparse.options)}')
print(f'\'-\' numeric argument:\n{pformat(quickparse.numeric)}')
print(f'\'+\' numeric argument:\n{pformat(quickparse.plusnumeric)}')
print(f'Functions to call:\n{pformat(quickparse.to_execute)}')
quickparse.execute()If the parser parameters 'commands_config' or 'options_config' are not valid, ValueError is rased from the underlying AssertionError.
If the arguments are not compliant with the config (e.g. no value provided for an option that requires one) then no exceptions are raised but an errors list is populated on the QuickParse object.
See the error object again from options_test.py
$ python options_test.py -uln
quickparse.options: {'-u': True, '--utc': True, '--universal': True, '-l': True, '--long': True, '-n': True, '--name': True}
quickparse.errors: {'-n': {'type': 1, 'message': "No value got for '-n/--name' - validator: str"}, '--name': {'type': 1, 'message': "No value got for '-n/--name' - validator: str"}}quickparse.errors dict is about validation of options. These are the types:
ERROR_TYPE_VALIDATION = 0
ERROR_VALUE_NOT_FOUND = 1
ERROR_INCOMPLETE_COMMAND = 2quickparse.has_errors is also available to check if any errors occurred.
Well, I still need to elaborate the docs on this but here is a quick example snippet.
quickparse.validate({
'parameters': { 'mincount': 1, },
'options': {
'mandatory': '--branch',
'optional': '--stage',
},
'numeric': { 'maxcount': 0 },
'plusnumeric': { 'maxcount': 0 },
})
assert 'parameters.mincount' not in quickparse.errors, f'Add a target'
assert not quickparse.has_errors, '\n'.join(quickparse.error_messages)