File size: 6,363 Bytes
2f85de4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# python3.7
"""Contains the utility functions for parsing arguments."""
import json
import argparse
import click
__all__ = [
'parse_int', 'parse_float', 'parse_bool', 'parse_index', 'parse_json',
'IntegerParamType', 'FloatParamType', 'BooleanParamType', 'IndexParamType',
'JsonParamType', 'DictAction'
]
def parse_int(arg):
"""Parses an argument to integer.
Support converting string `none` and `null` to `None`.
"""
if arg is None:
return None
if isinstance(arg, str) and arg.lower() in ['none', 'null']:
return None
return int(arg)
def parse_float(arg):
"""Parses an argument to float number.
Support converting string `none` and `null` to `None`.
"""
if arg is None:
return None
if isinstance(arg, str) and arg.lower() in ['none', 'null']:
return None
return float(arg)
def parse_bool(arg):
"""Parses an argument to boolean.
`None` will be converted to `False`.
"""
if isinstance(arg, bool):
return arg
if arg is None:
return False
if arg.lower() in ['1', 'true', 't', 'yes', 'y']:
return True
if arg.lower() in ['0', 'false', 'f', 'no', 'n', 'none', 'null']:
return False
raise ValueError(f'`{arg}` cannot be converted to boolean!')
def parse_index(arg, min_val=None, max_val=None):
"""Parses indices.
If the input is a list or tuple, this function has no effect.
If the input is a string, it can be either a comma separated list of numbers
`1, 3, 5`, or a dash separated range `3 - 10`. Spaces in the string will be
ignored.
Args:
arg: The input argument to parse indices from.
min_val: If not `None`, this function will check that all indices are
equal to or larger than this value. (default: None)
max_val: If not `None`, this function will check that all indices are
equal to or smaller than this field. (default: None)
Returns:
A list of integers.
Raises:
ValueError: If the input is invalid, i.e., neither a list or tuple, nor
a string.
"""
if arg is None or arg == '':
indices = []
elif isinstance(arg, int):
indices = [arg]
elif isinstance(arg, (list, tuple)):
indices = list(arg)
elif isinstance(arg, str):
indices = []
if arg.lower() not in ['none', 'null']:
splits = arg.replace(' ', '').split(',')
for split in splits:
numbers = list(map(int, split.split('-')))
if len(numbers) == 1:
indices.append(numbers[0])
elif len(numbers) == 2:
indices.extend(list(range(numbers[0], numbers[1] + 1)))
else:
raise ValueError(f'Invalid type of input: `{type(arg)}`!')
assert isinstance(indices, list)
indices = sorted(list(set(indices)))
for idx in indices:
assert isinstance(idx, int)
if min_val is not None:
assert idx >= min_val, f'{idx} is smaller than min val `{min_val}`!'
if max_val is not None:
assert idx <= max_val, f'{idx} is larger than max val `{max_val}`!'
return indices
def parse_json(arg):
"""Parses a string-like argument following JSON format.
- `None` arguments will be kept.
- Non-string arguments will be kept.
"""
if not isinstance(arg, str):
return arg
try:
return json.loads(arg)
except json.decoder.JSONDecodeError:
return arg
class IntegerParamType(click.ParamType):
"""Defines a `click.ParamType` to parse integer arguments."""
name = 'int'
def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements
try:
return parse_int(value)
except ValueError:
self.fail(f'`{value}` cannot be parsed as an integer!', param, ctx)
class FloatParamType(click.ParamType):
"""Defines a `click.ParamType` to parse float arguments."""
name = 'float'
def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements
try:
return parse_float(value)
except ValueError:
self.fail(f'`{value}` cannot be parsed as a float!', param, ctx)
class BooleanParamType(click.ParamType):
"""Defines a `click.ParamType` to parse boolean arguments."""
name = 'bool'
def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements
try:
return parse_bool(value)
except ValueError:
self.fail(f'`{value}` cannot be parsed as a boolean!', param, ctx)
class IndexParamType(click.ParamType):
"""Defines a `click.ParamType` to parse indices arguments."""
name = 'index'
def __init__(self, min_val=None, max_val=None):
self.min_val = min_val
self.max_val = max_val
def convert(self, value, param, ctx): # pylint: disable=inconsistent-return-statements
try:
return parse_index(value, self.min_val, self.max_val)
except ValueError:
self.fail(
f'`{value}` cannot be parsed as a list of indices!', param, ctx)
class JsonParamType(click.ParamType):
"""Defines a `click.ParamType` to parse arguments following JSON format."""
name = 'json'
def convert(self, value, param, ctx):
return parse_json(value)
class DictAction(argparse.Action):
"""Argparse action to split each argument into (key, value) pair.
Each argument should be with `key=value` format, where `value` should be a
string with JSON format.
For example, with an argparse:
parser.add_argument('--options', nargs='+', action=DictAction)
, you can use following arguments in the command line:
--options \
a=1 \
b=1.5
c=true \
d=null \
e=[1,2,3,4,5] \
f='{"x":1,"y":2,"z":3}' \
NOTE: No space is allowed in each argument. Also, the dictionary-type
argument should be quoted with single quotation marks `'`.
"""
def __call__(self, parser, namespace, values, option_string=None):
options = {}
for argument in values:
key, val = argument.split('=', maxsplit=1)
options[key] = parse_json(val)
setattr(namespace, self.dest, options)
|