Commit 37fe9592 authored by Paul McCarthy's avatar Paul McCarthy 🚵
Browse files

RF: New options to applyArgStyle to handle commands which mostly use the

standard --arg=val style, but also have short options which do not have a long
equivalent *and* which may expect multiple arguments (e.g. -A one two three) .
Definitely feels like over-engineering, but when style="--=", all other
arguments are set to sensible defaults.
parent 281160db
......@@ -248,13 +248,15 @@ def applyArgStyle(style,
argmap=None,
valmap=None,
singlechar_args=False,
charstyle=None,
charsep=None,
**kwargs):
"""Turns the given ``kwargs`` into command line options. This function
is intended to be used to automatically generate command line options
from arguments passed into a Python function.
The ``style`` and ``valsep`` arguments control how key-value pairs
are converted into command-line options:
The ``style`` and ``valsep`` (and ``charstyle`` and ``charsep``) arguments
control how key-value pairs are converted into command-line options:
========= ========== ===========================
......@@ -275,86 +277,101 @@ def applyArgStyle(style,
========= ========== ===========================
:arg style: Controls how the ``kwargs`` are converted into command-line
options - must be one of ``'-'``, ``'--'``, ``'-='``, or
``'--='``.
:arg style: Controls how the ``kwargs`` are converted into command-line
options - must be one of ``'-'``, ``'--'``, ``'-='``, or
``'--='``.
:arg valsep: Controls how the values passed to command-line options
which expect multiple arguments are delimited - must be
one of ``' '``, ``','`` or ``'"'``. Defaults to ``' '``
if ``'=' not in style``, ``','`` otherwise.
:arg valsep: Controls how the values passed to command-line options
which expect multiple arguments are delimited - must be
one of ``' '``, ``','`` or ``'"'``. Defaults to ``' '``
if ``'=' not in style``, ``','`` otherwise.
:arg argmap: Dictionary of ``{kwarg-name : cli-name}`` mappings. This can
be used if you want to use different argument names in your
Python function for the command-line options.
:arg argmap: Dictionary of ``{kwarg-name : cli-name}`` mappings. This be
used if you want to use different argument names in your
Python function for the command-line options.
:arg valmap: Dictionary of ``{cli-name : value}`` mappings. This can be
used to define specific semantics for some command-line
options. Acceptable values for ``value`` are as follows
:arg valmap: Dictionary of ``{cli-name : value}`` mappings. This can be
used to define specific semantics for some command-line
options. Acceptable values for ``value`` are as follows
- :data:`SHOW_IF_TRUE` - if the argument is present, and
``True`` in ``kwargs``, the command line option
will be added (without any arguments).
- :data:`SHOW_IF_TRUE` - if the argument is present, and
``True`` in ``kwargs``, the command line option
will be added (without any arguments).
- :data:`HIDE_IF_TRUE` - if the argument is present, and
``False`` in ``kwargs``, the command line option
will be added (without any arguments).
- :data:`HIDE_IF_TRUE` - if the argument is present, and
``False`` in ``kwargs``, the command line option
will be added (without any arguments).
- Any other constant value. If the argument is present
in ``kwargs``, its command-line option will be
added, with the constant value as its argument.
- Any other constant value. If the argument is present
in ``kwargs``, its command-line option will be
added, with the constant value as its argument.
The argument for any options not specified in the ``valmap``
will be converted into strings.
The argument for any options not specified in the
``valmap`` will be converted into strings.
:arg singlechar_args: If True, single character arguments always take a
single hyphen prefix (e.g. -h) regardless of the
style.
:arg charstyle: Separate style specification for single-character
arguments. If ``style == '--='``, defaults to ``'-'``,
matching UNIX conventions. Otherwise defaults to the
value of ``style``.
:arg charsep: Controls how the values passed to command-line options
which expect multiple arguments are delimited - must be
one of ``' '``, ``','`` or ``'"'``. Defaults to ``' '``
if ``'=' not in style``, ``','`` otherwise.
:arg singlechar_args: If ``True``, equivalent to ``charstyle='-'``.
:arg kwargs: Arguments to be converted into command-line options.
:returns: A list containing the generated command-line options.
"""
if charstyle is None:
if singlechar_args: charstyle = '-'
elif style == '--=': charstyle = '-'
else: charstyle = style
if valsep is None:
if '=' in style: valsep = ','
else: valsep = ' '
if charsep is None:
if '=' in charstyle: charsep = ','
else: charsep = ' '
if style not in ('-', '--', '-=', '--='):
raise ValueError('Invalid style: {}'.format(style))
raise ValueError(f'Invalid style: {style}')
if charstyle not in ('-', '--', '-=', '--='):
raise ValueError(f'Invalid charstyle: {style}')
if valsep not in (' ', ',', '"'):
raise ValueError('Invalid valsep: {}'.format(valsep))
# we don't handle the case where '=' in
# style, and valsep == ' ', because no
# sane CLI app would do this. Right?
if '=' in style and valsep == ' ':
raise ValueError('Incompatible style and valsep: s={} v={}'.format(
style, valsep))
raise ValueError(f'Invalid valsep: {style}')
if argmap is None: argmap = {}
if valmap is None: valmap = {}
def fmtarg(arg):
if style in ('-', '-=') or (singlechar_args and len(arg) == 1):
arg = '-{}'.format(arg)
elif style in ('--', '--='):
arg = '--{}'.format(arg)
return arg
# always returns a sequence
def fmtval(val):
# Format the argument.
def fmtarg(arg, style):
if style in ('--', '--='): return f'--{arg}'
else: return f'-{arg}'
# Formt the argument value. Always returns
# a sequence. Ultimately we rely on shlex
# to ensure that args are properly quoted,
# so we don't add quotes around multi-
# paramter values here.
def fmtval(val, sep):
if isinstance(val, abc.Sequence) and (not isinstance(val, str)):
val = [str(v) for v in val]
if valsep == ' ': return val
elif valsep == '"': return [' ' .join(val)]
else: return [valsep.join(val)]
if sep == ' ': return val
elif sep == '"': return [' '.join(val)]
else: return [sep.join(val)]
else:
return [str(val)]
# val is assumed to be a sequence
def fmtargval(arg, val):
# Format the argument and value together.
# val is assumed to be a sequence.
def fmtargval(arg, val, style):
# if '=' in style, val will
# always be a single string
if '=' in style: return ['{}={}'.format(arg, val[0])]
......@@ -366,16 +383,19 @@ def applyArgStyle(style,
if v is None: continue
if len(k) == 1: sty, sep = charstyle, charsep
else: sty, sep = style, valsep
k = argmap.get(k, k)
mapv = valmap.get(k, fmtval(v))
k = fmtarg(k)
mapv = valmap.get(k, fmtval(v, sep))
k = fmtarg(k, sty)
if mapv in (SHOW_IF_TRUE, HIDE_IF_TRUE):
if (mapv is SHOW_IF_TRUE and v) or \
(mapv is HIDE_IF_TRUE and not v):
args.append(k)
else:
args.extend(fmtargval(k, mapv))
args.extend(fmtargval(k, mapv, sty))
return args
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment