11from __future__ import annotations
22
33import contextlib
4+ import json
45import re
56import shutil
67from functools import partial
1213
1314DESTINATION_PATH = REPO_HOME_PATH / "pyrogram" / "raw"
1415
15-
1616SECTION_RE = re .compile (r"---(\w+)---" )
1717LAYER_RE = re .compile (r"//\sLAYER\s(\d+)" )
1818COMBINATOR_RE = re .compile (
3535 "Bool" ,
3636 "true" ,
3737]
38+
39+ WARNING = """
40+ # # # # # # # # # # # # # # # # # # # # # # # #
41+ # !!! WARNING !!! #
42+ # This is a generated file! #
43+ # All changes made in this file will be lost! #
44+ # # # # # # # # # # # # # # # # # # # # # # # #
45+ """ .strip ()
46+
3847open = partial (open , encoding = "utf-8" )
3948
4049types_to_constructors : dict [str , list [str ]] = {}
4453namespaces_to_constructors : dict [str , list [str ]] = {}
4554namespaces_to_functions : dict [str , list [str ]] = {}
4655
56+ try :
57+ with open (API_HOME_PATH / "docs.json" ) as f :
58+ docs = json .load (f )
59+ except FileNotFoundError :
60+ docs = {"type" : {}, "constructor" : {}, "method" : {}}
61+
4762
4863class Combinator (NamedTuple ):
4964 section : str
@@ -59,6 +74,7 @@ class Combinator(NamedTuple):
5974
6075
6176def snake (s : str ):
77+ # https://stackoverflow.com/q/1175208
6278 s = re .sub (r"(.)([A-Z][a-z]+)" , r"\1_\2" , s )
6379 return re .sub (r"([a-z0-9])([A-Z])" , r"\1_\2" , s ).lower ()
6480
@@ -85,7 +101,7 @@ def get_type_hint(type: str) -> str:
85101 type = "str"
86102 elif type in {"Bool" , "true" }:
87103 type = "bool"
88- else :
104+ else : # bytes and object
89105 type = "bytes"
90106
91107 if type in {"Object" , "!X" }:
@@ -131,7 +147,43 @@ def remove_whitespaces(source: str) -> str:
131147 return "\n " .join (lines )
132148
133149
134- def start () -> None :
150+ def get_docstring_arg_type (t : str ):
151+ if t in CORE_TYPES :
152+ if t == "long" :
153+ return "``int`` ``64-bit``"
154+ if "int" in t :
155+ size = INT_RE .match (t )
156+ return (
157+ f"``int`` ``{ size .group (1 )} -bit``" if size else "``int`` ``32-bit``"
158+ )
159+ if t == "double" :
160+ return "``float`` ``64-bit``"
161+ if t == "string" :
162+ return "``str``"
163+ if t == "true" :
164+ return "``bool``"
165+ return f"``{ t .lower ()} ``"
166+ if t in {"TLObject" , "X" }:
167+ return "Any object from :obj:`~pyrogram.raw.types`"
168+ if t == "!X" :
169+ return "Any function from :obj:`~pyrogram.raw.functions`"
170+ if t .lower ().startswith ("vector" ):
171+ return "List of " + get_docstring_arg_type (t .split ("<" , 1 )[1 ][:- 1 ])
172+ return f":obj:`{ t } <pyrogram.raw.base.{ t } >`"
173+
174+
175+ def get_references (t : str , kind : str ):
176+ if kind == "constructors" :
177+ items = constructors_to_functions .get (t )
178+ elif kind == "types" :
179+ items = types_to_functions .get (t )
180+ else :
181+ raise ValueError ("Invalid kind" )
182+
183+ return ("\n " .join (items ), len (items )) if items else (None , 0 )
184+
185+
186+ def start ():
135187 shutil .rmtree (DESTINATION_PATH / "types" , ignore_errors = True )
136188 shutil .rmtree (DESTINATION_PATH / "functions" , ignore_errors = True )
137189 shutil .rmtree (DESTINATION_PATH / "base" , ignore_errors = True )
@@ -182,6 +234,7 @@ def start() -> None:
182234
183235 args = ARGS_RE .findall (line )
184236
237+ # Fix arg name being "self" or "from" (reserved python keywords)
185238 for i , item in enumerate (args ):
186239 if item [0 ] == "self" :
187240 args [i ] = ("is_self" , item [1 ])
@@ -242,12 +295,34 @@ def start() -> None:
242295 dir_path .mkdir (parents = True , exist_ok = True )
243296
244297 constructors = sorted (qualval )
245- len (constructors )
246- "\n " .join ([f"{ c } " for c in constructors ])
298+ constr_count = len (constructors )
299+ items = "\n " .join ([f"{ c } " for c in constructors ])
300+
301+ type_docs = docs ["type" ].get (qualtype , None )
302+
303+ type_docs = type_docs ["desc" ] if type_docs else "Telegram API base type."
304+
305+ docstring = type_docs
306+
307+ docstring += (
308+ f"\n \n Constructors:\n "
309+ f" This base type has { constr_count } constructor{ 's' if constr_count > 1 else '' } available.\n \n "
310+ f" .. currentmodule:: pyrogram.raw.types\n \n "
311+ f" .. autosummary::\n "
312+ f" :nosignatures:\n \n "
313+ f" { items } "
314+ )
315+
316+ references , ref_count = get_references (qualtype , "types" )
317+
318+ if references :
319+ docstring += f"\n \n Functions:\n This object can be returned by { ref_count } function{ 's' if ref_count > 1 else '' } .\n \n .. currentmodule:: pyrogram.raw.functions\n \n .. autosummary::\n :nosignatures:\n \n { references } "
247320
248321 with open (dir_path / f"{ snake (module )} .py" , "w" ) as f :
249322 f .write (
250323 type_tmpl .format (
324+ warning = WARNING ,
325+ docstring = docstring ,
251326 name = type ,
252327 qualname = qualtype ,
253328 types = ", " .join ([f'"raw.types.{ c } "' for c in constructors ]),
@@ -272,11 +347,60 @@ def start() -> None:
272347 else "pass"
273348 )
274349
350+ docstring = ""
351+ docstring_args = []
352+
353+ combinator_docs = (
354+ docs ["method" ] if c .section == "functions" else docs ["constructor" ]
355+ )
356+
275357 for arg in sorted_args :
276358 arg_name , arg_type = arg
277- FLAGS_RE .match (arg_type )
359+ is_optional = FLAGS_RE .match (arg_type )
278360 arg_type = arg_type .split ("?" )[- 1 ]
279361
362+ arg_docs = combinator_docs .get (c .qualname , None )
363+
364+ arg_docs = arg_docs ["params" ].get (arg_name , "N/A" ) if arg_docs else "N/A"
365+
366+ docstring_args .append (
367+ f'{ arg_name } ({ get_docstring_arg_type (arg_type )} { ", *optional*" if is_optional else "" } ):\n { arg_docs } \n '
368+ )
369+
370+ if c .section == "types" :
371+ constructor_docs = docs ["constructor" ].get (c .qualname , None )
372+
373+ constructor_docs = (
374+ constructor_docs ["desc" ]
375+ if constructor_docs
376+ else "Telegram API type."
377+ )
378+ docstring += constructor_docs + "\n "
379+ docstring += (
380+ f"\n Constructor of :obj:`~pyrogram.raw.base.{ c .qualtype } `."
381+ )
382+ elif function_docs := docs ["method" ].get (c .qualname , None ):
383+ docstring += function_docs ["desc" ] + "\n "
384+ else :
385+ docstring += "Telegram API function."
386+
387+ docstring += f"\n \n Details:\n - Layer: ``{ layer } ``\n - ID: ``{ c .id [2 :].upper ()} ``\n \n "
388+ docstring += " Parameters:\n " + (
389+ "\n " .join (docstring_args )
390+ if docstring_args
391+ else "No parameters required.\n "
392+ )
393+
394+ if c .section == "functions" :
395+ docstring += "\n Returns:\n " + get_docstring_arg_type (
396+ c .qualtype
397+ )
398+ else :
399+ references , count = get_references (c .qualname , "constructors" )
400+
401+ if references :
402+ docstring += f"\n Functions:\n This object can be returned by { count } function{ 's' if count > 1 else '' } .\n \n .. currentmodule:: pyrogram.raw.functions\n \n .. autosummary::\n :nosignatures:\n \n { references } "
403+
280404 write_types = read_types = "" if c .has_flags else "# No flags\n "
281405
282406 for arg_name , arg_type in c .args :
@@ -375,7 +499,9 @@ def start() -> None:
375499 return_arguments = ", " .join ([f"{ i [0 ]} ={ i [0 ]} " for i in sorted_args ])
376500
377501 compiled_combinator = combinator_tmpl .format (
502+ warning = WARNING ,
378503 name = c .name ,
504+ docstring = docstring ,
379505 slots = slots ,
380506 id = c .id ,
381507 qualname = f"{ c .section } .{ c .qualname } " ,
@@ -413,6 +539,8 @@ def start() -> None:
413539
414540 for namespace , types in namespaces_to_types .items ():
415541 with open (DESTINATION_PATH / "base" / namespace / "__init__.py" , "w" ) as f :
542+ f .write (f"{ WARNING } \n \n " )
543+
416544 all = []
417545
418546 for t in types :
@@ -438,10 +566,9 @@ def start() -> None:
438566 f .write ("]\n " )
439567
440568 for namespace , types in namespaces_to_constructors .items ():
441- with open (
442- DESTINATION_PATH / "types" / namespace / "__init__.py" ,
443- "w" ,
444- ) as f :
569+ with open (DESTINATION_PATH / "types" / namespace / "__init__.py" , "w" ) as f :
570+ f .write (f"{ WARNING } \n \n " )
571+
445572 all = []
446573
447574 for t in types :
@@ -468,9 +595,10 @@ def start() -> None:
468595
469596 for namespace , types in namespaces_to_functions .items ():
470597 with open (
471- DESTINATION_PATH / "functions" / namespace / "__init__.py" ,
472- "w" ,
598+ DESTINATION_PATH / "functions" / namespace / "__init__.py" , "w"
473599 ) as f :
600+ f .write (f"{ WARNING } \n \n " )
601+
474602 all = []
475603
476604 for t in types :
@@ -496,6 +624,7 @@ def start() -> None:
496624 f .write ("]\n " )
497625
498626 with open (DESTINATION_PATH / "all.py" , "w" , encoding = "utf-8" ) as f :
627+ f .write (WARNING + "\n \n " )
499628 f .write (f"layer = { layer } \n \n " )
500629 f .write ("objects = {" )
501630
0 commit comments