def generate_documentation(scheme_file): """Generates the documentation HTML files from from scheme.tl to /methods and /constructors, etc.""" original_paths = { 'css': 'css/docs.css', 'arrow': 'img/arrow.svg', 'index_all': 'index.html', 'index_types': 'types/index.html', 'index_methods': 'methods/index.html', 'index_constructors': 'constructors/index.html' } tlobjects = tuple(TLParser.parse_file(scheme_file)) print('Generating constructors and functions documentation...') # Save 'Type: [Constructors]' for use in both: # * Seeing the return type or constructors belonging to the same type. # * Generating the types documentation, showing available constructors. # TODO Tried using 'defaultdict(list)' with strange results, make it work. tltypes = {} tlfunctions = {} for tlobject in tlobjects: # Select to which dictionary we want to store this type dictionary = tlfunctions if tlobject.is_function else tltypes if tlobject.result in dictionary: dictionary[tlobject.result].append(tlobject) else: dictionary[tlobject.result] = [tlobject] for tltype, constructors in tltypes.items(): tltypes[tltype] = list(sorted(constructors, key=lambda c: c.name)) for tlobject in tlobjects: filename = get_create_path_for(tlobject) # Determine the relative paths for this file paths = get_relative_paths(original_paths, relative_to=filename) with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: docs.write_head(title=get_class_name(tlobject), relative_css_path=paths['css']) # Create the menu (path to the current TLObject) docs.set_menu_separator(paths['arrow']) build_menu(docs, filename, relative_main_index=paths['index_all']) # Create the page title docs.write_title(get_class_name(tlobject)) # Write the code definition for this TLObject docs.write_code(tlobject) docs.write_copy_button('Copy import to the clipboard', get_import_code(tlobject)) # Write the return type (or constructors belonging to the same type) docs.write_title( 'Returns' if tlobject.is_function else 'Belongs to', level=3) generic_arg = next( (arg.name for arg in tlobject.args if arg.generic_definition), None) if tlobject.result == generic_arg: # We assume it's a function returning a generic type generic_arg = next( (arg.name for arg in tlobject.args if arg.is_generic)) docs.write_text('This function returns the result of whatever ' 'the result from invoking the request passed ' 'through <i>{}</i> is.'.format(generic_arg)) else: if re.search('^vector<', tlobject.result, re.IGNORECASE): docs.write_text( 'A list of the following type is returned.') _, inner = tlobject.result.split('<') inner = inner.strip('>') else: inner = tlobject.result docs.begin_table(column_count=1) docs.add_row(inner, link=get_path_for_type(inner, relative_to=filename)) docs.end_table() constructors = tltypes.get(inner, []) if not constructors: docs.write_text('This type has no instances available.') elif len(constructors) == 1: docs.write_text('This type can only be an instance of:') else: docs.write_text('This type can be an instance of either:') docs.begin_table(column_count=2) for constructor in constructors: link = get_create_path_for(constructor) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(constructor), link=link) docs.end_table() # Return (or similar types) written. Now parameters/members docs.write_title( 'Parameters' if tlobject.is_function else 'Members', level=3) # Sort the arguments in the same way they're sorted on the generated code (flags go last) args = [ a for a in tlobject.sorted_args() if not a.flag_indicator and not a.generic_definition ] if args: # Writing parameters docs.begin_table(column_count=3) for arg in args: # Name row docs.add_row(arg.name, bold=True) # Type row if arg.is_generic: docs.add_row('!' + arg.type, align='center') else: docs.add_row(arg.type, link=get_path_for_type( arg.type, relative_to=filename), align='center') # Create a description for this argument description = '' if arg.can_be_inferred: description += 'If left to None, it will be inferred automatically. ' if arg.is_vector: description += 'A list must be supplied for this argument. ' if arg.is_generic: description += 'A different MTProtoRequest must be supplied for this argument. ' if arg.is_flag: description += 'This argument can be omitted. ' docs.add_row(description.strip()) docs.end_table() else: if tlobject.is_function: docs.write_text('This request takes no input parameters.') else: docs.write_text('This type has no members.') docs.end_body() # Find all the available types (which are not the same as the constructors) # Each type has a list of constructors associated to it, so it should be a map print('Generating types documentation...') for tltype, constructors in tltypes.items(): filename = get_path_for_type(tltype) out_dir = os.path.dirname(filename) os.makedirs(out_dir, exist_ok=True) # Since we don't have access to the full TLObject, split the type into namespace.name if '.' in tltype: namespace, name = tltype.split('.') else: namespace, name = None, tltype # Determine the relative paths for this file paths = get_relative_paths(original_paths, relative_to=out_dir) with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: docs.write_head(title=get_class_name(name), relative_css_path=paths['css']) docs.set_menu_separator(paths['arrow']) build_menu(docs, filename, relative_main_index=paths['index_all']) # Main file title docs.write_title(get_class_name(name)) # List available constructors for this type docs.write_title('Available constructors', level=3) if not constructors: docs.write_text('This type has no constructors available.') elif len(constructors) == 1: docs.write_text('This type has one constructor available.') else: docs.write_text('This type has %d constructors available.' % len(constructors)) docs.begin_table(2) for constructor in constructors: # Constructor full name link = get_create_path_for(constructor) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(constructor), link=link) docs.end_table() # List all the methods which return this type docs.write_title('Methods returning this type', level=3) functions = tlfunctions.get(tltype, []) if not functions: docs.write_text('No method returns this type.') elif len(functions) == 1: docs.write_text('Only the following method returns this type.') else: docs.write_text( 'The following %d methods return this type as a result.' % len(functions)) docs.begin_table(2) for func in functions: link = get_create_path_for(func) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(func), link=link) docs.end_table() # List every other type which has this type as a member docs.write_title('Other types containing this type', level=3) other_types = sorted( (t for t in tlobjects if any(tltype == a.type for a in t.args)), key=lambda t: t.name) if not other_types: docs.write_text('No other types have a member of this type.') elif len(other_types) == 1: docs.write_text( 'You can find this type as a member of this other type.') else: docs.write_text('You can find this type as a member of any of ' 'the following %d types.' % len(other_types)) docs.begin_table(2) for ot in other_types: link = get_create_path_for(ot) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(ot), link=link) docs.end_table() docs.end_body() # After everything's been written, generate an index.html file for every folder. # This will be done automatically and not taking into account any additional # information that we have available, simply a file listing all the others # accessible by clicking on their title print('Generating indices...') for folder in ['types', 'methods', 'constructors']: generate_index(folder, original_paths) # Write the final core index, the main index for the rest of files layer = TLParser.find_layer(scheme_file) types = set() methods = [] for tlobject in tlobjects: if tlobject.is_function: methods.append(tlobject) types.add(tlobject.result) types = sorted(types) methods = sorted(methods, key=lambda m: m.name) request_names = ', '.join('"' + get_class_name(m) + '"' for m in methods) type_names = ', '.join('"' + get_class_name(t) + '"' for t in types) request_urls = ', '.join('"' + get_create_path_for(m) + '"' for m in methods) type_urls = ', '.join('"' + get_path_for_type(t) + '"' for t in types) replace_dict = { 'type_count': len(types), 'method_count': len(methods), 'constructor_count': len(tlobjects) - len(methods), 'layer': layer, 'request_names': request_names, 'type_names': type_names, 'request_urls': request_urls, 'type_urls': type_urls } with open('../res/core.html') as infile: with open(original_paths['index_all'], 'w') as outfile: text = infile.read() for key, value in replace_dict.items(): text = text.replace('{' + key + '}', str(value)) outfile.write(text) # Everything done print('Documentation generated.')
def generate_documentation(scheme_file): """Generates the documentation HTML files from from scheme.tl to /methods and /constructors, etc. """ original_paths = { 'css': 'css/docs.css', 'arrow': 'img/arrow.svg', 'search.js': 'js/search.js', '404': '404.html', 'index_all': 'index.html', 'index_types': 'types/index.html', 'index_methods': 'methods/index.html', 'index_constructors': 'constructors/index.html' } tlobjects = tuple(TLParser.parse_file(scheme_file)) print('Generating constructors and functions documentation...') # Save 'Type: [Constructors]' for use in both: # * Seeing the return type or constructors belonging to the same type. # * Generating the types documentation, showing available constructors. # TODO Tried using 'defaultdict(list)' with strange results, make it work. tltypes = {} tlfunctions = {} for tlobject in tlobjects: # Select to which dictionary we want to store this type dictionary = tlfunctions if tlobject.is_function else tltypes if tlobject.result in dictionary: dictionary[tlobject.result].append(tlobject) else: dictionary[tlobject.result] = [tlobject] for tltype, constructors in tltypes.items(): tltypes[tltype] = list(sorted(constructors, key=lambda c: c.name)) for tlobject in tlobjects: filename = get_create_path_for(tlobject) # Determine the relative paths for this file paths = get_relative_paths(original_paths, relative_to=filename) with DocsWriter(filename, type_to_path_function=get_path_for_type) \ as docs: docs.write_head(title=get_class_name(tlobject), relative_css_path=paths['css']) # Create the menu (path to the current TLObject) docs.set_menu_separator(paths['arrow']) build_menu(docs, filename, relative_main_index=paths['index_all']) # Create the page title docs.write_title(get_class_name(tlobject)) # Write the code definition for this TLObject docs.write_code(tlobject) docs.write_copy_button('Copy import to the clipboard', get_import_code(tlobject)) # Write the return type (or constructors belonging to the same type) docs.write_title( 'Returns' if tlobject.is_function else 'Belongs to', level=3) generic_arg = next( (arg.name for arg in tlobject.args if arg.generic_definition), None) if tlobject.result == generic_arg: # We assume it's a function returning a generic type generic_arg = next( (arg.name for arg in tlobject.args if arg.is_generic)) docs.write_text('This function returns the result of whatever ' 'the result from invoking the request passed ' 'through <i>{}</i> is.'.format(generic_arg)) else: if re.search('^vector<', tlobject.result, re.IGNORECASE): docs.write_text( 'A list of the following type is returned.') _, inner = tlobject.result.split('<') inner = inner.strip('>') else: inner = tlobject.result docs.begin_table(column_count=1) docs.add_row(inner, link=get_path_for_type(inner, relative_to=filename)) docs.end_table() constructors = tltypes.get(inner, []) if not constructors: docs.write_text('This type has no instances available.') elif len(constructors) == 1: docs.write_text('This type can only be an instance of:') else: docs.write_text('This type can be an instance of either:') docs.begin_table(column_count=2) for constructor in constructors: link = get_create_path_for(constructor) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(constructor), link=link) docs.end_table() # Return (or similar types) written. Now parameters/members docs.write_title( 'Parameters' if tlobject.is_function else 'Members', level=3) # Sort the arguments in the same way they're sorted # on the generated code (flags go last) args = [ a for a in tlobject.sorted_args() if not a.flag_indicator and not a.generic_definition ] if args: # Writing parameters docs.begin_table(column_count=3) for arg in args: # Name row docs.add_row(arg.name, bold=True) # Type row if arg.is_generic: docs.add_row('!' + arg.type, align='center') else: docs.add_row(arg.type, align='center', link=get_path_for_type( arg.type, relative_to=filename)) # Add a description for this argument docs.add_row(get_description(arg)) docs.end_table() else: if tlobject.is_function: docs.write_text('This request takes no input parameters.') else: docs.write_text('This type has no members.') # TODO Bit hacky, make everything like this? (prepending '../') depth = '../' * (2 if tlobject.namespace else 1) docs.add_script(src='prependPath = "{}";'.format(depth)) docs.add_script(relative_src=paths['search.js']) docs.end_body() # Find all the available types (which are not the same as the constructors) # Each type has a list of constructors associated to it, hence is a map print('Generating types documentation...') for tltype, constructors in tltypes.items(): filename = get_path_for_type(tltype) out_dir = os.path.dirname(filename) if out_dir: os.makedirs(out_dir, exist_ok=True) # Since we don't have access to the full TLObject, split the type if '.' in tltype: namespace, name = tltype.split('.') else: namespace, name = None, tltype # Determine the relative paths for this file paths = get_relative_paths(original_paths, relative_to=out_dir) with DocsWriter(filename, type_to_path_function=get_path_for_type) \ as docs: docs.write_head(title=get_class_name(name), relative_css_path=paths['css']) docs.set_menu_separator(paths['arrow']) build_menu(docs, filename, relative_main_index=paths['index_all']) # Main file title docs.write_title(get_class_name(name)) # List available constructors for this type docs.write_title('Available constructors', level=3) if not constructors: docs.write_text('This type has no constructors available.') elif len(constructors) == 1: docs.write_text('This type has one constructor available.') else: docs.write_text('This type has %d constructors available.' % len(constructors)) docs.begin_table(2) for constructor in constructors: # Constructor full name link = get_create_path_for(constructor) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(constructor), link=link) docs.end_table() # List all the methods which return this type docs.write_title('Methods returning this type', level=3) functions = tlfunctions.get(tltype, []) if not functions: docs.write_text('No method returns this type.') elif len(functions) == 1: docs.write_text('Only the following method returns this type.') else: docs.write_text( 'The following %d methods return this type as a result.' % len(functions)) docs.begin_table(2) for func in functions: link = get_create_path_for(func) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(func), link=link) docs.end_table() # List all the methods which take this type as input docs.write_title('Methods accepting this type as input', level=3) other_methods = sorted( (t for t in tlobjects if any(tltype == a.type for a in t.args) and t.is_function), key=lambda t: t.name) if not other_methods: docs.write_text( 'No methods accept this type as an input parameter.') elif len(other_methods) == 1: docs.write_text( 'Only this method has a parameter with this type.') else: docs.write_text( 'The following %d methods accept this type as an input ' 'parameter.' % len(other_methods)) docs.begin_table(2) for ot in other_methods: link = get_create_path_for(ot) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(ot), link=link) docs.end_table() # List every other type which has this type as a member docs.write_title('Other types containing this type', level=3) other_types = sorted( (t for t in tlobjects if any(tltype == a.type for a in t.args) and not t.is_function), key=lambda t: t.name) if not other_types: docs.write_text('No other types have a member of this type.') elif len(other_types) == 1: docs.write_text( 'You can find this type as a member of this other type.') else: docs.write_text('You can find this type as a member of any of ' 'the following %d types.' % len(other_types)) docs.begin_table(2) for ot in other_types: link = get_create_path_for(ot) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(ot), link=link) docs.end_table() docs.end_body() # After everything's been written, generate an index.html per folder. # This will be done automatically and not taking into account any extra # information that we have available, simply a file listing all the others # accessible by clicking on their title print('Generating indices...') for folder in ['types', 'methods', 'constructors']: generate_index(folder, original_paths) # Write the final core index, the main index for the rest of files layer = TLParser.find_layer(scheme_file) types = set() methods = [] constructors = [] for tlobject in tlobjects: if tlobject.is_function: methods.append(tlobject) else: constructors.append(tlobject) if not is_core_type(tlobject.result): if re.search('^vector<', tlobject.result, re.IGNORECASE): types.add(tlobject.result.split('<')[1].strip('>')) else: types.add(tlobject.result) types = sorted(types) methods = sorted(methods, key=lambda m: m.name) constructors = sorted(constructors, key=lambda c: c.name) def fmt(xs): ys = {x: get_class_name(x) for x in xs} # cache TLObject: display zs = {} # create a dict to hold those which have duplicated keys for y in ys.values(): zs[y] = y in zs return ', '.join( '"{}.{}"'.format(x.namespace, ys[x]) if zs[ys[x]] and getattr(x, 'namespace', None) else '"{}"'.format(ys[x]) for x in xs) request_names = fmt(methods) type_names = fmt(types) constructor_names = fmt(constructors) def fmt(xs, formatter): return ', '.join('"{}"'.format(formatter(x)) for x in xs) request_urls = fmt(methods, get_create_path_for) type_urls = fmt(types, get_path_for_type) constructor_urls = fmt(constructors, get_create_path_for) shutil.copy('../res/404.html', original_paths['404']) copy_replace( '../res/core.html', original_paths['index_all'], { '{type_count}': len(types), '{method_count}': len(methods), '{constructor_count}': len(tlobjects) - len(methods), '{layer}': layer, }) os.makedirs(os.path.abspath( os.path.join(original_paths['search.js'], os.path.pardir)), exist_ok=True) copy_replace( '../res/js/search.js', original_paths['search.js'], { '{request_names}': request_names, '{type_names}': type_names, '{constructor_names}': constructor_names, '{request_urls}': request_urls, '{type_urls}': type_urls, '{constructor_urls}': constructor_urls }) # Everything done print('Documentation generated.')
def generate_documentation(scheme_file): """Generates the documentation HTML files from from scheme.tl to /methods and /constructors, etc.""" original_paths = { 'css': 'css/docs.css', 'arrow': 'img/arrow.svg', 'index_all': 'index.html', 'index_types': 'types/index.html', 'index_methods': 'methods/index.html', 'index_constructors': 'constructors/index.html' } tlobjects = tuple(TLParser.parse_file(scheme_file)) print('Generating constructors and functions documentation...') for tlobject in tlobjects: filename = get_create_path_for(tlobject) # Determine the relative paths for this file paths = get_relative_paths(original_paths, relative_to=filename) with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: docs.write_head(title=get_class_name(tlobject), relative_css_path=paths['css']) # Create the menu (path to the current TLObject) docs.set_menu_separator(paths['arrow']) build_menu(docs, filename, relative_main_index=paths['index_all']) # Create the page title docs.write_title(get_class_name(tlobject)) # Write the code definition for this TLObject docs.write_code(tlobject) docs.write_title( 'Parameters' if tlobject.is_function else 'Members', level=3) # Sort the arguments in the same way they're sorted on the generated code (flags go last) args = sorted([ a for a in tlobject.args if not a.flag_indicator and not a.generic_definition ], key=lambda a: a.is_flag) if args: # Writing parameters docs.begin_table(column_count=3) for arg in args: # Name row docs.add_row(arg.name, bold=True) # Type row if arg.is_generic: docs.add_row('!' + arg.type, align='center') else: docs.add_row(arg.type, link=get_path_for_type( arg.type, relative_to=filename), align='center') # Create a description for this argument description = '' if arg.is_vector: description += 'A list must be supplied for this argument. ' if arg.is_generic: description += 'A different MTProtoRequest must be supplied for this argument. ' if arg.is_flag: description += 'This argument can be omitted. ' docs.add_row(description.strip()) docs.end_table() else: if tlobject.is_function: docs.write_text('This request takes no input parameters.') else: docs.write_text('This type has no members.') docs.end_body() # Find all the available types (which are not the same as the constructors) # Each type has a list of constructors associated to it, so it should be a map print('Generating types documentation...') tltypes = {} tlfunctions = {} for tlobject in tlobjects: # Select to which dictionary we want to store this type dictionary = tlfunctions if tlobject.is_function else tltypes if tlobject.result in dictionary: dictionary[tlobject.result].append(tlobject) else: dictionary[tlobject.result] = [tlobject] for tltype, constructors in tltypes.items(): filename = get_path_for_type(tltype) out_dir = os.path.dirname(filename) os.makedirs(out_dir, exist_ok=True) # Since we don't have access to the full TLObject, split the type into namespace.name if '.' in tltype: namespace, name = tltype.split('.') else: namespace, name = None, tltype # Determine the relative paths for this file paths = get_relative_paths(original_paths, relative_to=out_dir) with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: docs.write_head(title=get_class_name(name), relative_css_path=paths['css']) docs.set_menu_separator(paths['arrow']) build_menu(docs, filename, relative_main_index=paths['index_all']) # Main file title docs.write_title(get_class_name(name)) docs.write_title('Available constructors', level=3) if not constructors: docs.write_text('This type has no constructors available.') elif len(constructors) == 1: docs.write_text('This type has one constructor available.') else: docs.write_text('This type has %d constructors available.' % len(constructors)) docs.begin_table(2) for constructor in constructors: # Constructor full name link = get_create_path_for(constructor) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(constructor), link=link) docs.end_table() docs.write_title('Methods returning this type', level=3) functions = tlfunctions.get(tltype, []) if not functions: docs.write_text('No method returns this type.') elif len(functions) == 1: docs.write_text('Only the following method returns this type.') else: docs.write_text( 'The following %d methods return this type as a result.' % len(functions)) docs.begin_table(2) for func in functions: link = get_create_path_for(func) link = get_relative_path(link, relative_to=filename) docs.add_row(get_class_name(func), link=link) docs.end_table() docs.end_body() # After everything's been written, generate an index.html file for every folder. # This will be done automatically and not taking into account any additional # information that we have available, simply a file listing all the others # accessible by clicking on their title print('Generating indices...') for folder in ['types', 'methods', 'constructors']: generate_index(folder, original_paths) # Write the final core index, the main index for the rest of files layer = TLParser.find_layer(scheme_file) types = set() methods = [] for tlobject in tlobjects: if tlobject.is_function: methods.append(tlobject) types.add(tlobject.result) types = sorted(types) methods = sorted(methods, key=lambda m: m.name) request_names = ', '.join('"' + get_class_name(m) + '"' for m in methods) type_names = ', '.join('"' + get_class_name(t) + '"' for t in types) request_urls = ', '.join('"' + get_create_path_for(m) + '"' for m in methods) type_urls = ', '.join('"' + get_path_for_type(t) + '"' for t in types) replace_dict = { 'type_count': len(types), 'method_count': len(methods), 'constructor_count': len(tlobjects) - len(methods), 'layer': layer, 'request_names': request_names, 'type_names': type_names, 'request_urls': request_urls, 'type_urls': type_urls } with open('../res/core.html') as infile: with open(original_paths['index_all'], 'w') as outfile: text = infile.read() for key, value in replace_dict.items(): text = text.replace('{' + key + '}', str(value)) outfile.write(text) # Everything done print('Documentation generated.')