Tim Edwards | 9d3debb | 2020-10-20 20:52:18 -0400 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 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 | |
| 8 | import os |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 9 | import re |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 10 | import sys |
| 11 | |
| 12 | def usage(): |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 13 | print('change_gds_string.py <old_string> <new_string> [...] <path_to_gds_in> [<path_to_gds_out>]') |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 14 | |
| 15 | if __name__ == '__main__': |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 16 | debug = 0 |
| 17 | verbatim = False |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 18 | |
| 19 | if len(sys.argv) == 1: |
| 20 | print("No options given to change_gds_string.py.") |
| 21 | usage() |
| 22 | sys.exit(0) |
| 23 | |
| 24 | optionlist = [] |
| 25 | arguments = [] |
| 26 | |
| 27 | for option in sys.argv[1:]: |
| 28 | if option.find('-', 0) == 0: |
| 29 | optionlist.append(option) |
| 30 | else: |
| 31 | arguments.append(option) |
| 32 | |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 33 | if len(arguments) < 3: |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 34 | print("Wrong number of arguments given to change_gds_string.py.") |
| 35 | usage() |
| 36 | sys.exit(0) |
| 37 | |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 38 | for option in optionlist: |
| 39 | opval = option.split('=') |
| 40 | if opval[0] == '-debug': |
| 41 | if len(opval) == 2: |
| 42 | debug = int(opval[1]) |
| 43 | else: |
| 44 | debug = 1 |
| 45 | elif opval[0] == '-verbatim': |
| 46 | verbatim = True |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 47 | |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 48 | # If next-to-last argument is a valid path, then the last argument should |
| 49 | # be the path to GDS out. Otherwise, overwrite the source file. |
| 50 | |
| 51 | if os.path.isfile(arguments[-2]): |
| 52 | dest = arguments[-1] |
| 53 | source = arguments[-2] |
| 54 | oldstrings = arguments[0:-2:2] |
| 55 | newstrings = arguments[1:-2:2] |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 56 | else: |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 57 | dest = arguments[-1] |
| 58 | source = arguments[-1] |
| 59 | oldstrings = arguments[0:-1:2] |
| 60 | newstrings = arguments[1:-1:2] |
| 61 | |
| 62 | if len(oldstrings) != len(newstrings): |
| 63 | print('Error: List of strings and replacements is not in pairs.') |
| 64 | sys.exit(1) |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 65 | |
| 66 | sourcedir = os.path.split(source)[0] |
| 67 | gdsinfile = os.path.split(source)[1] |
| 68 | |
| 69 | destdir = os.path.split(dest)[0] |
| 70 | gdsoutfile = os.path.split(dest)[1] |
| 71 | |
| 72 | with open(source, 'rb') as ifile: |
| 73 | gdsdata = ifile.read() |
| 74 | |
| 75 | # To be done: Allow the user to select a specific record type or types |
| 76 | # in which to restrict the string substitution. If no restrictions are |
| 77 | # specified, then substitue in library name, structure name, and strings. |
| 78 | |
| 79 | recordtypes = ['libname', 'strname', 'sname', 'string'] |
| 80 | recordfilter = [2, 6, 18, 25] |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 81 | bsearchlist = list(bytes(item, 'ascii') for item in oldstrings) |
| 82 | breplist = list(bytes(item, 'ascii') for item in newstrings) |
| 83 | |
| 84 | if debug > 1: |
| 85 | print('Search list = ' + str(bsearchlist)) |
| 86 | print('Replace list = ' + str(breplist)) |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 87 | |
| 88 | datalen = len(gdsdata) |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 89 | if debug > 0: |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 90 | print('Original data length = ' + str(datalen)) |
| 91 | dataptr = 0 |
| 92 | while dataptr < datalen: |
| 93 | # Read stream records up to any string, then search for search text. |
| 94 | bheader = gdsdata[dataptr:dataptr + 2] |
| 95 | reclen = int.from_bytes(bheader, 'big') |
| 96 | newlen = reclen |
| 97 | if newlen == 0: |
| 98 | print('Error: found zero-length record at position ' + str(dataptr)) |
| 99 | break |
| 100 | |
| 101 | rectype = gdsdata[dataptr + 2] |
| 102 | datatype = gdsdata[dataptr + 3] |
| 103 | |
| 104 | if rectype in recordfilter: |
| 105 | # Datatype 6 is STRING |
| 106 | if datatype == 6: |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 107 | bstring = gdsdata[dataptr + 4: dataptr + reclen] |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 108 | if debug > 1: |
| 109 | idx = recordfilter.index(rectype) |
| 110 | print(recordtypes[idx] + ' string = ' + str(bstring)) |
| 111 | |
| 112 | for bsearch,brep in zip(bsearchlist, breplist): |
| 113 | # Verbatim option: search string must match GDS string exactly |
| 114 | if verbatim: |
| 115 | blen = reclen - 4 |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 116 | if bstring[-1] == 0: |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 117 | blen = blen - 1 |
| 118 | if len(bsearch) != blen: |
| 119 | continue |
| 120 | repstring = re.sub(bsearch, brep, bstring) |
| 121 | if repstring != bstring: |
| 122 | before = gdsdata[0:dataptr] |
| 123 | after = gdsdata[dataptr + reclen:] |
| 124 | newlen = len(repstring) + 4 |
| 125 | # Record sizes must be even |
| 126 | if newlen % 2 != 0: |
| 127 | # Was original string padded with null byte? If so, |
| 128 | # remove the null byte and reduce newlen. Otherwise, |
| 129 | # add a null byte and increase newlen. |
| 130 | if bstring[-1] == 0: |
| 131 | repstring = repstring[0:-1] |
| 132 | newlen -= 1 |
| 133 | else: |
| 134 | repstring += b'\x00' |
| 135 | newlen += 1 |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 136 | |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 137 | bnewlen = newlen.to_bytes(2, byteorder='big') |
| 138 | brectype = rectype.to_bytes(1, byteorder='big') |
| 139 | bdatatype = datatype.to_bytes(1, byteorder='big') |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 140 | |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 141 | # Assemble the new record |
| 142 | newrecord = bnewlen + brectype + bdatatype + repstring |
| 143 | # Reassemble the GDS data around the new record |
| 144 | gdsdata = before + newrecord[0:newlen] + after |
| 145 | # Adjust the data end location |
| 146 | datalen += (newlen - reclen) |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 147 | |
Tim Edwards | cc82b60 | 2021-06-06 16:35:34 -0400 | [diff] [blame] | 148 | if debug > 0: |
| 149 | print('Replaced ' + str(bstring) + ' with ' + str(repstring)) |
| 150 | else: |
| 151 | if debug > 1: |
| 152 | idx = recordfilter.index(rectype) |
| 153 | print(recordtypes[idx] + ' record = ' + str(datatype) + ' is not a string') |
Tim Edwards | 55f4d0e | 2020-07-05 15:41:02 -0400 | [diff] [blame] | 154 | |
| 155 | # Advance the pointer past the data |
| 156 | dataptr += newlen |
| 157 | |
| 158 | with open(dest, 'wb') as ofile: |
| 159 | ofile.write(gdsdata) |
| 160 | |
| 161 | exit(0) |