| |
| """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. |
| """ |
| if arg is None: |
| return None |
| 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): |
| 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): |
| 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): |
| 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): |
| 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) |
|
|