blob: c33e6b7445ef69cbeea9f22fb9b503dd63751384 [file] [log] [blame]
Neels Hofmeyr697a6172018-08-22 17:32:21 +02001#!/usr/bin/env python3
Neels Hofmeyr57e42882018-08-23 15:19:35 +02002'''Take values from a config file and fill them into a set of templates.
3Write the result to the current directory.'''
Neels Hofmeyr697a6172018-08-22 17:32:21 +02004
5import os, sys, re, shutil
Neels Hofmeyr57e42882018-08-23 15:19:35 +02006import argparse
Neels Hofmeyr697a6172018-08-22 17:32:21 +02007
Neels Hofmeyr57e42882018-08-23 15:19:35 +02008def file_newer(path_a, than_path_b):
9 return os.path.getmtime(path_a) > os.path.getmtime(than_path_b)
Neels Hofmeyr697a6172018-08-22 17:32:21 +020010
Neels Hofmeyr57e42882018-08-23 15:19:35 +020011LAST_LOCAL_CONFIG_FILE = '.last_config'
12LAST_TMPL_DIR = '.last_templates'
Neels Hofmeyr697a6172018-08-22 17:32:21 +020013
Neels Hofmeyr57e42882018-08-23 15:19:35 +020014parser = argparse.ArgumentParser(description=__doc__,
15 formatter_class=argparse.RawDescriptionHelpFormatter)
16parser.add_argument('sources', metavar='SRC', nargs='*',
17 help='Pass both a template directory and a config file.')
18parser.add_argument('-s', '--check-stale', dest='check_stale', action='store_true',
19 help='only verify age of generated files vs. config and templates.'
20 ' Exit nonzero when any source file is newer. Do not write anything.')
21
22args = parser.parse_args()
23
24local_config_file = None
25tmpl_dir = None
26
27for src in args.sources:
28 if os.path.isdir(src):
29 if tmpl_dir is not None:
30 print('Error: only one template dir permitted. (%r vs. %r)' % (tmpl_dir, src))
31 tmpl_dir = src
32 elif os.path.isfile(src):
33 if local_config_file is not None:
34 print('Error: only one config file permitted. (%r vs. %r)' % (local_config_file, src))
35 local_config_file = src
36
37if local_config_file is None and os.path.isfile(LAST_LOCAL_CONFIG_FILE):
38 local_config_file = open(LAST_LOCAL_CONFIG_FILE).read().strip()
39
40if tmpl_dir is None and os.path.isfile(LAST_TMPL_DIR):
41 tmpl_dir = open(LAST_TMPL_DIR).read().strip()
42
43if not tmpl_dir or not os.path.isdir(tmpl_dir):
Neels Hofmeyr697a6172018-08-22 17:32:21 +020044 print("Template dir does not exist: %r" % tmpl_dir)
45 exit(1)
46
Neels Hofmeyr57e42882018-08-23 15:19:35 +020047if not local_config_file or not os.path.isfile(local_config_file):
Neels Hofmeyrd924e332018-09-10 00:05:29 +020048 print("No such config file: %r" % local_config_file)
Neels Hofmeyr57e42882018-08-23 15:19:35 +020049 exit(1)
50
51local_config_file = os.path.realpath(local_config_file)
52tmpl_dir = os.path.realpath(tmpl_dir)
Oliver Smithfd7446f2018-10-22 11:59:55 +020053net_dir = os.path.realpath(".")
Neels Hofmeyr57e42882018-08-23 15:19:35 +020054
Oliver Smithfd7446f2018-10-22 11:59:55 +020055print('using config file %r\non templates %r\nwith NET_DIR %r' % (local_config_file, tmpl_dir, net_dir))
Neels Hofmeyr697a6172018-08-22 17:32:21 +020056
Neels Hofmeyr57e42882018-08-23 15:19:35 +020057with open(LAST_LOCAL_CONFIG_FILE, 'w') as last_file:
58 last_file.write(local_config_file)
59with open(LAST_TMPL_DIR, 'w') as last_file:
60 last_file.write(tmpl_dir)
61
Neels Hofmeyr697a6172018-08-22 17:32:21 +020062# read in variable values from config file
Oliver Smithfd7446f2018-10-22 11:59:55 +020063# NET_DIR is the folder where fill_config.py was started
64local_config = {"NET_DIR": net_dir}
Neels Hofmeyr697a6172018-08-22 17:32:21 +020065
66line_nr = 0
67for line in open(local_config_file):
68 line_nr += 1
69 line = line.strip('\n')
Neels Hofmeyr57e42882018-08-23 15:19:35 +020070
71 if line.startswith('#'):
72 continue
73
Neels Hofmeyr697a6172018-08-22 17:32:21 +020074 if not '=' in line:
75 if line:
76 print("Error: %r line %d: %r" % (local_config_file, line_nr, line))
77 exit(1)
78 continue
79
80 split_pos = line.find('=')
81 name = line[:split_pos]
82 val = line[split_pos + 1:]
83
84 if val.startswith('"') and val.endswith('"'):
85 val = val[1:-1]
86
87 if name in local_config:
88 print("Error: duplicate identifier in %r line %d: %r" % (local_config_file, line_nr, line))
89 local_config[name] = val
90
Neels Hofmeyr697a6172018-08-22 17:32:21 +020091# replace variable names with above values recursively
92replace_re = re.compile('\$\{([A-Za-z0-9_]*)\}')
93command_re = re.compile('\$\{([A-Za-z0-9_]*)\(([^)]*)\)\}')
94
95idx = 0
96
Neels Hofmeyr57e42882018-08-23 15:19:35 +020097def check_stale(src_path, target_path):
98 if file_newer(src_path, target_path):
99 print('Stale: %r is newer than %r' % (src_path, target_path))
100 exit(1)
101
Neels Hofmeyre02e0ae2018-09-26 14:57:57 +0200102def insert_includes(tmpl, tmpl_dir, tmpl_src):
103 for m in command_re.finditer(tmpl):
104 cmd = m.group(1)
105 arg = m.group(2)
106 if cmd == 'include':
107 include_path = os.path.join(tmpl_dir, arg)
108 if not os.path.isfile(include_path):
109 print('Error: included file does not exist: %r in %r' % (include_path, tmpl_src))
110 exit(1)
111 try:
112 incl = open(include_path).read()
113 except:
114 print('Cannot read %r for %r' % (include_path, tmpl_src))
115 raise
116 if args.check_stale:
117 check_stale(include_path, dst)
118
119 # recurse, to follow the paths that the included bits come from
120 incl = insert_includes(incl, os.path.dirname(include_path), include_path)
121
122 tmpl = tmpl.replace('${%s(%s)}' % (cmd, arg), incl)
123 else:
124 print('Error: unknown command: %r in %r' % (cmd, tmpl_src))
125 exit(1)
126 return tmpl
127
Neels Hofmeyr697a6172018-08-22 17:32:21 +0200128for tmpl_name in sorted(os.listdir(tmpl_dir)):
Neels Hofmeyr57e42882018-08-23 15:19:35 +0200129
130 # omit "hidden" files
131 if tmpl_name.startswith('.'):
132 continue
133
Neels Hofmeyr697a6172018-08-22 17:32:21 +0200134 tmpl_src = os.path.join(tmpl_dir, tmpl_name)
135 dst = tmpl_name
136
Neels Hofmeyr57e42882018-08-23 15:19:35 +0200137 if args.check_stale:
138 check_stale(local_config_file, dst)
139 check_stale(tmpl_src, dst)
140
Neels Hofmeyr697a6172018-08-22 17:32:21 +0200141 local_config['_fname'] = tmpl_name
142 local_config['_name'] = os.path.splitext(tmpl_name)[0]
143 local_config['_idx0'] = str(idx)
144 idx += 1
145 local_config['_idx1'] = str(idx)
146
147 try:
148 result = open(tmpl_src).read()
149 except:
150 print('Error in %r' % tmpl_src)
151 raise
152
153 while True:
154 used_vars = set()
Neels Hofmeyre02e0ae2018-09-26 14:57:57 +0200155
156 result = insert_includes(result, tmpl_dir, tmpl_src)
Neels Hofmeyr697a6172018-08-22 17:32:21 +0200157
158 for m in replace_re.finditer(result):
159 name = m.group(1)
160 if not name in local_config:
161 print('Error: undefined var %r in %r' % (name, tmpl_src))
162 exit(1)
163 used_vars.add(name)
164
165 if not used_vars:
166 break
167
168 for var in used_vars:
169 result = result.replace('${%s}' % var, local_config.get(var))
170
Neels Hofmeyr57e42882018-08-23 15:19:35 +0200171 if not args.check_stale:
172 with open(dst, 'w') as dst_file:
173 dst_file.write(result)
174 shutil.copymode(tmpl_src, dst)
Neels Hofmeyr697a6172018-08-22 17:32:21 +0200175
176# vim: ts=2 sw=2 expandtab