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:
"""Utility class used to write the HTML files used on the documentation"""
def __init__(self, filename, type_to_path):
"""Initializes the writer to the specified output file,
creating the parent directories when used if required.
'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
"""
Utility class used to write the HTML files used on the documentation.
"""
def __init__(self, root, filename, type_to_path):
"""
Initializes the writer to the specified output file,
creating the parent directories when used if required.
"""
self.root = root
self.filename = filename
self._parent = str(self.filename.parent)
self.handle = None
self.title = ''
# Should be set before calling adding items to the menu
self.menu_separator_tag = None
# Utility functions TODO There must be a better way
self.type_to_path = lambda t: type_to_path(
t, relative_to=self.filename
)
# Utility functions
self.type_to_path = lambda t: self._rel(type_to_path(t))
# Control signals
self.menu_began = False
@ -30,11 +30,20 @@ class DocsWriter:
self.write_copy_script = False
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
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,
with the given title and CSS
"""
#
self.title = title
self.write(
'''<!DOCTYPE html>
<html>
@ -54,17 +63,17 @@ class DocsWriter:
<body>
<div id="main_div">''',
title=title,
rel_css=str(relative_css_path).rstrip('/'),
rel_css=self._rel(css_path),
def_css=default_css
)
def set_menu_separator(self, relative_image_path):
def set_menu_separator(self, img):
"""Sets the menu separator.
Must be called before adding entries to the menu
"""
if relative_image_path:
self.menu_separator_tag = \
'<img src="{}" alt="/" />'.format(relative_image_path)
if img:
self.menu_separator_tag = '<img src="{}" alt="/" />'.format(
self._rel(img))
else:
self.menu_separator_tag = None
@ -80,7 +89,7 @@ class DocsWriter:
self.write('<li>')
if link:
self.write('<a href="{}">', link)
self.write('<a href="{}">', self._rel(link))
# Write the real menu entry text
self.write(name)
@ -210,7 +219,7 @@ class DocsWriter:
if bold:
self.write('<b>')
if link:
self.write('<a href="{}">', link)
self.write('<a href="{}">', self._rel(link))
# Finally write the real table data, the given text
self.write(text)

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3
import functools
import os
import re
import shutil
from collections import defaultdict
@ -35,6 +34,7 @@ def get_import_code(tlobject):
def _get_create_path_for(root, tlobject, make=True):
"""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')
if 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)
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."""
if type_.lower() in CORE_TYPES:
path = 'index.html#%s' % type_.lower()
return Path('index.html#%s' % type_.lower())
elif '.' in type_:
namespace, name = type_.split('.')
path = 'types/%s/%s' % (namespace, _get_file_name(name))
return Path('types', namespace, _get_file_name(name))
else:
path = 'types/%s' % _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))
return Path('types', _get_file_name(type_))
def _find_title(html_file):
"""Finds the <title> for the given HTML file, or (Unknown)."""
with open(html_file, 'r') as fp:
for line in fp:
# TODO Is it necessary to read files like this?
with html_file.open() as f:
for line in f:
if '<title>' in line:
# + 7 to skip len('<title>')
return line[line.index('<title>') + 7:line.index('</title>')]
@ -79,25 +68,27 @@ def _find_title(html_file):
return '(Unknown)'
def _build_menu(docs, filename, root, relative_main_index):
"""Builds the menu using the given DocumentWriter up to 'filename',
which must be a file (it cannot be a directory)"""
filename = _get_relative_path(filename, root)
docs.add_menu('API', relative_main_index)
def _build_menu(docs):
"""
Builds the menu used for the current ``DocumentWriter``.
"""
items = str(filename).split('/')
for i in range(len(items) - 1):
item = items[i]
link = '../' * (len(items) - (i + 2))
link += 'index.html'
docs.add_menu(item.title(), link=link)
paths = []
current = docs.filename
while current != docs.root:
current = current.parent
paths.append(current)
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()
def _generate_index(folder, original_paths, root,
def _generate_index(root, folder, paths,
bots_index=False, bots_index_paths=()):
"""Generates the index file for the specified folder"""
# Determine the namespaces listed here (as sub folders)
@ -107,39 +98,23 @@ def _generate_index(folder, original_paths, root,
INDEX = 'index.html'
BOT_INDEX = 'botindex.html'
if not bots_index:
for item in folder.iterdir():
if item.is_dir():
namespaces.append(item)
elif item not in (INDEX, BOT_INDEX):
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()}
for item in (bots_index_paths or folder.iterdir()):
if item.is_dir():
namespaces.append(item)
elif item.name not in (INDEX, BOT_INDEX):
files.append(item)
# Now that everything is setup, write the index.html file
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
docs.write_head(str(folder).title(),
relative_css_path=paths['css'],
default_css=original_paths['default_css'])
css_path=paths['css'],
default_css=paths['default_css'])
docs.set_menu_separator(paths['arrow'])
_build_menu(docs, filename, root,
relative_main_index=paths['index_all'])
docs.write_title(str(
_get_relative_path(folder, root, folder=True)).title())
_build_menu(docs)
docs.write_title(str(filename.parent.relative_to(root)).title())
if bots_index:
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 = []
if bots_index:
for item in bots_index_paths:
# TODO .name? or not
if item.parent.name == namespace:
namespace_paths.append(item.name)
_generate_index(folder / namespace,
original_paths, root,
if item.parent == namespace:
namespace_paths.append(item)
_generate_index(root, namespace, paths,
bots_index, namespace_paths)
if bots_index:
docs.add_row(namespace.title(), link=namespace / BOT_INDEX)
else:
docs.add_row(namespace.title(), link=namespace / INDEX)
docs.add_row(
namespace.stem.title(),
link=namespace / (BOT_INDEX if bots_index else INDEX))
docs.end_table()
docs.write_title('Available items')
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])
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``
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:
# * Seeing the return type or constructors belonging to the same type.
# * Generating the types documentation, showing available constructors.
original_paths = {
'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'
}
original_paths = {k: output_dir / v for k, v in original_paths.items()}
original_paths['default_css'] = 'light' # docs.<name>.css, local path
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.<name>.css, local path
type_to_constructors = defaultdict(list)
type_to_functions = defaultdict(list)
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}
# Since the output directory is needed everywhere partially 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)
create_path_for = functools.partial(_get_create_path_for, root)
path_for_type = lambda t: root / _get_path_for_type(t)
bot_docs_paths = []
for tlobject in tlobjects:
filename = create_path_for(tlobject)
paths = {k: _get_relative_path(v, filename)
for k, v in original_paths.items()}
with DocsWriter(filename, type_to_path=path_for_type) as docs:
with DocsWriter(root, filename, path_for_type) as docs:
docs.write_head(title=tlobject.class_name,
relative_css_path=paths['css'],
default_css=original_paths['default_css'])
css_path=paths['css'],
default_css=paths['default_css'])
# Create the menu (path to the current TLObject)
docs.set_menu_separator(paths['arrow'])
_build_menu(docs, filename, output_dir,
relative_main_index=paths['index_all'])
_build_menu(docs)
# Create the page title
docs.write_title(tlobject.class_name)
@ -334,9 +303,7 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
inner = tlobject.result
docs.begin_table(column_count=1)
docs.add_row(inner, link=path_for_type(
inner, relative_to=filename
))
docs.add_row(inner, link=path_for_type(inner))
docs.end_table()
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)
for constructor in cs:
link = create_path_for(constructor)
link = _get_relative_path(link, relative_to=filename)
docs.add_row(constructor.class_name, link=link)
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')
else:
docs.add_row(
friendly_type, align='center', link=
path_for_type(arg.type, relative_to=filename)
friendly_type, align='center',
link=path_for_type(arg.type)
)
# 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.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)
# Each type has a list of constructors associated to it, hence is a map
for t, cs in type_to_constructors.items():
@ -462,17 +422,13 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
else:
namespace, name = None, t
paths = {k: _get_relative_path(v, out_dir, folder=True)
for k, v in original_paths.items()}
with DocsWriter(filename, type_to_path=path_for_type) as docs:
with DocsWriter(root, filename, path_for_type) as docs:
docs.write_head(title=snake_to_camel_case(name),
relative_css_path=paths['css'],
default_css=original_paths['default_css'])
css_path=paths['css'],
default_css=paths['default_css'])
docs.set_menu_separator(paths['arrow'])
_build_menu(docs, filename, output_dir,
relative_main_index=paths['index_all'])
_build_menu(docs)
# Main file title
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:
# Constructor full name
link = create_path_for(constructor)
link = _get_relative_path(link, relative_to=filename)
docs.add_row(constructor.class_name, link=link)
docs.end_table()
@ -511,7 +466,6 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
docs.begin_table(2)
for func in functions:
link = create_path_for(func)
link = _get_relative_path(link, relative_to=filename)
docs.add_row(func.class_name, link=link)
docs.end_table()
@ -536,7 +490,6 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
docs.begin_table(2)
for ot in other_methods:
link = create_path_for(ot)
link = _get_relative_path(link, relative_to=filename)
docs.add_row(ot.class_name, link=link)
docs.end_table()
@ -562,7 +515,6 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
docs.begin_table(2)
for ot in other_types:
link = create_path_for(ot)
link = _get_relative_path(link, relative_to=filename)
docs.add_row(ot.class_name, link=link)
docs.end_table()
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
# accessible by clicking on their title
for folder in ['types', 'methods', 'constructors']:
_generate_index(output_dir / folder, original_paths,
output_dir)
_generate_index(root, root / folder, paths)
_generate_index(output_dir / 'methods', original_paths,
output_dir, True, bot_docs_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()
@ -598,9 +549,8 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
methods = sorted(methods, key=lambda m: m.name)
cs = sorted(cs, key=lambda c: c.name)
shutil.copy(str(input_res / '404.html'), str(original_paths['404']))
_copy_replace(input_res / 'core.html',
original_paths['index_all'], {
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),
@ -626,15 +576,15 @@ def _write_html_pages(tlobjects, methods, layer, input_res, output_dir):
type_names = fmt(types, formatter=lambda x: x)
# 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)
path_for_type = functools.partial(_get_path_for_type, '')
create_path_for = functools.partial(
_get_create_path_for, Path(), make=False)
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)
original_paths['search.js'].parent.mkdir(parents=True, exist_ok=True)
_copy_replace(input_res / 'js/search.js',
original_paths['search.js'], {
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,
@ -655,5 +605,5 @@ def _copy_resources(res_dir, out_dir):
def generate_docs(tlobjects, methods, layer, input_res, output_dir):
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)