1#!/usr/local/bin/python3.8 2 3import argparse 4import sys 5from pathlib import Path 6 7 8def handle_file(path): 9 ''' 10 Return a tuple of (header, path) for the file at path. 11 If the file does not have a header, the header is the empty string. 12 ''' 13 with open(path) as fd: 14 header = fd.readline() 15 if header.startswith('! '): 16 return header, path 17 else: 18 return '', path 19 20 21def merge(dest, files): 22 ''' 23 Merge the content of all files into the file dest. 24 25 The first line of each file is an optional section header in the form 26 e.g. 27 ! model = keycodes 28 Where two sections have identical headers, the second header is skipped. 29 30 Special case are header-less files which we store with the empty string 31 as header, these need to get written out first. 32 ''' 33 34 def sort_basename(path): 35 return path.name 36 37 # sort the file list by basename 38 files.sort(key=sort_basename) 39 40 # pre-populate with the empty header so it's the first one to be written 41 # out. We use section_names to keep the same order as we get the files 42 # passed in (superfluous with python 3.6+ since the dict keeps the 43 # insertion order anyway). 44 sections = {'': []} 45 section_names = [''] 46 for path in files: 47 # files may exist in srcdir or builddir, depending whether they're 48 # generated 49 header, path = handle_file(path) 50 paths = sections.get(header, []) 51 paths.append(path) 52 sections[header] = paths 53 if header not in section_names: 54 section_names.append(header) 55 56 for header in section_names: 57 if header: 58 dest.write('\n') 59 dest.write(header) 60 for f in sections[header]: 61 with open(f) as fd: 62 if header: 63 fd.readline() # drop the header 64 dest.write(fd.read()) 65 66 67if __name__ == '__main__': 68 parser = argparse.ArgumentParser('rules file merge script') 69 parser.add_argument('--dest', type=str, default=None) 70 parser.add_argument('--srcdir', type=str) 71 parser.add_argument('--builddir', type=str) 72 parser.add_argument('files', nargs='+', type=str) 73 ns = parser.parse_args() 74 75 if ns.dest is None: 76 dest = sys.stdout 77 else: 78 dest = ns.dest 79 80 with dest or open(dest, 'w') as fd: 81 basename = Path(sys.argv[0]).name 82 fd.write('// DO NOT EDIT THIS FILE - IT WAS AUTOGENERATED BY {} FROM rules/*.part\n'.format(basename)) 83 fd.write('//\n') 84 85 def find_file(f): 86 if ns.builddir: 87 path = Path(ns.builddir) / f 88 if path.exists(): 89 return path 90 if ns.srcdir: 91 path = Path(ns.srcdir) / f 92 if path.exists(): 93 return path 94 return Path(f) 95 96 merge(fd, [find_file(f) for f in ns.files]) 97