|
import sys |
|
from pathlib import Path |
|
from collections import defaultdict |
|
import urllib |
|
import lxml.html as html |
|
import bisect |
|
|
|
''' |
|
This file converts the existing documented pages to the new style |
|
Test targets: |
|
- target/doc/dashu_ratio/struct.RBig.html |
|
- target/doc/dashu_base/sign/enum.Sign.html |
|
''' |
|
|
|
def parse_trait_impls(list_node): |
|
trait_infos = {} |
|
for trait_item in list_node.findall("details"): |
|
trait_name = trait_item.findall('.//a[@class="trait"]')[-1].text |
|
trait_id = trait_item.find('./summary/section').get("id") |
|
trait_infos[trait_id] = (trait_name, trait_item) |
|
return trait_infos |
|
|
|
def improve_sidebar(this_type, sidebar_node, trait_infos): |
|
trait_section_ul = sidebar_node.find('.//h3/a[@href="#trait-implementations"]/..').getnext() |
|
|
|
# scan the side bar trait impls |
|
trait_list_infos = defaultdict(list) |
|
for trait_item in trait_section_ul.iter('li'): |
|
trait_link = trait_item.find('a') |
|
trait_text: str = trait_link.text |
|
trait_id: str = trait_link.get("href") |
|
|
|
# collect by name |
|
if trait_text.find('<') < 0: |
|
trait_name = trait_text |
|
else: |
|
trait_name = trait_text[:trait_text.find('<')] |
|
trait_list_infos[trait_name].append(trait_item) |
|
|
|
# modify the text if the target implemented for is not this type |
|
for_type = urllib.parse.unquote(trait_id[trait_id.find("-for-") + 5:]) |
|
if '<' in for_type: |
|
for_type = for_type[:for_type.find('<')] |
|
if for_type != this_type: |
|
suffix = html.fromstring('<span style="color: #999;"> for %s</span>' % for_type) |
|
trait_link.append(suffix) |
|
|
|
# find impls with duplicate name |
|
for trait_name, trait_items in trait_list_infos.items(): |
|
if len(trait_items) == 1: |
|
continue |
|
|
|
new_tree = html.fromstring('<details class="rustdoc-toggle sidebar-impl-toggle" style="margin: 4px 0;"><summary>%s (%d impls)</summary><ul class="sidebar-sublist" style="padding-left: 0"></ul></details>' % (trait_name, len(trait_items))) |
|
new_list = new_tree.find('ul') |
|
for item in trait_items: |
|
item.drop_tree() |
|
new_list.append(item) |
|
|
|
rem_names = [item.find('a').text for item in trait_section_ul.iter('li')] |
|
idx = bisect.bisect(rem_names, trait_name) |
|
trait_section_ul.insert(idx, new_tree) |
|
|
|
def create_summary(impls_node, traits_impls_node): |
|
# TODO: parse output type (strip generic params, display (..) for tuple) |
|
normal_members = defaultdict(list) # item_type -> [(id, name, doc, props)] |
|
if impls_node: |
|
# get nodes of all functions |
|
fn_nodes = [] |
|
for impl_block in impls_node[0].findall('details'): |
|
for fn_block in impl_block.find('div').findall('details'): |
|
fn_nodes.append(fn_block) |
|
|
|
# parse the info of the nodes |
|
for item in fn_nodes: |
|
item_props = set() |
|
summary = item.find('summary') |
|
item_id = summary.find('section').get('id') |
|
if item_id.startswith("method"): |
|
item_link = summary.find('.//a[@class="fn"]') |
|
item_name = item_link.text |
|
|
|
fn_signature = summary.text_content() |
|
args = fn_signature[fn_signature.find('(') + 1:fn_signature.find(')')].strip() |
|
multi_args = args.find(",") >= 0 |
|
if multi_args: |
|
first_arg = args[:args.find(",")].strip() |
|
else: |
|
first_arg = args |
|
|
|
if first_arg.startswith("self"): |
|
item_type = "method_self" |
|
elif first_arg.startswith("&self"): |
|
item_type = "method_self_ref" |
|
elif first_arg.startswith("&mut self"): |
|
item_type = "method_self_mut" |
|
else: |
|
assert first_arg.find("self") < 0 |
|
item_type = "method_static" |
|
|
|
if multi_args and item_type != "method_static": |
|
item_props.add("multi_args") |
|
if item_type == "method_static" and args: |
|
item_props.add("multi_args") |
|
|
|
mods = fn_signature[:fn_signature.find('(')] |
|
if mods.find("const ") >= 0: |
|
item_props.add("const") |
|
if mods.find("async ") >= 0: |
|
item_props.add("async") |
|
elif item_id.startswith("associatedconstant"): |
|
item_name = summary.find('.//a[@class="constant"]').text |
|
item_type = "asso_constant" |
|
else: |
|
raise RuntimeError("Unrecognized item") |
|
|
|
comment_block = item.find('div').find('p') |
|
normal_members[item_type].append((item_name, item_id, comment_block, item_props)) |
|
|
|
trait_members = {} # trait_path -> (trait name, trait id, [list of fn nodes]) |
|
if traits_impls_node: |
|
for impl_block in traits_impls_node[0].findall('details'): |
|
header_block = impl_block.find('summary').findall('.//a[@class="trait"]')[-1] |
|
trait_name = header_block.text |
|
trait_path = header_block.get("title").lstrip("trait ") |
|
trait_id = impl_block.find('summary/section').get('id') |
|
if trait_path in trait_members: |
|
continue |
|
|
|
methods = [] |
|
for sub_block in impl_block.find('div'): |
|
if sub_block.tag == "section": |
|
item_link = sub_block.find('.//a[@class="fn"]') |
|
item_id = sub_block.get('id') |
|
elif sub_block.tag == "details": |
|
item_link = sub_block.find('.//a[@class="fn"]') |
|
item_id = sub_block.find('./summary/section').get('id') |
|
if item_link is None: |
|
continue |
|
methods.append((item_link.text, item_id)) |
|
trait_members[trait_path] = (trait_name, trait_id, methods) |
|
|
|
# render |
|
header = '<h2 id="summary" class="small-section-header">Summary<a href="#summary" class="anchor"></a></h2>' |
|
header = html.fromstring(header) |
|
body = html.fromstring('<details class="rustdoc-toggle top-doc" open><summary><span>Expand Summary</span></summary></details>') |
|
body.append(html.fromstring("Superscripts: <code>a</code> - async; <code>c</code> - const; <code>u</code> - unsafe")) |
|
item_order = ["method_static", "asso_constant", "method_self", "method_self_ref", "method_self_mut"] |
|
for item_type in item_order: |
|
members = normal_members[item_type] |
|
if len(members) == 0: |
|
continue |
|
members.sort() |
|
|
|
if item_type == "method_static": |
|
title = "Static Methods" |
|
elif item_type == "asso_constant": |
|
title = "Associated Constants" |
|
elif item_type == "method_self": |
|
title = "Methods with <code>self</code>" |
|
elif item_type == "method_self_ref": |
|
title = "Methods with <code>&self</code>" |
|
elif item_type == "method_self_mut": |
|
title = "Methods with <code>&mut self</code>" |
|
else: |
|
title = "Unknown" |
|
body.append(html.fromstring('<h3 style="margin-top: 12; margin-bottom: 7">%s</h3>' % title)) |
|
|
|
list_body = html.fromstring('<div class="item-table"></div>') |
|
for (name, id, comment, props) in members: |
|
row = html.fromstring('<div class="item-row"></div>') |
|
|
|
icons = "" |
|
if "async" in props: |
|
icons += "<code>a</code>" |
|
if "const" in props: |
|
icons += "<code>c</code>" |
|
if "unsafe" in props: |
|
icons += "<code>u</code>" |
|
|
|
left = html.fromstring('<div class="item-left module-item"><a href="#%s" class="fn">%s</a><sup>%s</sup></div>' % (id, name, icons)) |
|
right = html.fromstring('<div class="item-right docblock-short"></div>') |
|
right.append(comment) |
|
row.append(left) |
|
row.append(right) |
|
list_body.append(row) |
|
body.append(list_body) |
|
|
|
if trait_members: |
|
body.append(html.fromstring('<h3 style="margin-top: 12; margin-bottom: 7">Trait Methods</h3>')) |
|
body.append(html.fromstring('<p>Sorted by full paths of the traits.</p>')) |
|
list_body = html.fromstring('<div class="item-table"></div>') |
|
|
|
# here the traits are sorted by module |
|
sorted_names = list(trait_members.keys()) |
|
sorted_names.sort() |
|
for name in sorted_names: |
|
trait_name, trait_id, fn_list = trait_members[name] |
|
row = html.fromstring('<div class="item-row"></div>') |
|
left = html.fromstring('<div class="item-left module-item"><a href="#%s" class="fn">%s</a></div>' % (trait_id, name)) |
|
|
|
right_content = "" |
|
prepend_comma = False |
|
for fn_name, fn_id in fn_list: |
|
if prepend_comma: |
|
right_content += ', <a href="#%s">%s</a>' % (fn_id, fn_name) |
|
else: |
|
right_content += '<a href="#%s">%s</a>' % (fn_id, fn_name) |
|
prepend_comma = True |
|
right = html.fromstring('<div class="item-right docblock-short">%s</div>' % right_content) |
|
row.append(left) |
|
row.append(right) |
|
list_body.append(row) |
|
|
|
body.append(list_body) |
|
|
|
return (header, body) |
|
|
|
def improve(doc_path: Path, override = False): |
|
# load |
|
doc = html.fromstring(doc_path.read_text()) |
|
sidebar = doc.xpath('//div[@class="sidebar-elems"]')[0] |
|
this_type = doc.xpath('//a[@href="#"]')[0].text |
|
|
|
extra_style = """<style> |
|
.sidebar details.sidebar-impl-toggle > summary { |
|
cursor: pointer; |
|
} |
|
.sidebar details.sidebar-impl-toggle > summary:hover { |
|
background-color: #444; |
|
} |
|
.sidebar details.sidebar-impl-toggle > summary::before { |
|
background: none; |
|
} |
|
.sidebar details.sidebar-impl-toggle[open] > summary::before { |
|
background: none; |
|
} |
|
.sidebar details.sidebar-impl-toggle[open] > summary { |
|
background-color: #444; |
|
} |
|
details.sidebar-impl-toggle ul { |
|
background-color: #444; |
|
} |
|
</style>""" |
|
|
|
doc.xpath('//head')[0].append(html.fromstring(extra_style)) |
|
|
|
# add summary |
|
impls = doc.xpath('//div[@id="implementations-list"]') |
|
traits_impls = doc.xpath('//div[@id="trait-implementations-list"]') |
|
header, body = create_summary(impls, traits_impls) |
|
impls_header = doc.xpath('//section[@id="main-content"]/h2')[0] |
|
impls_header.addprevious(header) |
|
impls_header.addprevious(body) |
|
doc.xpath('//div[@class="sidebar-elems"]/section')[0].insert(0, |
|
html.fromstring('<div class="block"><h3 class="sidebar-title"><a href="#summary">Summary</a></h3></div>')) |
|
|
|
# modify sidebar |
|
if traits_impls: |
|
trait_infos = parse_trait_impls(traits_impls[0]) |
|
improve_sidebar(this_type, sidebar, trait_infos) |
|
|
|
# save |
|
output = html.tostring(doc) |
|
if override: |
|
doc_path.write_bytes(output) |
|
else: |
|
doc_path.with_suffix(".edit.html").write_bytes(output) |
|
|
|
if __name__ == "__main__": |
|
doc_path = Path(sys.argv[1]) |
|
if doc_path.is_dir(): |
|
for page in doc_path.rglob('*.html'): |
|
if page.name.startswith("struct") or page.name.startswith("enum") or page.name.startswith("primitive"): |
|
try: |
|
improve(page, True) |
|
print("Processed", page.name) |
|
except: |
|
print("Failed", page.name) |
|
continue |
|
else: |
|
improve(doc_path) |
Some live examples: i64, std::vec::Vec, dashu_base::Sign, dashu_int::UBig
Some details to be finalized: