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:
|
2018-12-21 20:18:18 +03:00
|
|
|
"""
|
|
|
|
Utility class used to write the HTML files used on the documentation.
|
|
|
|
"""
|
2019-07-17 11:11:52 +03:00
|
|
|
def __init__(self, filename, type_to_path):
|
2017-09-04 18:10:04 +03:00
|
|
|
"""
|
2018-12-21 20:18:18 +03:00
|
|
|
Initializes the writer to the specified output file,
|
|
|
|
creating the parent directories when used if required.
|
|
|
|
"""
|
2017-04-07 21:07:55 +03:00
|
|
|
self.filename = filename
|
2018-12-21 20:18:18 +03:00
|
|
|
self._parent = str(self.filename.parent)
|
2017-04-07 21:07:55 +03:00
|
|
|
self.handle = None
|
2018-12-21 20:18:18 +03:00
|
|
|
self.title = ''
|
2017-04-07 21:07:55 +03:00
|
|
|
|
|
|
|
# Should be set before calling adding items to the menu
|
|
|
|
self.menu_separator_tag = None
|
|
|
|
|
2018-12-21 20:18:18 +03:00
|
|
|
# Utility functions
|
|
|
|
self.type_to_path = lambda t: self._rel(type_to_path(t))
|
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
|
|
|
|
2018-12-21 20:18:18 +03:00
|
|
|
def _rel(self, path):
|
|
|
|
"""
|
|
|
|
Get the relative path for the given path from the current
|
|
|
|
file by working around https://bugs.python.org/issue20012.
|
|
|
|
"""
|
2019-01-26 14:46:13 +03:00
|
|
|
return os.path.relpath(
|
|
|
|
str(path), self._parent).replace(os.path.sep, '/')
|
2018-12-21 20:18:18 +03:00
|
|
|
|
2017-04-07 21:07:55 +03:00
|
|
|
# High level writing
|
2018-12-21 20:18:18 +03:00
|
|
|
def write_head(self, title, 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-12-21 20:18:18 +03:00
|
|
|
self.title = title
|
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-12-21 12:56:40 +03:00
|
|
|
<link id="style" href="{rel_css}/docs.dark.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,
|
2018-12-21 20:18:18 +03:00
|
|
|
rel_css=self._rel(css_path),
|
2018-05-24 12:48:15 +03:00
|
|
|
def_css=default_css
|
|
|
|
)
|
2017-04-07 21:07:55 +03:00
|
|
|
|
2018-12-21 20:18:18 +03:00
|
|
|
def set_menu_separator(self, img):
|
2017-09-04 18:10:04 +03:00
|
|
|
"""Sets the menu separator.
|
|
|
|
Must be called before adding entries to the menu
|
|
|
|
"""
|
2018-12-21 20:18:18 +03:00
|
|
|
if img:
|
|
|
|
self.menu_separator_tag = '<img src="{}" alt="/" />'.format(
|
|
|
|
self._rel(img))
|
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-12-21 20:18:18 +03:00
|
|
|
self.write('<a href="{}">', self._rel(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>')
|
|
|
|
|
2018-10-16 10:29:48 +03:00
|
|
|
def write_title(self, title, level=1, id=None):
|
2017-09-04 18:10:04 +03:00
|
|
|
"""Writes a title header in the document body,
|
|
|
|
with an optional depth level
|
|
|
|
"""
|
2018-10-16 10:29:48 +03:00
|
|
|
if id:
|
|
|
|
self.write('<h{lv} id="{id}">{title}</h{lv}>',
|
|
|
|
title=title, lv=level, id=id)
|
|
|
|
else:
|
|
|
|
self.write('<h{lv}>{title}</h{lv}>',
|
|
|
|
title=title, lv=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
|
2022-08-30 14:21:17 +03:00
|
|
|
if arg.flag:
|
|
|
|
self.write('{}.{}?', arg.flag, 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-12-21 20:18:18 +03:00
|
|
|
self.write('<a href="{}">', self._rel(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))
|
|
|
|
|
2019-01-26 14:46:13 +03:00
|
|
|
def add_script(self, src='', path=None):
|
|
|
|
if path:
|
|
|
|
self._script += '<script src="{}"></script>'.format(
|
|
|
|
self._rel(path))
|
2018-01-20 15:11:22 +03:00
|
|
|
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
|
2018-12-21 15:24:16 +03:00
|
|
|
self.filename.parent.mkdir(parents=True, exist_ok=True)
|
2019-01-06 23:36:32 +03:00
|
|
|
self.handle = self.filename.open('w', encoding='utf-8')
|
2017-04-07 21:07:55 +03:00
|
|
|
return self
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
|
|
self.handle.close()
|