1#!/usr/bin/python3
2#
3# Copyright 2016-2023 The Khronos Group Inc.
4#
5# SPDX-License-Identifier: Apache-2.0
6
7# fixupRef.py - replace old // refBegin .. // refEnd syntax with new
8# open block syntax
9#
10# Usage: fixupRef.py [-outdir path] [-overwrite] files
11
12from reflib import *
13from vkapi import *
14import argparse, copy, io, os, pdb, re, string, sys
15
16# Return 'None' for None, the string otherwise
17def noneStr(str):
18    if str == None:
19        return '(None)'
20    else:
21        return str
22
23# Escape single quotes in a string for asciidoc
24def escapeQuote(str):
25    return str.replace("'", "\'")
26
27# Start a refpage open block
28def openBlock(pi, fp):
29    if pi.refs != '':
30        print("[open,refpage='" + pi.name +
31              "',desc='" + pi.desc +
32              "',type='" + pi.type +
33              "',xrefs='" + pi.refs + "']",
34              file=fp)
35    else:
36        print("[open,refpage='" + pi.name +
37              "',desc='" + pi.desc +
38              "',type='" + pi.type + "']",
39              file=fp)
40    print('--', file=fp)
41
42# End a refpage open block
43def closeBlock(pi, fp):
44    print('--', file=fp)
45    # Just for finding block ends while debugging
46    # print("// end [open,refpage='" + pi.name + "']", file=fp)
47
48# Replace old // refBegin .. // refEnd references in an asciidoc
49# file with open blocks, per # ??? .
50#   specFile - filename to extract from
51#   outDir - output directory to write updated file to, if not overwritten
52#   overwrite - True if the file should be overwritten in place
53#   skipped - set of filenames containing commands which were not
54#       rewritten with open blocks (e.g. enums). Updated in place.
55def replaceRef(specFile, outDir, overwrite = False, skipped = set()):
56    file = loadFile(specFile)
57    if file == None:
58        return
59
60    # Save the path to this file for later use in rewriting relative includes
61    specDir = os.path.dirname(os.path.abspath(specFile))
62
63    pageMap = findRefs(file)
64    logDiag(specFile + ': found', len(pageMap.keys()), 'potential pages')
65
66    sys.stderr.flush()
67
68    # Fix up references in pageMap
69    fixupRefs(pageMap, specFile, file)
70
71    # Map the page info dictionary into a dictionary of actions
72    # keyed by line number they are performed on/after:
73    #   'action' : 'begin' or 'end'. What to do on a refBegin or refEnd line
74    #   'replace': True if this line needs to be replaced
75    #   'name'   : Name of the ref page being defined
76    #   'desc'   : One-line description of the ref page being defined
77    #   'type'   : Type of the ref page being defined, 'structs', 'protos', etc.
78    #   'refs'   : Space-separated string of cross-referenced pages
79
80    actions = { }
81
82    for name in pageMap.keys():
83        pi = pageMap[name]
84
85        # Cleanup parameters for output
86        pi.name = noneStr(pi.name)
87        pi.desc = escapeQuote(noneStr(pi.desc))
88
89        if pi.extractPage:
90            if (file[pi.begin][0:11] == '// refBegin'):
91                # Replace line
92                actions[pi.begin] = {
93                    'action'   : 'begin',
94                    'replace'  : True,
95                    'pageinfo' : pi
96                }
97            else:
98                # Insert line
99                actions[pi.begin] = {
100                    'action'   : 'begin',
101                    'replace'  : False,
102                    'pageinfo' : pi
103                }
104
105            if (file[pi.end][0:9] == '// refEnd'):
106                # Replace line
107                actions[pi.end] = {
108                    'action'   : 'end',
109                    'replace'  : True,
110                    'pageinfo' : pi
111                }
112            else:
113                # Insert line
114                actions[pi.end] = {
115                    'action'   : 'end',
116                    'replace'  : False,
117                    'pageinfo' : pi
118                }
119        else:
120            logWarn('Skipping replacement for', pi.name, 'at', specFile,
121                    'line', pi.begin)
122            print('Skipping replacement for', pi.name, 'at', specFile,
123                  'line', pi.begin)
124            printPageInfo(pi, file)
125            skipped.add(specFile)
126
127    if overwrite:
128        pageName = specFile
129    else:
130        pageName = outDir + '/' + os.path.basename(specFile)
131
132    fp = open(pageName, 'w', encoding='utf-8')
133
134    line = 0
135    for text in file:
136        if line in actions.keys():
137            action = actions[line]['action']
138            replace = actions[line]['replace']
139            pi = actions[line]['pageinfo']
140
141            logDiag('ACTION:', action, 'REPLACE:', replace, 'at line', line)
142            logDiag('PageInfo of action:')
143            printPageInfo(pi, file)
144
145            if action == 'begin':
146                openBlock(pi, fp)
147                if not replace:
148                    print(text, file=fp, end='')
149            elif action == 'end':
150                if not replace:
151                    print(text, file=fp, end='')
152                closeBlock(pi, fp)
153            else:
154                print('ERROR: unrecognized action:', action, 'in',
155                      specFile, 'at line', line)
156                print(text, file=fp, end='')
157        else:
158            print(text, file=fp, end='')
159        line = line + 1
160
161    fp.close()
162
163    #for line in sorted(actions.keys()):
164    #    action = actions[line]
165    #    print('action at line', line, '\t',
166    #          action[0], action[1], action[2])
167
168if __name__ == '__main__':
169    global genDict
170    genDict = {}
171
172    parser = argparse.ArgumentParser()
173
174    parser.add_argument('-diag', action='store', dest='diagFile',
175                        help='Set the diagnostic file')
176    parser.add_argument('-warn', action='store', dest='warnFile',
177                        help='Set the warning file')
178    parser.add_argument('-log', action='store', dest='logFile',
179                        help='Set the log file for both diagnostics and warnings')
180    parser.add_argument('-outdir', action='store', dest='outDir',
181                        default='out',
182                        help='Set the base directory in which pages are generated')
183    parser.add_argument('-overwrite', action='store_true',
184                        help='Overwrite input filenames instead of writing different output filenames')
185    parser.add_argument('files', metavar='filename', nargs='*',
186                        help='a filename to extract ref pages from')
187    parser.add_argument('--version', action='version', version='%(prog)s 1.0')
188
189    results = parser.parse_args()
190
191    setLogFile(True,  True, results.logFile)
192    setLogFile(True, False, results.diagFile)
193    setLogFile(False, True, results.warnFile)
194
195    skipped = set()
196    for file in results.files:
197        replaceRef(file, results.outDir, results.overwrite, skipped)
198
199    if len(skipped) > 0:
200        print('Files containing skipped feature blocks:')
201        for file in sorted(skipped):
202            print('\t' + file)
203