Created a new library directory called "FreeLib". All OpenSource RFMKII components will reside there, fontTools being the flagship.


git-svn-id: svn://svn.code.sf.net/p/fonttools/code/trunk@2 4cde692c-a291-49d1-8350-778aa11640f8
diff --git a/Lib/sstruct.py b/Lib/sstruct.py
new file mode 100644
index 0000000..7bc4772
--- /dev/null
+++ b/Lib/sstruct.py
@@ -0,0 +1,204 @@
+"""sstruct.py -- SuperStruct
+
+Higher level layer on top of the struct module, enabling to 
+bind names to struct elements. The interface is similar to 
+struct, except the objects passed and returned are not tuples 
+(or argument lists), but dictionaries or instances. 
+
+Just like struct, we use format strings to describe a data 
+structure, except we use one line per element. Lines are 
+separated by newlines or semi-colons. Each line contains 
+either one of the special struct characters ('@', '=', '<', 
+'>' or '!') or a 'name:formatchar' combo (eg. 'myFloat:f'). 
+Repetitions, like the struct module offers them are not useful 
+in this context, except for fixed length strings  (eg. 'myInt:5h' 
+is not allowed but 'myString:5s' is). The 'x' format character 
+(pad byte) is treated as 'special', since it is by definition 
+anonymous. Extra whitespace is allowed everywhere.
+
+The sstruct module offers one feature that the "normal" struct
+module doesn't: support for fixed point numbers. These are spelled
+as "n.mF", where n is the number of bits before the point, and m
+the number of bits after the point. Fixed point numbers get 
+converted to floats.
+
+pack(format, object):
+	'object' is either a dictionary or an instance (or actually
+	anything that has a __dict__ attribute). If it is a dictionary, 
+	its keys are used for names. If it is an instance, it's 
+	attributes are used to grab struct elements from. Returns
+	a string containing the data.
+
+unpack(format, data, object=None)
+	If 'object' is omitted (or None), a new dictionary will be 
+	returned. If 'object' is a dictionary, it will be used to add 
+	struct elements to. If it is an instance (or in fact anything
+	that has a __dict__ attribute), an attribute will be added for 
+	each struct element. In the latter two cases, 'object' itself 
+	is returned.
+
+unpack2(format, data, object=None)
+	Convenience function. Same as unpack, except data may be longer 
+	than needed. The returned value is a tuple: (object, leftoverdata).
+
+calcsize(format)
+	like struct.calcsize(), but uses our own format strings:
+	it returns the size of the data in bytes.
+"""
+
+# XXX I would like to support pascal strings, too, but I'm not
+# sure if that's wise. Would be nice if struct supported them
+# "properly", but that would certainly break calcsize()...
+
+__version__ = "1.2"
+__copyright__ = "Copyright 1998, Just van Rossum <just@letterror.com>"
+
+import struct
+import re
+import types
+
+
+error = "sstruct.error"
+
+def pack(format, object):
+	formatstring, names, fixes = getformat(format)
+	elements = []
+	if type(object) is not types.DictType:
+		object = object.__dict__
+	for name in names:
+		value = object[name]
+		if fixes.has_key(name):
+			# fixed point conversion
+			value = int(round(value*fixes[name]))
+		elements.append(value)
+	data = apply(struct.pack, (formatstring,) + tuple(elements))
+	return data
+
+def unpack(format, data, object=None):
+	if object is None:
+		object = {}
+	formatstring, names, fixes = getformat(format)
+	if type(object) is types.DictType:
+		dict = object
+	else:
+		dict = object.__dict__
+	elements = struct.unpack(formatstring, data)
+	for i in range(len(names)):
+		name = names[i]
+		value = elements[i]
+		if fixes.has_key(name):
+			# fixed point conversion
+			value = value / fixes[name]
+		dict[name] = value
+	return object
+
+def unpack2(format, data, object=None):
+	length = calcsize(format)
+	return unpack(format, data[:length], object), data[length:]
+
+def calcsize(format):
+	formatstring, names, fixes = getformat(format)
+	return struct.calcsize(formatstring)
+
+
+# matches "name:formatchar" (whitespace is allowed)
+_elementRE = re.compile(
+		"\s*"							# whitespace
+		"([A-Za-z_][A-Za-z_0-9]*)"		# name (python identifier)
+		"\s*:\s*"						# whitespace : whitespace
+		"([cbBhHiIlLfd]|[0-9]+[ps]|"	# formatchar...
+			"([0-9]+)\.([0-9]+)(F))"	# ...formatchar
+		"\s*"							# whitespace
+		"(#.*)?$"						# [comment] + end of string
+	)
+
+# matches the special struct format chars and 'x' (pad byte)
+_extraRE = re.compile("\s*([x@=<>!])\s*(#.*)?$")
+
+# matches an "empty" string, possibly containing whitespace and/or a comment
+_emptyRE = re.compile("\s*(#.*)?$")
+
+_fixedpointmappings = {
+		8: "b",
+		16: "h",
+		32: "l"}
+
+_formatcache = {}
+
+def getformat(format):
+	try:
+		formatstring, names, fixes = _formatcache[format]
+	except KeyError:
+		lines = re.split("[\n;]", format)
+		formatstring = ""
+		names = []
+		fixes = {}
+		for line in lines:
+			if _emptyRE.match(line):
+				continue
+			m = _extraRE.match(line)
+			if m:
+				formatchar = m.group(1)
+				if formatchar <> 'x' and formatstring:
+					raise error, "a special format char must be first"
+			else:
+				m = _elementRE.match(line)
+				if not m:
+					raise error, "syntax error in format: '%s'" % line
+				name = m.group(1)
+				names.append(name)
+				formatchar = m.group(2)
+				if m.group(3):
+					# fixed point
+					before = int(m.group(3))
+					after = int(m.group(4))
+					bits = before + after
+					if bits not in [8, 16, 32]:
+						raise error, "fixed point must be 8, 16 or 32 bits long"
+					formatchar = _fixedpointmappings[bits]
+					assert m.group(5) == "F"
+					fixes[name] = float(1 << after)
+			formatstring = formatstring + formatchar
+		_formatcache[format] = formatstring, names, fixes
+	return formatstring, names, fixes
+
+def _test():
+	format = """
+		# comments are allowed
+		>  # big endian (see documentation for struct)
+		# empty lines are allowed:
+		
+		ashort: h
+		along: l
+		abyte: b	# a byte
+		achar: c
+		astr: 5s
+		afloat: f; adouble: d	# multiple "statements" are allowed
+		afixed: 16.16F
+	"""
+	
+	print 'size:', calcsize(format)
+	
+	class foo:
+		pass
+	
+	i = foo()
+	
+	i.ashort = 0x7fff
+	i.along = 0x7fffffff
+	i.abyte = 0x7f
+	i.achar = "a"
+	i.astr = "12345"
+	i.afloat = 0.5
+	i.adouble = 0.5
+	i.afixed = 1.5
+	
+	data = pack(format, i)
+	print 'data:', `data`
+	print unpack(format, data)
+	i2 = foo()
+	unpack(format, data, i2)
+	print vars(i2)
+
+if __name__ == "__main__":
+	_test()