#!/usr/bin/env python3 import functools 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 open(src, 'r') as infile, open(dst, '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\n'
'from telethon import functions, types\n'
'\n'
'with TelegramClient(name, api_id, api_hash) as client:\n'
' result = client(')
tlobject.as_example(docs, indent=1)
docs.write(')\n')
if tlobject.result.startswith('Vector'):
docs.write(
' for x in result:\n'
' 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(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
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)) 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)