• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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