diff options
author | syn <isaqtm@gmail.com> | 2021-02-10 23:19:10 +0300 |
---|---|---|
committer | syn <isaqtm@gmail.com> | 2021-02-10 23:19:10 +0300 |
commit | fe72c896a648cfff14bc1b606bcc486ccba0fc22 (patch) | |
tree | 801ef9a7ec1df24240a23c861e37381120a31bb0 /src | |
parent | b666618a881a4acade77b3d9885a4d2b6f75f812 (diff) | |
download | tdlib-autogen-fe72c896a648cfff14bc1b606bcc486ccba0fc22.tar.gz |
New generating code
Diffstat (limited to 'src')
-rw-r--r-- | src/entity.py | 220 | ||||
-rw-r--r-- | src/generate.py | 505 | ||||
-rw-r--r-- | src/makefile | 15 | ||||
-rw-r--r-- | src/render.py | 122 |
4 files changed, 441 insertions, 421 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'}}') diff --git a/src/generate.py b/src/generate.py index fcfe5de..2f3a2d9 100644 --- a/src/generate.py +++ b/src/generate.py @@ -1,253 +1,28 @@ from __future__ import annotations -from lark import Lark, Token -from dataclasses import dataclass, field as dataclass_field -from collections import defaultdict -from typing import Optional -import sys +from typing import Dict, Optional +from entity import Type, Enum, Struct, Method, Field +from util import to_camel_case, parse_param +from render import render -wanted_types = { - 'User', - 'Chat', - 'Message', - 'Error', - 'Ok', - 'TdlibParameters', - 'PhoneNumberAuthenticationSettings', -} +def get_logger(): + import logging + logger = logging.getLogger('tdlib-autogen') -wanted_classes = { - 'AuthorizationState', - #'MessageContent', - 'Update', - 'UserStatus', -} + formatter = logging.Formatter( + "[%(asctime)s] [%(name)s] %(levelname)s: %(message)s", + datefmt="%d-%b-%y %H:%M:%S", + ) + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.DEBUG) -wanted_methods = { - 'get_me', - 'set_tdlib_parameters', - 'get_network_statistics', - 'get_application_config', - 'get_authorization_state', - 'set_database_encryption_key', - 'set_authentication_phone_number', - 'check_authentication_code', - 'check_authentication_password', -} + return logger -if not "REMOVE 'NOT' IF YOU WANT EVERYTHING TO BE RENDERED": - with open('everything.json') as f: - import json - everything = json.load(f) - wanted_classes = list(everything['classes'].keys()) - wanted_types = everything['types'] - wanted_methods = everything['methods'] +log = get_logger() -import re -# https://stackoverflow.com/a/1176023/6938271 -MIXED_2_SNAKE_CASE = re.compile(r'(?<!^)(?=[A-Z])') - -CLASS_EXCLUDE_ALWAYS = [ - 'JsonValue' -] - -BOXED_TYPES = [ - 'RichText', - 'PageBlock' -] - -TYPE_EXCLUDE_ALWAYS = [ - 'JsonValueNull', - 'JsonValueBoolean', - 'JsonValueNumber', - 'JsonValueString', - 'JsonValueArray', - 'JsonValueObject' -] - - -def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - - -def to_snake_case(ident): - return MIXED_2_SNAKE_CASE.sub('_', ident).lower() - - -def to_camel_case(ident): - if len(ident) == 0: - return '' - return ident[0].upper() + ident[1:] - - -def escape_doc(doc): - return doc.translate(str.maketrans({"\"": '\\"', "\\": "\\\\"})).replace('\n', ' \\n') - - -@dataclass -class Mod: - depth: Optional[int] = None - exclude: bool = False - - -@dataclass -class Field: - name: str - type_: str - doc: str - mod: Mod = dataclass_field(default_factory=Mod) - optional: bool = False - doc_modifier: Optional[object] = None - - def __post_init__(self): - self.doc = escape_doc(self.doc) - assert(self.name == to_snake_case(self.name)) - self.check_optional() - - def check_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: - self.optional = True - break - - def is_literally_type(self) -> bool: - return self.name == 'type' - - def is_jsonvalue(self, all_types: dict) -> bool: - type_ = all_types[self.type_] - return type_.exclude - - def get_typename(self) -> str: - typename = self.type_ - if self.optional: - typename = f'Option<{typename}>' - if self.mod.exclude: - self.doc_modifier = f' \\n\\nOriginal type: {typename}' - if self.mod.exclude: - typename = f'SerdeJsonValue' - if self.optional: - typename = f'Option<{typename}>' - - return typename - - def get_doc(self): - return self.doc + (self.doc_modifier or '') - - def serde_rename(self): - return f'#[serde(rename = "{self.orig_name}")]' - - def get_name(self): - return 'type_' if self.is_literally_type() else self.name - - -@dataclass -class Type: - name: str - doc: str - fields: list[Field] - exclude: bool = False - non_camel_name: Optional[str] = None - - def __post_init__(self): - self.doc = escape_doc(self.doc) - self.non_camel_name = self.name - self.name = to_camel_case(self.name) - if self.name not in wanted_types: - self.exclude = True - - -@dataclass -class Class: - name: str - doc: Optional[str] - members: list[str] - non_camel_name: Optional[str] = None - - def __post_init__(self): - self.doc = escape_doc(self.doc) - self.non_camel_name = self.name - self.name = to_camel_case(self.name) - -@dataclass -class Method: - name: str - orig_name: str - doc: str - params: list[str] - ret: str - orig_ret: str - - def __post_init__(self): - self.doc = escape_doc(self.doc) - self.name = to_snake_case(self.name) - - -def convert_param_type(raw_type) -> tuple[str, Mod]: - ''' - return type and mod - ''' - if isinstance(raw_type, Token): - value = raw_type.value - if value == 'string': - return 'String', Mod() - elif value == 'int32': - return 'i32', Mod() - elif value == 'int53': - return 'i64', Mod() - elif value == 'int64': - return 'i64', Mod(depth=0) - elif value == 'double': - return 'f64', Mod() - elif value == 'bytes': - return 'String', Mod() - elif value == 'Bool': - return 'bool', Mod() - else: - final_type = to_camel_case(raw_type) - mod = Mod() - if final_type not in wanted_types and final_type not in wanted_classes: - mod.exclude = True - if final_type in BOXED_TYPES: - return f'Box<{final_type}>', mod - - return final_type, mod - else: - inner, mod = convert_param_type(raw_type.children[0]) - if mod.depth is not None: - mod.depth += 1 - return 'Vec<' + inner + '>', mod - -def parse_decl(decl) -> tuple: # -> (description, cname, list[Field], cname) - docs, cname1, params, cname2 = decl.children - docs = { - doc.children[0]: ''.join(doc.children[1].children) - for doc in docs.children - } - params = params.children - fields = [] - for param in params: - name, raw_type = param.children - type_, mod = convert_param_type(raw_type) - doc = docs[name] - if name == 'description': - doc = docs['param_description'] - fields.append(Field(name=name, type_=type_, doc=doc, mod=mod)) - return docs['description'], cname1, fields, cname2 - - -try: - with open('tree_cache.pkl', "rb") as tree: - import pickle - parsed = pickle.load(tree) - eprint('using cached tree') -except: - grammar = r''' +GRAMMAR = r''' start: type_decls "---functions---" "\n"* decls type_decls: (decl | class_decl)+ decls: decl+ @@ -272,185 +47,73 @@ WHITESPACE: (" ") %import common.CNAME %ignore WHITESPACE %import common.LF - ''' - lark = Lark(grammar) - - with open("td_api.tl") as f: - for i in range(14): - f.readline() - text = f.read() - parsed = lark.parse(text) - eprint('lark parsed') - - with open("tree_cache.pkl", "wb") as tree: - import pickle - pickle.dump(parsed, tree) - eprint("pickled cache") - -parsed_types, parsed_methods = parsed.children - -#print(parsed_types) - -types = dict() -classes = dict() -methods = dict() - -for decl in parsed_types.children: - if decl.data == 'decl': - description, type_name, fields, base_type = parse_decl(decl) - type_name_orig = type_name - base_type_orig = base_type - type_name = to_camel_case(type_name.value) - base_type = to_camel_case(base_type.value) - - types[type_name] = Type(name=type_name_orig, doc=description, fields=fields) - if type_name != base_type: - if base_type not in classes: - classes[base_type] = Class(name=base_type_orig, doc=None, members=[type_name]) - else: - classes[base_type].members.append(type_name) - - else: - classname, doc = decl.children - class_name_camel = to_camel_case(classname) - doc = ''.join(doc.children[1].children) - if classname not in classes: - classes[class_name_camel] = Class(name=classname, doc=doc, members=[]) - else: - classes[class_name_camel].doc = doc +''' + +if __name__ == '__main__': + import argparse + + argparser = argparse.ArgumentParser(description='Generate bindings') + argparser.add_argument('entities', metavar='entity', action='extend', type=str, nargs='*', + help='entities in addition to target.json') + argparser.add_argument('--target', dest='target', action='store', default='target.json', + help='file with target entities (default: target.json)') + argparser.add_argument('--no-write-cache', dest='write_cache', action='store_false', + help='do not write cache') + argparser.add_argument('--no-read-cache', dest='read_cache', action='store_false', + help='do not read cache') + argparser.add_argument('--cache', dest='cache', action='store', default='cache.pkl', + help='cache path (default: cache.pkl)') + + args = argparser.parse_args() + + + def try_read_cache(cache_path: str): + try: + with open(cache_path, "rb") as tree: + import pickle + parsed = pickle.load(tree) + return parsed + except Exception as e: + log.debug(f'could not read cache: {e}') + return None + + def try_true_parse(cache_path: Optional[str] = None): + from lark import Lark + lark = Lark(GRAMMAR) + try: + with open("td_api.tl") as f: + for _ in range(14): + f.readline() + text = f.read() + parsed = lark.parse(text) + + if cache_path is not None: + with open(cache_path, "wb") as tree: + import pickle + pickle.dump(parsed, tree) + log.debug(f'written to cache: {cache_path}') + + return parsed + + except Exception as e: + log.debug(f'could not parse tdlib api: {e}') + return None + + entities = args.entities + try: + with open(args.target, 'r') as f: + import json + entities.extend(json.load(f)) + except Exception as e: + log.debug(f'could not read entities from file: {e}') -classes = dict([(k, v) for (k, v) in classes.items() if k not in CLASS_EXCLUDE_ALWAYS]) -types = dict([(k, v) for (k, v) in types.items() if k not in TYPE_EXCLUDE_ALWAYS]) - -for w in wanted_classes: - if w not in [c for c in classes]: - eprint(f'WARN: {w} class is wanted, but not found') - -for w in wanted_types: - if w not in [t for t in types]: - eprint(f'WARN: {w} type is wanted, but not found') - -eprint('parsed types & classes') - -for decl in parsed_methods.children: - docs, name, params, ret = parse_decl(decl) - snake_name = to_snake_case(name) - orig_ret = to_camel_case(ret) - if orig_ret in types: - if types[orig_ret].exclude: - ret = 'SerdeJsonValue' - elif orig_ret in classes: - ret = orig_ret - else: - ret = 'SerdeJsonValue' - - methods[name] = Method(name=snake_name, orig_name=name, doc=docs, params=params, ret=ret, orig_ret=orig_ret) - - -for w in wanted_methods: - if w not in [m.name for m in methods.values()]: - eprint(f'WARN: {w} method is wanted, but not found') -eprint('parsed methods') - -for class_ in [cls for cls in classes.values() if cls.name in wanted_classes]: - for member in class_.members: - type_ = types[member] - if len(type_.fields) == 0: - type_.exclude = True - else: - type_.exclude = False - -print(''' -#![allow(unused)] -use serde::Deserializer; -use tdlib_rs::client::ClientLike; - -use serde_derive::{Serialize, Deserialize}; -use serde_json::{json, Value as SerdeJsonValue}; -use tdlib_rs::Client; -use tdlib_rs::client::ResponseFuture; -use super::{deserialize_i64_0, deserialize_i64_1}; -'''.lstrip()) - -for type_ in [tp for tp in types.values() if not tp.exclude]: - print(f'#[derive(Serialize, Deserialize, Debug, Clone)]') - print(f'#[doc="{type_.doc}"]') - print(f'pub struct {type_.name} {{') - for field in type_.fields: - typename = field.get_typename() - doc = field.get_doc() - name = field.name - if field.is_literally_type(): - print(f' #[serde(rename="type")]') - name = 'type_' - - print(f' #[doc="{doc}"]') - if field.mod.depth is not None: - print(f' #[serde(deserialize_with="deserialize_i64_{field.mod.depth}")]') - print(f' pub {name}: {typename},') - - print(f'}}') - -eprint('rendered types') - - -for class_ in [cls for cls in classes.values() if cls.name in wanted_classes]: - print(f'#[derive(Serialize, Deserialize, Debug, Clone)]') - print(f'#[doc="{class_.doc}"]') - print(f'#[serde(tag="@type")]') - print(f'pub enum {class_.name} {{') - for member in class_.members: - type_ = types[member] - if type_.name != type_.non_camel_name: # little optimization for serde not to rename good types - print(f' #[serde(rename = "{type_.non_camel_name}")]') - if len(type_.fields) == 0: - print(f' #[doc="{type_.doc}"]') - type_.exclude = True - print(f' {member},') - else: - type_.exclude = False - print(f' {member}({member}),') - print(f'}}') - -eprint('rendered classes') - - -print('pub trait ClientExt: ClientLike {') -for method in [m for m in methods.values() if m.name in wanted_methods]: - print(f' #[doc="{method.doc}"]') - for param in method.params: - param.get_typename() - doc = param.get_doc() - name = param.get_name() - print(f' #[doc=" \\n\\n"]') # ensure newline - print(f' #[doc="parameters: "]') - print(f' #[doc=" * `{name}`: {doc}"]') - if method.ret != method.orig_ret: - print(f' #[doc=" \\n\\n"]') # ensure newline - print(f' #[doc="Original return type: `{method.orig_ret}`"]') - print(f' fn {method.name}(&self,') - for param in method.params: - typename = param.get_typename() - print(f' {param.get_name()}: {typename},') - print(f' ) -> ResponseFuture<{method.ret}> {{') - print(f' self.send(json!({{') - for param in method.params: - name = 'type_' if param.is_literally_type() else param.name - print(f' "{param.name}": {name},') - print(f' "@type": "{method.orig_name}"') - print(f' }}))') - print(f' }}') -print(f'}}') + parsed = None + if args.read_cache: + parsed = try_read_cache(args.cache) -eprint('rendered methods') + if parsed is None: + read_cache_path = args.cache if args.write_cache else None + parsed = try_true_parse(read_cache_path) -from os import path -if not path.exists('everything.json'): - with open('everything.json', 'w') as f: - import json - json.dump(dict( - classes={c.name: c.members for c in classes.values()}, - types=[t.name for t in types.values()], - methods=[m.name for m in methods.values()] - ), f, indent=4) + render(parsed, args.entities, log) diff --git a/src/makefile b/src/makefile new file mode 100644 index 0000000..4899cc2 --- /dev/null +++ b/src/makefile @@ -0,0 +1,15 @@ +DEFAULT_ENV = gen-env8 + +VENV ?= $(shell pwd)/$(DEFAULT_ENV) +PYTHON = VIRTUAL_ENV=$(VIRTUAL_ENV) $(VENV)/bin/python3 + +all: core messaging + +core: venv + $(PYTHON) generate.py --target targets/core.json | rustfmt > core.rs + +messaging: venv + $(PYTHON) generate.py | rustfmt > messaging.rs + +venv: + [ -d $(VENV) ] || (python3 -m venv $(DEFAULT_ENV) && VIRTUAL_ENV=$(VENV) $(VENV)/bin/pip install lark-parser) diff --git a/src/render.py b/src/render.py new file mode 100644 index 0000000..2fedf7d --- /dev/null +++ b/src/render.py @@ -0,0 +1,122 @@ +from typing import Dict + +from util import parse_param, to_camel_case, to_snake_case, SCALAR_MAPPING +from entity import Field, Struct, Enum, Method, Type, Scalar + + +def render(parsed, entities, log): + structs: Dict[str, Type] = dict() + enums: Dict[str, Enum] = dict() + methods: Dict[str, Method] = dict() + + for (scalar, _) in SCALAR_MAPPING.values(): + structs.update({scalar: Scalar(name=scalar)}) + + def parse_decl(decl) -> tuple: # -> (description, ctor name, fields, base name) + docs, derived, params, base = decl.children + docs = { + doc.children[0]: ''.join(doc.children[1].children) + for doc in docs.children + } + params = params.children + fields = [] + for param in params: + name, raw_type = param.children + typ, inner_typ, deserializer = parse_param(raw_type) + doc = docs[name] + if name == 'description': + doc = docs['param_description'] + fields.append(Field(typeid=typ, name=str(name), deserializer=deserializer, doc=doc, inner_type=inner_typ)) + return docs['description'], str(derived), fields, str(base) + + def find_type(name: str) -> Type: + if name in structs: + return structs[name] + elif name in enums: + return enums[name] + else: + raise ValueError(f"could not find type {name}") + + + parsed_decls, parsed_methods = parsed.children + + for decl in parsed_decls.children: + if decl.data == 'decl': + description, type_name, fields, base_type = parse_decl(decl) + rusty_type_name = to_camel_case(type_name) + rusty_base_type = to_camel_case(base_type) + + + struct = Struct(name=rusty_type_name, doc=description) + structs.update({rusty_type_name: struct}) + for field in fields: + struct.add_field(field) + + if rusty_type_name != rusty_base_type: + enums[to_camel_case(rusty_base_type)].add_member(struct) + + else: + name, doc = decl.children + rusty_name = to_camel_case(name) + doc = ''.join(doc.children[1].children) + enums[rusty_name] = Enum(name=rusty_name, doc=doc) + + + for decl in parsed_methods.children: + docs, name, params, ret = parse_decl(decl) + ret_typ = find_type(to_camel_case(ret)) + m = Method(orig_name=name, ret=ret_typ, docs=docs) + for param in params: + param.initialize_with_type(find_type(param.inner_type)) + m.add_param(param) + + methods[m.name] = m + + + for struct in structs.values(): + if isinstance(struct, Scalar): + continue + for field in struct.deps: + field.initialize_with_type(find_type(field.inner_type)) + + + for e_name, enum in enums.items(): + if e_name not in entities: + enum.exclude = True + else: + for member in enum.deps: + if len(member.deps) == 0: + member.exclude = True + else: + entities.append(member.name) # force inclusion of children + + for s_name, struct in structs.items(): + if s_name not in entities: + struct.exclude = True + + prelude = ''' + #![allow(unused)] + use serde::Deserializer; + use tdlib_rs::client::ClientLike; + + use serde_derive::{Serialize, Deserialize}; + use serde_json::{json, Value as SerdeJsonValue}; + use tdlib_rs::Client; + use tdlib_rs::client::ResponseFuture; + use super::{deserialize_i64_0, deserialize_i64_1}; + ''' + print(prelude) + + for s_name, struct in structs.items(): + if not struct.is_excluded and s_name in entities: + struct.render_as_decl() + + for e_name, enum in enums.items(): + if not enum.is_excluded and e_name in entities: + enum.render_as_decl() + + print('pub trait ClientExt: ClientLike {') + for m_name, meth in methods.items(): + if m_name in entities: + meth.render_as_decl() + print('}') |