1#!/usr/bin/python3 2# 3# Copyright (c) 2016-2020 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7# genRef.py - create API ref pages from spec source files 8# 9# Usage: genRef.py files 10 11import argparse 12import io 13import os 14import re 15import sys 16from collections import OrderedDict 17from reflib import (findRefs, fixupRefs, loadFile, logDiag, logWarn, 18 printPageInfo, setLogFile) 19from reg import Registry 20from vkconventions import VulkanConventions as APIConventions 21 22 23def makeExtensionInclude(name): 24 """Return an include command, given an extension name.""" 25 return 'include::{}/refpage.{}{}[]'.format( 26 conventions.specification_path, 27 name, 28 conventions.file_suffix) 29 30 31def makeAPIInclude(type, name): 32 """Return an include command for a generated API interface 33 - type - type of the API, e.g. 'flags', 'handles', etc 34 - name - name of the API""" 35 36 return 'include::{}/api/{}/{}{}\n'.format( 37 conventions.refpage_generated_include_path, 38 type, name, conventions.file_suffix) 39 40 41def isextension(name): 42 """Return True if name is an API extension name (ends with an upper-case 43 author ID). 44 45 This assumes that author IDs are at least two characters.""" 46 return name[-2:].isalpha() and name[-2:].isupper() 47 48 49def printCopyrightSourceComments(fp): 50 """Print Khronos CC-BY copyright notice on open file fp. 51 52 Writes an asciidoc comment block, which copyrights the source 53 file.""" 54 print('// Copyright (c) 2014-2020 Khronos Group.', file=fp) 55 print('//', file=fp) 56 # This works around constraints of the 'reuse' tool 57 print('// SPDX' + '-License-Identifier: CC-BY-4.0', file=fp) 58 print('', file=fp) 59 60 61def printFooter(fp): 62 """Print footer material at the end of each refpage on open file fp. 63 64 If generating separate refpages, adds the copyright. 65 If generating the single combined refpage, just add a separator.""" 66 67 print('ifdef::doctype-manpage[]', 68 '== Copyright', 69 '', 70 'include::{config}/copyright-ccby.txt[]', 71 'endif::doctype-manpage[]', 72 '', 73 'ifndef::doctype-manpage[]', 74 '<<<', 75 'endif::doctype-manpage[]', 76 '', 77 sep='\n', file=fp) 78 79 80def macroPrefix(name): 81 """Add a spec asciidoc macro prefix to an API name, depending on its type 82 (protos, structs, enums, etc.). 83 84 If the name is not recognized, use the generic link macro 'reflink:'.""" 85 if name in api.basetypes: 86 return 'basetype:' + name 87 if name in api.defines: 88 return 'dlink:' + name 89 if name in api.enums: 90 return 'elink:' + name 91 if name in api.flags: 92 return 'elink:' + name 93 if name in api.funcpointers: 94 return 'tlink:' + name 95 if name in api.handles: 96 return 'slink:' + name 97 if name in api.protos: 98 return 'flink:' + name 99 if name in api.structs: 100 return 'slink:' + name 101 if name == 'TBD': 102 return 'No cross-references are available' 103 return 'reflink:' + name 104 105 106def seeAlsoList(apiName, explicitRefs=None): 107 """Return an asciidoc string with a list of 'See Also' references for the 108 API entity 'apiName', based on the relationship mapping in the api module. 109 110 'explicitRefs' is a list of additional cross-references. 111 If no relationships are available, return None.""" 112 refs = {} 113 114 # Add all the implicit references to refs 115 if apiName in api.mapDict: 116 for name in sorted(api.mapDict[apiName]): 117 refs[name] = None 118 119 # Add all the explicit references 120 if explicitRefs is not None: 121 if isinstance(explicitRefs, str): 122 explicitRefs = explicitRefs.split() 123 for name in explicitRefs: 124 refs[name] = None 125 126 if not refs: 127 return None 128 return ', '.join(macroPrefix(name) for name in sorted(refs.keys())) + '\n' 129 130 131def remapIncludes(lines, baseDir, specDir): 132 """Remap include directives in a list of lines so they can be extracted to a 133 different directory. 134 135 Returns remapped lines. 136 137 - lines - text to remap 138 - baseDir - target directory 139 - specDir - source directory""" 140 # This should be compiled only once 141 includePat = re.compile(r'^include::(?P<path>.*)\[\]') 142 143 newLines = [] 144 for line in lines: 145 matches = includePat.search(line) 146 if matches is not None: 147 path = matches.group('path') 148 149 if path[0] != '{': 150 # Relative path to include file from here 151 incPath = specDir + '/' + path 152 # Remap to be relative to baseDir 153 newPath = os.path.relpath(incPath, baseDir) 154 newLine = 'include::' + newPath + '[]\n' 155 logDiag('remapIncludes: remapping', line, '->', newLine) 156 newLines.append(newLine) 157 else: 158 # An asciidoctor variable starts the path. 159 # This must be an absolute path, not needing to be rewritten. 160 newLines.append(line) 161 else: 162 newLines.append(line) 163 return newLines 164 165 166def refPageShell(pageName, pageDesc, fp, head_content = None, sections=None, tail_content=None, man_section=3): 167 """Generate body of a reference page. 168 169 - pageName - string name of the page 170 - pageDesc - string short description of the page 171 - fp - file to write to 172 - head_content - text to include before the sections 173 - sections - iterable returning (title,body) for each section. 174 - tail_content - text to include after the sections 175 - man_section - Unix man page section""" 176 177 printCopyrightSourceComments(fp) 178 179 print(':data-uri:', 180 ':icons: font', 181 conventions.extra_refpage_headers, 182 '', 183 sep='\n', file=fp) 184 185 s = '{}({})'.format(pageName, man_section) 186 print('= ' + s, 187 '', 188 sep='\n', file=fp) 189 if pageDesc.strip() == '': 190 pageDesc = 'NO SHORT DESCRIPTION PROVIDED' 191 logWarn('refPageHead: no short description provided for', pageName) 192 193 print('== Name', 194 '{} - {}'.format(pageName, pageDesc), 195 '', 196 sep='\n', file=fp) 197 198 if head_content is not None: 199 print(head_content, 200 '', 201 sep='\n', file=fp) 202 203 if sections is not None: 204 for title, content in sections.items(): 205 print('== {}'.format(title), 206 '', 207 content, 208 '', 209 sep='\n', file=fp) 210 211 if tail_content is not None: 212 print(tail_content, 213 '', 214 sep='\n', file=fp) 215 216 217def refPageHead(pageName, pageDesc, specText, fieldName, fieldText, descText, fp): 218 """Generate header of a reference page. 219 220 - pageName - string name of the page 221 - pageDesc - string short description of the page 222 - specType - string containing 'spec' field from refpage open block, or None. 223 Used to determine containing spec name and URL. 224 - specText - string that goes in the "C Specification" section 225 - fieldName - string heading an additional section following specText, if not None 226 - fieldText - string that goes in the additional section 227 - descText - string that goes in the "Description" section 228 - fp - file to write to""" 229 sections = OrderedDict() 230 231 if specText is not None: 232 sections['C Specification'] = specText 233 234 if fieldName is not None: 235 sections[fieldName] = fieldText 236 237 if descText is None or descText.strip() == '': 238 logWarn('refPageHead: no description provided for', pageName) 239 240 if descText is not None: 241 sections['Description'] = descText 242 243 refPageShell(pageName, pageDesc, fp, head_content=None, sections=sections) 244 245 246def refPageTail(pageName, 247 specType=None, 248 specAnchor=None, 249 seeAlso=None, 250 fp=None, 251 auto=False): 252 """Generate end boilerplate of a reference page. 253 254 - pageName - name of the page 255 - specType - None or the 'spec' attribute from the refpage block, 256 identifying the specification name and URL this refpage links to. 257 - specAnchor - None or the 'anchor' attribute from the refpage block, 258 identifying the anchor in the specification this refpage links to. If 259 None, the pageName is assumed to be a valid anchor.""" 260 261 specName = conventions.api_name(specType) 262 specURL = conventions.specURL(specType) 263 if specAnchor is None: 264 specAnchor = pageName 265 266 if seeAlso is None: 267 seeAlso = 'No cross-references are available\n' 268 269 notes = [ 270 'For more information, see the {}#{}[{} Specification^]'.format( 271 specURL, specAnchor, specName), 272 '', 273 ] 274 275 if auto: 276 notes.extend(( 277 'This page is a generated document.', 278 'Fixes and changes should be made to the generator scripts, ' 279 'not directly.', 280 )) 281 else: 282 notes.extend(( 283 'This page is extracted from the ' + specName + ' Specification. ', 284 'Fixes and changes should be made to the Specification, ' 285 'not directly.', 286 )) 287 288 print('== See Also', 289 '', 290 seeAlso, 291 '', 292 sep='\n', file=fp) 293 294 print('== Document Notes', 295 '', 296 '\n'.join(notes), 297 '', 298 sep='\n', file=fp) 299 300 printFooter(fp) 301 302 303def xrefRewriteInitialize(): 304 """Initialize substitution patterns for asciidoctor xrefs.""" 305 306 global refLinkPattern, refLinkSubstitute 307 global refLinkTextPattern, refLinkTextSubstitute 308 global specLinkPattern, specLinkSubstitute 309 310 # These are xrefs to Vulkan API entities, rewritten to link to refpages 311 # The refLink variants are for xrefs with only an anchor and no text. 312 # The refLinkText variants are for xrefs with both anchor and text 313 refLinkPattern = re.compile(r'<<([Vv][Kk][^>,]+)>>') 314 refLinkSubstitute = r'link:\1.html[\1^]' 315 316 refLinkTextPattern = re.compile(r'<<([Vv][Kk][^>,]+)[,]?[ \t\n]*([^>,]*)>>') 317 refLinkTextSubstitute = r'link:\1.html[\2^]' 318 319 # These are xrefs to other anchors, rewritten to link to the spec 320 specLinkPattern = re.compile(r'<<([^>,]+)[,]?[ \t\n]*([^>,]*)>>') 321 322 # Unfortunately, specLinkSubstitute depends on the link target, 323 # so can't be constructed in advance. 324 specLinkSubstitute = None 325 326 327def xrefRewrite(text, specURL): 328 """Rewrite asciidoctor xrefs in text to resolve properly in refpages. 329 Xrefs which are to Vulkan refpages are rewritten to link to those 330 refpages. The remainder are rewritten to generate external links into 331 the supplied specification document URL. 332 333 - text - string to rewrite, or None 334 - specURL - URL to target 335 336 Returns rewritten text, or None, respectively""" 337 338 global refLinkPattern, refLinkSubstitute 339 global refLinkTextPattern, refLinkTextSubstitute 340 global specLinkPattern, specLinkSubstitute 341 342 specLinkSubstitute = r'link:{}#\1[\2^]'.format(specURL) 343 344 if text is not None: 345 text, _ = refLinkPattern.subn(refLinkSubstitute, text) 346 text, _ = refLinkTextPattern.subn(refLinkTextSubstitute, text) 347 text, _ = specLinkPattern.subn(specLinkSubstitute, text) 348 349 return text 350 351def emitPage(baseDir, specDir, pi, file): 352 """Extract a single reference page into baseDir. 353 354 - baseDir - base directory to emit page into 355 - specDir - directory extracted page source came from 356 - pi - pageInfo for this page relative to file 357 - file - list of strings making up the file, indexed by pi""" 358 pageName = baseDir + '/' + pi.name + '.txt' 359 360 # Add a dictionary entry for this page 361 global genDict 362 genDict[pi.name] = None 363 logDiag('emitPage:', pageName) 364 365 # Short description 366 if pi.desc is None: 367 pi.desc = '(no short description available)' 368 369 # Member/parameter section label and text, if there is one 370 field = None 371 fieldText = None 372 373 if pi.type != 'freeform': 374 # Not sure how this happens yet 375 if pi.include is None: 376 logWarn('emitPage:', pageName, 'INCLUDE is None, no page generated') 377 return 378 379 # Specification text 380 lines = remapIncludes(file[pi.begin:pi.include + 1], baseDir, specDir) 381 specText = ''.join(lines) 382 383 if pi.param is not None: 384 if pi.type == 'structs': 385 field = 'Members' 386 elif pi.type in ['protos', 'funcpointers']: 387 field = 'Parameters' 388 else: 389 logWarn('emitPage: unknown field type:', pi.type, 390 'for', pi.name) 391 lines = remapIncludes(file[pi.param:pi.body], baseDir, specDir) 392 fieldText = ''.join(lines) 393 394 # Description text 395 if pi.body != pi.include: 396 lines = remapIncludes(file[pi.body:pi.end + 1], baseDir, specDir) 397 descText = ''.join(lines) 398 else: 399 descText = None 400 logWarn('emitPage: INCLUDE == BODY, so description will be empty for', pi.name) 401 if pi.begin != pi.include: 402 logWarn('emitPage: Note: BEGIN != INCLUDE, so the description might be incorrectly located before the API include!') 403 else: 404 specText = None 405 descText = ''.join(file[pi.begin:pi.end + 1]) 406 407 # Rewrite asciidoctor xrefs to resolve properly in refpages 408 specURL = conventions.specURL(pi.spec) 409 410 specText = xrefRewrite(specText, specURL) 411 fieldText = xrefRewrite(fieldText, specURL) 412 descText = xrefRewrite(descText, specURL) 413 414 fp = open(pageName, 'w', encoding='utf-8') 415 refPageHead(pi.name, 416 pi.desc, 417 specText, 418 field, fieldText, 419 descText, 420 fp) 421 refPageTail(pageName=pi.name, 422 specType=pi.spec, 423 specAnchor=pi.anchor, 424 seeAlso=seeAlsoList(pi.name, pi.refs), 425 fp=fp, 426 auto=False) 427 fp.close() 428 429 430def autoGenEnumsPage(baseDir, pi, file): 431 """Autogenerate a single reference page in baseDir. 432 433 Script only knows how to do this for /enums/ pages, at present. 434 435 - baseDir - base directory to emit page into 436 - pi - pageInfo for this page relative to file 437 - file - list of strings making up the file, indexed by pi""" 438 pageName = baseDir + '/' + pi.name + '.txt' 439 fp = open(pageName, 'w', encoding='utf-8') 440 441 # Add a dictionary entry for this page 442 global genDict 443 genDict[pi.name] = None 444 logDiag('autoGenEnumsPage:', pageName) 445 446 # Short description 447 if pi.desc is None: 448 pi.desc = '(no short description available)' 449 450 # Description text. Allow for the case where an enum definition 451 # is not embedded. 452 if not pi.embed: 453 embedRef = '' 454 else: 455 embedRef = ''.join(( 456 ' * The reference page for ', 457 macroPrefix(pi.embed), 458 ', where this interface is defined.\n')) 459 460 txt = ''.join(( 461 'For more information, see:\n\n', 462 embedRef, 463 ' * The See Also section for other reference pages using this type.\n', 464 ' * The ' + apiName + ' Specification.\n')) 465 466 refPageHead(pi.name, 467 pi.desc, 468 ''.join(file[pi.begin:pi.include + 1]), 469 None, None, 470 txt, 471 fp) 472 refPageTail(pageName=pi.name, 473 specType=pi.spec, 474 specAnchor=pi.anchor, 475 seeAlso=seeAlsoList(pi.name, pi.refs), 476 fp=fp, 477 auto=True) 478 fp.close() 479 480 481# Pattern to break apart an API *Flags{authorID} name, used in 482# autoGenFlagsPage. 483flagNamePat = re.compile(r'(?P<name>\w+)Flags(?P<author>[A-Z]*)') 484 485 486def autoGenFlagsPage(baseDir, flagName): 487 """Autogenerate a single reference page in baseDir for an API *Flags type. 488 489 - baseDir - base directory to emit page into 490 - flagName - API *Flags name""" 491 pageName = baseDir + '/' + flagName + '.txt' 492 fp = open(pageName, 'w', encoding='utf-8') 493 494 # Add a dictionary entry for this page 495 global genDict 496 genDict[flagName] = None 497 logDiag('autoGenFlagsPage:', pageName) 498 499 # Short description 500 matches = flagNamePat.search(flagName) 501 if matches is not None: 502 name = matches.group('name') 503 author = matches.group('author') 504 logDiag('autoGenFlagsPage: split name into', name, 'Flags', author) 505 flagBits = name + 'FlagBits' + author 506 desc = 'Bitmask of ' + flagBits 507 else: 508 logWarn('autoGenFlagsPage:', pageName, 'does not end in "Flags{author ID}". Cannot infer FlagBits type.') 509 flagBits = None 510 desc = 'Unknown ' + apiName + ' flags type' 511 512 # Description text 513 if flagBits is not None: 514 txt = ''.join(( 515 'etext:' + flagName, 516 ' is a mask of zero or more elink:' + flagBits + '.\n', 517 'It is used as a member and/or parameter of the structures and commands\n', 518 'in the See Also section below.\n')) 519 else: 520 txt = ''.join(( 521 'etext:' + flagName, 522 ' is an unknown ' + apiName + ' type, assumed to be a bitmask.\n')) 523 524 refPageHead(flagName, 525 desc, 526 makeAPIInclude('flags', flagName), 527 None, None, 528 txt, 529 fp) 530 refPageTail(pageName=flagName, 531 specType=pi.spec, 532 specAnchor=pi.anchor, 533 seeAlso=seeAlsoList(flagName, None), 534 fp=fp, 535 auto=True) 536 fp.close() 537 538 539def autoGenHandlePage(baseDir, handleName): 540 """Autogenerate a single handle page in baseDir for an API handle type. 541 542 - baseDir - base directory to emit page into 543 - handleName - API handle name""" 544 # @@ Need to determine creation function & add handles/ include for the 545 # @@ interface in generator.py. 546 pageName = baseDir + '/' + handleName + '.txt' 547 fp = open(pageName, 'w', encoding='utf-8') 548 549 # Add a dictionary entry for this page 550 global genDict 551 genDict[handleName] = None 552 logDiag('autoGenHandlePage:', pageName) 553 554 # Short description 555 desc = apiName + ' object handle' 556 557 descText = ''.join(( 558 'sname:' + handleName, 559 ' is an object handle type, referring to an object used\n', 560 'by the ' + apiName + ' implementation. These handles are created or allocated\n', 561 'by the @@ TBD @@ function, and used by other ' + apiName + ' structures\n', 562 'and commands in the See Also section below.\n')) 563 564 refPageHead(handleName, 565 desc, 566 makeAPIInclude('handles', handleName), 567 None, None, 568 descText, 569 fp) 570 refPageTail(pageName=handleName, 571 specType=pi.spec, 572 specAnchor=pi.anchor, 573 seeAlso=seeAlsoList(handleName, None), 574 fp=fp, 575 auto=True) 576 fp.close() 577 578 579def genRef(specFile, baseDir): 580 """Extract reference pages from a spec asciidoc source file. 581 582 - specFile - filename to extract from 583 - baseDir - output directory to generate page in""" 584 file = loadFile(specFile) 585 if file is None: 586 return 587 588 # Save the path to this file for later use in rewriting relative includes 589 specDir = os.path.dirname(os.path.abspath(specFile)) 590 591 pageMap = findRefs(file, specFile) 592 logDiag(specFile + ': found', len(pageMap.keys()), 'potential pages') 593 594 sys.stderr.flush() 595 596 # Fix up references in pageMap 597 fixupRefs(pageMap, specFile, file) 598 599 # Create each page, if possible 600 pages = {} 601 602 for name in sorted(pageMap): 603 pi = pageMap[name] 604 605 printPageInfo(pi, file) 606 607 if pi.Warning: 608 logDiag('genRef:', pi.name + ':', pi.Warning) 609 610 if pi.extractPage: 611 emitPage(baseDir, specDir, pi, file) 612 elif pi.type == 'enums': 613 autoGenEnumsPage(baseDir, pi, file) 614 elif pi.type == 'flags': 615 autoGenFlagsPage(baseDir, pi.name) 616 else: 617 # Don't extract this page 618 logWarn('genRef: Cannot extract or autogenerate:', pi.name) 619 620 pages[pi.name] = pi 621 for alias in pi.alias.split(): 622 pages[alias] = pi 623 624 return pages 625 626 627def genSinglePageRef(baseDir): 628 """Generate baseDir/apispec.txt, the single-page version of the ref pages. 629 630 This assumes there's a page for everything in the api module dictionaries. 631 Extensions (KHR, EXT, etc.) are currently skipped""" 632 # Accumulate head of page 633 head = io.StringIO() 634 635 printCopyrightSourceComments(head) 636 637 print('= ' + apiName + ' API Reference Pages', 638 ':data-uri:', 639 ':icons: font', 640 ':doctype: book', 641 ':numbered!:', 642 ':max-width: 200', 643 ':data-uri:', 644 ':toc2:', 645 ':toclevels: 2', 646 '', 647 sep='\n', file=head) 648 649 print('== Copyright', file=head) 650 print('', file=head) 651 print('include::{config}/copyright-ccby.txt[]', file=head) 652 print('', file=head) 653 # Inject the table of contents. Asciidoc really ought to be generating 654 # this for us. 655 656 sections = [ 657 [api.protos, 'protos', apiName + ' Commands'], 658 [api.handles, 'handles', 'Object Handles'], 659 [api.structs, 'structs', 'Structures'], 660 [api.enums, 'enums', 'Enumerations'], 661 [api.flags, 'flags', 'Flags'], 662 [api.funcpointers, 'funcpointers', 'Function Pointer Types'], 663 [api.basetypes, 'basetypes', apiName + ' Scalar types'], 664 [api.defines, 'defines', 'C Macro Definitions'], 665 [extensions, 'extensions', apiName + ' Extensions'] 666 ] 667 668 # Accumulate body of page 669 body = io.StringIO() 670 671 for (apiDict, label, title) in sections: 672 # Add section title/anchor header to body 673 anchor = '[[' + label + ',' + title + ']]' 674 print(anchor, 675 '== ' + title, 676 '', 677 ':leveloffset: 2', 678 '', 679 sep='\n', file=body) 680 681 if label == 'extensions': 682 # preserve order of extensions since we already sorted the way we want. 683 keys = apiDict.keys() 684 else: 685 keys = sorted(apiDict.keys()) 686 687 for refPage in keys: 688 # Don't generate links for aliases, which are included with the 689 # aliased page 690 if refPage not in api.alias: 691 # Add page to body 692 if 'FlagBits' in refPage and conventions.unified_flag_refpages: 693 # OpenXR does not create separate ref pages for FlagBits: 694 # the FlagBits includes go in the Flags refpage. 695 # Previously the Vulkan script would only emit non-empty 696 # Vk*Flags pages, via the logic 697 # if refPage not in api.flags or api.flags[refPage] is not None 698 # emit page 699 # Now, all are emitted. 700 continue 701 else: 702 print('include::' + refPage + '.txt[]', file=body) 703 else: 704 # Alternatively, we could (probably should) link to the 705 # aliased refpage 706 logWarn('(Benign) Not including', refPage, 707 'in single-page reference', 708 'because it is an alias of', api.alias[refPage]) 709 710 print('\n' + ':leveloffset: 0' + '\n', file=body) 711 712 # Write head and body to the output file 713 pageName = baseDir + '/apispec.txt' 714 fp = open(pageName, 'w', encoding='utf-8') 715 716 print(head.getvalue(), file=fp, end='') 717 print(body.getvalue(), file=fp, end='') 718 719 head.close() 720 body.close() 721 fp.close() 722 723 724def genExtension(baseDir, extpath, name, info): 725 """Generate refpage, and add dictionary entry for an extension 726 727 - baseDir - output directory to generate page in 728 - extpath - None, or path to per-extension specification sources if 729 those are to be included in extension refpages 730 - name - extension name 731 - info - <extension> Element from XML""" 732 733 # Add a dictionary entry for this page 734 global genDict 735 genDict[name] = None 736 declares = [] 737 elem = info.elem 738 739 # Type of extension (instance, device, etc.) 740 ext_type = elem.get('type') 741 742 # Autogenerate interfaces from <extension> entry 743 for required in elem.find('require'): 744 req_name = required.get('name') 745 if not req_name: 746 # This isn't what we're looking for 747 continue 748 if req_name.endswith('_SPEC_VERSION') or req_name.endswith('_EXTENSION_NAME'): 749 # Don't link to spec version or extension name - those ref pages aren't created. 750 continue 751 752 if required.get('extends'): 753 # These are either extensions of enumerated types, or const enum 754 # values: neither of which get a ref page - although we could 755 # include the enumerated types in the See Also list. 756 continue 757 758 if req_name not in genDict: 759 logWarn('ERROR: {} (in extension {}) does not have a ref page.'.format(req_name, name)) 760 761 declares.append(req_name) 762 763 # import pdb 764 # pdb.set_trace() 765 766 appbody = None 767 if extpath is not None: 768 appfp = open('{}/{}.txt'.format(extpath, name), 'r', encoding='utf-8') 769 if appfp is not None: 770 appbody = appfp.read() 771 772 # Transform internal links to crosslinks 773 specURL = conventions.specURL() 774 appbody = xrefRewrite(appbody, specURL) 775 else: 776 logWarn('Cannot find extension appendix for', name) 777 778 # Fall through to autogenerated page 779 extpath = None 780 appbody = None 781 appfp.close() 782 783 # Include the extension appendix without an extra title 784 # head_content = 'include::{{appendices}}/{}.txt[]'.format(name) 785 786 # Write the extension refpage 787 pageName = baseDir + '/' + name + '.txt' 788 logDiag('genExtension:', pageName) 789 fp = open(pageName, 'w', encoding='utf-8') 790 791 # There are no generated titled sections 792 sections = None 793 794 # 'See link:{html_spec_relative}#%s[ %s] in the main specification for complete information.' % ( 795 # name, name) 796 refPageShell(name, 797 "{} extension".format(ext_type), 798 fp, 799 appbody, 800 sections=sections) 801 refPageTail(pageName=name, 802 specType=None, 803 specAnchor=name, 804 seeAlso=seeAlsoList(name, declares), 805 fp=fp, 806 auto=True) 807 fp.close() 808 809 810if __name__ == '__main__': 811 global genDict, extensions, conventions, apiName 812 genDict = {} 813 extensions = OrderedDict() 814 conventions = APIConventions() 815 apiName = conventions.api_name('api') 816 817 parser = argparse.ArgumentParser() 818 819 parser.add_argument('-diag', action='store', dest='diagFile', 820 help='Set the diagnostic file') 821 parser.add_argument('-warn', action='store', dest='warnFile', 822 help='Set the warning file') 823 parser.add_argument('-log', action='store', dest='logFile', 824 help='Set the log file for both diagnostics and warnings') 825 parser.add_argument('-genpath', action='store', 826 default='gen', 827 help='Path to directory containing generated files') 828 parser.add_argument('-basedir', action='store', dest='baseDir', 829 default=None, 830 help='Set the base directory in which pages are generated') 831 parser.add_argument('-noauto', action='store_true', 832 help='Don\'t generate inferred ref pages automatically') 833 parser.add_argument('files', metavar='filename', nargs='*', 834 help='a filename to extract ref pages from') 835 parser.add_argument('--version', action='version', version='%(prog)s 1.0') 836 parser.add_argument('-extension', action='append', 837 default=[], 838 help='Specify an extension or extensions to add to targets') 839 parser.add_argument('-rewrite', action='store', 840 default=None, 841 help='Name of output file to write Apache mod_rewrite directives to') 842 parser.add_argument('-toc', action='store', 843 default=None, 844 help='Name of output file to write an alphabetical TOC to') 845 parser.add_argument('-registry', action='store', 846 default=conventions.registry_path, 847 help='Use specified registry file instead of default') 848 parser.add_argument('-extpath', action='store', 849 default=None, 850 help='Use extension descriptions from this directory instead of autogenerating extension refpages') 851 852 results = parser.parse_args() 853 854 # Look for api.py in the specified directory 855 if results.genpath is not None: 856 sys.path.insert(0, results.genpath) 857 import api 858 859 setLogFile(True, True, results.logFile) 860 setLogFile(True, False, results.diagFile) 861 setLogFile(False, True, results.warnFile) 862 863 # Initialize static rewrite patterns for spec xrefs 864 xrefRewriteInitialize() 865 866 if results.baseDir is None: 867 baseDir = results.genpath + '/ref' 868 else: 869 baseDir = results.baseDir 870 871 # Dictionary of pages & aliases 872 pages = {} 873 874 for file in results.files: 875 d = genRef(file, baseDir) 876 pages.update(d) 877 878 # Now figure out which pages *weren't* generated from the spec. 879 # This relies on the dictionaries of API constructs in the api module. 880 881 if not results.noauto: 882 registry = Registry() 883 registry.loadFile(results.registry) 884 885 if conventions.write_refpage_include: 886 # Only extensions with a supported="..." attribute in this set 887 # will be considered for extraction/generation. 888 supported_strings = set((conventions.xml_api_name,)) 889 ext_names = set(k for k, v in registry.extdict.items() 890 if v.supported in supported_strings) 891 892 desired_extensions = ext_names.intersection(set(results.extension)) 893 for prefix in conventions.extension_index_prefixes: 894 # Splits up into chunks, sorted within each chunk. 895 filtered_extensions = sorted( 896 [name for name in desired_extensions 897 if name.startswith(prefix) and name not in extensions]) 898 for name in filtered_extensions: 899 # logWarn('NOT autogenerating extension refpage for', name) 900 extensions[name] = None 901 genExtension(baseDir, results.extpath, name, registry.extdict[name]) 902 903 # autoGenFlagsPage is no longer needed because they are added to 904 # the spec sources now. 905 # for page in api.flags: 906 # if page not in genDict: 907 # autoGenFlagsPage(baseDir, page) 908 909 # autoGenHandlePage is no longer needed because they are added to 910 # the spec sources now. 911 # for page in api.structs: 912 # if typeCategory[page] == 'handle': 913 # autoGenHandlePage(baseDir, page) 914 915 sections = [ 916 (api.flags, 'Flag Types'), 917 (api.enums, 'Enumerated Types'), 918 (api.structs, 'Structures'), 919 (api.protos, 'Prototypes'), 920 (api.funcpointers, 'Function Pointers'), 921 (api.basetypes, apiName + ' Scalar Types'), 922 (extensions, apiName + ' Extensions'), 923 ] 924 925 # Summarize pages that weren't generated, for good or bad reasons 926 927 for (apiDict, title) in sections: 928 # OpenXR was keeping a 'flagged' state which only printed out a 929 # warning for the first non-generated page, but was otherwise 930 # unused. This doesn't seem helpful. 931 for page in apiDict: 932 if page not in genDict: 933 # Page was not generated - why not? 934 if page in api.alias: 935 logWarn('(Benign, is an alias) Ref page for', title, page, 'is aliased into', api.alias[page]) 936 elif page in api.flags and api.flags[page] is None: 937 logWarn('(Benign, no FlagBits defined) No ref page generated for ', title, 938 page) 939 else: 940 # Could introduce additional logic to detect 941 # external types and not emit them. 942 logWarn('No ref page generated for ', title, page) 943 944 genSinglePageRef(baseDir) 945 946 if results.rewrite: 947 # Generate Apache rewrite directives for refpage aliases 948 fp = open(results.rewrite, 'w', encoding='utf-8') 949 950 for page in sorted(pages): 951 p = pages[page] 952 rewrite = p.name 953 954 if page != rewrite: 955 print('RewriteRule ^', page, '.html$ ', rewrite, '.html', 956 sep='', file=fp) 957 fp.close() 958 959 if results.toc: 960 # Generate dynamic portion of refpage TOC 961 fp = open(results.toc, 'w', encoding='utf-8') 962 963 # Run through dictionary of pages generating an TOC 964 print(12 * ' ', '<li class="Level1">Alphabetic Contents', sep='', file=fp) 965 print(16 * ' ', '<ul class="Level2">', sep='', file=fp) 966 lastLetter = None 967 968 for page in sorted(pages, key=str.upper): 969 p = pages[page] 970 letter = page[0:1].upper() 971 972 if letter != lastLetter: 973 if lastLetter: 974 # End previous block 975 print(24 * ' ', '</ul>', sep='', file=fp) 976 print(20 * ' ', '</li>', sep='', file=fp) 977 # Start new block 978 print(20 * ' ', '<li>', letter, sep='', file=fp) 979 print(24 * ' ', '<ul class="Level3">', sep='', file=fp) 980 lastLetter = letter 981 982 # Add this page to the list 983 print(28 * ' ', '<li><a href="', p.name, '.html" ', 984 'target="pagedisplay">', page, '</a></li>', 985 sep='', file=fp) 986 987 if lastLetter: 988 # Close the final letter block 989 print(24 * ' ', '</ul>', sep='', file=fp) 990 print(20 * ' ', '</li>', sep='', file=fp) 991 992 # Close the list 993 print(16 * ' ', '</ul>', sep='', file=fp) 994 print(12 * ' ', '</li>', sep='', file=fp) 995 996 # print('name {} -> page {}'.format(page, pages[page].name)) 997 998 fp.close() 999