You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
3.2 KiB
129 lines
3.2 KiB
import re
|
|
import copy
|
|
|
|
|
|
PARTS_RE = re.compile('(\<|\[)((?:\w+|\:|\||\.\.\.| (?:[0-9]+))+)(?:\>|\])')
|
|
|
|
TYPE_MAP = {
|
|
'str': str,
|
|
'int': int,
|
|
'float': float,
|
|
'snowflake': int,
|
|
}
|
|
|
|
|
|
class ArgumentError(Exception):
|
|
pass
|
|
|
|
|
|
class Argument(object):
|
|
def __init__(self, raw):
|
|
self.name = None
|
|
self.count = 1
|
|
self.required = False
|
|
self.types = None
|
|
self.parse(raw)
|
|
|
|
@property
|
|
def true_count(self):
|
|
return self.count or 1
|
|
|
|
def parse(self, raw):
|
|
prefix, part = raw
|
|
|
|
if prefix == '<':
|
|
self.required = True
|
|
else:
|
|
self.required = False
|
|
|
|
if part.endswith('...'):
|
|
part = part[:-3]
|
|
self.count = 0
|
|
elif ' ' in part:
|
|
split = part.split(' ', 1)
|
|
part, self.count = split[0], int(split[1])
|
|
|
|
if ':' in part:
|
|
part, typeinfo = part.split(':')
|
|
self.types = typeinfo.split('|')
|
|
|
|
self.name = part.strip()
|
|
|
|
|
|
class ArgumentSet(object):
|
|
def __init__(self, args=None, custom_types=None):
|
|
self.args = args or []
|
|
self.types = copy.copy(TYPE_MAP)
|
|
self.types.update(custom_types or {})
|
|
|
|
def convert(self, types, value):
|
|
for typ_name in types:
|
|
typ = self.types.get(typ_name)
|
|
if not typ:
|
|
raise Exception('Unknown type {}'.format(typ_name))
|
|
|
|
try:
|
|
return typ(value)
|
|
except Exception as e:
|
|
continue
|
|
|
|
raise e
|
|
|
|
def append(self, arg):
|
|
if self.args and not self.args[-1].required and arg.required:
|
|
raise Exception('Required argument cannot come after an optional argument')
|
|
|
|
if self.args and not self.args[-1].count:
|
|
raise Exception('No arguments can come after a catch-all')
|
|
|
|
self.args.append(arg)
|
|
|
|
def parse(self, rawargs):
|
|
parsed = []
|
|
|
|
for index, arg in enumerate(self.args):
|
|
if not arg.required and index + arg.true_count > len(rawargs):
|
|
continue
|
|
|
|
if arg.count == 0:
|
|
raw = rawargs[index:]
|
|
else:
|
|
raw = rawargs[index:index + arg.true_count]
|
|
|
|
if arg.types:
|
|
for idx, r in enumerate(raw):
|
|
try:
|
|
raw[idx] = self.convert(arg.types, r)
|
|
except:
|
|
raise ArgumentError('cannot convert `{}` to `{}`'.format(
|
|
r, ', '.join(arg.types)
|
|
))
|
|
|
|
if arg.count == 1:
|
|
raw = raw[0]
|
|
|
|
if (not arg.types or arg.types == ['str']) and isinstance(raw, list):
|
|
raw = ' '.join(raw)
|
|
|
|
parsed.append(raw)
|
|
|
|
return parsed
|
|
|
|
@property
|
|
def length(self):
|
|
return len(self.args)
|
|
|
|
@property
|
|
def required_length(self):
|
|
return sum([i.true_count for i in self.args if i.required])
|
|
|
|
|
|
def parse_arguments(line, custom_types=None):
|
|
args = ArgumentSet(custom_types=custom_types)
|
|
|
|
data = PARTS_RE.findall(line)
|
|
if len(data):
|
|
for item in data:
|
|
args.append(Argument(item))
|
|
|
|
return args
|
|
|