Further clean-up of the documentation generator

This commit is contained in:
Lonami Exo 2018-04-15 12:15:43 +02:00
parent 5b5edff624
commit 6058b80877
2 changed files with 69 additions and 105 deletions

View File

@ -4,7 +4,7 @@ import re
class DocsWriter: class DocsWriter:
"""Utility class used to write the HTML files used on the documentation""" """Utility class used to write the HTML files used on the documentation"""
def __init__(self, filename, type_to_path_function): def __init__(self, filename, type_to_path):
"""Initializes the writer to the specified output file, """Initializes the writer to the specified output file,
creating the parent directories when used if required. creating the parent directories when used if required.
@ -19,7 +19,7 @@ class DocsWriter:
self.menu_separator_tag = None self.menu_separator_tag = None
# Utility functions TODO There must be a better way # Utility functions TODO There must be a better way
self.type_to_path = lambda t: type_to_path_function( self.type_to_path = lambda t: type_to_path(
t, relative_to=self.filename t, relative_to=self.filename
) )

View File

@ -10,96 +10,65 @@ from ..parsers import TLObject
from ..utils import snake_to_camel_case from ..utils import snake_to_camel_case
# TLObject -> filename CORE_TYPES = {
def get_file_name(tlobject, add_extension=False):
"""Gets the file name in file_name_format.html for the given TLObject.
Only its name may also be given if the full TLObject is not available"""
if isinstance(tlobject, TLObject):
name = tlobject.name
else:
name = 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()
if add_extension:
return result + '.html'
else:
return result
# TLObject -> from ... import ...
def get_import_code(tlobject):
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_create_path_for(root, tlobject):
"""Gets the file path (and creates the parent directories)
for the given 'tlobject', relative to nothing; only its local path"""
# Determine the output directory
out_dir = 'methods' if tlobject.is_function else 'constructors'
if tlobject.namespace:
out_dir = os.path.join(out_dir, tlobject.namespace)
# Ensure that it exists
out_dir = os.path.join(root, out_dir)
os.makedirs(out_dir, exist_ok=True)
# Return the resulting filename
return os.path.join(out_dir, get_file_name(tlobject, add_extension=True))
def is_core_type(type_):
"""Returns "true" if the type is considered a core type"""
return type_.lower() in {
'int', 'long', 'int128', 'int256', 'double', 'int', 'long', 'int128', 'int256', 'double',
'vector', 'string', 'bool', 'true', 'bytes', 'date' '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_create_path_for(root, tlobject):
"""Creates and returns the path for the given TLObject at root."""
out_dir = 'methods' if tlobject.is_function else 'constructors'
if tlobject.namespace:
out_dir = os.path.join(out_dir, tlobject.namespace)
out_dir = os.path.join(root, out_dir)
os.makedirs(out_dir, exist_ok=True)
return os.path.join(out_dir, _get_file_name(tlobject))
def get_path_for_type(root, type_, relative_to='.'): def get_path_for_type(root, type_, relative_to='.'):
"""Similar to getting the path for a TLObject, it might not be possible """Similar to `_get_create_path_for` but for only type names."""
to have the TLObject itself but rather its name (the type); if type_.lower() in CORE_TYPES:
this method works in the same way, returning a relative path"""
if is_core_type(type_):
path = 'index.html#%s' % type_.lower() path = 'index.html#%s' % type_.lower()
elif '.' in type_: elif '.' in type_:
# If it's not a core type, then it has to be a custom Telegram type
namespace, name = type_.split('.') namespace, name = type_.split('.')
path = 'types/%s/%s' % (namespace, get_file_name(name, True)) path = 'types/%s/%s' % (namespace, _get_file_name(name))
else: else:
path = 'types/%s' % get_file_name(type_, True) path = 'types/%s' % _get_file_name(type_)
return get_relative_path(os.path.join(root, path), relative_to) return _get_relative_path(os.path.join(root, path), relative_to)
# Destination path from the current position -> relative to the given path def _get_relative_path(destination, relative_to, folder=False):
def get_relative_path(destination, relative_to, folder=False): """Return the relative path to destination from relative_to."""
if not folder: if not folder:
relative_to = os.path.dirname(relative_to) relative_to = os.path.dirname(relative_to)
return os.path.relpath(destination, start=relative_to) return os.path.relpath(destination, start=relative_to)
def get_relative_paths(original, relative_to, folder=False): def _find_title(html_file):
"""Converts the dictionary of 'original' paths to relative paths """Finds the <title> for the given HTML file, or (Unknown)."""
starting from the given 'relative_to' file""" with open(html_file) as fp:
return {k: get_relative_path(v, relative_to, folder) for line in fp:
for k, v in original.items()}
# Generate a index.html file for the given folder
def find_title(html_file):
"""Finds the <title> for the given HTML file, or (Unknown)"""
with open(html_file) as handle:
for line in handle:
if '<title>' in line: if '<title>' in line:
# + 7 to skip len('<title>') # + 7 to skip len('<title>')
return line[line.index('<title>') + 7:line.index('</title>')] return line[line.index('<title>') + 7:line.index('</title>')]
@ -107,11 +76,11 @@ def find_title(html_file):
return '(Unknown)' return '(Unknown)'
def build_menu(docs, filename, root, relative_main_index): def _build_menu(docs, filename, root, relative_main_index):
"""Builds the menu using the given DocumentWriter up to 'filename', """Builds the menu using the given DocumentWriter up to 'filename',
which must be a file (it cannot be a directory)""" which must be a file (it cannot be a directory)"""
# TODO Maybe this could be part of DocsWriter itself, "build path menu" # TODO Maybe this could be part of DocsWriter itself, "build path menu"
filename = get_relative_path(filename, root) filename = _get_relative_path(filename, root)
docs.add_menu('API', relative_main_index) docs.add_menu('API', relative_main_index)
items = filename.split('/') items = filename.split('/')
@ -126,9 +95,8 @@ def build_menu(docs, filename, root, relative_main_index):
docs.end_menu() docs.end_menu()
def generate_index(folder, original_paths, root): def _generate_index(folder, original_paths, root):
"""Generates the index file for the specified folder""" """Generates the index file for the specified folder"""
# Determine the namespaces listed here (as sub folders) # Determine the namespaces listed here (as sub folders)
# and the files (.html files) that we should link to # and the files (.html files) that we should link to
namespaces = [] namespaces = []
@ -139,28 +107,27 @@ def generate_index(folder, original_paths, root):
elif item != 'index.html': elif item != 'index.html':
files.append(item) files.append(item)
# We work with relative paths paths = {k: _get_relative_path(v, folder, folder=True)
paths = get_relative_paths(original_paths, relative_to=folder, folder=True) for k, v in original_paths.items()}
# Now that everything is setup, write the index.html file # Now that everything is setup, write the index.html file
filename = os.path.join(folder, 'index.html') filename = os.path.join(folder, 'index.html')
with DocsWriter(filename, type_to_path_function=get_path_for_type) as docs: with DocsWriter(filename, type_to_path=get_path_for_type) as docs:
# Title should be the current folder name # Title should be the current folder name
docs.write_head(folder.title(), relative_css_path=paths['css']) docs.write_head(folder.title(), relative_css_path=paths['css'])
docs.set_menu_separator(paths['arrow']) docs.set_menu_separator(paths['arrow'])
build_menu(docs, filename, root, _build_menu(docs, filename, root,
relative_main_index=paths['index_all']) relative_main_index=paths['index_all'])
docs.write_title(get_relative_path(folder, root, folder=True).title()) docs.write_title(_get_relative_path(folder, root, folder=True).title())
if namespaces: if namespaces:
docs.write_title('Namespaces', level=3) docs.write_title('Namespaces', level=3)
docs.begin_table(4) docs.begin_table(4)
namespaces.sort() namespaces.sort()
for namespace in namespaces: for namespace in namespaces:
# For every namespace, also write the index of it # For every namespace, also write the index of it
generate_index(os.path.join(folder, namespace), _generate_index(os.path.join(folder, namespace),
original_paths, root) original_paths, root)
docs.add_row(namespace.title(), docs.add_row(namespace.title(),
link=os.path.join(namespace, 'index.html')) link=os.path.join(namespace, 'index.html'))
@ -170,7 +137,7 @@ def generate_index(folder, original_paths, root):
docs.write_title('Available items') docs.write_title('Available items')
docs.begin_table(2) docs.begin_table(2)
files = [(f, find_title(os.path.join(folder, f))) for f in files] files = [(f, _find_title(os.path.join(folder, f))) for f in files]
files.sort(key=lambda t: t[1]) files.sort(key=lambda t: t[1])
for file, title in files: for file, title in files:
@ -180,8 +147,8 @@ def generate_index(folder, original_paths, root):
docs.end_body() docs.end_body()
def get_description(arg): def _get_description(arg):
"""Generates a proper description for the given argument""" """Generates a proper description for the given argument."""
desc = [] desc = []
otherwise = False otherwise = False
if arg.can_be_inferred: if arg.can_be_inferred:
@ -219,7 +186,7 @@ def get_description(arg):
) )
def copy_replace(src, dst, replacements): def _copy_replace(src, dst, replacements):
"""Copies the src file into dst applying the replacements dict""" """Copies the src file into dst applying the replacements dict"""
with open(src) as infile, open(dst, 'w') as outfile: with open(src) as infile, open(dst, 'w') as outfile:
outfile.write(re.sub( outfile.write(re.sub(
@ -230,8 +197,9 @@ def copy_replace(src, dst, replacements):
def _write_html_pages(tlobjects, errors, layer, input_res, output_dir): def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
"""Generates the documentation HTML files from from scheme.tl to """
/methods and /constructors, etc. Generates the documentation HTML files from from ``scheme.tl``
to ``/methods`` and ``/constructors``, etc.
""" """
# Save 'Type: [Constructors]' for use in both: # Save 'Type: [Constructors]' for use in both:
# * Seeing the return type or constructors belonging to the same type. # * Seeing the return type or constructors belonging to the same type.
@ -267,24 +235,22 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
for method in error.caused_by: for method in error.caused_by:
method_causes_errors[method].append(error) method_causes_errors[method].append(error)
# Since the output directory is needed everywhere apply it now # Since the output directory is needed everywhere partially apply it now
create_path_for = functools.partial(get_create_path_for, output_dir) create_path_for = functools.partial(_get_create_path_for, output_dir)
path_for_type = functools.partial(get_path_for_type, output_dir) path_for_type = functools.partial(get_path_for_type, output_dir)
for tlobject in tlobjects: for tlobject in tlobjects:
filename = create_path_for(tlobject) filename = create_path_for(tlobject)
paths = {k: _get_relative_path(v, filename)
for k, v in original_paths.items()}
# Determine the relative paths for this file with DocsWriter(filename, type_to_path=path_for_type) as docs:
paths = get_relative_paths(original_paths, relative_to=filename)
with DocsWriter(filename, type_to_path_function=path_for_type) \
as docs:
docs.write_head(title=tlobject.class_name, docs.write_head(title=tlobject.class_name,
relative_css_path=paths['css']) relative_css_path=paths['css'])
# Create the menu (path to the current TLObject) # Create the menu (path to the current TLObject)
docs.set_menu_separator(paths['arrow']) docs.set_menu_separator(paths['arrow'])
build_menu(docs, filename, output_dir, _build_menu(docs, filename, output_dir,
relative_main_index=paths['index_all']) relative_main_index=paths['index_all'])
# Create the page title # Create the page title
@ -334,7 +300,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
docs.begin_table(column_count=2) docs.begin_table(column_count=2)
for constructor in cs: for constructor in cs:
link = create_path_for(constructor) link = create_path_for(constructor)
link = get_relative_path(link, relative_to=filename) link = _get_relative_path(link, relative_to=filename)
docs.add_row(constructor.class_name, link=link) docs.add_row(constructor.class_name, link=link)
docs.end_table() docs.end_table()
@ -369,7 +335,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
) )
# Add a description for this argument # Add a description for this argument
docs.add_row(get_description(arg)) docs.add_row(_get_description(arg))
docs.end_table() docs.end_table()
else: else:
@ -417,18 +383,16 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
else: else:
namespace, name = None, t namespace, name = None, t
# Determine the relative paths for this file paths = {k: _get_relative_path(v, out_dir, folder=True)
paths = get_relative_paths(original_paths, relative_to=out_dir, for k, v in original_paths.items()}
folder=True)
with DocsWriter(filename, type_to_path_function=path_for_type) \ with DocsWriter(filename, type_to_path=path_for_type) as docs:
as docs:
docs.write_head( docs.write_head(
title=snake_to_camel_case(name), title=snake_to_camel_case(name),
relative_css_path=paths['css']) relative_css_path=paths['css'])
docs.set_menu_separator(paths['arrow']) docs.set_menu_separator(paths['arrow'])
build_menu(docs, filename, output_dir, _build_menu(docs, filename, output_dir,
relative_main_index=paths['index_all']) relative_main_index=paths['index_all'])
# Main file title # Main file title
@ -448,7 +412,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
for constructor in cs: for constructor in cs:
# Constructor full name # Constructor full name
link = create_path_for(constructor) link = create_path_for(constructor)
link = get_relative_path(link, relative_to=filename) link = _get_relative_path(link, relative_to=filename)
docs.add_row(constructor.class_name, link=link) docs.add_row(constructor.class_name, link=link)
docs.end_table() docs.end_table()
@ -468,7 +432,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
docs.begin_table(2) docs.begin_table(2)
for func in functions: for func in functions:
link = create_path_for(func) link = create_path_for(func)
link = get_relative_path(link, relative_to=filename) link = _get_relative_path(link, relative_to=filename)
docs.add_row(func.class_name, link=link) docs.add_row(func.class_name, link=link)
docs.end_table() docs.end_table()
@ -493,7 +457,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
docs.begin_table(2) docs.begin_table(2)
for ot in other_methods: for ot in other_methods:
link = create_path_for(ot) link = create_path_for(ot)
link = get_relative_path(link, relative_to=filename) link = _get_relative_path(link, relative_to=filename)
docs.add_row(ot.class_name, link=link) docs.add_row(ot.class_name, link=link)
docs.end_table() docs.end_table()
@ -520,7 +484,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
docs.begin_table(2) docs.begin_table(2)
for ot in other_types: for ot in other_types:
link = create_path_for(ot) link = create_path_for(ot)
link = get_relative_path(link, relative_to=filename) link = _get_relative_path(link, relative_to=filename)
docs.add_row(ot.class_name, link=link) docs.add_row(ot.class_name, link=link)
docs.end_table() docs.end_table()
docs.end_body() docs.end_body()
@ -530,7 +494,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
# information that we have available, simply a file listing all the others # information that we have available, simply a file listing all the others
# accessible by clicking on their title # accessible by clicking on their title
for folder in ['types', 'methods', 'constructors']: for folder in ['types', 'methods', 'constructors']:
generate_index(os.path.join(output_dir, folder), original_paths, _generate_index(os.path.join(output_dir, folder), original_paths,
output_dir) output_dir)
# Write the final core index, the main index for the rest of files # Write the final core index, the main index for the rest of files
@ -543,7 +507,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
else: else:
cs.append(tlobject) cs.append(tlobject)
if not is_core_type(tlobject.result): if not tlobject.result.lower() in CORE_TYPES:
if re.search('^vector<', tlobject.result, re.IGNORECASE): if re.search('^vector<', tlobject.result, re.IGNORECASE):
types.add(tlobject.result.split('<')[1].strip('>')) types.add(tlobject.result.split('<')[1].strip('>'))
else: else:
@ -554,7 +518,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
cs = sorted(cs, key=lambda c: c.name) cs = sorted(cs, key=lambda c: c.name)
shutil.copy(os.path.join(input_res, '404.html'), original_paths['404']) shutil.copy(os.path.join(input_res, '404.html'), original_paths['404'])
copy_replace(os.path.join(input_res, 'core.html'), _copy_replace(os.path.join(input_res, 'core.html'),
original_paths['index_all'], { original_paths['index_all'], {
'{type_count}': len(types), '{type_count}': len(types),
'{method_count}': len(methods), '{method_count}': len(methods),
@ -586,7 +550,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
os.makedirs(os.path.abspath(os.path.join( os.makedirs(os.path.abspath(os.path.join(
original_paths['search.js'], os.path.pardir original_paths['search.js'], os.path.pardir
)), exist_ok=True) )), exist_ok=True)
copy_replace(os.path.join(input_res, 'js', 'search.js'), _copy_replace(os.path.join(input_res, 'js', 'search.js'),
original_paths['search.js'], { original_paths['search.js'], {
'{request_names}': request_names, '{request_names}': request_names,
'{type_names}': type_names, '{type_names}': type_names,