328 lines
12 KiB
Python
328 lines
12 KiB
Python
import re
|
|
import traceback
|
|
from typing import List, Dict
|
|
|
|
from core.exceptions import InvalidTemplatePattern, InvalidCommandFormatError
|
|
|
|
|
|
class Pattern:
|
|
pass
|
|
|
|
|
|
class Template:
|
|
def __init__(self, args: List[Pattern], priority: int = 1):
|
|
self.args_ = args
|
|
self.priority = priority
|
|
|
|
@property
|
|
def args(self):
|
|
return self.args_
|
|
|
|
def __str__(self):
|
|
return 'Template({})'.format(self.args)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
class ArgumentPattern(Pattern):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
def __str__(self):
|
|
return 'ArgumentPattern("{}")'.format(self.name)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
class OptionalPattern(Pattern):
|
|
def __init__(self, flag: str, args: List[Template]):
|
|
self.flag = flag
|
|
self.args = args
|
|
|
|
def __str__(self):
|
|
return 'OptionalPattern("{}", {})'.format(self.flag, self.args)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
class DescPattern(Pattern):
|
|
def __init__(self, text: str):
|
|
self.text = text
|
|
|
|
def __str__(self):
|
|
return 'DescPattern("{}")'.format(self.text)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
class Argument:
|
|
def __init__(self, value: str):
|
|
self.value = value
|
|
|
|
|
|
class Optional:
|
|
def __init__(self, args: Dict[str, dict], flagged=False):
|
|
self.flagged = flagged
|
|
self.args = args
|
|
|
|
|
|
class MatchedResult:
|
|
def __init__(self, args: dict, original_template, priority: int = 1):
|
|
self.args = args
|
|
self.original_template = original_template
|
|
self.priority = priority
|
|
|
|
def __str__(self):
|
|
return 'MatchedResult({}, {})'.format(self.args, self.priority)
|
|
|
|
def __repr__(self):
|
|
return self.__str__()
|
|
|
|
|
|
def split_multi_arguments(lst: list):
|
|
new_lst = []
|
|
for x in lst:
|
|
spl = list(filter(None, re.split(r"(\(.*?\))", x)))
|
|
if len(spl) > 1:
|
|
for y in spl:
|
|
index_y = spl.index(y)
|
|
mat = re.match(r"\((.*?)\)", y)
|
|
if mat:
|
|
spl1 = mat.group(1).split('|')
|
|
for s in spl1:
|
|
cspl = spl.copy()
|
|
cspl.insert(index_y, s)
|
|
del cspl[index_y + 1]
|
|
new_lst.append(''.join(cspl))
|
|
else:
|
|
mat = re.match(r"\((.*?)\)", spl[0])
|
|
if mat:
|
|
spl1 = mat.group(1).split('|')
|
|
for s in spl1:
|
|
new_lst.append(s)
|
|
else:
|
|
new_lst.append(spl[0])
|
|
split_more = False
|
|
for n in new_lst:
|
|
if re.match(r"\((.*?)\)", n):
|
|
split_more = True
|
|
if split_more:
|
|
return split_multi_arguments(new_lst)
|
|
else:
|
|
return list(set(new_lst))
|
|
|
|
|
|
def parse_template(argv: List[str]) -> List[Template]:
|
|
templates = []
|
|
argv_ = []
|
|
for a in argv:
|
|
if isinstance(a, str):
|
|
spl = split_multi_arguments([a])
|
|
for split in spl:
|
|
argv_.append(split)
|
|
|
|
for a in argv_:
|
|
template = Template([])
|
|
patterns = filter(None, re.split(r'(\[.*?])|(<.*?>)|(\{.*})| ', a))
|
|
for p in patterns:
|
|
strip_pattern = p.strip()
|
|
if strip_pattern == '':
|
|
continue
|
|
if strip_pattern.startswith('['):
|
|
if not strip_pattern.endswith(']'):
|
|
raise InvalidTemplatePattern(p)
|
|
optional_patterns = strip_pattern[1:-1].split(' ')
|
|
flag = None
|
|
args = []
|
|
if optional_patterns[0][0] == '-':
|
|
flag = optional_patterns[0]
|
|
args += optional_patterns[1:]
|
|
else:
|
|
args += optional_patterns
|
|
template.args.append(OptionalPattern(flag=flag,
|
|
args=parse_template(
|
|
[' '.join(args).strip()]) if len(
|
|
args) > 0 else []))
|
|
elif strip_pattern.startswith('{'):
|
|
if not strip_pattern.endswith('}'):
|
|
raise InvalidTemplatePattern(p)
|
|
template.args.append(DescPattern(strip_pattern[1:-1]))
|
|
else:
|
|
if strip_pattern.startswith('<') and not strip_pattern.endswith('>'):
|
|
raise InvalidTemplatePattern(p)
|
|
template.args.append(ArgumentPattern(strip_pattern))
|
|
templates.append(template)
|
|
return templates
|
|
|
|
|
|
def templates_to_str(templates: List[Template], with_desc=False, simplify=True) -> List[str]:
|
|
text = []
|
|
last_desc = None
|
|
for template in templates:
|
|
arg_text = []
|
|
sub_arg_text = []
|
|
has_desc = False
|
|
for arg in template.args:
|
|
if isinstance(arg, ArgumentPattern):
|
|
sub_arg_text.append(arg.name)
|
|
elif isinstance(arg, OptionalPattern):
|
|
t = '['
|
|
if arg.flag is not None:
|
|
t += arg.flag
|
|
if arg.args:
|
|
if arg.flag is not None:
|
|
t += ' '
|
|
t += ' '.join(templates_to_str(arg.args, simplify=False))
|
|
t += ']'
|
|
sub_arg_text.append(t)
|
|
if isinstance(arg, DescPattern):
|
|
has_desc = True
|
|
sub_arg_text_ = ' '.join(sub_arg_text)
|
|
sub_arg_text.clear()
|
|
if simplify:
|
|
if last_desc == arg.text:
|
|
continue
|
|
if with_desc:
|
|
arg_text.append(sub_arg_text_ + ' - ' + arg.text)
|
|
last_desc = arg.text
|
|
if not has_desc:
|
|
arg_text.append(' '.join(sub_arg_text))
|
|
sub_arg_text.clear()
|
|
if arg_text:
|
|
text.append(" ".join(arg_text))
|
|
return text
|
|
|
|
|
|
def parse_argv(argv: List[str], templates: List[Template]) -> MatchedResult:
|
|
matched_result = []
|
|
for template in templates:
|
|
try:
|
|
argv_copy = argv.copy() # copy argv to avoid changing original argv
|
|
parsed_argv = {}
|
|
original_template = template
|
|
afters = []
|
|
args = [x for x in template.args if not isinstance(x, DescPattern)]
|
|
if not args:
|
|
continue
|
|
for a in args: # optional first
|
|
if isinstance(a, OptionalPattern):
|
|
if a.flag is None:
|
|
afters.append(a.args[0])
|
|
continue
|
|
parsed_argv[a.flag] = Optional({}, flagged=False)
|
|
if a.flag in argv_copy: # if flag is in argv
|
|
if not a.args: # no args
|
|
parsed_argv[a.flag] = Optional({}, flagged=True)
|
|
argv_copy.remove(a.flag)
|
|
else: # has args
|
|
index_flag = argv_copy.index(a.flag)
|
|
len_t_args = len(a.args[0].args)
|
|
if len(argv_copy[index_flag:]) >= len_t_args:
|
|
sub_argv = argv_copy[index_flag + 1: index_flag + len_t_args + 1]
|
|
parsed_argv[a.flag] = Optional(parse_argv(sub_argv, a.args).args, flagged=True)
|
|
del argv_copy[index_flag: index_flag + len_t_args + 1]
|
|
for a in args:
|
|
if isinstance(a, ArgumentPattern):
|
|
if a.name.startswith('<'):
|
|
if len(argv_copy) > 0:
|
|
parsed_argv[a.name] = Argument(argv_copy[0])
|
|
del argv_copy[0]
|
|
else:
|
|
parsed_argv[a.name] = False
|
|
elif a.name == '...':
|
|
if len(args) - 1 == args.index(a):
|
|
afters.append(Template([a]))
|
|
else:
|
|
raise InvalidTemplatePattern('... must be the last argument')
|
|
else:
|
|
parsed_argv[a.name] = a.name in argv_copy
|
|
if parsed_argv[a.name]:
|
|
argv_copy.remove(a.name)
|
|
if argv_copy: # if there are still some argv left
|
|
if afters:
|
|
for arg in afters:
|
|
for sub_args in arg.args:
|
|
if isinstance(sub_args, ArgumentPattern):
|
|
if sub_args.name.startswith('<'):
|
|
if len(argv_copy) > 0:
|
|
parsed_argv[sub_args.name] = Argument(argv_copy[0])
|
|
del argv_copy[0]
|
|
else:
|
|
parsed_argv[sub_args.name] = False
|
|
elif sub_args.name == '...':
|
|
parsed_argv[sub_args.name] = [Argument(x) for x in argv_copy]
|
|
del argv_copy[:]
|
|
else:
|
|
parsed_argv[sub_args.name] = sub_args.name in argv_copy
|
|
if parsed_argv[sub_args.name]:
|
|
argv_copy.remove(sub_args.name)
|
|
if argv_copy:
|
|
template_arguments = [arg for arg in args if isinstance(arg, ArgumentPattern)]
|
|
if template_arguments:
|
|
if isinstance(template_arguments[-1], ArgumentPattern):
|
|
if template_arguments[-1].name.startswith('<'): # if last arg is variable
|
|
argv_keys = list(parsed_argv.keys())
|
|
parsed_argv[argv_keys[argv_keys.index(template_arguments[-1].name)]] \
|
|
.value += ' ' + ' '.join(argv_copy)
|
|
del argv_copy[0]
|
|
matched_result.append(MatchedResult(parsed_argv, original_template, template.priority))
|
|
except TypeError:
|
|
traceback.print_exc()
|
|
continue
|
|
filtered_result = []
|
|
for m in matched_result: # convert to result dict
|
|
filtered = False
|
|
args_ = m.args
|
|
for keys in args_:
|
|
if isinstance(args_[keys], Optional):
|
|
if not args_[keys].flagged:
|
|
args_[keys] = False
|
|
else:
|
|
if not args_[keys].args:
|
|
args_[keys] = True
|
|
else:
|
|
args_[keys] = args_[keys].args
|
|
elif isinstance(args_[keys], Argument):
|
|
args_[keys] = args_[keys].value
|
|
elif isinstance(args_[keys], list):
|
|
args_[keys] = [v.value for v in args_[keys] if isinstance(v, Argument)]
|
|
elif isinstance(args_[keys], bool):
|
|
if not args_[keys]:
|
|
filtered = True
|
|
break
|
|
if not filtered:
|
|
filtered_result.append(m)
|
|
if (len_filtered_result := len(filtered_result)) > 1: # if multiple result, select one by priority
|
|
priority_result = {}
|
|
for f in filtered_result:
|
|
priority = f.priority # base priority
|
|
for keys in f.args: # add priority for each argument
|
|
if f.args[keys] is True: # if argument is not any else
|
|
priority += 1
|
|
if priority not in priority_result:
|
|
priority_result[priority] = [f]
|
|
else:
|
|
priority_result[priority].append(f)
|
|
max_ = max(priority_result.keys())
|
|
if len(priority_result[max_]) > 1: # if still...
|
|
new_priority_result = {}
|
|
for p in priority_result[max_]:
|
|
new_priority = p.priority
|
|
for keys in p.args:
|
|
if p.args[keys]:
|
|
new_priority += 1
|
|
if new_priority not in new_priority_result:
|
|
new_priority_result[new_priority] = [p]
|
|
else:
|
|
new_priority_result[new_priority].append(p)
|
|
max_ = max(new_priority_result.keys())
|
|
return new_priority_result[max_][0]
|
|
return priority_result[max_][0]
|
|
elif len_filtered_result == 0:
|
|
raise InvalidCommandFormatError
|
|
else:
|
|
return filtered_result[0]
|