diff options
Diffstat (limited to 'codegen/vulkan/scripts/genRef.py')
-rwxr-xr-x | codegen/vulkan/scripts/genRef.py | 1087 |
1 files changed, 1087 insertions, 0 deletions
diff --git a/codegen/vulkan/scripts/genRef.py b/codegen/vulkan/scripts/genRef.py new file mode 100755 index 00000000..f108204d --- /dev/null +++ b/codegen/vulkan/scripts/genRef.py @@ -0,0 +1,1087 @@ +#!/usr/bin/python3 +# +# Copyright 2016-2021 The Khronos Group Inc. +# +# SPDX-License-Identifier: Apache-2.0 + +# genRef.py - create API ref pages from spec source files +# +# Usage: genRef.py files + +import argparse +import io +import os +import re +import sys +from collections import OrderedDict +from reflib import (findRefs, fixupRefs, loadFile, logDiag, logWarn, + printPageInfo, setLogFile) +from reg import Registry +from vkconventions import VulkanConventions as APIConventions + + +# refpage 'type' attributes which are API entities and contain structured +# content such as API includes, valid usage blocks, etc. +refpage_api_types = ( + 'basetypes', + 'consts', + 'defines', + 'enums', + 'flags', + 'funcpointers', + 'handles', + 'protos', + 'structs', +) + +# Other refpage types - SPIR-V builtins, API feature blocks, etc. - which do +# not have structured content. +refpage_other_types = ( + 'builtins', + 'feature', + 'freeform', + 'spirv' +) + +def makeExtensionInclude(name): + """Return an include command, given an extension name.""" + return 'include::{}/refpage.{}{}[]'.format( + conventions.specification_path, + name, + conventions.file_suffix) + + +def makeAPIInclude(type, name): + """Return an include command for a generated API interface + - type - type of the API, e.g. 'flags', 'handles', etc + - name - name of the API""" + + return 'include::{}/api/{}/{}{}\n'.format( + conventions.refpage_generated_include_path, + type, name, conventions.file_suffix) + + +def isextension(name): + """Return True if name is an API extension name (ends with an upper-case + author ID). + + This assumes that author IDs are at least two characters.""" + return name[-2:].isalpha() and name[-2:].isupper() + + +def printCopyrightSourceComments(fp): + """Print Khronos CC-BY copyright notice on open file fp. + + Writes an asciidoc comment block, which copyrights the source + file.""" + print('// Copyright 2014-2021 The Khronos Group, Inc.', file=fp) + print('//', file=fp) + # This works around constraints of the 'reuse' tool + print('// SPDX' + '-License-Identifier: CC-BY-4.0', file=fp) + print('', file=fp) + + +def printFooter(fp, leveloffset=0): + """Print footer material at the end of each refpage on open file fp. + + If generating separate refpages, adds the copyright. + If generating the single combined refpage, just add a separator. + + - leveloffset - number of levels to bias section titles up or down.""" + + # Generate the section header. + # Default depth is 2. + depth = max(0, leveloffset + 2) + prefix = '=' * depth + + print('ifdef::doctype-manpage[]', + f'{prefix} Copyright', + '', + 'include::{config}/copyright-ccby.txt[]', + 'endif::doctype-manpage[]', + '', + 'ifndef::doctype-manpage[]', + '<<<', + 'endif::doctype-manpage[]', + '', + sep='\n', file=fp) + + +def macroPrefix(name): + """Add a spec asciidoc macro prefix to an API name, depending on its type + (protos, structs, enums, etc.). + + If the name is not recognized, use the generic link macro 'reflink:'.""" + if name in api.basetypes: + return 'basetype:' + name + if name in api.defines: + return 'dlink:' + name + if name in api.enums: + return 'elink:' + name + if name in api.flags: + return 'tlink:' + name + if name in api.funcpointers: + return 'tlink:' + name + if name in api.handles: + return 'slink:' + name + if name in api.protos: + return 'flink:' + name + if name in api.structs: + return 'slink:' + name + if name == 'TBD': + return 'No cross-references are available' + return 'reflink:' + name + + +def seeAlsoList(apiName, explicitRefs=None, apiAliases=[]): + """Return an asciidoc string with a list of 'See Also' references for the + API entity 'apiName', based on the relationship mapping in the api module. + + 'explicitRefs' is a list of additional cross-references. + + If apiAliases is not None, it is a list of aliases of apiName whose + cross-references will also be included. + + If no relationships are available, return None.""" + + refs = set(()) + + # apiName and its aliases are treated equally + allApis = apiAliases.copy() + allApis.append(apiName) + + # Add all the implicit references to refs + for name in allApis: + if name in api.mapDict: + refs.update(api.mapDict[name]) + + # Add all the explicit references + if explicitRefs is not None: + if isinstance(explicitRefs, str): + explicitRefs = explicitRefs.split() + refs.update(name for name in explicitRefs) + + # Add extensions / core versions based on dependencies + for name in allApis: + if name in api.requiredBy: + for (base,dependency) in api.requiredBy[name]: + refs.add(base) + if dependency is not None: + refs.add(dependency) + + if len(refs) == 0: + return None + else: + return ', '.join(macroPrefix(name) for name in sorted(refs)) + '\n' + + +def remapIncludes(lines, baseDir, specDir): + """Remap include directives in a list of lines so they can be extracted to a + different directory. + + Returns remapped lines. + + - lines - text to remap + - baseDir - target directory + - specDir - source directory""" + # This should be compiled only once + includePat = re.compile(r'^include::(?P<path>.*)\[\]') + + newLines = [] + for line in lines: + matches = includePat.search(line) + if matches is not None: + path = matches.group('path') + + if path[0] != '{': + # Relative path to include file from here + incPath = specDir + '/' + path + # Remap to be relative to baseDir + newPath = os.path.relpath(incPath, baseDir) + newLine = 'include::' + newPath + '[]\n' + logDiag('remapIncludes: remapping', line, '->', newLine) + newLines.append(newLine) + else: + # An asciidoctor variable starts the path. + # This must be an absolute path, not needing to be rewritten. + newLines.append(line) + else: + newLines.append(line) + return newLines + + +def refPageShell(pageName, pageDesc, fp, head_content = None, sections=None, tail_content=None, man_section=3): + """Generate body of a reference page. + + - pageName - string name of the page + - pageDesc - string short description of the page + - fp - file to write to + - head_content - text to include before the sections + - sections - iterable returning (title,body) for each section. + - tail_content - text to include after the sections + - man_section - Unix man page section""" + + printCopyrightSourceComments(fp) + + print(':data-uri:', + ':icons: font', + conventions.extra_refpage_headers, + '', + sep='\n', file=fp) + + s = '{}({})'.format(pageName, man_section) + print('= ' + s, + '', + sep='\n', file=fp) + if pageDesc.strip() == '': + pageDesc = 'NO SHORT DESCRIPTION PROVIDED' + logWarn('refPageHead: no short description provided for', pageName) + + print('== Name', + '{} - {}'.format(pageName, pageDesc), + '', + sep='\n', file=fp) + + if head_content is not None: + print(head_content, + '', + sep='\n', file=fp) + + if sections is not None: + for title, content in sections.items(): + print('== {}'.format(title), + '', + content, + '', + sep='\n', file=fp) + + if tail_content is not None: + print(tail_content, + '', + sep='\n', file=fp) + + +def refPageHead(pageName, pageDesc, specText, fieldName, fieldText, descText, fp): + """Generate header of a reference page. + + - pageName - string name of the page + - pageDesc - string short description of the page + - specType - string containing 'spec' field from refpage open block, or None. + Used to determine containing spec name and URL. + - specText - string that goes in the "C Specification" section + - fieldName - string heading an additional section following specText, if not None + - fieldText - string that goes in the additional section + - descText - string that goes in the "Description" section + - fp - file to write to""" + sections = OrderedDict() + + if specText is not None: + sections['C Specification'] = specText + + if fieldName is not None: + sections[fieldName] = fieldText + + if descText is None or descText.strip() == '': + logWarn('refPageHead: no description provided for', pageName) + + if descText is not None: + sections['Description'] = descText + + refPageShell(pageName, pageDesc, fp, head_content=None, sections=sections) + + +def refPageTail(pageName, + specType=None, + specAnchor=None, + seeAlso=None, + fp=None, + auto=False, + leveloffset=0): + """Generate end boilerplate of a reference page. + + - pageName - name of the page + - specType - None or the 'spec' attribute from the refpage block, + identifying the specification name and URL this refpage links to. + - specAnchor - None or the 'anchor' attribute from the refpage block, + identifying the anchor in the specification this refpage links to. If + None, the pageName is assumed to be a valid anchor. + - seeAlso - text of the "See Also" section + - fp - file to write the page to + - auto - True if this is an entirely generated refpage, False if it's + handwritten content from the spec. + - leveloffset - number of levels to bias section titles up or down.""" + + specName = conventions.api_name(specType) + specURL = conventions.specURL(specType) + if specAnchor is None: + specAnchor = pageName + + if seeAlso is None: + seeAlso = 'No cross-references are available\n' + + notes = [ + 'For more information, see the {}#{}[{} Specification^]'.format( + specURL, specAnchor, specName), + '', + ] + + if auto: + notes.extend(( + 'This page is a generated document.', + 'Fixes and changes should be made to the generator scripts, ' + 'not directly.', + )) + else: + notes.extend(( + 'This page is extracted from the ' + specName + ' Specification. ', + 'Fixes and changes should be made to the Specification, ' + 'not directly.', + )) + + # Generate the section header. + # Default depth is 2. + depth = max(0, leveloffset + 2) + prefix = '=' * depth + + print(f'{prefix} See Also', + '', + seeAlso, + '', + sep='\n', file=fp) + + print(f'{prefix} Document Notes', + '', + '\n'.join(notes), + '', + sep='\n', file=fp) + + printFooter(fp, leveloffset) + + +def xrefRewriteInitialize(): + """Initialize substitution patterns for asciidoctor xrefs.""" + + global refLinkPattern, refLinkSubstitute + global refLinkTextPattern, refLinkTextSubstitute + global specLinkPattern, specLinkSubstitute + + # These are xrefs to Vulkan API entities, rewritten to link to refpages + # The refLink variants are for xrefs with only an anchor and no text. + # The refLinkText variants are for xrefs with both anchor and text + refLinkPattern = re.compile(r'<<([Vv][Kk][^>,]+)>>') + refLinkSubstitute = r'link:\1.html[\1^]' + + refLinkTextPattern = re.compile(r'<<([Vv][Kk][^>,]+)[,]?[ \t\n]*([^>,]*)>>') + refLinkTextSubstitute = r'link:\1.html[\2^]' + + # These are xrefs to other anchors, rewritten to link to the spec + specLinkPattern = re.compile(r'<<([^>,]+)[,]?[ \t\n]*([^>,]*)>>') + + # Unfortunately, specLinkSubstitute depends on the link target, + # so can't be constructed in advance. + specLinkSubstitute = None + + +def xrefRewrite(text, specURL): + """Rewrite asciidoctor xrefs in text to resolve properly in refpages. + Xrefs which are to Vulkan refpages are rewritten to link to those + refpages. The remainder are rewritten to generate external links into + the supplied specification document URL. + + - text - string to rewrite, or None + - specURL - URL to target + + Returns rewritten text, or None, respectively""" + + global refLinkPattern, refLinkSubstitute + global refLinkTextPattern, refLinkTextSubstitute + global specLinkPattern, specLinkSubstitute + + specLinkSubstitute = r'link:{}#\1[\2^]'.format(specURL) + + if text is not None: + text, _ = refLinkPattern.subn(refLinkSubstitute, text) + text, _ = refLinkTextPattern.subn(refLinkTextSubstitute, text) + text, _ = specLinkPattern.subn(specLinkSubstitute, text) + + return text + +def emitPage(baseDir, specDir, pi, file): + """Extract a single reference page into baseDir. + + - baseDir - base directory to emit page into + - specDir - directory extracted page source came from + - pi - pageInfo for this page relative to file + - file - list of strings making up the file, indexed by pi""" + pageName = baseDir + '/' + pi.name + '.txt' + + # Add a dictionary entry for this page + global genDict + genDict[pi.name] = None + logDiag('emitPage:', pageName) + + # Short description + if pi.desc is None: + pi.desc = '(no short description available)' + + # Member/parameter section label and text, if there is one + field = None + fieldText = None + + # Only do structural checks on API pages + if pi.type in refpage_api_types: + if pi.include is None: + logWarn('emitPage:', pageName, 'INCLUDE is None, no page generated') + return + + # Specification text + lines = remapIncludes(file[pi.begin:pi.include + 1], baseDir, specDir) + specText = ''.join(lines) + + if pi.param is not None: + if pi.type == 'structs': + field = 'Members' + elif pi.type in ['protos', 'funcpointers']: + field = 'Parameters' + else: + logWarn('emitPage: unknown field type:', pi.type, + 'for', pi.name) + lines = remapIncludes(file[pi.param:pi.body], baseDir, specDir) + fieldText = ''.join(lines) + + # Description text + if pi.body != pi.include: + lines = remapIncludes(file[pi.body:pi.end + 1], baseDir, specDir) + descText = ''.join(lines) + else: + descText = None + logWarn('emitPage: INCLUDE == BODY, so description will be empty for', pi.name) + if pi.begin != pi.include: + logWarn('emitPage: Note: BEGIN != INCLUDE, so the description might be incorrectly located before the API include!') + elif pi.type in refpage_other_types: + specText = None + descText = ''.join(file[pi.begin:pi.end + 1]) + else: + # This should be caught in the spec markup checking tests + logErr(f"emitPage: refpage type='{pi.type}' is unrecognized") + + # Rewrite asciidoctor xrefs to resolve properly in refpages + specURL = conventions.specURL(pi.spec) + + specText = xrefRewrite(specText, specURL) + fieldText = xrefRewrite(fieldText, specURL) + descText = xrefRewrite(descText, specURL) + + fp = open(pageName, 'w', encoding='utf-8') + refPageHead(pi.name, + pi.desc, + specText, + field, fieldText, + descText, + fp) + refPageTail(pageName=pi.name, + specType=pi.spec, + specAnchor=pi.anchor, + seeAlso=seeAlsoList(pi.name, pi.refs, pi.alias.split()), + fp=fp, + auto=False) + fp.close() + + +def autoGenEnumsPage(baseDir, pi, file): + """Autogenerate a single reference page in baseDir. + + Script only knows how to do this for /enums/ pages, at present. + + - baseDir - base directory to emit page into + - pi - pageInfo for this page relative to file + - file - list of strings making up the file, indexed by pi""" + pageName = baseDir + '/' + pi.name + '.txt' + fp = open(pageName, 'w', encoding='utf-8') + + # Add a dictionary entry for this page + global genDict + genDict[pi.name] = None + logDiag('autoGenEnumsPage:', pageName) + + # Short description + if pi.desc is None: + pi.desc = '(no short description available)' + + # Description text. Allow for the case where an enum definition + # is not embedded. + if not pi.embed: + embedRef = '' + else: + embedRef = ''.join(( + ' * The reference page for ', + macroPrefix(pi.embed), + ', where this interface is defined.\n')) + + txt = ''.join(( + 'For more information, see:\n\n', + embedRef, + ' * The See Also section for other reference pages using this type.\n', + ' * The ' + apiName + ' Specification.\n')) + + refPageHead(pi.name, + pi.desc, + ''.join(file[pi.begin:pi.include + 1]), + None, None, + txt, + fp) + refPageTail(pageName=pi.name, + specType=pi.spec, + specAnchor=pi.anchor, + seeAlso=seeAlsoList(pi.name, pi.refs, pi.alias.split()), + fp=fp, + auto=True) + fp.close() + + +# Pattern to break apart an API *Flags{authorID} name, used in +# autoGenFlagsPage. +flagNamePat = re.compile(r'(?P<name>\w+)Flags(?P<author>[A-Z]*)') + + +def autoGenFlagsPage(baseDir, flagName): + """Autogenerate a single reference page in baseDir for an API *Flags type. + + - baseDir - base directory to emit page into + - flagName - API *Flags name""" + pageName = baseDir + '/' + flagName + '.txt' + fp = open(pageName, 'w', encoding='utf-8') + + # Add a dictionary entry for this page + global genDict + genDict[flagName] = None + logDiag('autoGenFlagsPage:', pageName) + + # Short description + matches = flagNamePat.search(flagName) + if matches is not None: + name = matches.group('name') + author = matches.group('author') + logDiag('autoGenFlagsPage: split name into', name, 'Flags', author) + flagBits = name + 'FlagBits' + author + desc = 'Bitmask of ' + flagBits + else: + logWarn('autoGenFlagsPage:', pageName, 'does not end in "Flags{author ID}". Cannot infer FlagBits type.') + flagBits = None + desc = 'Unknown ' + apiName + ' flags type' + + # Description text + if flagBits is not None: + txt = ''.join(( + 'etext:' + flagName, + ' is a mask of zero or more elink:' + flagBits + '.\n', + 'It is used as a member and/or parameter of the structures and commands\n', + 'in the See Also section below.\n')) + else: + txt = ''.join(( + 'etext:' + flagName, + ' is an unknown ' + apiName + ' type, assumed to be a bitmask.\n')) + + refPageHead(flagName, + desc, + makeAPIInclude('flags', flagName), + None, None, + txt, + fp) + refPageTail(pageName=flagName, + specType=pi.spec, + specAnchor=pi.anchor, + seeAlso=seeAlsoList(flagName, None), + fp=fp, + auto=True) + fp.close() + + +def autoGenHandlePage(baseDir, handleName): + """Autogenerate a single handle page in baseDir for an API handle type. + + - baseDir - base directory to emit page into + - handleName - API handle name""" + # @@ Need to determine creation function & add handles/ include for the + # @@ interface in generator.py. + pageName = baseDir + '/' + handleName + '.txt' + fp = open(pageName, 'w', encoding='utf-8') + + # Add a dictionary entry for this page + global genDict + genDict[handleName] = None + logDiag('autoGenHandlePage:', pageName) + + # Short description + desc = apiName + ' object handle' + + descText = ''.join(( + 'sname:' + handleName, + ' is an object handle type, referring to an object used\n', + 'by the ' + apiName + ' implementation. These handles are created or allocated\n', + 'by the @@ TBD @@ function, and used by other ' + apiName + ' structures\n', + 'and commands in the See Also section below.\n')) + + refPageHead(handleName, + desc, + makeAPIInclude('handles', handleName), + None, None, + descText, + fp) + refPageTail(pageName=handleName, + specType=pi.spec, + specAnchor=pi.anchor, + seeAlso=seeAlsoList(handleName, None), + fp=fp, + auto=True) + fp.close() + + +def genRef(specFile, baseDir): + """Extract reference pages from a spec asciidoc source file. + + - specFile - filename to extract from + - baseDir - output directory to generate page in""" + file = loadFile(specFile) + if file is None: + return + + # Save the path to this file for later use in rewriting relative includes + specDir = os.path.dirname(os.path.abspath(specFile)) + + pageMap = findRefs(file, specFile) + logDiag(specFile + ': found', len(pageMap.keys()), 'potential pages') + + sys.stderr.flush() + + # Fix up references in pageMap + fixupRefs(pageMap, specFile, file) + + # Create each page, if possible + pages = {} + + for name in sorted(pageMap): + pi = pageMap[name] + + # Only generate the page if it's in the requested build + # 'freeform' pages are always generated + # 'feature' pages (core versions & extensions) are generated if they're in + # the requested feature list + # All other pages (APIs) are generated if they're in the API map for + # the build. + if pi.type in refpage_api_types: + if name not in api.typeCategory: + # Also check aliases of name - api.nonexistent is the same + # mapping used to rewrite *link: macros in this build. + if name not in api.nonexistent: + logWarn(f'genRef: NOT generating feature page {name} - API not in this build') + continue + else: + logWarn(f'genRef: generating feature page {name} because its alias {api.nonexistent[name]} exists') + elif pi.type in refpage_other_types: + # The only non-API type which can be checked is a feature refpage + if pi.type == 'feature': + if name not in api.features: + logWarn(f'genRef: NOT generating feature page {name} - feature not in this build') + continue + + printPageInfo(pi, file) + + if pi.Warning: + logDiag('genRef:', pi.name + ':', pi.Warning) + + if pi.extractPage: + emitPage(baseDir, specDir, pi, file) + elif pi.type == 'enums': + autoGenEnumsPage(baseDir, pi, file) + elif pi.type == 'flags': + autoGenFlagsPage(baseDir, pi.name) + else: + # Don't extract this page + logWarn('genRef: Cannot extract or autogenerate:', pi.name) + + pages[pi.name] = pi + for alias in pi.alias.split(): + pages[alias] = pi + + return pages + + +def genSinglePageRef(baseDir): + """Generate baseDir/apispec.txt, the single-page version of the ref pages. + + This assumes there's a page for everything in the api module dictionaries. + Extensions (KHR, EXT, etc.) are currently skipped""" + # Accumulate head of page + head = io.StringIO() + + printCopyrightSourceComments(head) + + print('= ' + apiName + ' API Reference Pages', + ':data-uri:', + ':icons: font', + ':doctype: book', + ':numbered!:', + ':max-width: 200', + ':data-uri:', + ':toc2:', + ':toclevels: 2', + '', + sep='\n', file=head) + + print('== Copyright', file=head) + print('', file=head) + print('include::{config}/copyright-ccby.txt[]', file=head) + print('', file=head) + # Inject the table of contents. Asciidoc really ought to be generating + # this for us. + + sections = [ + [api.protos, 'protos', apiName + ' Commands'], + [api.handles, 'handles', 'Object Handles'], + [api.structs, 'structs', 'Structures'], + [api.enums, 'enums', 'Enumerations'], + [api.flags, 'flags', 'Flags'], + [api.funcpointers, 'funcpointers', 'Function Pointer Types'], + [api.basetypes, 'basetypes', apiName + ' Scalar types'], + [api.defines, 'defines', 'C Macro Definitions'], + [extensions, 'extensions', apiName + ' Extensions'] + ] + + # Accumulate body of page + body = io.StringIO() + + for (apiDict, label, title) in sections: + # Add section title/anchor header to body + anchor = '[[' + label + ',' + title + ']]' + print(anchor, + '== ' + title, + '', + ':leveloffset: 2', + '', + sep='\n', file=body) + + if label == 'extensions': + # preserve order of extensions since we already sorted the way we want. + keys = apiDict.keys() + else: + keys = sorted(apiDict.keys()) + + for refPage in keys: + # Don't generate links for aliases, which are included with the + # aliased page + if refPage not in api.alias: + # Add page to body + if 'FlagBits' in refPage and conventions.unified_flag_refpages: + # OpenXR does not create separate ref pages for FlagBits: + # the FlagBits includes go in the Flags refpage. + # Previously the Vulkan script would only emit non-empty + # Vk*Flags pages, via the logic + # if refPage not in api.flags or api.flags[refPage] is not None + # emit page + # Now, all are emitted. + continue + else: + print('include::' + refPage + '.txt[]', file=body) + else: + # Alternatively, we could (probably should) link to the + # aliased refpage + logWarn('(Benign) Not including', refPage, + 'in single-page reference', + 'because it is an alias of', api.alias[refPage]) + + print('\n' + ':leveloffset: 0' + '\n', file=body) + + # Write head and body to the output file + pageName = baseDir + '/apispec.txt' + fp = open(pageName, 'w', encoding='utf-8') + + print(head.getvalue(), file=fp, end='') + print(body.getvalue(), file=fp, end='') + + head.close() + body.close() + fp.close() + + +def genExtension(baseDir, extpath, name, info): + """Generate refpage, and add dictionary entry for an extension + + - baseDir - output directory to generate page in + - extpath - None, or path to per-extension specification sources if + those are to be included in extension refpages + - name - extension name + - info - <extension> Element from XML""" + + # Add a dictionary entry for this page + global genDict + genDict[name] = None + declares = [] + elem = info.elem + + # Type of extension (instance, device, etc.) + ext_type = elem.get('type') + + # Autogenerate interfaces from <extension> entry + for required in elem.find('require'): + req_name = required.get('name') + if not req_name: + # This isn't what we're looking for + continue + if req_name.endswith('_SPEC_VERSION') or req_name.endswith('_EXTENSION_NAME'): + # Don't link to spec version or extension name - those ref pages aren't created. + continue + + if required.get('extends'): + # These are either extensions of enumerated types, or const enum + # values: neither of which get a ref page - although we could + # include the enumerated types in the See Also list. + continue + + if req_name not in genDict: + logWarn('ERROR: {} (in extension {}) does not have a ref page.'.format(req_name, name)) + + declares.append(req_name) + + # import pdb + # pdb.set_trace() + + appbody = None + if extpath is not None: + appfp = open('{}/{}.txt'.format(extpath, name), 'r', encoding='utf-8') + if appfp is not None: + appbody = appfp.read() + + # Transform internal links to crosslinks + specURL = conventions.specURL() + appbody = xrefRewrite(appbody, specURL) + else: + logWarn('Cannot find extension appendix for', name) + + # Fall through to autogenerated page + extpath = None + appbody = None + appfp.close() + + # Include the extension appendix without an extra title + # head_content = 'include::{{appendices}}/{}.txt[]'.format(name) + + # Write the extension refpage + pageName = baseDir + '/' + name + '.txt' + logDiag('genExtension:', pageName) + fp = open(pageName, 'w', encoding='utf-8') + + # There are no generated titled sections + sections = None + + # 'See link:{html_spec_relative}#%s[ %s] in the main specification for complete information.' % ( + # name, name) + refPageShell(name, + "{} extension".format(ext_type), + fp, + appbody, + sections=sections) + + # The generated metadata include moved the leveloffset attribute by -1 + # to account for the relative structuring of the spec extension appendix + # section structure vs. the refpages. + # This restores leveloffset for the boilerplate in refPageTail. + refPageTail(pageName=name, + specType=None, + specAnchor=name, + seeAlso=seeAlsoList(name, declares), + fp=fp, + auto=True, + leveloffset=1) + fp.close() + + +if __name__ == '__main__': + global genDict, extensions, conventions, apiName + genDict = {} + extensions = OrderedDict() + conventions = APIConventions() + apiName = conventions.api_name('api') + + parser = argparse.ArgumentParser() + + parser.add_argument('-diag', action='store', dest='diagFile', + help='Set the diagnostic file') + parser.add_argument('-warn', action='store', dest='warnFile', + help='Set the warning file') + parser.add_argument('-log', action='store', dest='logFile', + help='Set the log file for both diagnostics and warnings') + parser.add_argument('-genpath', action='store', + default='gen', + help='Path to directory containing generated files') + parser.add_argument('-basedir', action='store', dest='baseDir', + default=None, + help='Set the base directory in which pages are generated') + parser.add_argument('-noauto', action='store_true', + help='Don\'t generate inferred ref pages automatically') + parser.add_argument('files', metavar='filename', nargs='*', + help='a filename to extract ref pages from') + parser.add_argument('--version', action='version', version='%(prog)s 1.0') + parser.add_argument('-extension', action='append', + default=[], + help='Specify an extension or extensions to add to targets') + parser.add_argument('-rewrite', action='store', + default=None, + help='Name of output file to write Apache mod_rewrite directives to') + parser.add_argument('-toc', action='store', + default=None, + help='Name of output file to write an alphabetical TOC to') + parser.add_argument('-registry', action='store', + default=conventions.registry_path, + help='Use specified registry file instead of default') + parser.add_argument('-extpath', action='store', + default=None, + help='Use extension descriptions from this directory instead of autogenerating extension refpages') + + results = parser.parse_args() + + # Look for api.py in the specified directory + if results.genpath is not None: + sys.path.insert(0, results.genpath) + import api + + setLogFile(True, True, results.logFile) + setLogFile(True, False, results.diagFile) + setLogFile(False, True, results.warnFile) + + # Initialize static rewrite patterns for spec xrefs + xrefRewriteInitialize() + + if results.baseDir is None: + baseDir = results.genpath + '/ref' + else: + baseDir = results.baseDir + + # Dictionary of pages & aliases + pages = {} + + for file in results.files: + d = genRef(file, baseDir) + pages.update(d) + + # Now figure out which pages *weren't* generated from the spec. + # This relies on the dictionaries of API constructs in the api module. + + if not results.noauto: + registry = Registry() + registry.loadFile(results.registry) + + if conventions.write_refpage_include: + # Only extensions with a supported="..." attribute in this set + # will be considered for extraction/generation. + supported_strings = set((conventions.xml_api_name,)) + ext_names = set(k for k, v in registry.extdict.items() + if v.supported in supported_strings) + + desired_extensions = ext_names.intersection(set(results.extension)) + for prefix in conventions.extension_index_prefixes: + # Splits up into chunks, sorted within each chunk. + filtered_extensions = sorted( + [name for name in desired_extensions + if name.startswith(prefix) and name not in extensions]) + for name in filtered_extensions: + # logWarn('NOT autogenerating extension refpage for', name) + extensions[name] = None + genExtension(baseDir, results.extpath, name, registry.extdict[name]) + + # autoGenFlagsPage is no longer needed because they are added to + # the spec sources now. + # for page in api.flags: + # if page not in genDict: + # autoGenFlagsPage(baseDir, page) + + # autoGenHandlePage is no longer needed because they are added to + # the spec sources now. + # for page in api.structs: + # if typeCategory[page] == 'handle': + # autoGenHandlePage(baseDir, page) + + sections = [ + (api.flags, 'Flag Types'), + (api.enums, 'Enumerated Types'), + (api.structs, 'Structures'), + (api.protos, 'Prototypes'), + (api.funcpointers, 'Function Pointers'), + (api.basetypes, apiName + ' Scalar Types'), + (extensions, apiName + ' Extensions'), + ] + + # Summarize pages that weren't generated, for good or bad reasons + + for (apiDict, title) in sections: + # OpenXR was keeping a 'flagged' state which only printed out a + # warning for the first non-generated page, but was otherwise + # unused. This doesn't seem helpful. + for page in apiDict: + if page not in genDict: + # Page was not generated - why not? + if page in api.alias: + logWarn('(Benign, is an alias) Ref page for', title, page, 'is aliased into', api.alias[page]) + elif page in api.flags and api.flags[page] is None: + logWarn('(Benign, no FlagBits defined) No ref page generated for ', title, + page) + else: + # Could introduce additional logic to detect + # external types and not emit them. + logWarn('No ref page generated for ', title, page) + + genSinglePageRef(baseDir) + + if results.rewrite: + # Generate Apache rewrite directives for refpage aliases + fp = open(results.rewrite, 'w', encoding='utf-8') + + for page in sorted(pages): + p = pages[page] + rewrite = p.name + + if page != rewrite: + print('RewriteRule ^', page, '.html$ ', rewrite, '.html', + sep='', file=fp) + fp.close() + + if results.toc: + # Generate dynamic portion of refpage TOC + fp = open(results.toc, 'w', encoding='utf-8') + + # Run through dictionary of pages generating an TOC + print(12 * ' ', '<li class="Level1">Alphabetic Contents', sep='', file=fp) + print(16 * ' ', '<ul class="Level2">', sep='', file=fp) + lastLetter = None + + for page in sorted(pages, key=str.upper): + p = pages[page] + letter = page[0:1].upper() + + if letter != lastLetter: + if lastLetter: + # End previous block + print(24 * ' ', '</ul>', sep='', file=fp) + print(20 * ' ', '</li>', sep='', file=fp) + # Start new block + print(20 * ' ', '<li>', letter, sep='', file=fp) + print(24 * ' ', '<ul class="Level3">', sep='', file=fp) + lastLetter = letter + + # Add this page to the list + print(28 * ' ', '<li><a href="', p.name, '.html" ', + 'target="pagedisplay">', page, '</a></li>', + sep='', file=fp) + + if lastLetter: + # Close the final letter block + print(24 * ' ', '</ul>', sep='', file=fp) + print(20 * ' ', '</li>', sep='', file=fp) + + # Close the list + print(16 * ' ', '</ul>', sep='', file=fp) + print(12 * ' ', '</li>', sep='', file=fp) + + # print('name {} -> page {}'.format(page, pages[page].name)) + + fp.close() |