|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +# |
| 4 | +# Formatter script for pins_MYPINS.h files |
| 5 | +# |
| 6 | +# Usage: pinsformat.py [infile] [outfile] |
| 7 | +# |
| 8 | +# With no parameters convert STDIN to STDOUT |
| 9 | +# |
| 10 | + |
| 11 | +import sys, re |
| 12 | + |
| 13 | +do_log = False |
| 14 | +def logmsg(msg, line): |
| 15 | + if do_log: print(msg, line) |
| 16 | + |
| 17 | +col_comment = 50 |
| 18 | + |
| 19 | +# String lpad / rpad |
| 20 | +def lpad(astr, fill, c=None): |
| 21 | + if not fill: return astr |
| 22 | + if c == None: c = ' ' |
| 23 | + need = fill - len(astr) |
| 24 | + return astr if need <= 0 else (need * c) + astr |
| 25 | + |
| 26 | +def rpad(astr, fill, c=None): |
| 27 | + if not fill: return astr |
| 28 | + if c == None: c = ' ' |
| 29 | + need = fill - len(astr) |
| 30 | + return astr if need <= 0 else astr + (need * c) |
| 31 | + |
| 32 | +# Pin patterns |
| 33 | +mpatt = [ r'-?\d{1,3}', r'P[A-I]\d+', r'P\d_\d+', r'Pin[A-Z]\d\b' ] |
| 34 | +mstr = '|'.join(mpatt) |
| 35 | +mexpr = [ re.compile(f'^{m}$') for m in mpatt ] |
| 36 | + |
| 37 | +# Corrsponding padding for each pattern |
| 38 | +ppad = [ 3, 4, 5, 5 ] |
| 39 | + |
| 40 | +# Match a define line |
| 41 | +definePatt = re.compile(rf'^\s*(//)?#define\s+[A-Z_][A-Z0-9_]+\s+({mstr})\s*(//.*)?$') |
| 42 | + |
| 43 | +def format_pins(argv): |
| 44 | + src_file = 'stdin' |
| 45 | + dst_file = None |
| 46 | + |
| 47 | + scnt = 0 |
| 48 | + for arg in argv: |
| 49 | + if arg == '-v': |
| 50 | + do_log = True |
| 51 | + elif scnt == 0: |
| 52 | + # Get a source file if specified. Default destination is the same file |
| 53 | + src_file = dst_file = arg |
| 54 | + scnt += 1 |
| 55 | + elif scnt == 1: |
| 56 | + # Get destination file if specified |
| 57 | + dst_file = arg |
| 58 | + scnt += 1 |
| 59 | + |
| 60 | + # No text to process yet |
| 61 | + file_text = '' |
| 62 | + |
| 63 | + if src_file == 'stdin': |
| 64 | + # If no source file specified read from STDIN |
| 65 | + file_text = sys.stdin.read() |
| 66 | + else: |
| 67 | + # Open the file src_file |
| 68 | + with open(src_file, 'r') as rf: |
| 69 | + file_text = rf.read() |
| 70 | + |
| 71 | + if len(file_text) == 0: |
| 72 | + print('No text to process') |
| 73 | + return |
| 74 | + |
| 75 | + # Read from file or STDIN until it terminates |
| 76 | + filtered = process_text(file_text) |
| 77 | + if dst_file: |
| 78 | + with open(dst_file, 'w') as wf: |
| 79 | + wf.write(filtered) |
| 80 | + else: |
| 81 | + print(filtered) |
| 82 | + |
| 83 | +# Find the pin pattern so non-pin defines can be skipped |
| 84 | +def get_pin_pattern(txt): |
| 85 | + r = '' |
| 86 | + m = 0 |
| 87 | + match_count = [ 0, 0, 0, 0 ] |
| 88 | + |
| 89 | + # Find the most common matching pattern |
| 90 | + match_threshold = 5 |
| 91 | + for line in txt.split('\n'): |
| 92 | + r = definePatt.match(line) |
| 93 | + if r == None: continue |
| 94 | + ind = -1 |
| 95 | + for p in mexpr: |
| 96 | + ind += 1 |
| 97 | + if not p.match(r[2]): continue |
| 98 | + match_count[ind] += 1 |
| 99 | + if match_count[ind] >= match_threshold: |
| 100 | + return { 'match': mpatt[ind], 'pad':ppad[ind] } |
| 101 | + return None |
| 102 | + |
| 103 | +def process_text(txt): |
| 104 | + if len(txt) == 0: return '(no text)' |
| 105 | + patt = get_pin_pattern(txt) |
| 106 | + if patt == None: return txt |
| 107 | + |
| 108 | + pindefPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(' + patt['match'] + r')\s*(//.*)?$') |
| 109 | + noPinPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(-1)\s*(//.*)?$') |
| 110 | + skipPatt1 = re.compile(r'^(\s*(//)?#define)\s+(AT90USB|USBCON|(BOARD|DAC|FLASH|HAS|IS|USE)_.+|.+_(ADDRESS|AVAILABLE|BAUDRATE|CLOCK|CONNECTION|DEFAULT|FREQ|ITEM|MODULE|NAME|ONLY|PERIOD|RANGE|RATE|SERIAL|SIZE|SPI|STATE|STEP|TIMER))\s+(.+)\s*(//.*)?$') |
| 111 | + skipPatt2 = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+(0x[0-9A-Fa-f]+|\d+|.+[a-z].+)\s*(//.*)?$') |
| 112 | + aliasPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+([A-Z_][A-Z0-9_()]+)\s*(//.*)?$') |
| 113 | + switchPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s*(//.*)?$') |
| 114 | + undefPatt = re.compile(r'^(\s*(//)?#undef)\s+([A-Z_][A-Z0-9_]+)\s*(//.*)?$') |
| 115 | + defPatt = re.compile(r'^(\s*(//)?#define)\s+([A-Z_][A-Z0-9_]+)\s+([-_\w]+)\s*(//.*)?$') |
| 116 | + condPatt = re.compile(r'^(\s*(//)?#(if|ifn?def|else|elif)(\s+\S+)*)\s+(//.*)$') |
| 117 | + commPatt = re.compile(r'^\s{20,}(//.*)?$') |
| 118 | + |
| 119 | + col_value_lj = col_comment - patt['pad'] - 2 |
| 120 | + col_value_rj = col_comment - 3 |
| 121 | + |
| 122 | + # |
| 123 | + # #define SKIP_ME |
| 124 | + # |
| 125 | + def trySkip1(d): |
| 126 | + if skipPatt1.match(d['line']) == None: return False |
| 127 | + logmsg("skip:", d['line']) |
| 128 | + return True |
| 129 | + |
| 130 | + # |
| 131 | + # #define MY_PIN [pin] |
| 132 | + # |
| 133 | + def tryPindef(d): |
| 134 | + line = d['line'] |
| 135 | + r = pindefPatt.match(line) |
| 136 | + if r == None: return False |
| 137 | + logmsg("pin:", line) |
| 138 | + pinnum = r[4] if r[4][0] == 'P' else lpad(r[4], patt['pad']) |
| 139 | + line = r[1] + ' ' + r[3] |
| 140 | + line = rpad(line, col_value_lj) + pinnum |
| 141 | + if r[5]: line = rpad(line, col_comment) + r[5] |
| 142 | + d['line'] = line |
| 143 | + return True |
| 144 | + |
| 145 | + # |
| 146 | + # #define MY_PIN -1 |
| 147 | + # |
| 148 | + def tryNoPin(d): |
| 149 | + line = d['line'] |
| 150 | + r = noPinPatt.match(line) |
| 151 | + if r == None: return False |
| 152 | + logmsg("pin -1:", line) |
| 153 | + line = r[1] + ' ' + r[3] |
| 154 | + line = rpad(line, col_value_lj) + '-1' |
| 155 | + if r[5]: line = rpad(line, col_comment) + r[5] |
| 156 | + d['line'] = line |
| 157 | + return True |
| 158 | + |
| 159 | + # |
| 160 | + # #define SKIP_ME_TOO |
| 161 | + # |
| 162 | + def trySkip2(d): |
| 163 | + if skipPatt2.match( d['line']) == None: return False |
| 164 | + logmsg("skip:", d['line']) |
| 165 | + return True |
| 166 | + |
| 167 | + # |
| 168 | + # #define ALIAS OTHER |
| 169 | + # |
| 170 | + def tryAlias(d): |
| 171 | + line = d['line'] |
| 172 | + r = aliasPatt.match(line) |
| 173 | + if r == None: return False |
| 174 | + logmsg("alias:", line) |
| 175 | + line = f'{r[1]} {r[3]}' |
| 176 | + line += lpad(r[4], col_value_rj + 1 - len(line)) |
| 177 | + if r[5]: line = rpad(line, col_comment) + r[5] |
| 178 | + d['line'] = line |
| 179 | + return True |
| 180 | + |
| 181 | + # |
| 182 | + # #define SWITCH |
| 183 | + # |
| 184 | + def trySwitch(d): |
| 185 | + line = d['line'] |
| 186 | + r = switchPatt.match(line) |
| 187 | + if r == None: return False |
| 188 | + logmsg("switch:", line) |
| 189 | + line = f'{r[1]} {r[3]}' |
| 190 | + if r[4]: line = rpad(line, col_comment) + r[4] |
| 191 | + d['line'] = line |
| 192 | + d['check_comment_next'] = True |
| 193 | + return True |
| 194 | + |
| 195 | + # |
| 196 | + # #define ... |
| 197 | + # |
| 198 | + def tryDef(d): |
| 199 | + line = d['line'] |
| 200 | + r = defPatt.match(line) |
| 201 | + if r == None: return False |
| 202 | + logmsg("def:", line) |
| 203 | + line = f'{r[1]} {r[3]} ' |
| 204 | + line += lpad(r[4], col_value_rj + 1 - len(line)) |
| 205 | + if r[5]: line = rpad(line, col_comment - 1) + ' ' + r[5] |
| 206 | + d['line'] = line |
| 207 | + return True |
| 208 | + |
| 209 | + # |
| 210 | + # #undef ... |
| 211 | + # |
| 212 | + def tryUndef(d): |
| 213 | + line = d['line'] |
| 214 | + r = undefPatt.match(line) |
| 215 | + if r == None: return False |
| 216 | + logmsg("undef:", line) |
| 217 | + line = f'{r[1]} {r[3]}' |
| 218 | + if r[4]: line = rpad(line, col_comment) + r[4] |
| 219 | + d['line'] = line |
| 220 | + return True |
| 221 | + |
| 222 | + # |
| 223 | + # #if ... |
| 224 | + # |
| 225 | + def tryCond(d): |
| 226 | + line = d['line'] |
| 227 | + r = condPatt.match(line) |
| 228 | + if r == None: return False |
| 229 | + logmsg("cond:", line) |
| 230 | + line = rpad(r[1], col_comment) + r[5] |
| 231 | + d['line'] = line |
| 232 | + d['check_comment_next'] = True |
| 233 | + return True |
| 234 | + |
| 235 | + out = '' |
| 236 | + wDict = { 'check_comment_next': False } |
| 237 | + |
| 238 | + # Transform each line and add it to the output |
| 239 | + for line in txt.split('\n'): |
| 240 | + wDict['line'] = line |
| 241 | + if wDict['check_comment_next']: |
| 242 | + r = commPatt.match(line) |
| 243 | + wDict['check_comment_next'] = (r != None) |
| 244 | + |
| 245 | + if wDict['check_comment_next']: |
| 246 | + # Comments in column 45 |
| 247 | + line = rpad('', col_comment) + r[1] |
| 248 | + |
| 249 | + elif trySkip1(wDict): pass #define SKIP_ME |
| 250 | + elif tryPindef(wDict): pass #define MY_PIN [pin] |
| 251 | + elif tryNoPin(wDict): pass #define MY_PIN -1 |
| 252 | + elif trySkip2(wDict): pass #define SKIP_ME_TOO |
| 253 | + elif tryAlias(wDict): pass #define ALIAS OTHER |
| 254 | + elif trySwitch(wDict): pass #define SWITCH |
| 255 | + elif tryDef(wDict): pass #define ... |
| 256 | + elif tryUndef(wDict): pass #undef ... |
| 257 | + elif tryCond(wDict): pass #if ... |
| 258 | + |
| 259 | + out += wDict['line'] + '\n' |
| 260 | + |
| 261 | + return re.sub('\n\n$', '\n', re.sub(r'\n\n+', '\n\n', out)) |
| 262 | + |
| 263 | +# Python standard startup for command line with arguments |
| 264 | +if __name__ == '__main__': |
| 265 | + format_pins(sys.argv[1:]) |
0 commit comments