git » pymisc » commit a7d5a88

Add a simple and friendly config parser.

author Alberto Bertogli
2007-08-17 20:09:20 UTC
committer Alberto Bertogli
2007-08-17 20:09:20 UTC
parent 09219f05edcb166e2c29e9d1fadfa03e9415a972

Add a simple and friendly config parser.

configparser.py +138 -0

diff --git a/configparser.py b/configparser.py
new file mode 100644
index 0000000..92e9696
--- /dev/null
+++ b/configparser.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+
+"""
+A simple, pythonic configuration parser.
+Alberto Bertogli (albertito@gmail.com)
+
+It parses files of the form:
+
+------------------ 8< ------------------ 8< ------------------
+key1 = value1
+key2 = 3453
+
+group1.key1 = 26.56
+group1.key2 = 01.59
+------------------ 8< ------------------ 8< ------------------
+
+It knows the available keys, and can check for data types and parse
+accordingly. It supports default values.
+
+Use it like:
+
+# Define the available keys, their types, and the default values
+settings = (
+	('key1', str),
+	('key2', int),
+	('key3', str, 'default value'),
+
+	('group1.key1', float),
+	('group1.key2', float, 9283),
+)
+
+# Parse the given filename
+config = Config('config', settings)
+
+# Use the results in a python-friendly way
+print config.key1
+print config.group1.key2
+"""
+
+
+# Use new style objects everywhere
+__metaclass__ = type
+
+class ConfigError (Exception):
+	pass
+
+class Config:
+	def __init__(self, filename = None, settings = ()):
+		if not filename:
+			return
+
+		# settings is a list of either:
+		# - name or (name,), which means mandatory string
+		# - (name, class), which means mandatory of the given class
+		# - (name, class, default_value), which means optional of the
+		#	 given class, with the given default
+		mandatory = {}
+		optional = {}
+		have_set = []
+		for i in settings:
+			if isinstance(i, str):
+				mandatory[i] = str
+			elif len(i) == 1:
+				mandatory[i[0]] = str
+			elif len(i) == 2:
+				mandatory[i[0]] = i[1]
+			elif len(i) == 3:
+				optional[i[0]] = (i[1], i[2])
+
+		try:
+			f = open(filename)
+		except:
+			desc = "Can't find config file: %s" % filename
+			raise ConfigError, desc
+
+		count = 0
+		for l in f:
+			count += 1
+			l = l.strip()
+
+			if (not l) or l.startswith('#'):
+				continue
+
+			if '=' in l:
+				name, val = l.split('=', 1)
+				name = name.strip()
+				name = name.replace('\s', '_')
+				val = val.strip()
+				if name in mandatory:
+					val = self._convert_to(name, val,
+							mandatory[name], count)
+				elif name in optional:
+					val = self._convert_to(name, val,
+							optional[name][0],
+							count)
+
+				have_set.append(name)
+				self._setattr(name, val, count)
+
+		for i in mandatory.keys():
+			if i not in have_set:
+				desc = "Mandatory option %s is not set" % i
+				raise ConfigError, desc
+
+		for (name, (cls, default)) in optional.iteritems():
+			if name not in have_set:
+				self._setattr(name, default, -1)
+
+
+	def _convert_to(self, name, val, cls, lineno):
+		try:
+			return cls(val)
+		except:
+			desc = "Line %d: " % lineno
+			desc += "Error converting %s " % name
+			desc += "to mandatory data type %s" % cls
+			raise ConfigError, desc
+
+
+	def _setattr(self, name, val, lineno):
+		if '.' in name:
+			parent, name = name.split('.', 1)
+			try:
+				obj = getattr(self, parent)
+			except AttributeError:
+				obj = Config()
+				setattr(self, parent, obj)
+			obj._setattr(name, val, lineno)
+		else:
+			obj = getattr(self, name, None)
+			if isinstance(obj, Config):
+				desc = "Line %d: " % lineno
+				desc += "Overriding a group with a value"
+				raise ConfigError, desc
+
+			setattr(self, name, val)
+
+