Telethon/telethon_generator/docs_writer.py

285 lines
9.3 KiB
Python
Raw Normal View History

import os
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.
2017-09-04 18:10:04 +03:00
'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
"""
self.filename = filename
self.handle = None
# 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(
2017-09-04 18:10:04 +03:00
t, relative_to=self.filename
)
# Control signals
self.menu_began = False
self.table_columns = 0
self.table_columns_left = None
self.write_copy_script = False
self._script = ''
# High level writing
2018-05-23 19:50:28 +03:00
def write_head(self, title, relative_css_path, default_css):
2017-09-04 18:10:04 +03:00
"""Writes the head part for the generated document,
with the given title and CSS
"""
2018-05-24 12:48:15 +03:00
self.write(
'''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
2018-05-24 12:48:15 +03:00
<title>{title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
2018-05-24 12:48:15 +03:00
<link id="style" href="{rel_css}/docs.{def_css}.css" rel="stylesheet">
2018-05-23 19:50:28 +03:00
<script>
2018-05-24 12:48:15 +03:00
document.getElementById("style").href = "{rel_css}/docs."
+ (document.cookie.split(";")[0].split("=")[1] || "{def_css}")
+ ".css";
2018-05-23 19:50:28 +03:00
</script>
2018-05-24 12:48:15 +03:00
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro"
rel="stylesheet">
</head>
<body>
2018-05-24 12:48:15 +03:00
<div id="main_div">''',
title=title,
rel_css=relative_css_path.rstrip('/'),
def_css=default_css
)
def set_menu_separator(self, relative_image_path):
2017-09-04 18:10:04 +03:00
"""Sets the menu separator.
Must be called before adding entries to the menu
"""
if relative_image_path:
2017-09-04 18:10:04 +03:00
self.menu_separator_tag = \
'<img src="{}" alt="/" />'.format(relative_image_path)
else:
self.menu_separator_tag = None
def add_menu(self, name, link=None):
"""Adds a menu entry, will create it if it doesn't exist yet"""
if self.menu_began:
if self.menu_separator_tag:
self.write(self.menu_separator_tag)
else:
# First time, create the menu tag
self.write('<ul class="horizontal">')
self.menu_began = True
self.write('<li>')
if link:
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">', link)
# Write the real menu entry text
self.write(name)
if link:
self.write('</a>')
self.write('</li>')
def end_menu(self):
"""Ends an opened menu"""
if not self.menu_began:
raise RuntimeError('No menu had been started in the first place.')
self.write('</ul>')
def write_title(self, title, level=1):
2017-09-04 18:10:04 +03:00
"""Writes a title header in the document body,
with an optional depth level
"""
2018-05-24 12:48:15 +03:00
self.write('<h{level}>{title}</h{level}>', title=title, level=level)
def write_code(self, tlobject):
2017-09-04 18:10:04 +03:00
"""Writes the code for the given 'tlobject' properly
formatted with hyperlinks
"""
2018-05-24 12:48:15 +03:00
self.write('<pre>---{}---\n',
'functions' if tlobject.is_function else 'types')
# Write the function or type and its ID
if tlobject.namespace:
self.write(tlobject.namespace)
self.write('.')
2018-05-24 12:48:15 +03:00
self.write('{}#{:08x}', tlobject.name, tlobject.id)
# Write all the arguments (or do nothing if there's none)
for arg in tlobject.args:
self.write(' ')
add_link = not arg.generic_definition and not arg.is_generic
# "Opening" modifiers
if arg.generic_definition:
self.write('{')
# Argument name
self.write(arg.name)
self.write(':')
# "Opening" modifiers
if arg.is_flag:
2018-05-24 12:48:15 +03:00
self.write('flags.{}?', arg.flag_index)
if arg.is_generic:
self.write('!')
if arg.is_vector:
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">Vector</a>&lt;',
self.type_to_path('vector'))
# Argument type
if arg.type:
if add_link:
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">', self.type_to_path(arg.type))
self.write(arg.type)
if add_link:
self.write('</a>')
else:
self.write('#')
# "Closing" modifiers
if arg.is_vector:
self.write('&gt;')
if arg.generic_definition:
self.write('}')
2017-09-04 18:10:04 +03:00
# Now write the resulting type (result from a function/type)
self.write(' = ')
generic_name = next((arg.name for arg in tlobject.args
if arg.generic_definition), None)
if tlobject.result == generic_name:
# Generic results cannot have any link
self.write(tlobject.result)
else:
if re.search('^vector<', tlobject.result, re.IGNORECASE):
# Notice that we don't simply make up the "Vector" part,
# because some requests (as of now, only FutureSalts),
# use a lower type name for it (see #81)
vector, inner = tlobject.result.split('<')
inner = inner.strip('>')
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">{}</a>&lt;',
self.type_to_path(vector), vector)
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">{}</a>&gt;',
self.type_to_path(inner), inner)
else:
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">{}</a>',
self.type_to_path(tlobject.result), tlobject.result)
self.write('</pre>')
def begin_table(self, column_count):
"""Begins a table with the given 'column_count', required to automatically
create the right amount of columns when adding items to the rows"""
self.table_columns = column_count
self.table_columns_left = 0
self.write('<table>')
def add_row(self, text, link=None, bold=False, align=None):
"""This will create a new row, or add text to the next column
of the previously created, incomplete row, closing it if complete"""
if not self.table_columns_left:
# Starting a new row
self.write('<tr>')
self.table_columns_left = self.table_columns
self.write('<td')
if align:
2018-05-24 12:48:15 +03:00
self.write(' style="text-align:{}"', align)
self.write('>')
if bold:
self.write('<b>')
if link:
2018-05-24 12:48:15 +03:00
self.write('<a href="{}">', link)
# Finally write the real table data, the given text
self.write(text)
if link:
self.write('</a>')
if bold:
self.write('</b>')
self.write('</td>')
self.table_columns_left -= 1
if not self.table_columns_left:
self.write('</tr>')
def end_table(self):
# If there was any column left, finish it before closing the table
if self.table_columns_left:
self.write('</tr>')
self.write('</table>')
def write_text(self, text):
"""Writes a paragraph of text"""
2018-05-24 12:48:15 +03:00
self.write('<p>{}</p>', text)
def write_copy_button(self, text, text_to_copy):
"""Writes a button with 'text' which can be used
to copy 'text_to_copy' to clipboard when it's clicked."""
self.write_copy_script = True
self.write('<button onclick="cp(\'{}\');">{}</button>'
.format(text_to_copy, text))
def add_script(self, src='', relative_src=None):
if relative_src:
self._script += '<script src="{}"></script>'.format(relative_src)
elif src:
self._script += '<script>{}</script>'.format(src)
def end_body(self):
"""Ends the whole document. This should be called the last"""
if self.write_copy_script:
self.write(
'<textarea id="c" class="invisible"></textarea>'
'<script>'
'function cp(t){'
'var c=document.getElementById("c");'
'c.value=t;'
'c.select();'
'try{document.execCommand("copy")}'
'catch(e){}}'
2018-05-24 12:48:15 +03:00
'</script>'
)
2018-05-24 12:48:15 +03:00
self.write('</div>{}</body></html>', self._script)
# "Low" level writing
2018-05-24 12:48:15 +03:00
def write(self, s, *args, **kwargs):
"""Wrapper around handle.write"""
2018-05-24 12:48:15 +03:00
if args or kwargs:
self.handle.write(s.format(*args, **kwargs))
else:
self.handle.write(s)
# With block
def __enter__(self):
# Sanity check
parent = os.path.dirname(self.filename)
if parent:
os.makedirs(parent, exist_ok=True)
self.handle = open(self.filename, 'w', encoding='utf-8')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.handle.close()