Many code-style improvements

This commit is contained in:
Fadi Hadzh
2016-11-30 00:29:42 +03:00
parent ef264ae83f
commit d087941bd0
25 changed files with 698 additions and 499 deletions

View File

@@ -18,7 +18,8 @@ class SourceBuilder:
"""Writes a string into the source code, applying indentation if required"""
if self.on_new_line:
self.on_new_line = False # We're not on a new line anymore
if string.strip(): # If the string was not empty, indent; Else it probably was a new line
if string.strip(
): # If the string was not empty, indent; Else it probably was a new line
self.indent()
self.out_stream.write(string)

View File

@@ -5,13 +5,13 @@ class TLObject:
""".tl core types IDs (such as vector, booleans, etc.)"""
CORE_TYPES = (0x1cb5c415, 0xbc799737, 0x997275b5, 0x3fedd339)
def __init__(self, fullname, id, args, result, is_function):
def __init__(self, fullname, object_id, args, result, is_function):
"""
Initializes a new TLObject, given its properties.
Usually, this will be called from `from_tl` instead
:param fullname: The fullname of the TL object (namespace.name)
The namespace can be omitted
:param id: The hexadecimal string representing the object ID
:param object_id: The hexadecimal string representing the object ID
:param args: The arguments, if any, of the TL object
:param result: The result type of the TL object
:param is_function: Is the object a function or a type?
@@ -25,7 +25,7 @@ class TLObject:
self.name = fullname
# The ID should be an hexadecimal string
self.id = int(id, base=16)
self.id = int(object_id, base=16)
self.args = args
self.result = result
self.is_function = is_function
@@ -67,14 +67,16 @@ class TLObject:
''', tl, re.IGNORECASE | re.VERBOSE)
# Retrieve the matched arguments
args = [TLArg(name, type, brace != '') for brace, name, type, _ in args_match]
args = [TLArg(name, arg_type, brace != '')
for brace, name, arg_type, _ in args_match]
# And initialize the TLObject
return TLObject(fullname=match.group(1),
id=match.group(2),
args=args,
result=match.group(3),
is_function=is_function)
return TLObject(
fullname=match.group(1),
object_id=match.group(2),
args=args,
result=match.group(3),
is_function=is_function)
def is_core_type(self):
"""Determines whether the TLObject is a "core type"
@@ -82,19 +84,19 @@ class TLObject:
return self.id in TLObject.CORE_TYPES
def __repr__(self):
fullname = ('{}.{}'.format(self.namespace, self.name) if self.namespace is not None
else self.name)
fullname = ('{}.{}'.format(self.namespace, self.name)
if self.namespace is not None else self.name)
hex_id = hex(self.id)[2:].rjust(8, '0') # Skip 0x and add 0's for padding
hex_id = hex(self.id)[2:].rjust(8,
'0') # Skip 0x and add 0's for padding
return '{}#{} {} = {}'.format(fullname,
hex_id,
' '.join([str(arg) for arg in self.args]),
self.result)
return '{}#{} {} = {}'.format(
fullname, hex_id, ' '.join([str(arg) for arg in self.args]),
self.result)
def __str__(self):
fullname = ('{}.{}'.format(self.namespace, self.name) if self.namespace is not None
else self.name)
fullname = ('{}.{}'.format(self.namespace, self.name)
if self.namespace is not None else self.name)
# Some arguments are not valid for being represented, such as the flag indicator or generic definition
# (these have no explicit values until used)
@@ -104,20 +106,21 @@ class TLObject:
args = ', '.join(['{}={{}}'.format(arg.name) for arg in valid_args])
# Since Python's default representation for lists is using repr(), we need to str() manually on every item
args_format = ', '.join(['str(self.{})'.format(arg.name) if not arg.is_vector else
'None if not self.{0} else [str(_) for _ in self.{0}]'.format(arg.name)
for arg in valid_args])
args_format = ', '.join(
['str(self.{})'.format(arg.name) if not arg.is_vector else
'None if not self.{0} else [str(_) for _ in self.{0}]'.format(
arg.name) for arg in valid_args])
return ("'({} (ID: {}) = ({}))'.format({})"
.format(fullname, hex(self.id), args, args_format))
class TLArg:
def __init__(self, name, type, generic_definition):
def __init__(self, name, arg_type, generic_definition):
"""
Initializes a new .tl argument
:param name: The name of the .tl argument
:param type: The type 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})
"""
@@ -132,14 +135,15 @@ class TLArg:
self.flag_index = -1
# The type can be an indicator that other arguments will be flags
if type == '#':
if arg_type == '#':
self.flag_indicator = True
self.type = None
self.is_generic = False
else:
self.flag_indicator = False
self.is_generic = type.startswith('!')
self.type = type.lstrip('!') # Strip the exclamation mark always to have only the name
self.is_generic = arg_type.startswith('!')
self.type = arg_type.lstrip(
'!') # Strip the exclamation mark always to have only the name
# 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
@@ -148,13 +152,15 @@ class TLArg:
if flag_match:
self.is_flag = True
self.flag_index = int(flag_match.group(1))
self.type = flag_match.group(2) # Update the type to match the exact type, not the "flagged" one
self.type = flag_match.group(
2) # Update the type to match the exact type, not the "flagged" one
# Then check if the type is a Vector<REAL_TYPE>
vector_match = re.match(r'vector<(\w+)>', self.type, re.IGNORECASE)
if vector_match:
self.is_vector = True
self.type = vector_match.group(1) # Update the type to match the one inside the vector
self.type = vector_match.group(
1) # Update the type to match the one inside the vector
# 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.

View File

@@ -2,7 +2,7 @@ import os
import re
import shutil
from parser import SourceBuilder, TLParser
from .parser import SourceBuilder, TLParser
def get_output_path(normal_path):
@@ -60,8 +60,8 @@ class TLGenerator:
continue
# Determine the output directory and create it
out_dir = get_output_path('functions' if tlobject.is_function
else 'types')
out_dir = get_output_path('functions'
if tlobject.is_function else 'types')
if tlobject.namespace:
out_dir = os.path.join(out_dir, tlobject.namespace)
@@ -77,39 +77,51 @@ class TLGenerator:
TLGenerator.get_class_name(tlobject)))
# Create the file for this TLObject
filename = os.path.join(out_dir, TLGenerator.get_file_name(tlobject, add_extension=True))
filename = os.path.join(
out_dir,
TLGenerator.get_file_name(
tlobject, add_extension=True))
with open(filename, 'w', encoding='utf-8') as file:
# Let's build the source code!
with SourceBuilder(file) as builder:
# Both types and functions inherit from MTProtoRequest so they all can be sent
builder.writeln('from telethon.tl.mtproto_request import MTProtoRequest')
builder.writeln(
'from telethon.tl.mtproto_request import MTProtoRequest')
builder.writeln()
builder.writeln()
builder.writeln('class {}(MTProtoRequest):'.format(TLGenerator.get_class_name(tlobject)))
builder.writeln('class {}(MTProtoRequest):'.format(
TLGenerator.get_class_name(tlobject)))
# Write the original .tl definition, along with a "generated automatically" message
builder.writeln('"""Class generated by TLObjects\' generator. '
'All changes will be ERASED. Original .tl definition below.')
builder.writeln(
'"""Class generated by TLObjects\' generator. '
'All changes will be ERASED. Original .tl definition below.')
builder.writeln('{}"""'.format(repr(tlobject)))
builder.writeln()
# Create an class-level variable that stores the TLObject's constructor ID
builder.writeln("# Telegram's constructor ID (and unique identifier) for this class")
builder.writeln('constructor_id = {}'.format(hex(tlobject.id)))
builder.writeln(
"# Telegram's constructor ID (and unique identifier) for this class")
builder.writeln('constructor_id = {}'.format(
hex(tlobject.id)))
builder.writeln()
# First sort the arguments so that those not being a flag come first
args = sorted([arg for arg in tlobject.args if not arg.flag_indicator],
key=lambda x: x.is_flag)
args = sorted(
[arg for arg in tlobject.args
if not arg.flag_indicator],
key=lambda x: x.is_flag)
# Then convert the args to string parameters, the flags having =None
args = [(arg.name if not arg.is_flag
else '{}=None'.format(arg.name)) for arg in args
if not arg.flag_indicator and not arg.generic_definition]
args = [(arg.name if not arg.is_flag else
'{}=None'.format(arg.name)) for arg in args
if not arg.flag_indicator and
not arg.generic_definition]
# Write the __init__ function
if args:
builder.writeln('def __init__(self, {}):'.format(', '.join(args)))
builder.writeln('def __init__(self, {}):'.format(
', '.join(args)))
else:
builder.writeln('def __init__(self):')
@@ -117,18 +129,23 @@ class TLGenerator:
# those which are generated automatically: flag indicator and generic definitions.
# We don't need the generic definitions in Python because arguments can be any type
args = [arg for arg in tlobject.args
if not arg.flag_indicator and not arg.generic_definition]
if not arg.flag_indicator and
not arg.generic_definition]
if args:
# Write the docstring, so we know the type of the arguments
builder.writeln('"""')
for arg in args:
if not arg.flag_indicator:
builder.write(':param {}: Telegram type: «{}».'.format(arg.name, arg.type))
builder.write(
':param {}: Telegram type: «{}».'.format(
arg.name, arg.type))
if arg.is_vector:
builder.write(' Must be a list.'.format(arg.name))
builder.write(' Must be a list.'.format(
arg.name))
if arg.is_generic:
builder.write(' This should be another MTProtoRequest.')
builder.write(
' This should be another MTProtoRequest.')
builder.writeln()
builder.writeln('"""')
@@ -136,7 +153,8 @@ class TLGenerator:
# Functions have a result object and are confirmed by default
if tlobject.is_function:
builder.writeln('self.result = None')
builder.writeln('self.confirmed = True # Confirmed by default')
builder.writeln(
'self.confirmed = True # Confirmed by default')
# Set the arguments
if args:
@@ -148,22 +166,24 @@ class TLGenerator:
# Write the on_send(self, writer) function
builder.writeln('def on_send(self, writer):')
builder.writeln('writer.write_int({}.constructor_id, signed=False)'
.format(TLGenerator.get_class_name(tlobject)))
builder.writeln(
'writer.write_int({}.constructor_id, signed=False)'
.format(TLGenerator.get_class_name(tlobject)))
for arg in tlobject.args:
TLGenerator.write_onsend_code(builder, arg, tlobject.args)
TLGenerator.write_onsend_code(builder, arg,
tlobject.args)
builder.end_block()
# Write the empty() function, which returns an "empty"
# instance, in which all attributes are set to None
builder.writeln('@staticmethod')
builder.writeln('def empty():')
builder.writeln('"""Returns an "empty" instance (all attributes are None)"""')
builder.writeln(
'"""Returns an "empty" instance (all attributes are None)"""')
builder.writeln('return {}({})'.format(
TLGenerator.get_class_name(tlobject),
', '.join('None' for _ in range(len(args)))
))
TLGenerator.get_class_name(tlobject), ', '.join(
'None' for _ in range(len(args)))))
builder.end_block()
# Write the on_response(self, reader) function
@@ -174,7 +194,8 @@ class TLGenerator:
else:
if tlobject.args:
for arg in tlobject.args:
TLGenerator.write_onresponse_code(builder, arg, tlobject.args)
TLGenerator.write_onresponse_code(
builder, arg, tlobject.args)
else:
# If there were no arguments, we still need an on_response method, and hence "pass" if empty
builder.writeln('pass')
@@ -186,23 +207,26 @@ class TLGenerator:
builder.end_block()
builder.writeln('def __str__(self):')
builder.writeln("return {}".format(str(tlobject)))
builder.writeln('return {}'.format(str(tlobject)))
# builder.end_block() # There is no need to end the last block
# Step 3: Once all the objects have been generated, we can now group them in a single file
# Step 3: Once all the objects have been generated, we can now group them in a single file
filename = os.path.join(get_output_path('all_tlobjects.py'))
with open(filename, 'w', encoding='utf-8') as file:
with SourceBuilder(file) as builder:
builder.writeln('"""File generated by TLObjects\' generator. All changes will be ERASED"""')
builder.writeln(
'"""File generated by TLObjects\' generator. All changes will be ERASED"""')
builder.writeln()
# First add imports
for tlobject in tlobjects:
builder.writeln('import {}'.format(TLGenerator.get_full_file_name(tlobject)))
builder.writeln('import {}'.format(
TLGenerator.get_full_file_name(tlobject)))
builder.writeln()
# Create a variable to indicate which layer this is
builder.writeln('layer = {} # Current generated layer'.format(TLParser.find_layer(scheme_file)))
builder.writeln('layer = {} # Current generated layer'.format(
TLParser.find_layer(scheme_file)))
builder.writeln()
# Then create the dictionary containing constructor_id: class
@@ -211,10 +235,9 @@ class TLGenerator:
# Fill the dictionary (0x1a2b3c4f: tl.full.type.path.Class)
for tlobject in tlobjects:
builder.writeln('{}: {}.{},'
.format(hex(tlobject.id),
TLGenerator.get_full_file_name(tlobject),
TLGenerator.get_class_name(tlobject)))
builder.writeln('{}: {}.{},'.format(
hex(tlobject.id), TLGenerator.get_full_file_name(
tlobject), TLGenerator.get_class_name(tlobject)))
builder.current_indent -= 1
builder.writeln('}')
@@ -225,8 +248,10 @@ class TLGenerator:
# Courtesy of http://stackoverflow.com/a/31531797/4759433
# Also, '_' could be replaced for ' ', then use .title(), and then remove ' '
result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(), tlobject.name)
result = result[:1].upper() + result[1:].replace('_', '') # Replace again to fully ensure!
result = re.sub(r'_([a-z])', lambda m: m.group(1).upper(),
tlobject.name)
result = result[:1].upper() + result[1:].replace(
'_', '') # Replace again to fully ensure!
# If it's a function, let it end with "Request" to identify them more easily
if tlobject.is_function:
result += 'Request'
@@ -283,22 +308,25 @@ class TLGenerator:
builder.writeln('if {}:'.format(name))
if arg.is_vector:
builder.writeln("writer.write_int(0x1cb5c415, signed=False) # Vector's constructor ID")
builder.writeln(
"writer.write_int(0x1cb5c415, signed=False) # Vector's constructor ID")
builder.writeln('writer.write_int(len({}))'.format(name))
builder.writeln('for {}_item in {}:'.format(arg.name, name))
# Temporary disable .is_vector, not to enter this if again
arg.is_vector = False
TLGenerator.write_onsend_code(builder, arg, args, name='{}_item'.format(arg.name))
TLGenerator.write_onsend_code(
builder, arg, args, name='{}_item'.format(arg.name))
arg.is_vector = True
elif arg.flag_indicator:
# Calculate the flags with those items which are not None
builder.writeln('# Calculate the flags. This equals to those flag arguments which are NOT None')
builder.writeln(
'# Calculate the flags. This equals to those flag arguments which are NOT None')
builder.writeln('flags = 0')
for flag in args:
if flag.is_flag:
builder.writeln('flags |= (1 << {}) if {} else 0'
.format(flag.flag_index, 'self.{}'.format(flag.name)))
builder.writeln('flags |= (1 << {}) if {} else 0'.format(
flag.flag_index, 'self.{}'.format(flag.name)))
builder.writeln('writer.write_int(flags)')
builder.writeln()
@@ -310,10 +338,12 @@ class TLGenerator:
builder.writeln('writer.write_long({})'.format(name))
elif 'int128' == arg.type:
builder.writeln('writer.write_large_int({}, bits=128)'.format(name))
builder.writeln('writer.write_large_int({}, bits=128)'.format(
name))
elif 'int256' == arg.type:
builder.writeln('writer.write_large_int({}, bits=256)'.format(name))
builder.writeln('writer.write_large_int({}, bits=256)'.format(
name))
elif 'double' == arg.type:
builder.writeln('writer.write_double({})'.format(name))
@@ -366,7 +396,8 @@ class TLGenerator:
was_flag = False
if arg.is_flag:
was_flag = True
builder.writeln('if (flags & (1 << {})) != 0:'.format(arg.flag_index))
builder.writeln('if (flags & (1 << {})) != 0:'.format(
arg.flag_index))
# Temporary disable .is_flag not to enter this if again when calling the method recursively
arg.is_flag = False
@@ -377,7 +408,8 @@ class TLGenerator:
builder.writeln('for _ in range({}_len):'.format(arg.name))
# Temporary disable .is_vector, not to enter this if again
arg.is_vector = False
TLGenerator.write_onresponse_code(builder, arg, args, name='{}_item'.format(arg.name))
TLGenerator.write_onresponse_code(
builder, arg, args, name='{}_item'.format(arg.name))
builder.writeln('{}.append({}_item)'.format(name, arg.name))
arg.is_vector = True
@@ -393,10 +425,12 @@ class TLGenerator:
builder.writeln('{} = reader.read_long()'.format(name))
elif 'int128' == arg.type:
builder.writeln('{} = reader.read_large_int(bits=128)'.format(name))
builder.writeln('{} = reader.read_large_int(bits=128)'.format(
name))
elif 'int256' == arg.type:
builder.writeln('{} = reader.read_large_int(bits=256)'.format(name))
builder.writeln('{} = reader.read_large_int(bits=256)'.format(
name))
elif 'double' == arg.type:
builder.writeln('{} = reader.read_double()'.format(name))
@@ -408,7 +442,9 @@ class TLGenerator:
builder.writeln('{} = reader.tgread_bool()'.format(name))
elif 'true' == arg.type: # Awkwardly enough, Telegram has both bool and "true", used in flags
builder.writeln('{} = True # Arbitrary not-None value, no need to read since it is a flag'.format(name))
builder.writeln(
'{} = True # Arbitrary not-None value, no need to read since it is a flag'.
format(name))
elif 'bytes' == arg.type:
builder.writeln('{} = reader.tgread_bytes()'.format(name))
@@ -429,6 +465,7 @@ class TLGenerator:
# Restore .is_flag
arg.is_flag = True
if __name__ == '__main__':
if TLGenerator.tlobjects_exist():
print('Detected previous TLObjects. Cleaning...')