Make complete use of pathlib (fix up 8224e5a)

This commit is contained in:
Lonami Exo 2018-12-21 18:18:18 +01:00
parent 8224e5aabf
commit aefa429236
2 changed files with 110 additions and 151 deletions

View File

@ -3,25 +3,25 @@ import re
class DocsWriter: class DocsWriter:
"""Utility class used to write the HTML files used on the documentation""" """
def __init__(self, filename, type_to_path): Utility class used to write the HTML files used on the documentation.
"""Initializes the writer to the specified output file, """
creating the parent directories when used if required. def __init__(self, root, filename, type_to_path):
'type_to_path_function' should be a function which, given a type
name and a named argument relative_to, returns the file path for
the specified type, relative to the given filename
""" """
Initializes the writer to the specified output file,
creating the parent directories when used if required.
"""
self.root = root
self.filename = filename self.filename = filename
self._parent = str(self.filename.parent)
self.handle = None self.handle = None
self.title = ''
# Should be set before calling adding items to the menu # Should be set before calling adding items to the menu
self.menu_separator_tag = None self.menu_separator_tag = None
# Utility functions TODO There must be a better way # Utility functions
self.type_to_path = lambda t: type_to_path( self.type_to_path = lambda t: self._rel(type_to_path(t))
t, relative_to=self.filename
)
# Control signals # Control signals
self.menu_began = False self.menu_began = False
@ -30,11 +30,20 @@ class DocsWriter:
self.write_copy_script = False self.write_copy_script = False
self._script = '' self._script = ''
def _rel(self, path):
"""
Get the relative path for the given path from the current
file by working around https://bugs.python.org/issue20012.
"""
return os.path.relpath(str(path), self._parent)
# High level writing # High level writing
def write_head(self, title, relative_css_path, default_css): def write_head(self, title, css_path, default_css):
"""Writes the head part for the generated document, """Writes the head part for the generated document,
with the given title and CSS with the given title and CSS
""" """
#
self.title = title
self.write( self.write(
'''<!DOCTYPE html> '''<!DOCTYPE html>
<html> <html>
@ -54,17 +63,17 @@ class DocsWriter:
<body> <body>
<div id="main_div">''', <div id="main_div">''',
title=title, title=title,
rel_css=str(relative_css_path).rstrip('/'), rel_css=self._rel(css_path),
def_css=default_css def_css=default_css
) )
def set_menu_separator(self, relative_image_path): def set_menu_separator(self, img):
"""Sets the menu separator. """Sets the menu separator.
Must be called before adding entries to the menu Must be called before adding entries to the menu
""" """
if relative_image_path: if img:
self.menu_separator_tag = \ self.menu_separator_tag = '<img src="{}" alt="/" />'.format(
'<img src="{}" alt="/" />'.format(relative_image_path) self._rel(img))
else: else:
self.menu_separator_tag = None self.menu_separator_tag = None
@ -80,7 +89,7 @@ class DocsWriter:
self.write('<li>') self.write('<li>')
if link: if link:
self.write('<a href="{}">', link) self.write('<a href="{}">', self._rel(link))
# Write the real menu entry text # Write the real menu entry text
self.write(name) self.write(name)
@ -210,7 +219,7 @@ class DocsWriter:
if bold: if bold:
self.write('<b>') self.write('<b>')
if link: if link:
self.write('<a href="{}">', link) self.write('<a href="{}">', self._rel(link))
# Finally write the real table data, the given text # Finally write the real table data, the given text
self.write(text) self.write(text)

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import functools import functools
import os
import re import re
import shutil import shutil
from collections import defaultdict from collections import defaultdict
@ -35,6 +34,7 @@ def get_import_code(tlobject):
def _get_create_path_for(root, tlobject, make=True): def _get_create_path_for(root, tlobject, make=True):
"""Creates and returns the path for the given TLObject at root.""" """Creates and returns the path for the given TLObject at root."""
# TODO Can we pre-create all required directories?
out_dir = root / ('methods' if tlobject.is_function else 'constructors') out_dir = root / ('methods' if tlobject.is_function else 'constructors')
if tlobject.namespace: if tlobject.namespace:
out_dir /= tlobject.namespace out_dir /= tlobject.namespace
@ -45,33 +45,22 @@ def _get_create_path_for(root, tlobject, make=True):
return out_dir / _get_file_name(tlobject) return out_dir / _get_file_name(tlobject)
def _get_path_for_type(root, type_, relative_to='.'): def _get_path_for_type(type_):
"""Similar to `_get_create_path_for` but for only type names.""" """Similar to `_get_create_path_for` but for only type names."""
if type_.lower() in CORE_TYPES: if type_.lower() in CORE_TYPES:
path = 'index.html#%s' % type_.lower() return Path('index.html#%s' % type_.lower())
elif '.' in type_: elif '.' in type_:
namespace, name = type_.split('.') namespace, name = type_.split('.')
path = 'types/%s/%s' % (namespace, _get_file_name(name)) return Path('types', namespace, _get_file_name(name))
else: else:
path = 'types/%s' % _get_file_name(type_) return Path('types', _get_file_name(type_))
return _get_relative_path(root / path, relative_to)
def _get_relative_path(destination, relative_to, folder=False):
"""Return the relative path to destination from relative_to."""
relative_to = Path(relative_to)
if not folder:
relative_to = relative_to.parent
# TODO Use pathlib here
return Path(os.path.relpath(destination, start=relative_to))
def _find_title(html_file): def _find_title(html_file):
"""Finds the <title> for the given HTML file, or (Unknown).""" """Finds the <title> for the given HTML file, or (Unknown)."""
with open(html_file, 'r') as fp: # TODO Is it necessary to read files like this?
for line in fp: with html_file.open() as f:
for line in f:
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>')]
@ -79,25 +68,27 @@ def _find_title(html_file):
return '(Unknown)' return '(Unknown)'
def _build_menu(docs, filename, root, relative_main_index): def _build_menu(docs):
"""Builds the menu using the given DocumentWriter up to 'filename', """
which must be a file (it cannot be a directory)""" Builds the menu used for the current ``DocumentWriter``.
filename = _get_relative_path(filename, root) """
docs.add_menu('API', relative_main_index)
items = str(filename).split('/') paths = []
for i in range(len(items) - 1): current = docs.filename
item = items[i] while current != docs.root:
link = '../' * (len(items) - (i + 2)) current = current.parent
link += 'index.html' paths.append(current)
docs.add_menu(item.title(), link=link)
for path in reversed(paths):
docs.add_menu(path.stem.title(), link=path / 'index.html')
if docs.filename.stem != 'index':
docs.add_menu(docs.title, link=docs.filename)
if items[-1] != 'index.html':
docs.add_menu(os.path.splitext(items[-1])[0])
docs.end_menu() docs.end_menu()
def _generate_index(folder, original_paths, root, def _generate_index(root, folder, paths,
bots_index=False, bots_index_paths=()): bots_index=False, bots_index_paths=()):
"""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)
@ -107,39 +98,23 @@ def _generate_index(folder, original_paths, root,
INDEX = 'index.html' INDEX = 'index.html'
BOT_INDEX = 'botindex.html' BOT_INDEX = 'botindex.html'
if not bots_index: for item in (bots_index_paths or folder.iterdir()):
for item in folder.iterdir(): if item.is_dir():
if item.is_dir(): namespaces.append(item)
namespaces.append(item) elif item.name not in (INDEX, BOT_INDEX):
elif item not in (INDEX, BOT_INDEX): files.append(item)
files.append(item)
else:
# bots_index_paths should be a list of "namespace/method.html"
# or "method.html"
for item in bots_index_paths:
dirname = item.parent
if dirname and dirname not in namespaces:
namespaces.append(dirname)
elif not dirname and item not in (INDEX, BOT_INDEX):
files.append(item)
paths = {k: _get_relative_path(v, 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 = folder / (BOT_INDEX if bots_index else INDEX) filename = folder / (BOT_INDEX if bots_index else INDEX)
with DocsWriter(filename, type_to_path=_get_path_for_type) as docs: with DocsWriter(root, filename, _get_path_for_type) as docs:
# Title should be the current folder name # Title should be the current folder name
docs.write_head(str(folder).title(), docs.write_head(str(folder).title(),
relative_css_path=paths['css'], css_path=paths['css'],
default_css=original_paths['default_css']) default_css=paths['default_css'])
docs.set_menu_separator(paths['arrow']) docs.set_menu_separator(paths['arrow'])
_build_menu(docs, filename, root, _build_menu(docs)
relative_main_index=paths['index_all']) docs.write_title(str(filename.parent.relative_to(root)).title())
docs.write_title(str(
_get_relative_path(folder, root, folder=True)).title())
if bots_index: if bots_index:
docs.write_text('These are the methods that you may be able to ' docs.write_text('These are the methods that you may be able to '
@ -157,23 +132,22 @@ def _generate_index(folder, original_paths, root,
namespace_paths = [] namespace_paths = []
if bots_index: if bots_index:
for item in bots_index_paths: for item in bots_index_paths:
# TODO .name? or not if item.parent == namespace:
if item.parent.name == namespace: namespace_paths.append(item)
namespace_paths.append(item.name)
_generate_index(folder / namespace, _generate_index(root, namespace, paths,
original_paths, root,
bots_index, namespace_paths) bots_index, namespace_paths)
if bots_index:
docs.add_row(namespace.title(), link=namespace / BOT_INDEX) docs.add_row(
else: namespace.stem.title(),
docs.add_row(namespace.title(), link=namespace / INDEX) link=namespace / (BOT_INDEX if bots_index else INDEX))
docs.end_table() docs.end_table()
docs.write_title('Available items') docs.write_title('Available items')
docs.begin_table(2) docs.begin_table(2)
files = [(f, _find_title(folder / f)) for f in files] files = [(f, _find_title(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:
@ -234,7 +208,7 @@ def _copy_replace(src, dst, replacements):
)) ))
def _write_html_pages(tlobjects, methods, layer, input_res, output_dir): def _write_html_pages(root, tlobjects, methods, layer, input_res):
""" """
Generates the documentation HTML files from from ``scheme.tl`` Generates the documentation HTML files from from ``scheme.tl``
to ``/methods`` and ``/constructors``, etc. to ``/methods`` and ``/constructors``, etc.
@ -242,19 +216,18 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
# 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.
# * Generating the types documentation, showing available constructors. # * Generating the types documentation, showing available constructors.
original_paths = { paths = {k: root / v for k, v in (
'css': 'css', ('css', 'css'),
'arrow': 'img/arrow.svg', ('arrow', 'img/arrow.svg'),
'search.js': 'js/search.js', ('search.js', 'js/search.js'),
'404': '404.html', ('404', '404.html'),
'index_all': 'index.html', ('index_all', 'index.html'),
'bot_index': 'botindex.html', ('bot_index', 'botindex.html'),
'index_types': 'types/index.html', ('index_types', 'types/index.html'),
'index_methods': 'methods/index.html', ('index_methods', 'methods/index.html'),
'index_constructors': 'constructors/index.html' ('index_constructors', 'constructors/index.html')
} )}
original_paths = {k: output_dir / v for k, v in original_paths.items()} paths['default_css'] = 'light' # docs.<name>.css, local path
original_paths['default_css'] = 'light' # docs.<name>.css, local path
type_to_constructors = defaultdict(list) type_to_constructors = defaultdict(list)
type_to_functions = defaultdict(list) type_to_functions = defaultdict(list)
for tlobject in tlobjects: for tlobject in tlobjects:
@ -267,24 +240,20 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
methods = {m.name: m for m in methods} methods = {m.name: m for m in methods}
# Since the output directory is needed everywhere partially 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, root)
path_for_type = functools.partial(_get_path_for_type, output_dir) path_for_type = lambda t: root / _get_path_for_type(t)
bot_docs_paths = [] bot_docs_paths = []
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) with DocsWriter(root, filename, path_for_type) as docs:
for k, v in original_paths.items()}
with DocsWriter(filename, type_to_path=path_for_type) as docs:
docs.write_head(title=tlobject.class_name, docs.write_head(title=tlobject.class_name,
relative_css_path=paths['css'], css_path=paths['css'],
default_css=original_paths['default_css']) default_css=paths['default_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)
relative_main_index=paths['index_all'])
# Create the page title # Create the page title
docs.write_title(tlobject.class_name) docs.write_title(tlobject.class_name)
@ -334,9 +303,7 @@ def _write_html_pages(tlobjects, methods, 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=path_for_type( docs.add_row(inner, link=path_for_type(inner))
inner, relative_to=filename
))
docs.end_table() docs.end_table()
cs = type_to_constructors.get(inner, []) cs = type_to_constructors.get(inner, [])
@ -350,7 +317,6 @@ def _write_html_pages(tlobjects, methods, 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)
docs.add_row(constructor.class_name, link=link) docs.add_row(constructor.class_name, link=link)
docs.end_table() docs.end_table()
@ -381,8 +347,8 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
docs.add_row('!' + friendly_type, align='center') docs.add_row('!' + friendly_type, align='center')
else: else:
docs.add_row( docs.add_row(
friendly_type, align='center', link= friendly_type, align='center',
path_for_type(arg.type, relative_to=filename) link=path_for_type(arg.type)
) )
# Add a description for this argument # Add a description for this argument
@ -442,12 +408,6 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
docs.add_script(relative_src=paths['search.js']) docs.add_script(relative_src=paths['search.js'])
docs.end_body() docs.end_body()
temp = []
for item in bot_docs_paths:
# TODO What?
temp.append(os.path.sep.join(str(item).split(os.path.sep)[2:]))
bot_docs_paths = temp
# 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 t, cs in type_to_constructors.items(): for t, cs in type_to_constructors.items():
@ -462,17 +422,13 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
else: else:
namespace, name = None, t namespace, name = None, t
paths = {k: _get_relative_path(v, out_dir, folder=True) with DocsWriter(root, filename, path_for_type) as docs:
for k, v in original_paths.items()}
with DocsWriter(filename, type_to_path=path_for_type) as docs:
docs.write_head(title=snake_to_camel_case(name), docs.write_head(title=snake_to_camel_case(name),
relative_css_path=paths['css'], css_path=paths['css'],
default_css=original_paths['default_css']) default_css=paths['default_css'])
docs.set_menu_separator(paths['arrow']) docs.set_menu_separator(paths['arrow'])
_build_menu(docs, filename, output_dir, _build_menu(docs)
relative_main_index=paths['index_all'])
# Main file title # Main file title
docs.write_title(snake_to_camel_case(name)) docs.write_title(snake_to_camel_case(name))
@ -491,7 +447,6 @@ def _write_html_pages(tlobjects, methods, 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)
docs.add_row(constructor.class_name, link=link) docs.add_row(constructor.class_name, link=link)
docs.end_table() docs.end_table()
@ -511,7 +466,6 @@ def _write_html_pages(tlobjects, methods, 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)
docs.add_row(func.class_name, link=link) docs.add_row(func.class_name, link=link)
docs.end_table() docs.end_table()
@ -536,7 +490,6 @@ def _write_html_pages(tlobjects, methods, 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)
docs.add_row(ot.class_name, link=link) docs.add_row(ot.class_name, link=link)
docs.end_table() docs.end_table()
@ -562,7 +515,6 @@ def _write_html_pages(tlobjects, methods, 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)
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()
@ -572,11 +524,10 @@ def _write_html_pages(tlobjects, methods, 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(output_dir / folder, original_paths, _generate_index(root, root / folder, paths)
output_dir)
_generate_index(output_dir / 'methods', original_paths, _generate_index(root, root / 'methods', paths, True,
output_dir, True, bot_docs_paths) bot_docs_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()
@ -598,9 +549,8 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
methods = sorted(methods, key=lambda m: m.name) methods = sorted(methods, key=lambda m: m.name)
cs = sorted(cs, key=lambda c: c.name) cs = sorted(cs, key=lambda c: c.name)
shutil.copy(str(input_res / '404.html'), str(original_paths['404'])) shutil.copy(str(input_res / '404.html'), str(paths['404']))
_copy_replace(input_res / 'core.html', _copy_replace(input_res / 'core.html', paths['index_all'], {
original_paths['index_all'], {
'{type_count}': len(types), '{type_count}': len(types),
'{method_count}': len(methods), '{method_count}': len(methods),
'{constructor_count}': len(tlobjects) - len(methods), '{constructor_count}': len(tlobjects) - len(methods),
@ -626,15 +576,15 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
type_names = fmt(types, formatter=lambda x: x) type_names = fmt(types, formatter=lambda x: x)
# Local URLs shouldn't rely on the output's root, so set empty root # Local URLs shouldn't rely on the output's root, so set empty root
create_path_for = functools.partial(_get_create_path_for, '', make=False) create_path_for = functools.partial(
path_for_type = functools.partial(_get_path_for_type, '') _get_create_path_for, Path(), make=False)
request_urls = fmt(methods, create_path_for) request_urls = fmt(methods, create_path_for)
type_urls = fmt(types, path_for_type) type_urls = fmt(types, _get_path_for_type)
constructor_urls = fmt(cs, create_path_for) constructor_urls = fmt(cs, create_path_for)
original_paths['search.js'].parent.mkdir(parents=True, exist_ok=True) paths['search.js'].parent.mkdir(parents=True, exist_ok=True)
_copy_replace(input_res / 'js/search.js', _copy_replace(input_res / 'js/search.js', paths['search.js'], {
original_paths['search.js'], {
'{request_names}': request_names, '{request_names}': request_names,
'{type_names}': type_names, '{type_names}': type_names,
'{constructor_names}': constructor_names, '{constructor_names}': constructor_names,
@ -655,5 +605,5 @@ def _copy_resources(res_dir, out_dir):
def generate_docs(tlobjects, methods, layer, input_res, output_dir): def generate_docs(tlobjects, methods, layer, input_res, output_dir):
output_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)
_write_html_pages(tlobjects, methods, layer, input_res, output_dir) _write_html_pages(output_dir, tlobjects, methods, layer, input_res)
_copy_resources(input_res, output_dir) _copy_resources(input_res, output_dir)