blob: b8a0321bdd3f39ad9811c9545f36a0d6e5dc6ef2 [file] [log] [blame]
Tim Edwards55f4d0e2020-07-05 15:41:02 -04001#!/bin/env python3
2# Script to read a GDS file, modify the given string, and rewrite the GDS file.
3# The string may be a substring; the GDS file will be parsed completely for
4# library name, structure name, instance name, and other strings, and the
5# replacement made everywhere it occurs, finding the bounds of the entire
6# string around the search text, and adjusting the record bounds accordingly.
7
8import os
9import sys
10
11def usage():
12 print('change_gds_string.py <old_string> <new_string> <path_to_gds_in> [<path_to_gds_out>]')
13
14if __name__ == '__main__':
15 debug = False
16
17 if len(sys.argv) == 1:
18 print("No options given to change_gds_string.py.")
19 usage()
20 sys.exit(0)
21
22 optionlist = []
23 arguments = []
24
25 for option in sys.argv[1:]:
26 if option.find('-', 0) == 0:
27 optionlist.append(option)
28 else:
29 arguments.append(option)
30
31 if len(arguments) < 3 or len(arguments) > 4:
32 print("Wrong number of arguments given to change_gds_string.py.")
33 usage()
34 sys.exit(0)
35
36 if '-debug' in optionlist:
37 debug = True
38
39 oldstring = arguments[0]
40 newstring = arguments[1]
41 source = arguments[2]
42
43 # If only three arguments are provided, then overwrite the source file.
44 if len(arguments) == 4:
45 dest = arguments[3]
46 else:
47 dest = arguments[2]
48
49 sourcedir = os.path.split(source)[0]
50 gdsinfile = os.path.split(source)[1]
51
52 destdir = os.path.split(dest)[0]
53 gdsoutfile = os.path.split(dest)[1]
54
55 with open(source, 'rb') as ifile:
56 gdsdata = ifile.read()
57
58 # To be done: Allow the user to select a specific record type or types
59 # in which to restrict the string substitution. If no restrictions are
60 # specified, then substitue in library name, structure name, and strings.
61
62 recordtypes = ['libname', 'strname', 'sname', 'string']
63 recordfilter = [2, 6, 18, 25]
64 bsearch = bytes(oldstring, 'ascii')
65 brep = bytes(newstring, 'ascii')
66
67 datalen = len(gdsdata)
68 if debug:
69 print('Original data length = ' + str(datalen))
70 dataptr = 0
71 while dataptr < datalen:
72 # Read stream records up to any string, then search for search text.
73 bheader = gdsdata[dataptr:dataptr + 2]
74 reclen = int.from_bytes(bheader, 'big')
75 newlen = reclen
76 if newlen == 0:
77 print('Error: found zero-length record at position ' + str(dataptr))
78 break
79
80 rectype = gdsdata[dataptr + 2]
81 datatype = gdsdata[dataptr + 3]
82
83 if rectype in recordfilter:
84 # Datatype 6 is STRING
85 if datatype == 6:
86 if debug:
87 print('Record type = ' + str(rectype) + ' data type = ' + str(datatype) + ' length = ' + str(reclen))
88
89 bstring = gdsdata[dataptr + 4: dataptr + reclen]
90 repstring = bstring.replace(bsearch, brep)
91 if repstring != bstring:
92 before = gdsdata[0:dataptr]
93 after = gdsdata[dataptr + reclen:]
94 newlen = len(repstring) + 4
95 # Record sizes must be even
96 if newlen % 2 != 0:
97 # Was original string padded with null byte? If so,
98 # remove the null byte and reduce newlen. Otherwise,
99 # add a null byte and increase newlen.
100 if bstring[-1] == 0:
101 repstring = repstring[0:-1]
102 newlen -= 1
103 else:
104 repstring += b'\x00'
105 newlen += 1
106
107 bnewlen = newlen.to_bytes(2, byteorder='big')
108 brectype = rectype.to_bytes(1, byteorder='big')
109 bdatatype = datatype.to_bytes(1, byteorder='big')
110
111 # Assemble the new record
112 newrecord = bnewlen + brectype + bdatatype + repstring
113 # Reassemble the GDS data around the new record
114 gdsdata = before + newrecord[0:newlen] + after
115 # Adjust the data end location
116 datalen += (newlen - reclen)
117
118 if debug:
119 print('Replaced ' + str(bstring) + ' with ' + str(repstring))
120
121 # Advance the pointer past the data
122 dataptr += newlen
123
124 with open(dest, 'wb') as ofile:
125 ofile.write(gdsdata)
126
127 exit(0)