|
1 #!/usr/bin/env python3 |
|
2 # coding: utf-8 |
|
3 # |
|
4 # Copyright 2015 - 2017 Teemu Piippo |
|
5 # All rights reserved. |
|
6 # |
|
7 # Redistribution and use in source and binary forms, with or without |
|
8 # modification, are permitted provided that the following conditions |
|
9 # are met: |
|
10 # |
|
11 # 1. Redistributions of source code must retain the above copyright |
|
12 # notice, this list of conditions and the following disclaimer. |
|
13 # 2. Redistributions in binary form must reproduce the above copyright |
|
14 # notice, this list of conditions and the following disclaimer in the |
|
15 # documentation and/or other materials provided with the distribution. |
|
16 # 3. Neither the name of the copyright holder nor the names of its |
|
17 # contributors may be used to endorse or promote products derived from |
|
18 # this software without specific prior written permission. |
|
19 # |
|
20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
|
22 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A |
|
23 # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER |
|
24 # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
|
25 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, |
|
26 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
|
27 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
|
28 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING |
|
29 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
|
30 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
31 # |
|
32 |
|
33 from argparse import ArgumentParser |
|
34 from collections import OrderedDict |
|
35 import caseconversions |
|
36 import outputfile |
|
37 |
|
38 # These types are passed by value |
|
39 passbyvalue = {'int', 'bool', 'float', 'double', 'qreal'} |
|
40 |
|
41 def deduce_type(value): |
|
42 ''' |
|
43 Try to determine the type of value from the value itself. |
|
44 ''' |
|
45 if value in('true', 'false'): |
|
46 return 'bool' |
|
47 elif value.startswith(('"', 'R"')) and value.endswith('"'): |
|
48 return 'QString' |
|
49 |
|
50 try: |
|
51 int(value) |
|
52 return 'int' |
|
53 except: |
|
54 pass |
|
55 |
|
56 try: |
|
57 float(value) |
|
58 return 'double' |
|
59 except: |
|
60 pass |
|
61 |
|
62 if value.endswith('f'): |
|
63 try: |
|
64 float(value[:-1]) |
|
65 return 'float' |
|
66 except: |
|
67 pass |
|
68 |
|
69 raise ValueError('unable to deduce type of %r' % value) |
|
70 |
|
71 class ConfigCollector: |
|
72 def __init__(self, args): |
|
73 self.declarations = OrderedDict() |
|
74 self.qtTypes = set() |
|
75 self.args = args |
|
76 |
|
77 def collect(self, filename): |
|
78 with open(filename) as file: |
|
79 for linenumber, line in enumerate(file, 1): |
|
80 try: |
|
81 line = line.strip() |
|
82 if line and not line.startswith('#'): |
|
83 from re import search |
|
84 match = search('^option (\w+) = (.+)$', line) |
|
85 if not match: |
|
86 raise ValueError('unable to parse: %r' % line) |
|
87 name, value = match.groups() |
|
88 match = search(r'^([a-zA-Z0-9_<>]+)\s*\{(.*)\}$', value) |
|
89 try: |
|
90 typename = match.group(1) |
|
91 except: |
|
92 typename = deduce_type(value) |
|
93 self.declare(name, typename, value) |
|
94 except ValueError as error: |
|
95 from sys import stderr, exit |
|
96 print(str.format( |
|
97 '{file}:{line}: {error}', |
|
98 file = filename, |
|
99 line = linenumber, |
|
100 error = str(error), |
|
101 ), file = stderr) |
|
102 exit(1) |
|
103 # Sort the declarations in alphabetical order |
|
104 self.declarations = OrderedDict(sorted(self.declarations.items(), key = lambda t: t[1]['name'])) |
|
105 # Fill in additional information |
|
106 for declaration in self.declarations.values(): |
|
107 declaration['readgate'] = caseconversions.convert_case(declaration['name'], style='java') |
|
108 declaration['writegate'] = 'set' + caseconversions.convert_case(declaration['name'], style='camel') |
|
109 declaration['togglefunction'] = 'toggle' + caseconversions.convert_case(declaration['name'], style='camel') |
|
110 if declaration['type'] in passbyvalue: |
|
111 declaration['typereference'] = declaration['type'] |
|
112 else: |
|
113 declaration['typereference'] = 'const %s&' % declaration['type'] |
|
114 |
|
115 def declare(self, name, typename, default): |
|
116 from re import findall |
|
117 if name in self.declarations: |
|
118 raise ValueError('Attempted to redeclare %r' % name) |
|
119 self.declarations[name] = { |
|
120 'name': name, |
|
121 'type': typename, |
|
122 'default': default |
|
123 } |
|
124 # Keep a file of any Qt types, we'll need to #include them. |
|
125 self.qtTypes.update(findall(r'Q\w+', typename)) |
|
126 |
|
127 def writeHeader(self, device): |
|
128 device.write('#pragma once\n') |
|
129 device.write('#include <QMap>\n') |
|
130 device.write('#include <QSettings>\n') |
|
131 device.write('#include <glm/glm.hpp>\n') |
|
132 for qtType in sorted(self.qtTypes): |
|
133 device.write('#include <%s>\n' % qtType) |
|
134 device.write('#include "libraries.h"\n') |
|
135 device.write('\n') |
|
136 formatargs = {} |
|
137 write = lambda value: device.write(value) |
|
138 write('class Configuration : private QSettings\n') |
|
139 write('{\n') |
|
140 write('public:\n') |
|
141 write('\tusing QSettings::QSettings;\n'); |
|
142 write('\tbool exists(const QString& name);\n') |
|
143 write('\tQVariant value(const QString& name);\n') |
|
144 write('\tQVariant setValue(const QString& name);\n') |
|
145 for declaration in self.declarations.values(): |
|
146 write('\t{type} {readgate}();\n'.format(**declaration)) |
|
147 for declaration in self.declarations.values(): |
|
148 write('\tvoid {writegate}({typereference} value);\n'.format(**declaration)) |
|
149 for declaration in filter(lambda declaration: declaration['type'] == 'bool', self.declarations.values()): |
|
150 write('\tvoid {togglefunction}();\n'.format(**declaration)) |
|
151 write('private:\n') |
|
152 write('\tusing QSettings::value;\n') |
|
153 write('\tusing QSettings::setValue;\n') |
|
154 write('\tstatic const QMap<QString, QVariant>& defaults();\n') |
|
155 write('};\n') |
|
156 |
|
157 def writeSource(self, device, headername): |
|
158 device.write('#include <QSet>\n') |
|
159 device.write('#include <QSettings>\n') |
|
160 device.write('#include <QVariant>\n') |
|
161 device.write('#include "%s/mainwindow.h"\n' % (self.args.sourcedir)) |
|
162 device.write('#include "%s"\n' % headername) |
|
163 device.write( |
|
164 'const QMap<QString, QVariant>& Configuration::defaults()\n' |
|
165 '{\n' |
|
166 '\tstatic const QMap<QString, QVariant> defaults {\n' |
|
167 ) |
|
168 for declaration in self.declarations.values(): |
|
169 device.write('\t\t{{"{name}", QVariant::fromValue<{type}>({default})}},\n'.format(**declaration)) |
|
170 device.write('\t};\n' |
|
171 '\treturn defaults;\n' |
|
172 '}\n' |
|
173 '\n') |
|
174 device.write('bool Configuration::exists(const QString& name)\n') |
|
175 device.write('{\n') |
|
176 device.write('\treturn defaults().contains(name);\n') |
|
177 device.write('}\n') |
|
178 device.write('\n') |
|
179 device.write('QVariant Configuration::value(const QString& name)\n' |
|
180 '{\n' |
|
181 '\treturn this->value(name, Configuration::defaults().value(name));\n' |
|
182 '}\n') |
|
183 device.write('\n') |
|
184 for declaration in self.declarations.values(): |
|
185 device.write('{type} Configuration::{readgate}()\n'.format(**declaration)) |
|
186 device.write('{\n') |
|
187 device.write('\tstatic const QVariant defaultvalue = QVariant::fromValue<{type}>({default});\n'.format(**declaration)) |
|
188 device.write('\treturn this->value("{name}", defaultvalue).value<{type}>();\n'.format(**declaration)) |
|
189 device.write('}\n') |
|
190 device.write('\n') |
|
191 for declaration in self.declarations.values(): |
|
192 device.write('void Configuration::{writegate}({typereference} value)\n'.format(**declaration)) |
|
193 device.write('{\n') |
|
194 device.write('\tif (value != {default})\n'.format(**declaration)) |
|
195 device.write('\t\tthis->setValue("{name}", QVariant::fromValue<{type}>(value));\n'.format(**declaration)) |
|
196 device.write('\telse\n') |
|
197 device.write('\t\tthis->remove("{name}");\n'.format(**declaration)) |
|
198 device.write('}\n') |
|
199 device.write('\n') |
|
200 for declaration in filter(lambda declaration: declaration['type'] == 'bool', self.declarations.values()): |
|
201 device.write('void Configuration::{togglefunction}()\n'.format(**declaration)) |
|
202 device.write('{\n') |
|
203 device.write('\t{writegate}(not {readgate}());\n'.format(**declaration)) |
|
204 device.write('}\n') |
|
205 device.write('\n') |
|
206 |
|
207 def main(): |
|
208 parser = ArgumentParser(description='Collects a list of configuration objects') |
|
209 parser.add_argument('input') |
|
210 parser.add_argument('--header', required=True) |
|
211 parser.add_argument('--source', required=True) |
|
212 parser.add_argument('--sourcedir', required=True) |
|
213 args = parser.parse_args() |
|
214 collector = ConfigCollector(args) |
|
215 collector.collect(args.input) |
|
216 from outputfile import OutputFile |
|
217 header = OutputFile(args.header) |
|
218 source = OutputFile(args.source) |
|
219 collector.writeSource(source, headername=args.header) |
|
220 collector.writeHeader(header) |
|
221 header.save(verbose = True) |
|
222 source.save(verbose = True) |
|
223 |
|
224 if __name__ == '__main__': |
|
225 main() |