summaryrefslogtreecommitdiffstats
path: root/src/entity.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/entity.py')
-rw-r--r--src/entity.py220
1 files changed, 220 insertions, 0 deletions
diff --git a/src/entity.py b/src/entity.py
new file mode 100644
index 0000000..b17cda9
--- /dev/null
+++ b/src/entity.py
@@ -0,0 +1,220 @@
+from __future__ import annotations
+from typing import Optional, List
+from dataclasses import dataclass
+from sys import stderr
+import util
+
+@dataclass
+class Field:
+ name: str
+ typeid: str # full type, e.g. Vec<i32>
+ doc: str
+ inner_type: str # inner type, e.g. if typeid is 'Vec<i32>', then inner_type is 'i32'
+ deserializer: Optional[int] = None # depth of vectorness of i64
+ typ: Optional[Type] = None
+ rename: Optional[str] = None
+
+ def __post_init__(self):
+ self.maybe_enable_optional()
+ self.doc = util.escape_doc(self.doc)
+ if self.name == 'type':
+ self.name = 'type_'
+ self.rename = 'type'
+
+ def maybe_enable_optional(self):
+ optional_heuristics = {
+ 'may be null',
+ 'only available to bots',
+ 'bots only',
+ 'or null'
+ }
+ for s in optional_heuristics:
+ if s in self.doc:
+ if self.deserializer is not None:
+ raise ValueError("i64 cannot be optional")
+ self.typeid = f"Option<{self.typeid}>"
+ break
+
+ def initialize_with_type(self, typ: Type):
+ self.typ = typ
+
+
+class Type:
+ def __init__(self, name: str, inner_doc: Optional[str]=None):
+ self.name = name
+ self.doc = util.escape_doc(inner_doc or '')
+ self.exclude = False
+
+ @property
+ def is_excluded(self) -> bool:
+ return self.exclude
+
+ def render_as_decl(self):
+ '''
+ Fully declare self, e.g.
+
+ #[doc = ...] // for everything
+ #[derive([De]Serialize)] // for types and classes only
+ #[serde(tag = "@type")] // for classes only
+ #[serde(rename_all = "camelCase)] // classes only
+ '''
+ raise NotImplementedError()
+
+ def render_as_param(self, param_name: str):
+ '''
+ Render self as param, e.g.
+
+ param_name: self.name
+ '''
+ raise NotImplementedError()
+
+ def render_as_field(self, field_name: str):
+ '''
+ Render self as field, e.g.
+
+ #[doc = ...] // for types only
+ #[serde(rename = ...)] // for types and classes
+ #[serde(deserialize_with = ...)] // for types only
+ field_name: self.name,
+ '''
+ raise NotImplementedError()
+
+ def render_as_variant(self):
+ '''
+ Just print `self.name(self.name),`
+ '''
+ raise NotImplementedError()
+
+ def rusty_name(self) -> str:
+ '''
+ Sometimes, name should convert case for rust
+ '''
+ raise NotImplementedError()
+
+
+class Enum(Type):
+ def __init__(self, name, doc):
+ Type.__init__(self, name, doc)
+ self.deps = list()
+
+ def render_as_decl(self):
+ print(f'#[doc = "{self.doc}"]')
+ print('#[derive(Serialize, Deserialize, Clone, Debug)]')
+ print(f"pub enum {self.rusty_name()} {{")
+ for dep in self.deps:
+ if len(dep.deps) > 0:
+ print(f"{dep.name}({dep.name}),")
+ else:
+ print(f"{dep.name},")
+ print(f"}}")
+
+ def render_as_param(self, param_name: str):
+ print(f"{param_name}: {self.rusty_name()},")
+
+ def render_as_field(self, field_name: str):
+ print(f'#[serde(rename = {self.name})]')
+ print(f'inline serde rename for {self.name}', file=stderr)
+ print(f"{field_name}: {self.rusty_name()},")
+
+ def rusty_name(self) -> str:
+ return self.name[0].upper() + self.name[1:]
+
+ def add_member(self, member: Struct):
+ self.deps.append(member)
+
+ @property
+ def is_excluded(self):
+ return self.exclude or self.name in util.ENUM_EXCLUDE_ALWAYS
+
+
+class Struct(Type):
+ def __init__(self, name: str, doc: str, deserializer: Optional[str]=None):
+ Type.__init__(self, name, doc)
+ self.deserializer = deserializer
+ self.deps: List[Field] = list()
+
+ def render_as_decl(self):
+ print(f'#[doc = "{self.doc}"]')
+ print('#[derive(Serialize, Deserialize, Clone, Debug)]')
+ print(f'pub struct {self.rusty_name()} {{')
+ for field in self.deps:
+ print(f'#[doc = "{field.doc}"]')
+ if field.rename is not None:
+ print(f"#[serde(rename = \"{field.rename}\")]")
+ if field.deserializer is not None and not field.typ.is_excluded:
+ print(f'#[serde(deserialize_with="deserialize_i64_{field.deserializer}")]')
+
+ if field.typ.is_excluded:
+ print(f'pub {field.name}: SerdeJsonValue,')
+ else:
+ print(f'pub {field.name}: {field.typeid},')
+
+ print(f'}}')
+
+ def render_as_variant(self):
+ print('//rename?')
+ print(f'{self.name}({self.name}),')
+
+ def rusty_name(self) -> str:
+ return self.name[0].upper() + self.name[1:]
+
+ def add_field(self, field: Field):
+ self.deps.append(field)
+
+ @property
+ def is_excluded(self):
+ return self.exclude or self.name in util.STRUCT_EXCLUDE_ALWAYS
+
+
+class Scalar(Type):
+ def render_as_decl(self):
+ pass
+
+ def render_as_variant(self):
+ raise TypeError("Attempt to use scalar as variant")
+
+ def rusty_name(self):
+ return util.convert_scalar_name(self.name)
+
+ def add_field(self, name: str, doc: str, typ: Type):
+ raise TypeError("Attempt to add dependency to scalar")
+
+ @property
+ def is_excluded(self):
+ return False
+
+class Method:
+ def __init__(self, orig_name: str, ret: Type, docs: str):
+ self.name = util.to_snake_case(orig_name)
+ self.orig_name = orig_name
+ self.params: List[Field] = list()
+ self.ret = ret
+ self.doc = util.escape_doc(docs)
+
+ def add_param(self, field: Field):
+ self.params.append(field)
+
+ def render_as_decl(self):
+ actual_ret = 'SerdeJsonValue' if self.ret.is_excluded else self.ret.name
+ print(f'#[doc="{self.doc}"]')
+ if len(self.params) > 0:
+ print(f'#[doc=" \\n\\n"]') # ensure newline
+ print(f'#[doc="parameters: "]')
+ for param in self.params:
+ print(f'#[doc=" * `{param.name}`: {param.doc}"]')
+ if self.ret.is_excluded:
+ print(f'#[doc=" \\n\\n"]') # ensure newline
+ print(f'#[doc="Return type: `{self.ret.name}`"]')
+
+ print(f'fn {self.name}(&self,')
+ for param in self.params:
+ typeid = 'SerdeJsonValue' if param.typ.is_excluded else param.typeid
+ print(f'{param.name}: {typeid},')
+ print(f') -> ResponseFuture<{actual_ret}> {{')
+ print(f'self.send(json!({{')
+ for param in self.params:
+ name = param.rename or param.name
+ print(f'"{param.name}": {name},')
+ print(f'"@type": "{self.orig_name}"')
+ print(f'}}))')
+ print(f'}}')