1#!/usr/bin/python3 -i 2# 3# Copyright (c) 2013-2020 The Khronos Group Inc. 4# 5# SPDX-License-Identifier: Apache-2.0 6 7import re 8import sys 9from functools import total_ordering 10from generator import GeneratorOptions, OutputGenerator, regSortFeatures, write 11 12class ExtensionMetaDocGeneratorOptions(GeneratorOptions): 13 """ExtensionMetaDocGeneratorOptions - subclass of GeneratorOptions. 14 15 Represents options during extension metainformation generation for Asciidoc""" 16 def __init__(self, *args, **kwargs): 17 super().__init__(*args, **kwargs) 18 19 20EXT_NAME_DECOMPOSE_RE = re.compile(r'[A-Z]+_(?P<tag>[A-Z]+)_(?P<name>[\w_]+)') 21 22 23@total_ordering 24class Extension: 25 def __init__(self, 26 generator, # needed for logging and API conventions 27 filename, 28 name, 29 number, 30 ext_type, 31 requires, 32 requiresCore, 33 contact, 34 promotedTo, 35 deprecatedBy, 36 obsoletedBy, 37 provisional, 38 revision, 39 specialuse ): 40 self.generator = generator 41 self.conventions = generator.genOpts.conventions 42 self.filename = filename 43 self.name = name 44 self.number = number 45 self.ext_type = ext_type 46 self.requires = requires 47 self.requiresCore = requiresCore 48 self.contact = contact 49 self.promotedTo = promotedTo 50 self.deprecatedBy = deprecatedBy 51 self.obsoletedBy = obsoletedBy 52 self.provisional = provisional 53 self.revision = revision 54 self.specialuse = specialuse 55 56 self.deprecationType = None 57 self.supercedingAPIVersion = None 58 self.supercedingExtension = None 59 60 if self.promotedTo is not None and self.deprecatedBy is not None and self.obsoletedBy is not None: 61 self.generator.logMsg('warn', 'All \'promotedto\', \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\' and \'deprecatedby\'.') 62 elif self.promotedTo is not None and self.deprecatedBy is not None: 63 self.generator.logMsg('warn', 'Both \'promotedto\' and \'deprecatedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') 64 elif self.promotedTo is not None and self.obsoletedBy is not None: 65 self.generator.logMsg('warn', 'Both \'promotedto\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'promotedto\'.') 66 elif self.deprecatedBy is not None and self.obsoletedBy is not None: 67 self.generator.logMsg('warn', 'Both \'deprecatedby\' and \'obsoletedby\' attributes used on extension ' + self.name + '! Ignoring \'deprecatedby\'.') 68 69 supercededBy = None 70 if self.promotedTo is not None: 71 self.deprecationType = 'promotion' 72 supercededBy = promotedTo 73 elif self.deprecatedBy is not None: 74 self.deprecationType = 'deprecation' 75 supercededBy = deprecatedBy 76 elif self.obsoletedBy is not None: 77 self.deprecationType = 'obsoletion' 78 supercededBy = obsoletedBy 79 80 if supercededBy is not None: 81 if supercededBy == '' and not self.deprecationType == 'promotion': 82 pass # supercedingAPIVersion, supercedingExtension is None 83 elif supercededBy.startswith(self.conventions.api_version_prefix): 84 self.supercedingAPIVersion = supercededBy 85 elif supercededBy.startswith(self.conventions.api_prefix): 86 self.supercedingExtension = supercededBy 87 else: 88 self.generator.logMsg('error', 'Unrecognized ' + self.deprecationType + ' attribute value \'' + supercededBy + '\'!') 89 90 match = EXT_NAME_DECOMPOSE_RE.match(self.name) 91 self.vendor = match.group('tag') 92 self.bare_name = match.group('name') 93 94 def __str__(self): 95 return self.name 96 def __eq__(self, other): 97 return self.name == other.name 98 def __ne__(self, other): 99 return self.name != other.name 100 101 def __lt__(self, other): 102 self_is_KHR = self.name.startswith(self.conventions.KHR_prefix) 103 self_is_EXT = self.name.startswith(self.conventions.EXT_prefix) 104 other_is_KHR = other.name.startswith(self.conventions.KHR_prefix) 105 other_is_EXT = other.name.startswith(self.conventions.EXT_prefix) 106 107 swap = False 108 if self_is_KHR and not other_is_KHR: 109 return not swap 110 if other_is_KHR and not self_is_KHR: 111 return swap 112 if self_is_EXT and not other_is_EXT: 113 return not swap 114 if other_is_EXT and not self_is_EXT: 115 return swap 116 117 return self.name < other.name 118 119 def typeToStr(self): 120 if self.ext_type == 'instance': 121 return 'Instance extension' 122 if self.ext_type == 'device': 123 return 'Device extension' 124 125 if self.ext_type is not None: 126 self.generator.logMsg('warn', 'The type attribute of ' + self.name + ' extension is neither \'instance\' nor \'device\'. That is invalid (at the time this script was written).') 127 else: # should be unreachable 128 self.generator.logMsg('error', 'Logic error in typeToStr(): Missing type attribute!') 129 return None 130 131 def specLink(self, xrefName, xrefText, isRefpage = False): 132 """Generate a string containing a link to a specification anchor in 133 asciidoctor markup form. 134 135 - xrefName - anchor name in the spec 136 - xrefText - text to show for the link, or None 137 - isRefpage = True if generating a refpage include, False if 138 generating a specification extension appendix include""" 139 140 if isRefpage: 141 # Always link into API spec 142 specURL = self.conventions.specURL('api') 143 return 'link:{}#{}[{}^]'.format(specURL, xrefName, xrefText) 144 else: 145 return '<<' + xrefName + ', ' + xrefText + '>>' 146 147 def conditionalLinkCoreAPI(self, apiVersion, linkSuffix, isRefpage): 148 versionMatch = re.match(self.conventions.api_version_prefix + r'(\d+)_(\d+)', apiVersion) 149 major = versionMatch.group(1) 150 minor = versionMatch.group(2) 151 152 dottedVersion = major + '.' + minor 153 154 xrefName = 'versions-' + dottedVersion + linkSuffix 155 xrefText = self.conventions.api_name() + ' ' + dottedVersion 156 157 doc = 'ifdef::' + apiVersion + '[]\n' 158 doc += ' ' + self.specLink(xrefName, xrefText, isRefpage) + '\n' 159 doc += 'endif::' + apiVersion + '[]\n' 160 doc += 'ifndef::' + apiVersion + '[]\n' 161 doc += ' ' + self.conventions.api_name() + ' ' + dottedVersion + '\n' 162 doc += 'endif::' + apiVersion + '[]\n' 163 164 return doc 165 166 def conditionalLinkExt(self, extName, indent = ' '): 167 doc = 'ifdef::' + extName + '[]\n' 168 doc += indent + self.conventions.formatExtension(extName) + '\n' 169 doc += 'endif::' + extName + '[]\n' 170 doc += 'ifndef::' + extName + '[]\n' 171 doc += indent + '`' + extName + '`\n' 172 doc += 'endif::' + extName + '[]\n' 173 174 return doc 175 176 def resolveDeprecationChain(self, extensionsList, succeededBy, isRefpage, file): 177 ext = next(x for x in extensionsList if x.name == succeededBy) 178 179 if ext.deprecationType: 180 if ext.deprecationType == 'promotion': 181 if ext.supercedingAPIVersion: 182 write(' ** Which in turn was _promoted_ to\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-promotions', isRefpage), file=file) 183 else: # ext.supercedingExtension 184 write(' ** Which in turn was _promoted_ to extension\n' + ext.conditionalLinkExt(ext.supercedingExtension), file=file) 185 ext.resolveDeprecationChain(extensionsList, ext.supercedingExtension, file) 186 elif ext.deprecationType == 'deprecation': 187 if ext.supercedingAPIVersion: 188 write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) 189 elif ext.supercedingExtension: 190 write(' ** Which in turn was _deprecated_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) 191 ext.resolveDeprecationChain(extensionsList, ext.supercedingExtension, file) 192 else: 193 write(' ** Which in turn was _deprecated_ without replacement', file=file) 194 elif ext.deprecationType == 'obsoletion': 195 if ext.supercedingAPIVersion: 196 write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkCoreAPI(ext.supercedingAPIVersion, '-new-feature', isRefpage), file=file) 197 elif ext.supercedingExtension: 198 write(' ** Which in turn was _obsoleted_ by\n' + ext.conditionalLinkExt(ext.supercedingExtension) + ' extension', file=file) 199 ext.resolveDeprecationChain(extensionsList, ext.supercedingExtension, file) 200 else: 201 write(' ** Which in turn was _obsoleted_ without replacement', file=file) 202 else: # should be unreachable 203 self.generator.logMsg('error', 'Logic error in resolveDeprecationChain(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') 204 205 206 def writeTag(self, tag, value, isRefpage, fp): 207 """Write a tag and (if non-None) a tag value to a file. 208 209 - tag - string tag name 210 - value - tag value, or None 211 - isRefpage - controls style in which the tag is marked up 212 - fp - open file pointer to write to""" 213 214 if isRefpage: 215 # Use subsection headers for the tag name 216 tagPrefix = '== ' 217 tagSuffix = '' 218 else: 219 # Use an bolded item list for the tag name 220 tagPrefix = '*' 221 tagSuffix = '*::' 222 223 write(tagPrefix + tag + tagSuffix, file=fp) 224 if value is not None: 225 write(value, file=fp) 226 227 if isRefpage: 228 write('', file=fp) 229 230 def makeMetafile(self, extensionsList, isRefpage = False): 231 """Generate a file containing extension metainformation in 232 asciidoctor markup form. 233 234 - extensionsList - list of extensions spec is being generated against 235 - isRefpage - True if generating a refpage include, False if 236 generating a specification extension appendix include""" 237 238 if isRefpage: 239 filename = self.filename.replace('meta/', 'meta/refpage.') 240 else: 241 filename = self.filename 242 243 fp = self.generator.newFile(filename) 244 245 if not isRefpage: 246 write('[[' + self.name + ']]', file=fp) 247 write('=== ' + self.name, file=fp) 248 write('', file=fp) 249 250 self.writeTag('Name String', '`' + self.name + '`', isRefpage, fp) 251 self.writeTag('Extension Type', self.typeToStr(), isRefpage, fp) 252 253 self.writeTag('Registered Extension Number', self.number, isRefpage, fp) 254 self.writeTag('Revision', self.revision, isRefpage, fp) 255 256 # Only API extension dependencies are coded in XML, others are explicit 257 self.writeTag('Extension and Version Dependencies', None, isRefpage, fp) 258 259 write(' * Requires ' + self.conventions.api_name() + ' ' + self.requiresCore, file=fp) 260 if self.requires: 261 for dep in self.requires.split(','): 262 write(' * Requires', self.conventions.formatExtension(dep), 263 file=fp) 264 if self.provisional == 'true': 265 write(' * *This is a _provisional_ extension and must: be used with caution.', file=fp) 266 write(' See the ' + 267 self.specLink(xrefName = 'boilerplate-provisional-header', 268 xrefText = 'description', 269 isRefpage = isRefpage) + 270 ' of provisional header files for enablement and stability details.*', file=fp) 271 write('', file=fp) 272 273 if self.deprecationType: 274 self.writeTag('Deprecation state', None, isRefpage, fp) 275 276 if self.deprecationType == 'promotion': 277 if self.supercedingAPIVersion: 278 write(' * _Promoted_ to\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-promotions', isRefpage), file=fp) 279 else: # ext.supercedingExtension 280 write(' * _Promoted_ to\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension', file=fp) 281 self.resolveDeprecationChain(extensionsList, self.supercedingExtension, isRefpage, fp) 282 elif self.deprecationType == 'deprecation': 283 if self.supercedingAPIVersion: 284 write(' * _Deprecated_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) 285 elif self.supercedingExtension: 286 write(' * _Deprecated_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) 287 self.resolveDeprecationChain(extensionsList, self.supercedingExtension, isRefpage, fp) 288 else: 289 write(' * _Deprecated_ without replacement' , file=fp) 290 elif self.deprecationType == 'obsoletion': 291 if self.supercedingAPIVersion: 292 write(' * _Obsoleted_ by\n' + self.conditionalLinkCoreAPI(self.supercedingAPIVersion, '-new-features', isRefpage), file=fp) 293 elif self.supercedingExtension: 294 write(' * _Obsoleted_ by\n' + self.conditionalLinkExt(self.supercedingExtension) + ' extension' , file=fp) 295 self.resolveDeprecationChain(extensionsList, self.supercedingExtension, isRefpage, fp) 296 else: 297 # TODO: Does not make sense to retroactively ban use of extensions from 1.0. 298 # Needs some tweaks to the semantics and this message, when such extension(s) occur. 299 write(' * _Obsoleted_ without replacement' , file=fp) 300 else: # should be unreachable 301 self.generator.logMsg('error', 'Logic error in makeMetafile(): deprecationType is neither \'promotion\', \'deprecation\' nor \'obsoletion\'!') 302 303 if self.specialuse is not None: 304 specialuses = self.specialuse.split(',') 305 if len(specialuses) > 1: 306 header = 'Special Uses' 307 else: 308 header = 'Special Use' 309 self.writeTag(header, None, isRefpage, fp) 310 311 for use in specialuses: 312 # Each specialuse attribute value expands an asciidoctor 313 # attribute of the same name, instead of using the shorter, 314 # and harder to understand attribute 315 write('* {}'.format( 316 self.specLink( 317 xrefName = self.conventions.special_use_section_anchor, 318 xrefText = '{' + use + '}', 319 isRefpage = isRefpage)), file=fp) 320 321 if self.conventions.write_contacts and not isRefpage: 322 write('*Contact*::', file=fp) 323 contacts = self.contact.split(',') 324 for contact in contacts: 325 contactWords = contact.strip().split() 326 name = ' '.join(contactWords[:-1]) 327 handle = contactWords[-1] 328 if handle.startswith('gitlab:'): 329 prettyHandle = 'icon:gitlab[alt=GitLab, role="red"]' + handle.replace('gitlab:@', '') 330 elif handle.startswith('@'): 331 trackerLink = 'link:++https://github.com/KhronosGroup/Vulkan-Docs/issues/new?title=' + self.name + ':%20&body=' + handle + '%20++' 332 prettyHandle = trackerLink + '[icon:github[alt=GitHub, role="black"]' + handle[1:] + ']' 333 else: 334 prettyHandle = handle 335 336 write(' * ' + name + ' ' + prettyHandle, file=fp) 337 338 fp.close() 339 340class ExtensionMetaDocOutputGenerator(OutputGenerator): 341 """ExtensionMetaDocOutputGenerator - subclass of OutputGenerator. 342 343 Generates AsciiDoc includes with metainformation for the API extension 344 appendices. The fields used from <extension> tags in the API XML are: 345 346 - name extension name string 347 - number extension number (optional) 348 - contact name and github login or email address (optional) 349 - type 'instance' | 'device' (optional) 350 - requires list of comma-separated required API extensions (optional) 351 - requiresCore required core version of API (optional) 352 - promotedTo extension or API version it was promoted to 353 - deprecatedBy extension or API version which deprecated this extension, 354 or empty string if deprecated without replacement 355 - obsoletedBy extension or API version which obsoleted this extension, 356 or empty string if obsoleted without replacement 357 - provisional 'true' if this extension is released provisionally""" 358 359 def __init__(self, *args, **kwargs): 360 super().__init__(*args, **kwargs) 361 self.extensions = [] 362 # List of strings containing all vendor tags 363 self.vendor_tags = [] 364 self.file_suffix = '' 365 366 def newFile(self, filename): 367 self.logMsg('diag', '# Generating include file:', filename) 368 fp = open(filename, 'w', encoding='utf-8') 369 write(self.genOpts.conventions.warning_comment, file=fp) 370 return fp 371 372 def beginFile(self, genOpts): 373 OutputGenerator.beginFile(self, genOpts) 374 375 self.directory = self.genOpts.directory 376 self.file_suffix = self.genOpts.conventions.file_suffix 377 378 # Iterate over all 'tag' Elements and add the names of all the valid vendor 379 # tags to the list 380 root = self.registry.tree.getroot() 381 for tag in root.findall('tags/tag'): 382 self.vendor_tags.append(tag.get('name')) 383 384 # Create subdirectory, if needed 385 self.makeDir(self.directory) 386 387 def conditionalExt(self, extName, content, ifdef = None, condition = None): 388 doc = '' 389 390 innerdoc = 'ifdef::' + extName + '[]\n' 391 innerdoc += content + '\n' 392 innerdoc += 'endif::' + extName + '[]\n' 393 394 if ifdef: 395 if ifdef == 'ifndef': 396 if condition: 397 doc += 'ifndef::' + condition + '[]\n' 398 doc += innerdoc 399 doc += 'endif::' + condition + '[]\n' 400 else: # no condition is as if condition is defined; "nothing" is always defined :p 401 pass # so no output 402 elif ifdef == 'ifdef': 403 if condition: 404 doc += 'ifdef::' + condition + '+' + extName + '[]\n' 405 doc += content + '\n' # does not include innerdoc; the ifdef was merged with the one above 406 doc += 'endif::' + condition + '+' + extName + '[]\n' 407 else: # no condition is as if condition is defined; "nothing" is always defined :p 408 doc += innerdoc 409 else: # should be unreachable 410 raise RuntimeError('Should be unreachable: ifdef is neither \'ifdef \' nor \'ifndef\'!') 411 else: 412 doc += innerdoc 413 414 return doc 415 416 def makeExtensionInclude(self, ext): 417 return self.conventions.extension_include_string(ext) 418 419 def endFile(self): 420 self.extensions.sort() 421 422 # Generate metadoc extension files, in refpage and non-refpage form 423 for ext in self.extensions: 424 ext.makeMetafile(self.extensions, isRefpage = False) 425 if self.conventions.write_refpage_include: 426 ext.makeMetafile(self.extensions, isRefpage = True) 427 428 # Generate list of promoted extensions 429 promotedExtensions = {} 430 for ext in self.extensions: 431 if ext.deprecationType == 'promotion' and ext.supercedingAPIVersion: 432 promotedExtensions.setdefault(ext.supercedingAPIVersion, []).append(ext) 433 434 for coreVersion, extensions in promotedExtensions.items(): 435 promoted_extensions_fp = self.newFile(self.directory + '/promoted_extensions_' + coreVersion + self.file_suffix) 436 437 for ext in extensions: 438 indent = '' 439 write(' * {blank}\n+\n' + ext.conditionalLinkExt(ext.name, indent), file=promoted_extensions_fp) 440 441 promoted_extensions_fp.close() 442 443 # Re-sort to match earlier behavior 444 # TODO: Remove this extra sort when re-arranging section order OK. 445 446 def makeSortKey(ext): 447 name = ext.name.lower() 448 prefixes = self.conventions.extension_index_prefixes 449 for i, prefix in enumerate(prefixes): 450 if ext.name.startswith(prefix): 451 return (i, name) 452 return (len(prefixes), name) 453 454 self.extensions.sort(key=makeSortKey) 455 456 # Generate include directives for the extensions appendix, grouping 457 # extensions by status (current, deprecated, provisional, etc.) 458 with self.newFile(self.directory + '/current_extensions_appendix' + self.file_suffix) as current_extensions_appendix_fp, \ 459 self.newFile(self.directory + '/deprecated_extensions_appendix' + self.file_suffix) as deprecated_extensions_appendix_fp, \ 460 self.newFile(self.directory + '/current_extension_appendices' + self.file_suffix) as current_extension_appendices_fp, \ 461 self.newFile(self.directory + '/current_extension_appendices_toc' + self.file_suffix) as current_extension_appendices_toc_fp, \ 462 self.newFile(self.directory + '/deprecated_extension_appendices' + self.file_suffix) as deprecated_extension_appendices_fp, \ 463 self.newFile(self.directory + '/deprecated_extension_appendices_toc' + self.file_suffix) as deprecated_extension_appendices_toc_fp, \ 464 self.newFile(self.directory + '/deprecated_extensions_guard_macro' + self.file_suffix) as deprecated_extensions_guard_macro_fp, \ 465 self.newFile(self.directory + '/provisional_extensions_appendix' + self.file_suffix) as provisional_extensions_appendix_fp, \ 466 self.newFile(self.directory + '/provisional_extension_appendices' + self.file_suffix) as provisional_extension_appendices_fp, \ 467 self.newFile(self.directory + '/provisional_extension_appendices_toc' + self.file_suffix) as provisional_extension_appendices_toc_fp, \ 468 self.newFile(self.directory + '/provisional_extensions_guard_macro' + self.file_suffix) as provisional_extensions_guard_macro_fp: 469 470 write('include::deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 471 write('', file=current_extensions_appendix_fp) 472 write('ifndef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 473 write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) 474 write('== List of Extensions', file=current_extensions_appendix_fp) 475 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 476 write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 477 write('[[extension-appendices-list]]', file=current_extensions_appendix_fp) 478 write('== List of Current Extensions', file=current_extensions_appendix_fp) 479 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=current_extensions_appendix_fp) 480 write('', file=current_extensions_appendix_fp) 481 write('include::current_extension_appendices_toc' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 482 write('<<<', file=current_extensions_appendix_fp) 483 write('include::current_extension_appendices' + self.file_suffix + '[]', file=current_extensions_appendix_fp) 484 485 write('include::deprecated_extensions_guard_macro' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 486 write('', file=deprecated_extensions_appendix_fp) 487 write('ifdef::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) 488 write('[[deprecated-extension-appendices-list]]', file=deprecated_extensions_appendix_fp) 489 write('== List of Deprecated Extensions', file=deprecated_extensions_appendix_fp) 490 write('include::deprecated_extension_appendices_toc' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 491 write('<<<', file=deprecated_extensions_appendix_fp) 492 write('include::deprecated_extension_appendices' + self.file_suffix + '[]', file=deprecated_extensions_appendix_fp) 493 write('endif::HAS_DEPRECATED_EXTENSIONS[]', file=deprecated_extensions_appendix_fp) 494 495 # add include guard to allow multiple includes 496 write('ifndef::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) 497 write(':DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=deprecated_extensions_guard_macro_fp) 498 write('ifndef::PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=provisional_extensions_guard_macro_fp) 499 write(':PROVISIONAL_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD:\n', file=provisional_extensions_guard_macro_fp) 500 501 write('include::provisional_extensions_guard_macro' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 502 write('', file=provisional_extensions_appendix_fp) 503 write('ifdef::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) 504 write('[[provisional-extension-appendices-list]]', file=provisional_extensions_appendix_fp) 505 write('== List of Provisional Extensions', file=provisional_extensions_appendix_fp) 506 write('include::provisional_extension_appendices_toc' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 507 write('<<<', file=provisional_extensions_appendix_fp) 508 write('include::provisional_extension_appendices' + self.file_suffix + '[]', file=provisional_extensions_appendix_fp) 509 write('endif::HAS_PROVISIONAL_EXTENSIONS[]', file=provisional_extensions_appendix_fp) 510 511 for ext in self.extensions: 512 include = self.makeExtensionInclude(ext) 513 link = ' * ' + self.conventions.formatExtension(ext.name) 514 if ext.provisional == 'true': 515 write(self.conditionalExt(ext.name, include), file=provisional_extension_appendices_fp) 516 write(self.conditionalExt(ext.name, link), file=provisional_extension_appendices_toc_fp) 517 write(self.conditionalExt(ext.name, ':HAS_PROVISIONAL_EXTENSIONS:'), file=provisional_extensions_guard_macro_fp) 518 elif ext.deprecationType is None: 519 write(self.conditionalExt(ext.name, include), file=current_extension_appendices_fp) 520 write(self.conditionalExt(ext.name, link), file=current_extension_appendices_toc_fp) 521 else: 522 condition = ext.supercedingAPIVersion if ext.supercedingAPIVersion else ext.supercedingExtension # potentially None too 523 524 write(self.conditionalExt(ext.name, include, 'ifndef', condition), file=current_extension_appendices_fp) 525 write(self.conditionalExt(ext.name, link, 'ifndef', condition), file=current_extension_appendices_toc_fp) 526 527 write(self.conditionalExt(ext.name, include, 'ifdef', condition), file=deprecated_extension_appendices_fp) 528 write(self.conditionalExt(ext.name, link, 'ifdef', condition), file=deprecated_extension_appendices_toc_fp) 529 530 write(self.conditionalExt(ext.name, ':HAS_DEPRECATED_EXTENSIONS:', 'ifdef', condition), file=deprecated_extensions_guard_macro_fp) 531 532 write('endif::DEPRECATED_EXTENSIONS_GUARD_MACRO_INCLUDE_GUARD[]', file=deprecated_extensions_guard_macro_fp) 533 534 OutputGenerator.endFile(self) 535 536 def beginFeature(self, interface, emit): 537 # Start processing in superclass 538 OutputGenerator.beginFeature(self, interface, emit) 539 540 if interface.tag != 'extension': 541 self.logMsg('diag', 'beginFeature: ignoring non-extension feature', self.featureName) 542 return 543 544 # These attributes must exist 545 name = self.featureName 546 number = self.getAttrib(interface, 'number') 547 ext_type = self.getAttrib(interface, 'type') 548 revision = self.getSpecVersion(interface, name) 549 550 # These attributes are optional 551 OPTIONAL = False 552 requires = self.getAttrib(interface, 'requires', OPTIONAL) 553 requiresCore = self.getAttrib(interface, 'requiresCore', OPTIONAL, '1.0') # TODO update this line with update_version.py 554 contact = self.getAttrib(interface, 'contact', OPTIONAL) 555 promotedTo = self.getAttrib(interface, 'promotedto', OPTIONAL) 556 deprecatedBy = self.getAttrib(interface, 'deprecatedby', OPTIONAL) 557 obsoletedBy = self.getAttrib(interface, 'obsoletedby', OPTIONAL) 558 provisional = self.getAttrib(interface, 'provisional', OPTIONAL, 'false') 559 specialuse = self.getAttrib(interface, 'specialuse', OPTIONAL) 560 561 filename = self.directory + '/' + name + self.file_suffix 562 563 extdata = Extension( 564 generator = self, 565 filename = filename, 566 name = name, 567 number = number, 568 ext_type = ext_type, 569 requires = requires, 570 requiresCore = requiresCore, 571 contact = contact, 572 promotedTo = promotedTo, 573 deprecatedBy = deprecatedBy, 574 obsoletedBy = obsoletedBy, 575 provisional = provisional, 576 revision = revision, 577 specialuse = specialuse) 578 self.extensions.append(extdata) 579 580 581 def endFeature(self): 582 # Finish processing in superclass 583 OutputGenerator.endFeature(self) 584 585 def getAttrib(self, elem, attribute, required=True, default=None): 586 """Query an attribute from an element, or return a default value 587 588 - elem - element to query 589 - attribute - attribute name 590 - required - whether attribute must exist 591 - default - default value if attribute not present""" 592 attrib = elem.get(attribute, default) 593 if required and (attrib is None): 594 name = elem.get('name', 'UNKNOWN') 595 self.logMsg('error', 'While processing \'' + self.featureName + ', <' + elem.tag + '> \'' + name + '\' does not contain required attribute \'' + attribute + '\'') 596 return attrib 597 598 def numbersToWords(self, name): 599 allowlist = ['WIN32', 'INT16', 'D3D1'] 600 601 # temporarily replace allowlist items 602 for i, w in enumerate(allowlist): 603 name = re.sub(w, '{' + str(i) + '}', name) 604 605 name = re.sub(r'(?<=[A-Z])(\d+)(?![A-Z])', r'_\g<1>', name) 606 607 # undo allowlist substitution 608 for i, w in enumerate(allowlist): 609 name = re.sub('\\{' + str(i) + '}', w, name) 610 611 return name 612 613 def getSpecVersion(self, elem, extname, default=None): 614 """Determine the extension revision from the EXTENSION_NAME_SPEC_VERSION 615 enumerant. 616 617 - elem - <extension> element to query 618 - extname - extension name from the <extension> 'name' attribute 619 - default - default value if SPEC_VERSION token not present""" 620 # The literal enumerant name to match 621 versioningEnumName = self.numbersToWords(extname.upper()) + '_SPEC_VERSION' 622 623 for enum in elem.findall('./require/enum'): 624 enumName = self.getAttrib(enum, 'name') 625 if enumName == versioningEnumName: 626 return self.getAttrib(enum, 'value') 627 628 #if not found: 629 for enum in elem.findall('./require/enum'): 630 enumName = self.getAttrib(enum, 'name') 631 if enumName.find('SPEC_VERSION') != -1: 632 self.logMsg('diag', 'Missing ' + versioningEnumName + '! Potential misnamed candidate ' + enumName + '.') 633 return self.getAttrib(enum, 'value') 634 635 self.logMsg('error', 'Missing ' + versioningEnumName + '!') 636 return default 637