blob: 6aa7838e3280795bafed941ebb3c52db20eaea94 [file] [log] [blame]
#!/usr/bin/env vpython
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# [VPYTHON:BEGIN]
# python_version: "2.7"
# wheel <
# name: "infra/python/wheels/pyyaml/${platform}_${py_python}_${py_abi}"
# version: "version:3.12"
# >
# [VPYTHON:END]
import argparse
import collections
import json
import os
import sys
import yaml
# Dart types that can contain members.
ENCLOSING_TYPES = ["library", "class", "enum", "mixin", "extension"]
TYPE_HEADINGS = {
"class": "Classes",
"enum": "Enums",
"extension": "Extensions",
"mixin": "Mixins",
"constant": "Constants",
"constructor": "Constructors",
"function": "Functions",
"method": "Methods",
"property": "Properties",
"top-level constant": "Top-level Constants",
"top-level property": "Top-level Properties",
"typedef": "Typedefs",
}
def configure_yaml():
# yaml.dump by default sorts keys alphabetically, but most of the time we
# need a specific order. One workaround is by passing sort_keys=False, but
# there is another problem: Python itself prior to 3.7 does not guarantee
# dict key order. Therefore we use collections.OrderedDict, and we set up a
# yaml representer to emit keys & values without the
# "!!python/object/apply:collections.OrderedDict" tag.
def represent_dictionary_order(dumper, data):
return dumper.represent_mapping("tag:yaml.org,2002:map", data.items())
yaml.add_representer(collections.OrderedDict, represent_dictionary_order)
if sys.version_info[0] < 3:
# This avoids emitting "!!python/unicode" tags for some strings.
def represent_unicode(dumper, data):
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
yaml.add_representer(unicode, represent_unicode)
def build_toc_item(title, path=None, sub_items=None):
"""Create an item in the TOC.
Args:
title (str) - Title for this item. Required.
path (str) - Path to the page for this item. Optional.
sub_items (list) - Items to place in a collapsible subsection. Optional.
"""
item = collections.OrderedDict()
item["title"] = title
if path:
item["path"] = path
if sub_items:
item["section"] = sub_items
return item
def build_toc_content(index_file):
"""Build a TOC hierarchy from a json index produced by Dartdoc."""
with open(index_file) as f:
data = json.load(f)
libraries = treeify_index(data)
toc_items = [build_toc_item("Overview", path="/reference/dart/index.md")]
if libraries:
if "packageName" in libraries[0]:
# Organize by packageName, then alphabetically
libraries.sort(key=lambda lib: (lib["packageName"], lib["name"]))
current_package = None
for lib in libraries:
# Insert a heading when we encounter a new packageName.
if current_package != lib["packageName"]:
current_package = lib["packageName"]
toc_items.append({"heading": current_package})
toc_items.append(element_to_toc_item(lib))
else:
# Older versions of Dartdoc do not provide packageName in the index.
toc_items.append({"heading": "Libraries"})
toc_items.extend(element_to_toc_item(lib) for lib in libraries)
return {"toc": toc_items}
def treeify_index(data):
"""Convert the index data to a tree and return a list of libraries.
Index data is a flat list where each element identifies its enclosing
element, if applicable. Libraries are not enclosed by anything and are
effectively the root(s) of the tree. Other elements are added to a list
named 'members' in their enclosing element. All other data is unchanged.
"""
assert type(data) is list
libraries = []
lookup = {}
for element in data:
if element["type"] == "library":
libraries.append(element)
if element["type"] in ENCLOSING_TYPES:
lookup[element["qualifiedName"]] = element
element["members"] = []
if "enclosedBy" in element:
# enclosedBy['name'] is not specific enough for lookup (e.g. two
# libraries could both define a class 'Foo', and if we encounter
# something enclosed by 'Foo', we wouldn't know which is the correct
# 'Foo'). Instead we use this element's qualifiedName up to but not
# including the last dot.
lookup_key = element["qualifiedName"]
lookup_key = lookup_key[: lookup_key.rfind(".")]
enclosingElement = lookup[lookup_key]
enclosingElement["members"].append(element)
return libraries
def element_to_toc_item(element):
"""Convert an element to a TOC item, recursively converting children."""
sub_items = []
if "members" in element:
# Group members by type, then alphabetically.
element["members"].sort(key=lambda member: (member["type"], member["name"]))
current_type = None
for member in element["members"]:
# Insert a heading when we encounter a new type grouping.
if current_type != member["type"]:
current_type = member["type"]
sub_items.append({"heading": TYPE_HEADINGS[current_type]})
sub_items.append(element_to_toc_item(member))
return build_toc_item(
element["name"],
path="/reference/dart/%s" % element["href"],
sub_items=sub_items,
)
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"-i", "--index-file", type=str, required=True, help="Location of generated docs"
)
parser.add_argument(
"-o",
"--outfile",
type=str,
required=True,
help="Location where generated toc file should go",
)
args = parser.parse_args()
if not os.path.isfile(args.index_file):
print("%s does not exist or is not a file" % args.index_file)
return 1
configure_yaml()
content = build_toc_content(args.index_file)
if not content:
return 1
with open(args.outfile, "w") as f:
yaml.dump(content, f, default_flow_style=False)
if __name__ == "__main__":
sys.exit(main())