2017-04-07 21:07:55 +03:00
|
|
|
import os
|
2017-05-19 11:01:58 +03:00
|
|
|
import re
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
|
|
|
|
class DocsWriter:
|
|
|
|
"""Utility class used to write the HTML files used on the documentation"""
|
|
|
|
def __init__(self, filename, type_to_path_function):
|
|
|
|
"""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
|
|
|
|
"""
|
2017-04-07 21:07:55 +03:00
|
|
|
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
|
2017-09-04 18:10:04 +03:00
|
|
|
self.type_to_path = lambda t: type_to_path_function(
|
|
|
|
t, relative_to=self.filename
|
|
|
|
)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# Control signals
|
|
|
|
self.menu_began = False
|
|
|
|
self.table_columns = 0
|
|
|
|
self.table_columns_left = None
|
2017-06-04 20:57:20 +03:00
|
|
|
self.write_copy_script = False
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# High level writing
|
|
|
|
def write_head(self, title, relative_css_path):
|
2017-09-04 18:10:04 +03:00
|
|
|
"""Writes the head part for the generated document,
|
|
|
|
with the given title and CSS
|
|
|
|
"""
|
2017-04-07 21:07:55 +03:00
|
|
|
self.write('''<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
|
|
<title>''')
|
|
|
|
|
|
|
|
self.write(title)
|
|
|
|
|
|
|
|
self.write('''</title>
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<link href="''')
|
|
|
|
|
|
|
|
self.write(relative_css_path)
|
|
|
|
|
|
|
|
self.write('''" rel="stylesheet">
|
2017-04-08 13:26:48 +03:00
|
|
|
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro" rel="stylesheet">
|
2017-04-07 21:07:55 +03:00
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="main_div">''')
|
|
|
|
|
|
|
|
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
|
|
|
|
"""
|
2017-04-07 21:07:55 +03:00
|
|
|
if relative_image_path:
|
2017-09-04 18:10:04 +03:00
|
|
|
self.menu_separator_tag = \
|
|
|
|
'<img src="{}" alt="/" />'.format(relative_image_path)
|
2017-04-07 21:07:55 +03:00
|
|
|
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:
|
|
|
|
self.write('<a href="')
|
|
|
|
self.write(link)
|
|
|
|
self.write('">')
|
|
|
|
|
|
|
|
# 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:
|
2017-12-28 02:22:28 +03:00
|
|
|
raise RuntimeError('No menu had been started in the first place.')
|
2017-04-07 21:07:55 +03:00
|
|
|
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
|
|
|
|
"""
|
2017-04-07 21:07:55 +03:00
|
|
|
self.write('<h%d>' % level)
|
|
|
|
self.write(title)
|
|
|
|
self.write('</h%d>' % 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
|
|
|
|
"""
|
2017-04-07 21:07:55 +03:00
|
|
|
self.write('<pre>---')
|
|
|
|
self.write('functions' if tlobject.is_function else 'types')
|
|
|
|
self.write('---\n')
|
|
|
|
|
|
|
|
# Write the function or type and its ID
|
|
|
|
if tlobject.namespace:
|
|
|
|
self.write(tlobject.namespace)
|
|
|
|
self.write('.')
|
|
|
|
|
|
|
|
self.write(tlobject.name)
|
|
|
|
self.write('#')
|
|
|
|
self.write(hex(tlobject.id)[2:].rjust(8, '0'))
|
|
|
|
|
|
|
|
# Write all the arguments (or do nothing if there's none)
|
|
|
|
for arg in tlobject.args:
|
|
|
|
self.write(' ')
|
2017-04-08 13:41:31 +03:00
|
|
|
add_link = not arg.generic_definition and not arg.is_generic
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# "Opening" modifiers
|
|
|
|
if arg.generic_definition:
|
|
|
|
self.write('{')
|
|
|
|
|
|
|
|
# Argument name
|
|
|
|
self.write(arg.name)
|
|
|
|
self.write(':')
|
|
|
|
|
|
|
|
# "Opening" modifiers
|
|
|
|
if arg.is_flag:
|
|
|
|
self.write('flags.%d?' % arg.flag_index)
|
|
|
|
|
|
|
|
if arg.is_generic:
|
|
|
|
self.write('!')
|
|
|
|
|
|
|
|
if arg.is_vector:
|
2017-09-04 18:10:04 +03:00
|
|
|
self.write(
|
|
|
|
'<a href="%s">Vector</a><' % self.type_to_path('vector')
|
|
|
|
)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# Argument type
|
|
|
|
if arg.type:
|
2017-04-08 13:41:31 +03:00
|
|
|
if add_link:
|
|
|
|
self.write('<a href="%s">' % self.type_to_path(arg.type))
|
|
|
|
self.write(arg.type)
|
|
|
|
if add_link:
|
|
|
|
self.write('</a>')
|
2017-04-07 21:07:55 +03:00
|
|
|
else:
|
|
|
|
self.write('#')
|
|
|
|
|
|
|
|
# "Closing" modifiers
|
|
|
|
if arg.is_vector:
|
|
|
|
self.write('>')
|
|
|
|
|
|
|
|
if arg.generic_definition:
|
|
|
|
self.write('}')
|
|
|
|
|
2017-09-04 18:10:04 +03:00
|
|
|
# Now write the resulting type (result from a function/type)
|
2017-04-08 13:41:31 +03:00
|
|
|
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:
|
2017-05-19 11:01:58 +03:00
|
|
|
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('>')
|
|
|
|
self.write('<a href="')
|
|
|
|
self.write(self.type_to_path(vector))
|
|
|
|
self.write('">%s</a><' % vector)
|
|
|
|
|
|
|
|
self.write('<a href="')
|
|
|
|
self.write(self.type_to_path(inner))
|
|
|
|
self.write('">%s</a>' % inner)
|
|
|
|
|
|
|
|
self.write('>')
|
|
|
|
else:
|
|
|
|
self.write('<a href="')
|
|
|
|
self.write(self.type_to_path(tlobject.result))
|
|
|
|
self.write('">%s</a>' % tlobject.result)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
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:
|
|
|
|
self.write(' style="text-align:')
|
|
|
|
self.write(align)
|
|
|
|
self.write('"')
|
|
|
|
self.write('>')
|
|
|
|
|
|
|
|
if bold:
|
|
|
|
self.write('<b>')
|
|
|
|
if link:
|
|
|
|
self.write('<a href="')
|
|
|
|
self.write(link)
|
|
|
|
self.write('">')
|
|
|
|
|
|
|
|
# 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"""
|
|
|
|
self.write('<p>')
|
|
|
|
self.write(text)
|
|
|
|
self.write('</p>')
|
|
|
|
|
2017-06-04 20:57:20 +03:00
|
|
|
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))
|
|
|
|
|
2017-04-07 21:07:55 +03:00
|
|
|
def end_body(self):
|
|
|
|
"""Ends the whole document. This should be called the last"""
|
2017-06-04 20:57:20 +03:00
|
|
|
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){}}'
|
|
|
|
'</script>')
|
|
|
|
|
2017-04-07 21:07:55 +03:00
|
|
|
self.write('</div></body></html>')
|
|
|
|
|
|
|
|
# "Low" level writing
|
|
|
|
def write(self, s):
|
|
|
|
"""Wrapper around handle.write"""
|
|
|
|
self.handle.write(s)
|
|
|
|
|
|
|
|
# With block
|
|
|
|
def __enter__(self):
|
|
|
|
# Sanity check
|
2017-04-08 15:07:36 +03:00
|
|
|
parent = os.path.dirname(self.filename)
|
|
|
|
if parent:
|
|
|
|
os.makedirs(parent, exist_ok=True)
|
|
|
|
|
2017-04-07 21:07:55 +03:00
|
|
|
self.handle = open(self.filename, 'w', encoding='utf-8')
|
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
self.handle.close()
|