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"""
|
2018-04-15 13:15:43 +03:00
|
|
|
def __init__(self, filename, type_to_path):
|
2017-04-07 21:07:55 +03:00
|
|
|
"""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
|
2018-04-15 13:15:43 +03:00
|
|
|
self.type_to_path = lambda t: type_to_path(
|
2017-09-04 18:10:04 +03:00
|
|
|
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
|
2018-01-20 15:11:22 +03:00
|
|
|
self._script = ''
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# 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>
|
2017-04-07 21:07:55 +03:00
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
2018-05-24 12:48:15 +03:00
|
|
|
<title>{title}</title>
|
2017-04-07 21:07:55 +03:00
|
|
|
<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."
|
2018-06-27 11:36:56 +03:00
|
|
|
+ (localStorage.getItem("theme") || "{def_css}")
|
2018-05-24 12:48:15 +03:00
|
|
|
+ ".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">
|
2017-04-07 21:07:55 +03:00
|
|
|
</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
|
|
|
|
)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
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:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">', link)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# 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
|
|
|
|
"""
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<h{level}>{title}</h{level}>', title=title, level=level)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
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')
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# 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)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# 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:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('flags.{}?', arg.flag_index)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
if arg.is_generic:
|
|
|
|
self.write('!')
|
|
|
|
|
|
|
|
if arg.is_vector:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">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:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">', self.type_to_path(arg.type))
|
2017-04-08 13:41:31 +03:00
|
|
|
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('>')
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">{}</a><',
|
|
|
|
self.type_to_path(vector), vector)
|
2017-05-19 11:01:58 +03:00
|
|
|
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">{}</a>>',
|
|
|
|
self.type_to_path(inner), inner)
|
2017-05-19 11:01:58 +03:00
|
|
|
else:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">{}</a>',
|
|
|
|
self.type_to_path(tlobject.result), 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:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write(' style="text-align:{}"', align)
|
2017-04-07 21:07:55 +03:00
|
|
|
self.write('>')
|
|
|
|
|
|
|
|
if bold:
|
|
|
|
self.write('<b>')
|
|
|
|
if link:
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('<a href="{}">', link)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# 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)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
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))
|
|
|
|
|
2018-01-20 15:11:22 +03:00
|
|
|
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)
|
|
|
|
|
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){}}'
|
2018-05-24 12:48:15 +03:00
|
|
|
'</script>'
|
|
|
|
)
|
2017-06-04 20:57:20 +03:00
|
|
|
|
2018-05-24 12:48:15 +03:00
|
|
|
self.write('</div>{}</body></html>', self._script)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# "Low" level writing
|
2018-05-24 12:48:15 +03:00
|
|
|
def write(self, s, *args, **kwargs):
|
2017-04-07 21:07:55 +03:00
|
|
|
"""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)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# 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()
|