diff --git a/docs/doxygen/Doxyfile b/docs/doxygen/Doxyfile index 20ff961c27..8a61aabfc6 100644 --- a/docs/doxygen/Doxyfile +++ b/docs/doxygen/Doxyfile @@ -819,7 +819,7 @@ SHOW_USED_FILES = NO # (if specified). # The default value is: YES. -SHOW_FILES = NO +SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the diff --git a/docs/doxygen/DoxygenLayout.xml b/docs/doxygen/DoxygenLayout.xml index ec3fdcddc4..4befb93417 100644 --- a/docs/doxygen/DoxygenLayout.xml +++ b/docs/doxygen/DoxygenLayout.xml @@ -1,19 +1,20 @@ - - + + + - - + + - + - + @@ -26,7 +27,7 @@ - + @@ -46,56 +47,57 @@ - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + @@ -113,23 +115,25 @@ - - - - - - + + + + + + + - + - - - - - - - + + + + + + + + @@ -139,7 +143,7 @@ - + @@ -158,25 +162,27 @@ - - - - - - - + + + + + + + + - + - - - - - - - - + + + + + + + + + @@ -193,42 +199,42 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -240,13 +246,13 @@ - - - - - + + + + + - + @@ -260,6 +266,6 @@ - + diff --git a/docs/doxygen/build.py b/docs/doxygen/build.py index f8391474d6..9a2876b28a 100644 --- a/docs/doxygen/build.py +++ b/docs/doxygen/build.py @@ -1,20 +1,14 @@ +from pathlib import Path import argparse import os import subprocess -from pathlib import Path, PurePath import xml.etree.ElementTree as ET from xml.etree.ElementTree import Element +import re -def get_modules_directory_xml_element(xml_dir: Path) -> Element: - for entry in os.listdir('xml'): - if not entry.startswith('dir_'): - continue - root = ET.parse(xml_dir / entry).getroot() - location = root.find('.//location') - path = PurePath(location.attrib['file']) - referenced_dir_path_parts = path.parts - if referenced_dir_path_parts[-1] == 'modules': - return root, path +MODULES_DIR = Path('../../modules') +assert MODULES_DIR.is_dir() +ABS_MODULES_DIR = MODULES_DIR.resolve() def get_module_description(module_header: Path) -> str: with open(module_header, 'r') as f: @@ -24,52 +18,107 @@ def get_module_description(module_header: Path) -> str: if line.startswith('description:'): return line.removeprefix('description:').strip() -def get_doxygen_items_recursive(xml_file_ref: str, items: list): - element = ET.parse(f'{Path('xml') / xml_file_ref}.xml').getroot() - item_type = element.find('compounddef').attrib['kind'] - if item_type == 'dir': - name = element.find('.//compoundname').text - new_item = { - 'name': name, - 'classes': [], - 'dirs': [] - } - items.append(new_item) - items = new_item['classes'] - for class_reference in element.findall('.//innerclass'): - html_filename = f'{class_reference.attrib['refid']}.html' - if (Path('doc') / html_filename).exists(): - items.append({ - 'name': class_reference.text.removeprefix('juce::'), - 'url': f'./{html_filename}', - }) - for file_reference in element.findall('.//innerfile'): - get_doxygen_items_recursive(file_reference.attrib['refid'], items) - for dir_reference in element.findall('.//innerdir'): - get_doxygen_items_recursive(dir_reference.attrib['refid'], new_item['dirs']) +def createContentDict() -> dict: + return { + 'classes/structs': {}, + 'functions': {}, + 'typedefs': {}, + 'macros': {}, + 'enums': {}, + 'dirs': {} + } -def remove_empty_doxygen_items_recursive(parent: dict): - parent['dirs'] = list(filter(lambda x: x['classes'] or x['dirs'], parent['dirs'])) - for d in parent['dirs']: - remove_empty_doxygen_items_recursive(d) +def getContentObjectFromPath(root_object: dict, location: str) -> dict: + path_in_modules = Path(location).relative_to(ABS_MODULES_DIR) + path_components = path_in_modules.parts + contents = root_object[path_components[0]] + for path_component in path_components[1:-1]: + if not (path_component in contents['dirs']): + contents['dirs'][path_component] = createContentDict() + contents = contents['dirs'][path_component] + return contents + +def parseNamespaceXml(root_object, filename): + element = ET.parse(f'xml/{filename}').getroot() + + for class_reference in element.findall('.//innerclass'): + refid = class_reference.attrib['refid'] + html_filename = f'{refid}.html' + if not Path(f'doc/{html_filename}').is_file(): + continue + xml_path = Path(f'xml/{refid}.xml') + if not xml_path.is_file(): + continue + class_element = ET.parse(xml_path).getroot() + location = class_element.find('.//location').attrib['file'] + contents = getContentObjectFromPath(root_object, location) + contents['classes/structs'][class_reference.text.removeprefix('juce::')] = { + 'url': f'./{html_filename}' + } + + namespace_def_id = element.find('.//compounddef').attrib['id'] + namespace_id_prefix = f'{namespace_def_id}_1' + + for kind, contents_key in (('function', 'functions'), ('typedef', 'typedefs'), ('enum', 'enums')): + for reference in element.findall(f".//memberdef[@kind='{kind}']"): + desc = reference.find('briefdescription') + if (len(desc) == 0) and (not desc.text.strip()): + continue + ref_id = reference.attrib['id'] + if not ref_id.startswith(namespace_id_prefix): + continue + name = reference.find('.//qualifiedname').text.removeprefix('juce::') + if re.search(r'operator[+\-*/=!<>]*$', name): + continue + anchor = ref_id.removeprefix(namespace_id_prefix) + location = reference.find('.//location').attrib['file'] + contents = getContentObjectFromPath(modules, location) + contents[contents_key][name] = { + 'url': f'./{namespace_def_id}.html#{anchor}' + } + +def parseFileXml(root_object, filename): + element = ET.parse(f'xml/{filename}').getroot() + compounddef_id = element.find('compounddef').attrib['id'] + def_reference_link_prefix = compounddef_id + '_1' + for def_reference in element.findall(".//memberdef[@kind='define']"): + def_id = def_reference.attrib['id'] + if not def_id.startswith(def_reference_link_prefix): + continue + def_desc = def_reference.find('briefdescription') + if (len(def_desc) == 0) and (not def_desc.text.strip()): + continue + html_filename = f'{compounddef_id}.html' + if not (Path('doc') / html_filename).exists(): + continue + location = def_reference.find('.//location').attrib['file'] + contents = getContentObjectFromPath(root_object, location) + anchor = def_id.removeprefix(def_reference_link_prefix) + contents['macros'][def_reference.find('name').text] = { + 'url': f'./{html_filename}#{anchor}', + } def write_module_html_recursive(parent: Element, data: dict): - if data['classes']: - class_list = ET.SubElement(parent, 'ul', {'class': 'juce-module-class-list'}) - for c in data['classes']: - item = ET.SubElement(class_list, 'li', {'class': 'juce-module-class-item'}) - ET.SubElement(item, 'a', {'href': c['url'], 'class': 'juce-module-class-link'}).text = c['name'] + for section_key, section_contents in data.items(): + if section_key in ('dirs', 'description'): + continue + if not section_contents: + continue + #ET.SubElement(parent, 'span', {'class': 'juce-module-contents-title'}).text = section_key + section_list = ET.SubElement(parent, 'ul', {'class': 'juce-module-class-list'}) + for section_item_key, section_item_contents in sorted(section_contents.items()): + item = ET.SubElement(section_list, 'li', {'class': 'juce-module-class-item'}) + ET.SubElement(item, 'a', {'href': section_item_contents['url'], 'class': 'juce-module-class-link'}).text = section_item_key if data['dirs']: dir_table = ET.SubElement(parent, 'table', {'class': 'juce-module-dir-table'}) - for d in data['dirs']: + for dir_key, dir_contents in sorted(data['dirs'].items()): row = ET.SubElement(dir_table, 'tr', {'class': 'juce-module-dir-row'}) - ET.SubElement(row, 'td', {'class': 'juce-module-dir-name'}).text = d['name'] + ET.SubElement(row, 'td', {'class': 'juce-module-dir-name'}).text = dir_key content = ET.SubElement(row, 'td', {'class': 'juce-module-dir-contents'}) - write_module_html_recursive(content, d) - + write_module_html_recursive(content, dir_contents) parser = argparse.ArgumentParser() -parser.add_argument('--sitemap-url', help='a sitemap URL for Doxygen') +parser.add_argument('--sitemap-url', help='The Doxygen sitemap configuration variable') args = parser.parse_args() if args.sitemap_url: @@ -78,18 +127,25 @@ if args.sitemap_url: print('--- Running Doxygen') subprocess.run("doxygen", shell=True, check=True) +print('--- Parsing module headers') +modules = {} +for module_name in os.listdir(MODULES_DIR): + module_dir = MODULES_DIR / module_name + if not module_dir.is_dir(): + continue + module_header = module_dir / f'{module_name}.h' + if not module_header.is_file(): + continue + modules[module_name] = createContentDict() + modules[module_name]['description'] = get_module_description(module_header) + print('--- Parsing Doxygen XML') -xml_dir = Path('xml') -root, root_path = get_modules_directory_xml_element(xml_dir) -modules = [] -module_descriptions = {} -for dir_reference in root.findall('.//innerdir'): - module_name = dir_reference.text - module_path = root_path / module_name - module_header_path = module_path / f'{module_name}.h' - module_descriptions[module_name] = get_module_description(module_header_path) - get_doxygen_items_recursive(dir_reference.attrib['refid'], modules) - remove_empty_doxygen_items_recursive(modules[-1]) +for xml_filename in os.listdir('xml'): + if xml_filename.startswith('namespacejuce'): + parseNamespaceXml(modules, xml_filename) + elif xml_filename.startswith('juce__'): + # There are no macros in the namespace XML so we need to get them separately + parseFileXml(modules, xml_filename) print('--- Creating JUCE Module HTML') @@ -99,24 +155,24 @@ ET.SubElement(module_icon, 'path', {'d': 'M 28.0000 26.6406 L 50.0783 14.1016 C main_div = ET.Element('div', {'class': 'juce-modules-continer'}) toc_div = ET.SubElement(main_div, 'div', {'class': 'juce-module-toc-container'}) -ET.SubElement(toc_div, 'p', {'class': 'juce-module-toc-desc'}).text = "Here are the JUCE modules with some brief descriptions:" +ET.SubElement(toc_div, 'p', {'class': 'juce-module-toc-desc'}).text = "Here is a summary of the JUCE modules. To search absolutely everything please use the search bar." toc_table = ET.SubElement(toc_div, 'table', {'class': 'juce-module-toc-table'}) -for module in modules: +for key, contents in sorted(modules.items()): toc_row = ET.SubElement(toc_table, 'tr', {'class': 'juce-module-toc-row'}) module_toc_name = ET.SubElement(toc_row, 'td', {'class': 'juce-module-toc-module-name'}) - ET.SubElement(module_toc_name, 'a', {'href': f'#{module['name']}', 'class': 'juce-module-toc-module-name-link'}).text = module['name'] - ET.SubElement(toc_row, 'td', {'class': 'juce-module-toc-module-decs'}).text = module_descriptions[module['name']] + ET.SubElement(module_toc_name, 'a', {'href': f'#{key}', 'class': 'juce-module-toc-module-name-link'}).text = key + ET.SubElement(toc_row, 'td', {'class': 'juce-module-toc-module-decs'}).text = contents['description'] ET.SubElement(main_div, 'div', {'class': 'juce-module-toc-divider'}) -for module in modules: +for key, contents in sorted(modules.items()): module_div = ET.SubElement(main_div, 'div', {'class': 'juce-module'}) module_header_div = ET.SubElement(module_div, 'div', {'class': 'juce-module-header'}) module_title_div = ET.SubElement(module_header_div, 'div', {'class': 'juce-module-title'}) module_title_div.append(module_icon) - ET.SubElement(module_title_div, 'span', {'id': module['name'], 'class': 'juce-module-name'}).text = module['name'] - ET.SubElement(module_header_div, 'span', {'class': 'juce-module-desc'}).text = module_descriptions[module['name']] + ET.SubElement(module_title_div, 'span', {'id': key, 'class': 'juce-module-name'}).text = key + ET.SubElement(module_header_div, 'span', {'class': 'juce-module-desc'}).text = contents['description'] module_contents_div = ET.SubElement(module_div, 'div', {'class': 'juce-module-contents'}) - write_module_html_recursive(module_contents_div, module) + write_module_html_recursive(module_contents_div, contents) print('--- Updating Doxygen HTML') html = ET.tostring(main_div, encoding='utf-8', method='html').decode('utf-8')