#!/usr/bin/env python3 import functools import os import re import shutil from collections import defaultdict from pathlib import Path from ..docswriter import DocsWriter from ..parsers import TLObject, Usability from ..utils import snake_to_camel_case CORE_TYPES = { 'int', 'long', 'int128', 'int256', 'double', 'vector', 'string', 'bool', 'true', 'bytes', 'date' } def _get_file_name(tlobject): """``ClassName -> class_name.html``.""" name = tlobject.name if isinstance(tlobject, TLObject) else tlobject # Courtesy of http://stackoverflow.com/a/1176023/4759433 s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) result = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() return '{}.html'.format(result) def get_import_code(tlobject): """``TLObject -> from ... import ...``.""" kind = 'functions' if tlobject.is_function else 'types' ns = '.' + tlobject.namespace if tlobject.namespace else '' return 'from telethon.tl.{}{} import {}'\ .format(kind, ns, tlobject.class_name) def _get_path_for(root, tlobject): """Creates and returns the path for the given TLObject at root.""" out_dir = root / ('methods' if tlobject.is_function else 'constructors') if tlobject.namespace: out_dir /= tlobject.namespace return out_dir / _get_file_name(tlobject) def _get_path_for_type(type_): """Similar to `_get_path_for` but for only type names.""" if type_.lower() in CORE_TYPES: return Path('index.html#%s' % type_.lower()) elif '.' in type_: namespace, name = type_.split('.') return Path('types', namespace, _get_file_name(name)) else: return Path('types', _get_file_name(type_)) def _find_title(html_file): """Finds the
None
and can be omitted.')
otherwise = True
if arg.type in {'InputPeer', 'InputUser', 'InputChannel',
'InputNotifyPeer', 'InputDialogPeer'}:
desc.append(
'Anything entity-like will work if the library can find its '
'Input
version (e.g., usernames, Peer
, '
'User
or Channel
objects, etc.).'
)
if arg.is_vector:
if arg.is_generic:
desc.append('A list of other Requests must be supplied.')
else:
desc.append('A list must be supplied.')
elif arg.is_generic:
desc.append('A different Request must be supplied for this argument.')
else:
otherwise = False # Always reset to false if no other text is added
if otherwise:
desc.insert(1, 'Otherwise,')
desc[-1] = desc[-1][:1].lower() + desc[-1][1:]
return ' '.join(desc).replace(
'list',
'list'
)
def _copy_replace(src, dst, replacements):
"""Copies the src file into dst applying the replacements dict"""
with src.open() as infile, dst.open('w') as outfile:
outfile.write(re.sub(
'|'.join(re.escape(k) for k in replacements),
lambda m: str(replacements[m.group(0)]),
infile.read()
))
def _write_html_pages(root, tlobjects, methods, layer, input_res):
"""
Generates the documentation HTML files from from ``scheme.tl``
to ``/methods`` and ``/constructors``, etc.
"""
# 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.
paths = {k: root / v for k, v in (
('css', 'css'),
('arrow', 'img/arrow.svg'),
('search.js', 'js/search.js'),
('404', '404.html'),
('index_all', 'index.html'),
('bot_index', 'botindex.html'),
('index_types', 'types/index.html'),
('index_methods', 'methods/index.html'),
('index_constructors', 'constructors/index.html')
)}
paths['default_css'] = 'light' # docs.{}
'.format(error.name))
docs.add_row('{}.'.format(error.description))
docs.end_table()
docs.write_text('You can import these from '
'telethon.errors
.')
docs.write_title('Example', id='examples')
docs.write('''\ from telethon.sync import TelegramClient from telethon import functions, types with TelegramClient(name, api_id, api_hash) as client: result = client(''') tlobject.as_example(docs, indent=1) docs.write(')\n') if tlobject.result.startswith('Vector'): docs.write('''\ for x in result: print(x''') else: docs.write(' print(result') if tlobject.result != 'Bool' \ and not tlobject.result.startswith('Vector'): docs.write('.stringify()') docs.write(')') depth = '../' * (2 if tlobject.namespace else 1) docs.add_script(src='prependPath = "{}";'.format(depth)) docs.add_script(path=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 for t, cs in type_to_constructors.items(): filename = path_for_type(t) out_dir = filename.parent if out_dir: out_dir.mkdir(parents=True, exist_ok=True) # Since we don't have access to the full TLObject, split the type if '.' in t: namespace, name = t.split('.') else: namespace, name = None, t with DocsWriter(root, filename, path_for_type) as docs: docs.write_head(title=snake_to_camel_case(name), css_path=paths['css'], default_css=paths['default_css']) docs.set_menu_separator(paths['arrow']) _build_menu(docs) # Main file title docs.write_title(snake_to_camel_case(name)) # List available constructors for this type docs.write_title('Available constructors', level=3) if not cs: docs.write_text('This type has no constructors available.') elif len(cs) == 1: docs.write_text('This type has one constructor available.') else: docs.write_text('This type has %d constructors available.' % len(cs)) docs.begin_table(2) for constructor in cs: # Constructor full name link = create_path_for(constructor) docs.add_row(constructor.class_name, link=link) docs.end_table() # List all the methods which return this type docs.write_title('Methods returning this type', level=3) functions = type_to_functions.get(t, []) 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 = create_path_for(func) docs.add_row(func.class_name, 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( (u for u in tlobjects if any(a.type == t for a in u.args) and u.is_function), key=lambda u: u.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 = create_path_for(ot) docs.add_row(ot.class_name, 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( (u for u in tlobjects if any(a.type == t for a in u.args) and not u.is_function), key=lambda u: u.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 = create_path_for(ot) docs.add_row(ot.class_name, 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 for folder in ['types', 'methods', 'constructors']: _generate_index(root, root / folder, paths) _generate_index(root, root / 'methods', paths, True, bot_docs_paths) # Write the final core index, the main index for the rest of files types = set() methods = [] cs = [] for tlobject in tlobjects: if tlobject.is_function: methods.append(tlobject) else: cs.append(tlobject) if not tlobject.result.lower() in CORE_TYPES: 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) cs = sorted(cs, key=lambda c: c.name) shutil.copy(str(input_res / '404.html'), str(paths['404'])) _copy_replace(input_res / 'core.html', paths['index_all'], { '{type_count}': len(types), '{method_count}': len(methods), '{constructor_count}': len(tlobjects) - len(methods), '{layer}': layer, }) def fmt(xs): zs = {} # create a dict to hold those which have duplicated keys for x in xs: zs[x.class_name] = x.class_name in zs return ', '.join( '"{}.{}"'.format(x.namespace, x.class_name) if zs[x.class_name] and x.namespace else '"{}"'.format(x.class_name) for x in xs ) request_names = fmt(methods) constructor_names = fmt(cs) def fmt(xs, formatter): return ', '.join('"{}"'.format( formatter(x)).replace(os.path.sep, '/') for x in xs) type_names = fmt(types, formatter=lambda x: x) # Local URLs shouldn't rely on the output's root, so set empty root get_path_for = functools.partial(_get_path_for, Path()) request_urls = fmt(methods, get_path_for) type_urls = fmt(types, _get_path_for_type) constructor_urls = fmt(cs, get_path_for) paths['search.js'].parent.mkdir(parents=True, exist_ok=True) _copy_replace(input_res / 'js/search.js', 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 }) def _copy_resources(res_dir, out_dir): for dirname, files in [('css', ['docs.light.css', 'docs.dark.css']), ('img', ['arrow.svg'])]: dirpath = out_dir / dirname dirpath.mkdir(parents=True, exist_ok=True) for file in files: shutil.copy(str(res_dir / dirname / file), str(dirpath)) def _create_structure(tlobjects, output_dir): """ Pre-create the required directory structure in `output_dir` for the input objects. """ types_ns = set() method_ns = set() for obj in tlobjects: if obj.namespace: if obj.is_function: method_ns.add(obj.namespace) else: types_ns.add(obj.namespace) output_dir.mkdir(exist_ok=True) type_dir = output_dir / 'types' type_dir.mkdir(exist_ok=True) cons_dir = output_dir / 'constructors' cons_dir.mkdir(exist_ok=True) for ns in types_ns: (type_dir / ns).mkdir(exist_ok=True) (cons_dir / ns).mkdir(exist_ok=True) meth_dir = output_dir / 'methods' meth_dir.mkdir(exist_ok=True) for ns in types_ns: (meth_dir / ns).mkdir(exist_ok=True) def generate_docs(tlobjects, methods_info, layer, input_res, output_dir): _create_structure(tlobjects, output_dir) _write_html_pages(output_dir, tlobjects, methods_info, layer, input_res) _copy_resources(input_res, output_dir)