Fix relative paths when generating documentation

This commit is contained in:
Lonami Exo 2018-04-15 10:47:55 +02:00
parent 1f8316c0bb
commit d924f97b58

View File

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import functools
import os import os
import re import re
import shutil import shutil
@ -51,7 +52,7 @@ def get_import_code(tlobject):
.format(kind, ns, get_class_name(tlobject)) .format(kind, ns, get_class_name(tlobject))
def get_create_path_for(tlobject): def get_create_path_for(root, tlobject):
"""Gets the file path (and creates the parent directories) """Gets the file path (and creates the parent directories)
for the given 'tlobject', relative to nothing; only its local path""" for the given 'tlobject', relative to nothing; only its local path"""
@ -62,6 +63,7 @@ def get_create_path_for(tlobject):
out_dir = os.path.join(out_dir, tlobject.namespace) out_dir = os.path.join(out_dir, tlobject.namespace)
# Ensure that it exists # Ensure that it exists
out_dir = os.path.join(root, out_dir)
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
# Return the resulting filename # Return the resulting filename
@ -76,7 +78,7 @@ def is_core_type(type_):
} }
def get_path_for_type(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 getting the path for a TLObject, it might not be possible
to have the TLObject itself but rather its name (the type); to have the TLObject itself but rather its name (the type);
this method works in the same way, returning a relative path""" this method works in the same way, returning a relative path"""
@ -90,21 +92,22 @@ def get_path_for_type(type_, relative_to='.'):
else: else:
path = 'types/%s' % get_file_name(type_, True) path = 'types/%s' % get_file_name(type_, True)
return get_relative_path(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 # Destination path from the current position -> relative to the given path
def get_relative_path(destination, relative_to): def get_relative_path(destination, relative_to, folder=False):
if os.path.isfile(relative_to): 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): def get_relative_paths(original, relative_to, folder=False):
"""Converts the dictionary of 'original' paths to relative paths """Converts the dictionary of 'original' paths to relative paths
starting from the given 'relative_to' file""" starting from the given 'relative_to' file"""
return {k: get_relative_path(v, relative_to) for k, v in original.items()} return {k: get_relative_path(v, relative_to, folder)
for k, v in original.items()}
# Generate a index.html file for the given folder # Generate a index.html file for the given folder
@ -151,7 +154,7 @@ def generate_index(folder, original_paths):
files.append(item) files.append(item)
# We work with relative paths # We work with relative paths
paths = get_relative_paths(original_paths, relative_to=folder) paths = get_relative_paths(original_paths, relative_to=folder, folder=True)
# 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')
@ -242,6 +245,10 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
"""Generates the documentation HTML files from from scheme.tl to """Generates the documentation HTML files from from scheme.tl to
/methods and /constructors, etc. /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.
# TODO Tried using 'defaultdict(list)' with strange results, make it work.
original_paths = { original_paths = {
'css': 'css/docs.css', 'css': 'css/docs.css',
'arrow': 'img/arrow.svg', 'arrow': 'img/arrow.svg',
@ -255,32 +262,29 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
original_paths = {k: os.path.join(output_dir, v) original_paths = {k: os.path.join(output_dir, v)
for k, v in original_paths.items()} for k, v in original_paths.items()}
# Save 'Type: [Constructors]' for use in both: type_to_constructors = {}
# * Seeing the return type or constructors belonging to the same type. type_to_functions = {}
# * Generating the types documentation, showing available constructors.
# TODO Tried using 'defaultdict(list)' with strange results, make it work.
tltypes = {}
tlfunctions = {}
for tlobject in tlobjects: for tlobject in tlobjects:
# Select to which dictionary we want to store this type d = type_to_functions if tlobject.is_function else type_to_constructors
dictionary = tlfunctions if tlobject.is_function else tltypes if tlobject.result in d:
d[tlobject.result].append(tlobject)
if tlobject.result in dictionary:
dictionary[tlobject.result].append(tlobject)
else: else:
dictionary[tlobject.result] = [tlobject] d[tlobject.result] = [tlobject]
for tltype, constructors in tltypes.items(): for t, cs in type_to_constructors.items():
tltypes[tltype] = list(sorted(constructors, key=lambda c: c.name)) type_to_constructors[t] = list(sorted(cs, key=lambda c: c.name))
# Since the output directory is needed everywhere apply it now
create_path_for = functools.partial(get_create_path_for, output_dir)
path_for_type = functools.partial(get_path_for_type, output_dir)
# TODO Fix the fact that get_create_path_for doesn't know about out_dir
for tlobject in tlobjects: for tlobject in tlobjects:
filename = get_create_path_for(tlobject) filename = create_path_for(tlobject)
# Determine the relative paths for this file # Determine the relative paths for this file
paths = get_relative_paths(original_paths, relative_to=filename) paths = get_relative_paths(original_paths, relative_to=filename)
with DocsWriter(filename, type_to_path_function=get_path_for_type) \ with DocsWriter(filename, type_to_path_function=path_for_type) \
as docs: as docs:
docs.write_head( docs.write_head(
title=get_class_name(tlobject), title=get_class_name(tlobject),
@ -321,22 +325,22 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
inner = tlobject.result inner = tlobject.result
docs.begin_table(column_count=1) docs.begin_table(column_count=1)
docs.add_row(inner, link=get_path_for_type( docs.add_row(inner, link=path_for_type(
inner, relative_to=filename inner, relative_to=filename
)) ))
docs.end_table() docs.end_table()
constructors = tltypes.get(inner, []) cs = type_to_constructors.get(inner, [])
if not constructors: if not cs:
docs.write_text('This type has no instances available.') docs.write_text('This type has no instances available.')
elif len(constructors) == 1: elif len(cs) == 1:
docs.write_text('This type can only be an instance of:') docs.write_text('This type can only be an instance of:')
else: else:
docs.write_text('This type can be an instance of either:') docs.write_text('This type can be an instance of either:')
docs.begin_table(column_count=2) docs.begin_table(column_count=2)
for constructor in constructors: for constructor in cs:
link = get_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(get_class_name(constructor), link=link) docs.add_row(get_class_name(constructor), link=link)
docs.end_table() docs.end_table()
@ -368,7 +372,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
else: else:
docs.add_row( docs.add_row(
arg.type, align='center', link= arg.type, align='center', link=
get_path_for_type(arg.type, relative_to=filename) path_for_type(arg.type, relative_to=filename)
) )
# Add a description for this argument # Add a description for this argument
@ -389,22 +393,23 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
# Find all the available types (which are not the same as the constructors) # 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 # Each type has a list of constructors associated to it, hence is a map
for tltype, constructors in tltypes.items(): for t, cs in type_to_constructors.items():
filename = get_path_for_type(tltype) filename = path_for_type(t)
out_dir = os.path.dirname(filename) out_dir = os.path.dirname(filename)
if out_dir: if out_dir:
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
# Since we don't have access to the full TLObject, split the type # Since we don't have access to the full TLObject, split the type
if '.' in tltype: if '.' in t:
namespace, name = tltype.split('.') namespace, name = t.split('.')
else: else:
namespace, name = None, tltype namespace, name = None, t
# Determine the relative paths for this file # Determine the relative paths for this file
paths = get_relative_paths(original_paths, relative_to=out_dir) paths = get_relative_paths(original_paths, relative_to=out_dir,
folder=True)
with DocsWriter(filename, type_to_path_function=get_path_for_type) \ with DocsWriter(filename, type_to_path_function=path_for_type) \
as docs: as docs:
docs.write_head( docs.write_head(
title=get_class_name(name), title=get_class_name(name),
@ -418,25 +423,25 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
# List available constructors for this type # List available constructors for this type
docs.write_title('Available constructors', level=3) docs.write_title('Available constructors', level=3)
if not constructors: if not cs:
docs.write_text('This type has no constructors available.') docs.write_text('This type has no constructors available.')
elif len(constructors) == 1: elif len(cs) == 1:
docs.write_text('This type has one constructor available.') docs.write_text('This type has one constructor available.')
else: else:
docs.write_text('This type has %d constructors available.' % docs.write_text('This type has %d constructors available.' %
len(constructors)) len(cs))
docs.begin_table(2) docs.begin_table(2)
for constructor in constructors: for constructor in cs:
# Constructor full name # Constructor full name
link = get_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(get_class_name(constructor), link=link) docs.add_row(get_class_name(constructor), link=link)
docs.end_table() docs.end_table()
# List all the methods which return this type # List all the methods which return this type
docs.write_title('Methods returning this type', level=3) docs.write_title('Methods returning this type', level=3)
functions = tlfunctions.get(tltype, []) functions = type_to_functions.get(t, [])
if not functions: if not functions:
docs.write_text('No method returns this type.') docs.write_text('No method returns this type.')
elif len(functions) == 1: elif len(functions) == 1:
@ -449,7 +454,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 = get_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(get_class_name(func), link=link) docs.add_row(get_class_name(func), link=link)
docs.end_table() docs.end_table()
@ -458,7 +463,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
docs.write_title('Methods accepting this type as input', level=3) docs.write_title('Methods accepting this type as input', level=3)
other_methods = sorted( other_methods = sorted(
(t for t in tlobjects (t for t in tlobjects
if any(tltype == a.type for a in t.args) and t.is_function), if any(t == a.type for a in t.args) and t.is_function),
key=lambda t: t.name key=lambda t: t.name
) )
if not other_methods: if not other_methods:
@ -474,7 +479,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 = get_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(get_class_name(ot), link=link) docs.add_row(get_class_name(ot), link=link)
docs.end_table() docs.end_table()
@ -483,7 +488,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
docs.write_title('Other types containing this type', level=3) docs.write_title('Other types containing this type', level=3)
other_types = sorted( other_types = sorted(
(t for t in tlobjects (t for t in tlobjects
if any(tltype == a.type for a in t.args) if any(t == a.type for a in t.args)
and not t.is_function and not t.is_function
), key=lambda t: t.name ), key=lambda t: t.name
) )
@ -501,7 +506,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 = get_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(get_class_name(ot), link=link) docs.add_row(get_class_name(ot), link=link)
docs.end_table() docs.end_table()
@ -512,17 +517,17 @@ 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(folder, original_paths) generate_index(os.path.join(output_dir, folder), original_paths)
# 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
types = set() types = set()
methods = [] methods = []
constructors = [] cs = []
for tlobject in tlobjects: for tlobject in tlobjects:
if tlobject.is_function: if tlobject.is_function:
methods.append(tlobject) methods.append(tlobject)
else: else:
constructors.append(tlobject) cs.append(tlobject)
if not is_core_type(tlobject.result): if not is_core_type(tlobject.result):
if re.search('^vector<', tlobject.result, re.IGNORECASE): if re.search('^vector<', tlobject.result, re.IGNORECASE):
@ -532,7 +537,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
types = sorted(types) types = sorted(types)
methods = sorted(methods, key=lambda m: m.name) methods = sorted(methods, key=lambda m: m.name)
constructors = sorted(constructors, key=lambda c: c.name) cs = sorted(cs, key=lambda c: c.name)
def fmt(xs): def fmt(xs):
ys = {x: get_class_name(x) for x in xs} # cache TLObject: display ys = {x: get_class_name(x) for x in xs} # cache TLObject: display
@ -547,14 +552,14 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
request_names = fmt(methods) request_names = fmt(methods)
type_names = fmt(types) type_names = fmt(types)
constructor_names = fmt(constructors) constructor_names = fmt(cs)
def fmt(xs, formatter): def fmt(xs, formatter):
return ', '.join('"{}"'.format(formatter(x)) for x in xs) return ', '.join('"{}"'.format(formatter(x)) for x in xs)
request_urls = fmt(methods, get_create_path_for) request_urls = fmt(methods, create_path_for)
type_urls = fmt(types, get_path_for_type) type_urls = fmt(types, path_for_type)
constructor_urls = fmt(constructors, get_create_path_for) constructor_urls = fmt(cs, create_path_for)
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'),