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