aboutsummaryrefslogtreecommitdiff
path: root/src/utils/parse/spec.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/parse/spec.py')
-rw-r--r--src/utils/parse/spec.py198
1 files changed, 198 insertions, 0 deletions
diff --git a/src/utils/parse/spec.py b/src/utils/parse/spec.py
new file mode 100644
index 00000000..cfc5e386
--- /dev/null
+++ b/src/utils/parse/spec.py
@@ -0,0 +1,198 @@
+import enum, typing
+from .time import duration
+from .types import try_int
+from src.utils.datetime.parse import date_human
+
+class SpecArgumentContext(enum.IntFlag):
+ CHANNEL = 1
+ PRIVATE = 2
+ ALL = 3
+
+class SpecArgumentType(object):
+ context = SpecArgumentContext.ALL
+
+ def __init__(self, type_name: str, name: typing.Optional[str],
+ exported: typing.Optional[str]):
+ self.type = type_name
+ self._name = name
+ self.exported = exported
+
+ def name(self) -> typing.Optional[str]:
+ return self._name
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ return None, -1
+ def error(self) -> typing.Optional[str]:
+ return None
+
+class SpecArgumentTypeWord(SpecArgumentType):
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ if args:
+ return args[0], 1
+ return None, 1
+class SpecArgumentTypeAdditionalWord(SpecArgumentType):
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ if len(args) > 1:
+ return args[0], 1
+ return None, 1
+class SpecArgumentTypeWordLower(SpecArgumentTypeWord):
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ out = SpecArgumentTypeWord.simple(self, args)
+ if out[0]:
+ return out[0].lower(), out[1]
+ return out
+
+class SpecArgumentTypeString(SpecArgumentType):
+ def name(self):
+ return "%s ..." % SpecArgumentType.name(self)
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ if args:
+ return " ".join(args), len(args)
+ return None, 1
+class SpecArgumentTypeTrimString(SpecArgumentTypeString):
+ def simple(self, args: typing.List[str]):
+ return SpecArgumentTypeString.simple(self, list(filter(None, args)))
+class SpecArgumentTypeWords(SpecArgumentTypeString):
+ def simple(self, args: typing.List[str]):
+ if args:
+ out = list(filter(None, args))
+ return out, len(out)
+ return None, 1
+
+class SpecArgumentTypeInt(SpecArgumentType):
+ def simple(self, args):
+ if args:
+ return try_int(args[0]), 1
+ return None, 1
+
+class SpecArgumentTypeDuration(SpecArgumentType):
+ def name(self):
+ return "+%s" % (SpecArgumentType.name(self) or "duration")
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ if args:
+ return duration(args[0]), 1
+ return None, 1
+ def error(self) -> typing.Optional[str]:
+ return "Invalid timeframe"
+
+class SpecArgumentTypeDate(SpecArgumentType):
+ def name(self):
+ return SpecArgumentType.name(self) or "yyyy-mm-dd"
+ def simple(self, args):
+ if args:
+ return date_human(args[0]), 1
+ return None, 1
+
+class SpecArgumentPrivateType(SpecArgumentType):
+ context = SpecArgumentContext.PRIVATE
+
+SPEC_ARGUMENT_TYPES = {
+ "word": SpecArgumentTypeWord,
+ "aword": SpecArgumentTypeAdditionalWord,
+ "wordlower": SpecArgumentTypeWordLower,
+ "string": SpecArgumentTypeString,
+ "words": SpecArgumentTypeWords,
+ "tstring": SpecArgumentTypeTrimString,
+ "int": SpecArgumentTypeInt,
+ "date": SpecArgumentTypeDate,
+ "duration": SpecArgumentTypeDuration
+}
+
+class SpecArgument(object):
+ consume = True
+ optional: bool = False
+ types: typing.List[SpecArgumentType] = []
+
+ @staticmethod
+ def parse(optional: bool, argument_types: typing.List[str]):
+ out: typing.List[SpecArgumentType] = []
+ for argument_type in argument_types:
+ exported = None
+ if "~" in argument_type:
+ exported = argument_type.split("~", 1)[1]
+ argument_type = argument_type.replace("~", "", 1)
+
+ argument_type_name: typing.Optional[str] = None
+ name_end = argument_type.find(">")
+ if argument_type.startswith("<") and name_end > 0:
+ argument_type_name = argument_type[1:name_end]
+ argument_type = argument_type[name_end+1:]
+
+ argument_type_class = SpecArgumentType
+ if argument_type in SPEC_ARGUMENT_TYPES:
+ argument_type_class = SPEC_ARGUMENT_TYPES[argument_type]
+ elif exported:
+ argument_type_class = SpecArgumentPrivateType
+
+ out.append(argument_type_class(argument_type,
+ argument_type_name, exported))
+
+ spec_argument = SpecArgument()
+ spec_argument.optional = optional
+ spec_argument.types = out
+ return spec_argument
+
+ def format(self, context: SpecArgumentContext) -> typing.Optional[str]:
+ if self.optional:
+ format = "[%s]"
+ else:
+ format = "<%s>"
+
+ names: typing.List[str] = []
+ for argument_type in self.types:
+ if not (context&argument_type.context) == 0:
+ name = argument_type.name() or argument_type.type
+ if name:
+ names.append(name)
+ if names:
+ return format % "|".join(names)
+ return None
+
+class SpecArgumentTypeLiteral(SpecArgumentType):
+ def simple(self, args: typing.List[str]) -> typing.Tuple[typing.Any, int]:
+ if args and args[0] == self.name():
+ return args[0], 1
+ return None, 1
+ def error(self) -> typing.Optional[str]:
+ return None
+class SpecLiteralArgument(SpecArgument):
+ @staticmethod
+ def parse(optional: bool, literals: typing.List[str]) -> SpecArgument:
+ spec_argument = SpecLiteralArgument()
+ spec_argument.optional = optional
+ spec_argument.types = [
+ SpecArgumentTypeLiteral("literal", l, None) for l in literals]
+ return spec_argument
+
+ def format(self, context: SpecArgumentContext) -> typing.Optional[str]:
+ return "|".join(t.name() or "" for t in self.types)
+
+def argument_spec(spec: str) -> typing.List[SpecArgument]:
+ out: typing.List[SpecArgument] = []
+ for spec_argument in spec.split(" "):
+ optional = spec_argument[0] == "?"
+
+ if spec_argument[1] == "'":
+ out.append(SpecLiteralArgument.parse(optional,
+ spec_argument[2:].split(",")))
+ else:
+ consume = True
+ if spec_argument[1] == "-":
+ consume = False
+ spec_argument = spec_argument[1:]
+
+ spec_argument_obj = SpecArgument.parse(optional,
+ spec_argument[1:].split("|"))
+ spec_argument_obj.consume = consume
+ out.append(spec_argument_obj)
+
+ return out
+
+def argument_spec_human(spec: typing.List[SpecArgument],
+ context: SpecArgumentContext=SpecArgumentContext.ALL) -> str:
+ arguments: typing.List[str] = []
+ for spec_argument in spec:
+ if spec_argument.consume:
+ out = spec_argument.format(context)
+ if out:
+ arguments.append(out)
+ return " ".join(arguments)