Telethon/telethon_generator/parsers/tlobject/tlarg.py
Lonami Exo a5f5d6ef23 Minor doc updates, fixes and improvements
In particular, removed code which no longer worked, made light
theme easier on the eyes, added slight syntax highlightning,
and fixed search for exact matches.
2019-03-16 17:19:19 +01:00

246 lines
8.0 KiB
Python

import re
def _fmt_strings(*dicts):
for d in dicts:
for k, v in d.items():
if v in ('None', 'True', 'False'):
d[k] = '<strong>{}</strong>'.format(v)
else:
d[k] = re.sub(
r'([brf]?([\'"]).*\2)',
lambda m: '<em>{}</em>'.format(m.group(1)),
v
)
KNOWN_NAMED_EXAMPLES = {
('message', 'string'): "'Hello there!'",
('expires_at', 'date'): 'datetime.timedelta(minutes=5)',
('until_date', 'date'): 'datetime.timedelta(days=14)',
('view_messages', 'true'): 'None',
('send_messages', 'true'): 'None',
('limit', 'int'): '100',
('hash', 'int'): '0',
('hash', 'string'): "'A4LmkR23G0IGxBE71zZfo1'",
('min_id', 'int'): '0',
('max_id', 'int'): '0',
('min_id', 'long'): '0',
('max_id', 'long'): '0',
('add_offset', 'int'): '0',
('title', 'string'): "'My awesome title'",
('device_model', 'string'): "'ASUS Laptop'",
('system_version', 'string'): "'Arch Linux'",
('app_version', 'string'): "'1.0'",
('system_lang_code', 'string'): "'en'",
('lang_pack', 'string'): "''",
('lang_code', 'string'): "'en'",
('chat_id', 'int'): '478614198',
('client_id', 'long'): 'random.randrange(-2**63, 2**63)'
}
KNOWN_TYPED_EXAMPLES = {
'int128': "int.from_bytes(os.urandom(16), 'big')",
'bytes': "b'arbitrary\\x7f data \\xfa here'",
'long': "-12398745604826",
'string': "'some string here'",
'int': '42',
'date': 'datetime.datetime(2018, 6, 25)',
'double': '7.13',
'Bool': 'False',
'true': 'True',
'InputChatPhoto': "client.upload_file('/path/to/photo.jpg')",
'InputFile': "client.upload_file('/path/to/file.jpg')",
'InputPeer': "'username'"
}
_fmt_strings(KNOWN_NAMED_EXAMPLES, KNOWN_TYPED_EXAMPLES)
SYNONYMS = {
'InputUser': 'InputPeer',
'InputChannel': 'InputPeer',
'InputDialogPeer': 'InputPeer',
'InputNotifyPeer': 'InputPeer',
'InputMessage': 'int'
}
# These are flags that are cleaner to leave off
OMITTED_EXAMPLES = {
'silent',
'background',
'clear_draft',
'reply_to_msg_id',
'random_id',
'reply_markup',
'entities',
'embed_links',
'hash',
'min_id',
'max_id',
'add_offset',
'grouped',
'broadcast',
'admins',
'edit',
'delete'
}
class TLArg:
def __init__(self, name, arg_type, generic_definition):
"""
Initializes a new .tl argument
:param name: The name of the .tl argument
:param arg_type: The type of the .tl argument
:param generic_definition: Is the argument a generic definition?
(i.e. {X:Type})
"""
self.name = 'is_self' if name == 'self' else name
# Default values
self.is_vector = False
self.is_flag = False
self.skip_constructor_id = False
self.flag_index = -1
self.cls = None
# Special case: some types can be inferred, which makes it
# less annoying to type. Currently the only type that can
# be inferred is if the name is 'random_id', to which a
# random ID will be assigned if left as None (the default)
self.can_be_inferred = name == 'random_id'
# The type can be an indicator that other arguments will be flags
if arg_type == '#':
self.flag_indicator = True
self.type = None
self.is_generic = False
else:
self.flag_indicator = False
self.is_generic = arg_type.startswith('!')
# Strip the exclamation mark always to have only the name
self.type = arg_type.lstrip('!')
# The type may be a flag (flags.IDX?REAL_TYPE)
# Note that 'flags' is NOT the flags name; this
# is determined by a previous argument
# However, we assume that the argument will always be called 'flags'
flag_match = re.match(r'flags.(\d+)\?([\w<>.]+)', self.type)
if flag_match:
self.is_flag = True
self.flag_index = int(flag_match.group(1))
# Update the type to match the exact type, not the "flagged" one
self.type = flag_match.group(2)
# Then check if the type is a Vector<REAL_TYPE>
vector_match = re.match(r'[Vv]ector<([\w\d.]+)>', self.type)
if vector_match:
self.is_vector = True
# If the type's first letter is not uppercase, then
# it is a constructor and we use (read/write) its ID
# as pinpointed on issue #81.
self.use_vector_id = self.type[0] == 'V'
# Update the type to match the one inside the vector
self.type = vector_match.group(1)
# See use_vector_id. An example of such case is ipPort in
# help.configSpecial
if self.type.split('.')[-1][0].islower():
self.skip_constructor_id = True
# The name may contain "date" in it, if this is the case and
# the type is "int", we can safely assume that this should be
# treated as a "date" object. Note that this is not a valid
# Telegram object, but it's easier to work with
if self.type == 'int' and (
re.search(r'(\b|_)(date|until|since)\b', name) or
name in ('expires', 'expires_at', 'was_online')):
self.type = 'date'
self.generic_definition = generic_definition
def type_hint(self):
cls = self.type
if '.' in cls:
cls = cls.split('.')[1]
result = {
'int': 'int',
'long': 'int',
'int128': 'int',
'int256': 'int',
'string': 'str',
'date': 'Optional[datetime]', # None date = 0 timestamp
'bytes': 'bytes',
'true': 'bool',
}.get(cls, "Type{}".format(cls))
if self.is_vector:
result = 'List[{}]'.format(result)
if self.is_flag and cls != 'date':
result = 'Optional[{}]'.format(result)
return result
def real_type(self):
# Find the real type representation by updating it as required
real_type = self.type
if self.flag_indicator:
real_type = '#'
if self.is_vector:
if self.use_vector_id:
real_type = 'Vector<{}>'.format(real_type)
else:
real_type = 'vector<{}>'.format(real_type)
if self.is_generic:
real_type = '!{}'.format(real_type)
if self.is_flag:
real_type = 'flags.{}?{}'.format(self.flag_index, real_type)
return real_type
def __str__(self):
if self.generic_definition:
return '{{{}:{}}}'.format(self.name, self.real_type())
else:
return '{}:{}'.format(self.name, self.real_type())
def __repr__(self):
return str(self).replace(':date', ':int').replace('?date', '?int')
def to_dict(self):
return {
'name': self.name.replace('is_self', 'self'),
'type': re.sub(r'\bdate$', 'int', self.real_type())
}
def as_example(self, f, indent=0):
if self.is_generic:
f.write('other_request')
return
known = (KNOWN_NAMED_EXAMPLES.get((self.name, self.type))
or KNOWN_TYPED_EXAMPLES.get(self.type)
or KNOWN_TYPED_EXAMPLES.get(SYNONYMS.get(self.type)))
if known:
f.write(known)
return
assert self.omit_example() or self.cls, 'TODO handle ' + str(self)
# Pick an interesting example if any
for cls in self.cls:
if cls.is_good_example():
cls.as_example(f, indent)
break
else:
# If no example is good, just pick the first
self.cls[0].as_example(f, indent)
def omit_example(self):
return (self.is_flag or self.can_be_inferred) \
and self.name in OMITTED_EXAMPLES