#!/usr/bin/env python import os import shutil import re import argparse def get_curly_brace_scope_end(string, start_pos): """Given a string and the position of an opening curly brace, find the position of the closing brace. """ if string[start_pos] != "{": raise ValueError("string must have \"{\" at start pos") string_end = len(string) bracket_counter = 1 start_pos += 1 while start_pos < string_end: if string[start_pos] == "{": bracket_counter += 1 elif string[start_pos] == "}": bracket_counter -= 1 if bracket_counter == 0: return start_pos start_pos += 1 return -1 def add_doxygen_group(path, group_name): """Add a Doxygen group to the file at 'path'. Namespaces cause all kinds of problems, and we need to ensure that if the classes in a source file are contained within a namespace then we also put the @weakgroup inside the namespace. """ filename = os.path.basename(path) if re.match(r"^juce_.*\.(h|dox)", filename): group_definition_start = ("\r\n/** @weakgroup " + group_name + "\r\n * @{\r\n */\r\n") group_definition_end = "\r\n/** @}*/\r\n" with open(path, "r") as f: content = f.read() # Put the group definitions inside all namespaces. namespace_regex = re.compile(r"\s+namespace\s+\S+\s+{") match = namespace_regex.search(content) while (match is not None): namespace_end = get_curly_brace_scope_end(content, match.end() - 1) if namespace_end == -1: raise ValueError("error finding end of namespace " + match.group() + " in " + path) content = (content[:match.end()] + group_definition_start + content[match.end():namespace_end] + group_definition_end + content[namespace_end:]) search_start = (namespace_end + len(group_definition_start) + len(group_definition_end)) match = namespace_regex.search(content, search_start) with open(path, "w") as f: f.write(group_definition_start) f.write(content) f.write(group_definition_end) ############################################################################### if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("source_dir", help="the directory to search for source files") parser.add_argument("dest_dir", help="the directory in which to place processed files") parser.add_argument("--subdirs", help="if specified, only include these comma separated" "subdirectories") args = parser.parse_args() # Get the list of JUCE modules to include. if args.subdirs: juce_modules = args.subdirs.split(",") else: juce_modules = [] for item in os.listdir(args.source_dir): if os.path.isdir(os.path.join(args.source_dir, item)): juce_modules.append(item) # Copy the JUCE modules to the temporary directory, and process the source # files. module_definitions = [] for module_name in juce_modules: # Copy the required modules. original_module_dir = os.path.join(args.source_dir, module_name) module_path = os.path.join(args.dest_dir, module_name) shutil.copytree(original_module_dir, module_path) # Parse the module header to get module information. module_header = os.path.join(module_path, module_name + ".h") with open(module_header, "r") as f: content = f.read() block_info_result = re.match(r".*BEGIN_JUCE_MODULE_DECLARATION" "(.*)" "END_JUCE_MODULE_DECLARATION.*", content, re.DOTALL) detail_lines = [] for line in block_info_result.group(1).split("\n"): stripped_line = line.strip() if stripped_line: result = re.match(r"^.*?description:\s*(.*)$", stripped_line) if result: short_description = result.group(1) else: detail_lines.append(stripped_line) # The module header causes problems for Doxygen, so delete it. os.remove(module_header) # Create a Doxygen group definition for the module. module_definiton = [] module_definiton.append("/** @defgroup {n} {n}".format(n=module_name)) module_definiton.append(" {d}".format(d=short_description)) module_definiton.append("") for line in detail_lines: module_definiton.append(" - {l}".format(l=line)) module_definiton.append("") module_definiton.append(" @{") module_definiton.append("*/") # Create a list of the directories in the module that we can use as # subgroups and create the Doxygen group hierarchy string. dir_contents = os.listdir(module_path) # Ignore "native" folders as these are excluded by doxygen. try: dir_contents.remove("native") except ValueError: pass subdirs = [] for item in dir_contents: if (os.path.isdir(os.path.join(module_path, item))): subdirs.append(item) module_groups = {} for subdir in subdirs: subgroup_name = "{n}-{s}".format(n=module_name, s=subdir) module_groups[subgroup_name] = os.path.join(module_path, subdir) module_definiton.append("") module_definiton.append( "/** @defgroup {tag} {n} */".format(tag=subgroup_name, n=subdir) ) module_definiton.append("") module_definiton.append("/** @} */") module_definitions.append("\r\n".join(module_definiton)) # Put the top level files into the main group. for filename in (set(dir_contents) - set(subdirs)): add_doxygen_group(os.path.join(module_path, filename), module_name) # Put subdirectory files into their respective groups. for group_name in module_groups: for dirpath, dirnames, filenames in os.walk(module_groups[group_name]): for filename in filenames: try: add_doxygen_group(os.path.join(dirpath, filename), group_name) except: print("Error preprocessing " + filename) continue # Create an extra header file containing the module hierarchy. with open(os.path.join(args.dest_dir, "juce_modules.dox"), "w") as f: f.write("\r\n\r\n".join(module_definitions))