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