diff options
Diffstat (limited to 'src/entity.py')
-rw-r--r-- | src/entity.py | 220 |
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'}}') |