tools/configcollector.py

Thu, 09 Mar 2017 12:50:14 +0200

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 09 Mar 2017 12:50:14 +0200
changeset 1209
c2723022b173
parent 1206
743dc95e0be6
permissions
-rwxr-xr-x

Laid groundwork for library collection support.

#!/usr/bin/env python3
# coding: utf-8
#
#	Copyright 2015 - 2017 Teemu Piippo
#	All rights reserved.
#
#	Redistribution and use in source and binary forms, with or without
#	modification, are permitted provided that the following conditions
#	are met:
#
#	1. Redistributions of source code must retain the above copyright
#	   notice, this list of conditions and the following disclaimer.
#	2. Redistributions in binary form must reproduce the above copyright
#	   notice, this list of conditions and the following disclaimer in the
#	   documentation and/or other materials provided with the distribution.
#	3. Neither the name of the copyright holder nor the names of its
#	   contributors may be used to endorse or promote products derived from
#	   this software without specific prior written permission.
#
#	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
#	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
#	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
#	PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
#	OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
#	EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO,
#	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#	PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#	LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING
#	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

from argparse import ArgumentParser
from collections import OrderedDict
import caseconversions
import outputfile

# These types are passed by value
passbyvalue = {'int', 'bool', 'float', 'double', 'qreal'}

def deduce_type(value):
	'''
		Try to determine the type of value from the value itself.
	'''
	if value in('true', 'false'):
		return 'bool'
	elif value.startswith('"') and value.endswith('"'):
		return 'QString'

	try:
		int(value)
		return 'int'
	except:
		pass

	try:
		float(value)
		return 'double'
	except:
		pass

	if endswith(value, 'f'):
		try:
			float(value[:-1])
			return 'float'
		except:
			pass

	raise ValueError('unable to deduce type of %r' % value)

class ConfigCollector:
	def __init__(self, args):
		self.declarations = OrderedDict()
		self.qtTypes = set()
		self.args = args
		self.includes = []

	def collect(self, filename):
		with open(filename) as file:
			for line in file:
				line = line.strip()
				if line and not line.startswith('#'):
					from re import search
					match = search('^option (\w+) = (.+)$', line)
					if match:
						name, value = match.groups()
						match = search(r'^(\w+)\s*\{(.*)\}$', value)
						try:
							typename, value = match.groups()
							if not value:
								value = typename + ' {}'
						except:
							typename = deduce_type(value)
						self.declare(name, typename, value)
					else:
						match = search('^include (.+)$', line)
						if match:
							filename = match.group(1)
							if filename.startswith('"'):
								from os.path import join
								filename = '"' + join(self.args.sourcedir, filename[1:-1]) + '"'
							self.includes.append(filename)
						else:
							raise ValueError('unable to parse: %r' % line)

		# Sort the declarations in alphabetical order
		self.declarations = OrderedDict(sorted(self.declarations.items(), key = lambda t: t[1]['name']))
		# Fill in additional information
		for declaration in self.declarations.values():
			declaration['readgate'] = caseconversions.convert_case(declaration['name'], style='java')
			declaration['writegate'] = 'set' + caseconversions.convert_case(declaration['name'], style='camel')
			declaration['togglefunction'] = 'toggle' + caseconversions.convert_case(declaration['name'], style='camel')
			if declaration['type'] in passbyvalue:
				declaration['typereference'] = declaration['type']
			else:
				declaration['typereference'] = 'const %s&' % declaration['type']

	def declare(self, name, typename, default):
		from re import findall
		if name in self.declarations:
			raise ValueError('Attempted to redeclare %r' % name)
		self.declarations[name] = {
			'name': name,
			'type': typename,
			'default': default
		}
		# Keep a file of any Qt types, we'll need to #include them.
		self.qtTypes.update(findall(r'Q\w+', typename))

	def writeHeader(self, device):
		write = lambda value: device.write(value + '\n')
		write('#pragma once')
		write('#include "{sourcedir}/baseconfiguration.h"'.format(sourcedir = self.args.sourcedir))
		write('class Configuration : public BaseConfiguration')
		write('{')
		write('public:')
		write('\tvoid initDefaults() override;')
		for declaration in self.declarations.values():
			write('\t{type} {readgate}();'.format(**declaration))
		for declaration in self.declarations.values():
			write('\tvoid {writegate}({typereference} value);'.format(**declaration))
		for declaration in filter(lambda declaration: declaration['type'] == 'bool', self.declarations.values()):
			write('\tvoid {togglefunction}();'.format(**declaration))
		write('};')
		write('')
	
	def writeSource(self, device, headername):
		for qttype in sorted(self.qtTypes):
			device.write('#include <%s>\n' % qttype)
		device.write('#include "configuration.h"\n')
		device.write('void Configuration::initDefaults()\n')
		device.write('{\n')
		device.write('\tBaseConfiguration::initDefaults();\n')
		for declaration in self.declarations.values():
			device.write('\tregisterConfigurationEntry("{name}", QVariant::fromValue<{type}>({default}));\n'.format(**declaration))
		device.write('}\n')
		for declaration in self.declarations.values():
			device.write('{type} Configuration::{readgate}()\n'.format(**declaration))
			device.write('{\n')
			device.write('\treturn value("{name}").value<{type}>();\n'.format(**declaration))
			device.write('}\n')
			device.write('\n')
		for declaration in self.declarations.values():
			device.write('void Configuration::{writegate}({typereference} value)\n'.format(**declaration))
			device.write('{\n')
			device.write('\tsetValue("{name}", QVariant::fromValue(value));\n'.format(**declaration))
			device.write('}\n')
			device.write('\n')
		for declaration in filter(lambda declaration: declaration['type'] == 'bool', self.declarations.values()):
			device.write('void Configuration::{togglefunction}()\n'.format(**declaration))
			device.write('{\n')
			device.write('\t{writegate}(not {readgate}());\n'.format(**declaration))
			device.write('}\n')
			device.write('\n')

def main():
	parser = ArgumentParser(description='Collects a list of configuration objects')
	parser.add_argument('input')
	parser.add_argument('--header', required=True)
	parser.add_argument('--source', required=True)
	parser.add_argument('--sourcedir', required=True)
	args = parser.parse_args()
	collector = ConfigCollector(args)
	collector.collect(args.input)
	header = outputfile.OutputFile(args.header)
	source = outputfile.OutputFile(args.source)
	collector.writeSource(source, headername=args.header)
	collector.writeHeader(header)
	header.save(verbose = True)
	source.save(verbose = True)

if __name__ == '__main__':
	main()

mercurial