Broke things out into files (for simplicity, I hope).
Broke things out into files (for simplicity, I hope).

file:b/KSP_Components.py (new)
--- /dev/null
+++ b/KSP_Components.py
@@ -1,1 +1,287 @@
-
+#!/usr/bin/python3

+

+'''

+Modular data components for KSP_Parser.

+'''

+

+from Modular import Component, MultiValueComponent

+from KSP_Part import Resource

+from collections import OrderedDict

+

+# This is here to make PyDev stop whining, because it doesn't understand metaclasses properly.

+__classname__ = None

+

+global G_ACCEL

+G_ACCEL = 9.81

+

+class Title(Component):

+	HumanName = "Title"

+	HumanUnits = ""

+	

+	@classmethod

+	def SetName(cls, container, name, *_):

+		o = container.addComponent(cls)

+		if o._value is 0:

+			if isinstance(name, list):

+				name = ';'.join(name)

+			o._value = name.replace('"', "'")

+		return o

+	

+	filter = {

+		'title': [(__classname__, 'SetName')]

+	}

+

+class CrewCapacity(Component):

+	HumanName = "Crew Capacity"

+	HumanUnits = ""

+	

+	@classmethod

+	def SetCapacity(cls, container, cap, *_):

+		o = container.addComponent(cls)

+		o._value = cap

+		return o

+	

+	filter = {

+		'CrewCapacity': [(__classname__, 'SetCapacity')]

+	}

+

+class ResourceStorage(MultiValueComponent):

+	HumanName = "Storage"

+	HumanUnits = "L"

+	

+	@classmethod

+	def AddResourceStorage(cls, container, val, name):

+		o = container.addComponent(cls)

+		o._value[name] = val

+		return o

+	

+	def getValueByKey(self, name):

+		return self._storage[name]

+	

+	filter = {

+		'RESOURCE': {

+			'maxAmount': [(__classname__, 'AddResourceStorage')]

+		}

+	}

+

+class Mass(Component):

+	moduleDepends = [ResourceStorage]

+	HumanName = "Mass"

+	HumanUnits = "t"

+	def __init__(self):

+		super().__init__()

+		

+		self._mass = 0.

+	

+	@classmethod

+	def addMassComponent(cls, container, mass, *_):

+		o = container.addComponent(cls)

+		o._mass += float(mass)

+		return o

+	

+	def getTareMass(self):

+		return self._mass

+	

+	def getMass(self):

+		m = self._mass

+		if ResourceStorage.__name__ in self._container._components:

+			storage = self._container.getResourceStorageNode()

+			for resource, volume in storage._value.items():

+				density = Resource.get(resource)._properties['density']

+				m += volume * density

+		for subpart in self._container._Children.values():

+			if self.__classname__ in subpart._components:

+				m += subpart.getMass()

+		

+		return m

+	

+	def getValue(self):

+		return self.getMass()

+	

+	filter = {

+		'mass': [(__classname__, 'addMassComponent')],

+	}

+

+class Thrust(Component):

+	HumanName = "Thrust"

+	HumanUnits = "kN"

+	def __init__(self):

+		super().__init__()

+		self._ratio = 0.

+	

+	@classmethod

+	def addThrustComponent(cls, container, thrust, *_):

+		o = container.addComponent(cls)

+		o._ratio += float(thrust)

+		if 'Mass' in container._components:

+			container.addComponent(ThrustWeight)

+		return o

+	

+	def getThrust(self):

+		m = self._ratio

+		for subpart in self._container._Children.values():

+			if self.__classname__ in subpart._components:

+				m += subpart.getThrust()

+		

+		return m

+	

+	def getValue(self):

+		return self.getThrust()

+	

+	filter = {

+		'maxThrust': [(__classname__, 'addThrustComponent')]

+	}

+

+class SpecImpulse(MultiValueComponent):

+	HumanName = "Specific Impulse"

+	HumanUnits = "s"

+	def __init__(self):

+		super().__init__()

+		self._value = {}

+	

+	@classmethod

+	def addImpulseComponent(cls, container, specimp_str, *_):

+		o = container.addComponent(cls)

+		coeff, val = specimp_str.split(' ')

+		o._value[coeff] = float(val)

+		return o

+	

+	filter = {

+		'atmosphereCurve': {

+			'key': [(__classname__, 'addImpulseComponent')]

+		}

+	}

+	

+	keyNames = {

+		'0':	'Vac',

+		'1':	'Atm'

+	}

+

+class FuelRatio(MultiValueComponent):

+	HumanName = "Fuel Ratio"

+	HumanUnits = ''

+	

+	@classmethod

+	def addFuelRatio(cls, container, val, name):

+		o = container.addComponent(cls)

+		o._value[name] = val

+	

+	filter = {

+		'PROPELLANT': {

+			'ratio': [((__classname__), 'addFuelRatio')]

+		}

+	}

+

+class ThrustWeight(Component):

+	moduleDepends = [Mass, Thrust]

+	HumanName = 'Thrust-to-Weight Ratio'

+	HumanUnits = ''

+	

+	def getValue(self):

+		return self._container.getThrust() / ( self._container.getMass() * G_ACCEL)

+

+class MassFlow(MultiValueComponent):

+	moduleDepends = [Thrust, SpecImpulse]

+	HumanName = "Mass Flow"

+	HumanUnits = "t/s"

+	

+	def getValue(self):

+		_Thrust = self._container.getThrust()

+		_SpecImpulse = self._container.getSpecImpulseNode().getValue()

+		self.keyNames = self._container.getSpecImpulseNode().keyNames

+		result = {}

+		for key, val in _SpecImpulse.items():

+			result[key] = _Thrust / (val * G_ACCEL)

+		return result

+

+class FuelUsage(MultiValueComponent):

+	moduleDepends = [MassFlow, FuelRatio]

+	HumanName = "Fuel Flow"

+	HumanUnits = "L/s"

+	

+	def getValue(self):

+		result = OrderedDict()

+		_MassFlow = self._container.getMassFlowNode().getValue()

+		_FuelRatios = self._container.getFuelRatioNode().getValue()

+		

+		sumRatios = 0

+		for fuel, ratio in _FuelRatios.items():

+			density = Resource.get(fuel)._properties['density']

+			if density != 0:

+				sumRatios += ratio

+		

+		densityRatios = {}

+		sumDensity = 0

+		for fuel, ratio in _FuelRatios.items():

+			density = Resource.get(fuel)._properties['density']

+			densityRatio = density * ratio / sumRatios

+			densityRatios[fuel] = densityRatio

+			sumDensity += densityRatio

+		

+		for atmo, flow in _MassFlow.items():

+			volFlow = flow / sumDensity

+			for fuel, ratio in _FuelRatios.items():

+				density = Resource.get(fuel)._properties['density']

+				if atmo in SpecImpulse.keyNames:

+					atmo = SpecImpulse.keyNames[atmo]

+				key = "{0} ({1})".format(fuel, atmo)

+				fuelFlow = volFlow * ratio / sumRatios

+				result[key] = fuelFlow

+		

+		return result

+	

+	keyUnits = {

+		'ElectricCharge (Vac)': 'E/s'

+	}

+

+class ElectricRate(Component):

+	moduleDepends = [FuelUsage]

+	HumanName = "Electric Rate"

+	HumanUnits = "E/s"

+	def __init__(self):

+		super().__init__()

+		self._charge_rate = 0.

+	

+	@classmethod

+	def addElectricRateComponent(cls, container, charge_rate, *_):

+		o = container.addComponent(cls)

+		o._value = float(charge_rate)

+		return o

+	

+	def getValue(self):

+		m = self._value

+		for subpart in self._container._Children.values():

+			if self.__classname__ in subpart._components:

+				m += subpart.getElectricRateNode().getValue()

+		# This bit is a dirty, dirty hack to make Ion engines draw power.

+		if 'FuelUsage' in self._container._components:

+			if "ElectricCharge (Vac)" in self._container.getFuelUsageNode().getValue() and m == 0:

+				m = -self._container.getFuelUsageNode().getValue()["ElectricCharge (Vac)"]

+		# KSP determines the input or output of ElectricCharge per module, so we have to, too.

+		inout = 1.

+		if self._container._name in self.charge_users:

+			inout = -1.

+		return m * inout

+	

+	charge_users = [

+		'ModuleCommand',

+		'ModuleWheel'

+	]

+	

+	filter = {

+		'OUTPUT_RESOURCE': {

+			'ElectricCharge': {

+				'rate': [(__classname__, 'addElectricRateComponent')]

+			},

+		},

+		'RESOURCE': {

+			'ElectricCharge': {

+				'rate': [(__classname__, 'addElectricRateComponent')]

+			},

+		},

+		'MODULE': {

+			'ModuleDeployableSolarPanel': {

+				'chargeRate': [(__classname__, 'addElectricRateComponent')]

+			}

+		}

+	}

--- a/KSP_Parser.py
+++ b/KSP_Parser.py
@@ -4,591 +4,11 @@
 # Module-aware parser for KSP part and craft files.

 #===============================================================================

 

-from sys import stderr

+from Modular import Component, MultiValueComponent

+from KSP_Part import *

+import KSP_Components

+from _tools import warn

 from collections import OrderedDict

-

-global G_ACCEL

-G_ACCEL = 9.81

-

-# This is here to make PyDev stop whining, because it doesn't understand metaclasses properly.

-__classname__ = None

-

-def merge_dicts(*dicts):

-	r = {}

-	for d in dicts:

-		for k, v in d.items():

-			if k not in r:

-				r[k] = v

-			elif isinstance(r[k], dict) and isinstance(v, dict):

-				r[k] = merge_dicts(r[k], v)

-			else: r[k] += v

-	

-	return r

-

-def warn(text):

-	stderr.write("{0}\n".format(text))

-

-class ModularType(type):

-	subclasses = []

-	DerivedValues = {}

-	filter = {}

-	@classmethod

-	def __prepare__(mcls, name, bases):

-		return {'__classname__': name}

-	

-	def __new__(mcls, name, bases, dict):

-		cls = type.__new__(mcls, name, bases, dict)

-		if 'moduleDepends' in dict:

-			for module in dict['moduleDepends']:

-				if module not in mcls.DerivedValues:

-					mcls.DerivedValues[module] = {}

-				mcls.DerivedValues[module] = merge_dicts(mcls.DerivedValues[module], {cls: dict['moduleDepends']})

-		if 'filter' in dict:

-			mcls.filter = merge_dicts(mcls.filter, dict['filter'])

-		if cls not in mcls.subclasses:

-			mcls.subclasses.append(cls)

-		return cls

-

-class Modular(metaclass=ModularType):

-	def __init__(self):

-		self._components = {}

-		self.__setattr__("get{0}Node".format(self.__class__.__name__), self.getComponent)

-	

-	def addComponent(self, comp):

-		if isinstance(comp, Component):

-			o = comp

-		elif isinstance(comp, type):

-			o = comp()

-		else:

-			raise TypeError("Got '{0}', expected Component object or class.".format(comp.__class__.__name__))

-		component_name = o.__class__.__name__

-		if component_name in self._components:

-			return self._components[component_name]

-		

-		self._components[component_name] = o

-		o._container = self

-		

-		if o.__class__ in self.__class__.DerivedValues:

-			for mod, depends in self.__class__.DerivedValues[o.__class__].items():

-				dependsMet = True

-				for depend in depends:

-					if depend.__name__ not in self._components:

-						dependsMet = False

-						break

-				if dependsMet:

-					c = mod()

-					if isinstance(c, mod):

-						self.addComponent(mod)

-		

-		return o

-			

-	

-	def getComponent(self):

-		return self

-	

-	def __getattribute__(self, *args):

-		ex = True

-		rval = None

-		try:

-			rval = object.__getattribute__(self, *args)

-			ex = False

-		except AttributeError as e:

-			for component in self._components.values():

-				try:

-					rval = object.__getattribute__(component, *args)

-					ex = False

-				except AttributeError:

-					pass

-				except Exception as f:

-					e = f

-			if ex:

-				raise e

-		

-		return rval

-

-class Node(Modular):

-	id_count = 0

-	@classmethod

-	def next_id(cls):

-		cls.id_count += 1

-		return cls.id_count

-	

-	def __new__(cls, *args, **kwargs):

-		o = super().__new__(cls)

-		o._ID = cls.next_id()

-		

-		return o

-	

-	def __init__(self):

-		super().__init__()

-		self._Parent = None

-		self._Children = {}

-	

-	@classmethod

-	def _ChangeParent(cls, nod, new_parent):

-		if nod._Parent is not None:

-			nod._Parent._RemoveChildByIndex(nod)

-		new_parent._AddChildByIndex(nod)

-	

-	def _AddChildByIndex(self, child, idx=None):

-		if idx is not None:

-			index = idx

-		else:

-			index = child._ID

-		if child._ID in self._Children:

-			return False

-		if child._Parent is not None:

-			child._Parent._RemoveChildByIndex(idx)

-		child._Parent = self

-		self._Children[index] = child

-		

-		return True

-	

-	def _RemoveChildByIndex(self, Idx):

-		s = "Removing child at index '{0}' from {1}.".format(Idx, self)

-		if Idx in self._Children:

-			child = self._Children[Idx]

-			child._Parent = None

-			self.UpdateBulkGlyph(-child.GetExteriorVolume(), child.Glyph())

-			del self._Children[Idx]

-			child.UnsubscribeAll(self)

-			s += "  Done!"

-			return child

-		s += "  Failed. ({0})".format(self._Children)

-		return False

-

-class Component(Node):

-	HumanName = "ComponentName"

-	HumanUnits = "units"

-	def __init__(self):

-		super().__init__()

-		self._identifier = self.__classname__

-		self._value = 0

-	

-	def getValue(self):

-		return self._value

-	

-	def __str__(self):

-		return " ".join((self.HumanName + ":", str(self.getValue()), self.HumanUnits))

-

-class MultiValueComponent(Component):

-	def __init__(self):

-		super().__init__()

-		self._value = {}

-	

-	def getValueByKey(self, key):

-		return self._value[key]

-	

-	def getValue(self):

-		s = self._value

-		for child in self._container._Children.values():

-			if self.__classname__ in child._components:

-				s = merge_dicts(s, child._components[self.__classname__].getValue())

-		return s

-	

-	def __str__(self):

-		s = []

-		for key, value in self.getValue().items():

-			units = self.HumanUnits

-			if key in self.keyNames:

-				key = self.keyNames[key]

-			if key in self.keyUnits:

-				units = self.keyUnits[key]

-			s.append("{0} ({1}): {2} {3}".format(self.HumanName, key, value, units))

-		return "\n{0: <{w}}".format('', w=self._container._depth * 4).join(s)

-	

-	keyNames = {}

-	keyUnits = {}

-

-class Title(Component):

-	HumanName = "Title"

-	HumanUnits = ""

-	

-	@classmethod

-	def SetName(cls, container, name, *_):

-		o = container.addComponent(cls)

-		if o._value is 0:

-			if isinstance(name, list):

-				name = ';'.join(name)

-			o._value = name.replace('"', "'")

-		return o

-	

-	filter = {

-		'title': [(__classname__, 'SetName')]

-	}

-

-class CrewCapacity(Component):

-	HumanName = "Crew Capacity"

-	HumanUnits = ""

-	

-	@classmethod

-	def SetCapacity(cls, container, cap, *_):

-		o = container.addComponent(cls)

-		o._value = cap

-		return o

-	

-	filter = {

-		'CrewCapacity': [(__classname__, 'SetCapacity')]

-	}

-

-class ResourceStorage(MultiValueComponent):

-	HumanName = "Storage"

-	HumanUnits = "L"

-	

-	@classmethod

-	def AddResourceStorage(cls, container, val, name):

-		o = container.addComponent(cls)

-		o._value[name] = val

-		return o

-	

-	def getValueByKey(self, name):

-		return self._storage[name]

-	

-	filter = {

-		'RESOURCE': {

-			'maxAmount': [(__classname__, 'AddResourceStorage')]

-		}

-	}

-

-class Mass(Component):

-	moduleDepends = [ResourceStorage]

-	HumanName = "Mass"

-	HumanUnits = "t"

-	def __init__(self):

-		super().__init__()

-		

-		self._mass = 0.

-	

-	@classmethod

-	def addMassComponent(cls, container, mass, *_):

-		o = container.addComponent(cls)

-		o._mass += float(mass)

-		return o

-	

-	def getTareMass(self):

-		return self._mass

-	

-	def getMass(self):

-		m = self._mass

-		if ResourceStorage.__name__ in self._container._components:

-			storage = self._container.getResourceStorageNode()

-			for resource, volume in storage._value.items():

-				density = resources._Children[resource]._properties['density']

-				m += volume * density

-		for subpart in self._container._Children.values():

-			if self.__classname__ in subpart._components:

-				m += subpart.getMass()

-		

-		return m

-	

-	def getValue(self):

-		return self.getMass()

-	

-	filter = {

-		'mass': [(__classname__, 'addMassComponent')],

-	}

-

-class Thrust(Component):

-	HumanName = "Thrust"

-	HumanUnits = "kN"

-	def __init__(self):

-		super().__init__()

-		self._ratio = 0.

-	

-	@classmethod

-	def addThrustComponent(cls, container, thrust, *_):

-		o = container.addComponent(cls)

-		o._ratio += float(thrust)

-		if 'Mass' in container._components:

-			container.addComponent(ThrustWeight)

-		return o

-	

-	def getThrust(self):

-		m = self._ratio

-		for subpart in self._container._Children.values():

-			if self.__classname__ in subpart._components:

-				m += subpart.getThrust()

-		

-		return m

-	

-	def getValue(self):

-		return self.getThrust()

-	

-	filter = {

-		'maxThrust': [(__classname__, 'addThrustComponent')]

-	}

-

-class SpecImpulse(MultiValueComponent):

-	HumanName = "Specific Impulse"

-	HumanUnits = "s"

-	def __init__(self):

-		super().__init__()

-		self._value = {}

-	

-	@classmethod

-	def addImpulseComponent(cls, container, specimp_str, *_):

-		o = container.addComponent(cls)

-		coeff, val = specimp_str.split(' ')

-		o._value[coeff] = float(val)

-		return o

-	

-	filter = {

-		'atmosphereCurve': {

-			'key': [(__classname__, 'addImpulseComponent')]

-		}

-	}

-	

-	keyNames = {

-		'0':	'Vac',

-		'1':	'Atm'

-	}

-

-class FuelRatio(MultiValueComponent):

-	HumanName = "Fuel Ratio"

-	HumanUnits = ''

-	

-	@classmethod

-	def addFuelRatio(cls, container, val, name):

-		o = container.addComponent(cls)

-		o._value[name] = val

-	

-	filter = {

-		'PROPELLANT': {

-			'ratio': [((__classname__), 'addFuelRatio')]

-		}

-	}

-

-class ThrustWeight(Component):

-	moduleDepends = [Mass, Thrust]

-	HumanName = 'Thrust-to-Weight Ratio'

-	HumanUnits = ''

-	

-	def getValue(self):

-		return self._container.getThrust() / ( self._container.getMass() * G_ACCEL)

-

-class MassFlow(MultiValueComponent):

-	moduleDepends = [Thrust, SpecImpulse]

-	HumanName = "Mass Flow"

-	HumanUnits = "t/s"

-	

-	def getValue(self):

-		_Thrust = self._container.getThrust()

-		_SpecImpulse = self._container.getSpecImpulseNode().getValue()

-		self.keyNames = self._container.getSpecImpulseNode().keyNames

-		result = {}

-		for key, val in _SpecImpulse.items():

-			result[key] = _Thrust / (val * G_ACCEL)

-		return result

-

-class FuelUsage(MultiValueComponent):

-	moduleDepends = [MassFlow, FuelRatio]

-	HumanName = "Fuel Flow"

-	HumanUnits = "L/s"

-	

-	def getValue(self):

-		result = OrderedDict()

-		_MassFlow = self._container.getMassFlowNode().getValue()

-		_FuelRatios = self._container.getFuelRatioNode().getValue()

-		

-		sumRatios = 0

-		for fuel, ratio in _FuelRatios.items():

-			density = resources._Children[fuel]._properties['density']

-			if density != 0:

-				sumRatios += ratio

-		

-		densityRatios = {}

-		sumDensity = 0

-		for fuel, ratio in _FuelRatios.items():

-			density = resources._Children[fuel]._properties['density']

-			densityRatio = density * ratio / sumRatios

-			densityRatios[fuel] = densityRatio

-			sumDensity += densityRatio

-		

-		for atmo, flow in _MassFlow.items():

-			volFlow = flow / sumDensity

-			for fuel, ratio in _FuelRatios.items():

-				density = resources._Children[fuel]._properties['density']

-				if atmo in SpecImpulse.keyNames:

-					atmo = SpecImpulse.keyNames[atmo]

-				key = "{0} ({1})".format(fuel, atmo)

-				fuelFlow = volFlow * ratio / sumRatios

-				result[key] = fuelFlow

-		

-		return result

-	

-	keyUnits = {

-		'ElectricCharge (Vac)': 'E/s'

-	}

-

-class ElectricRate(Component):

-	moduleDepends = [FuelUsage]

-	HumanName = "Electric Rate"

-	HumanUnits = "E/s"

-	def __init__(self):

-		super().__init__()

-		self._charge_rate = 0.

-	

-	@classmethod

-	def addElectricRateComponent(cls, container, charge_rate, *_):

-		o = container.addComponent(cls)

-		o._value = float(charge_rate)

-		return o

-	

-	def getValue(self):

-		m = self._value

-		for subpart in self._container._Children.values():

-			if self.__classname__ in subpart._components:

-				m += subpart.getElectricRateNode().getValue()

-		# This bit is a dirty, dirty hack to make Ion engines draw power.

-		if 'FuelUsage' in self._container._components:

-			if "ElectricCharge (Vac)" in self._container.getFuelUsageNode().getValue() and m == 0:

-				m = -self._container.getFuelUsageNode().getValue()["ElectricCharge (Vac)"]

-		# KSP determines the input or output of ElectricCharge per module, so we have to, too.

-		inout = 1.

-		if self._container._name in self.charge_users:

-			inout = -1.

-		return m * inout

-	

-	charge_users = [

-		'ModuleCommand',

-		'ModuleWheel'

-	]

-	

-	filter = {

-		'OUTPUT_RESOURCE': {

-			'ElectricCharge': {

-				'rate': [(__classname__, 'addElectricRateComponent')]

-			},

-		},

-		'RESOURCE': {

-			'ElectricCharge': {

-				'rate': [(__classname__, 'addElectricRateComponent')]

-			},

-		},

-		'MODULE': {

-			'ModuleDeployableSolarPanel': {

-				'chargeRate': [(__classname__, 'addElectricRateComponent')]

-			}

-		}

-	}

-

-class Part(Node):

-	@classmethod

-	def createFromFile(cls, filename):

-		with open(filename, 'r', errors="replace") as file:

-			part = cls(file)

-		

-		return part

-	

-	def __init__(self, stream, blocktype='PART', depth = 0):

-		super().__init__()

-		self._type = blocktype

-		self._depth = depth

-		self._properties = {}

-		self._subparts = []

-		self._name = ''

-		self.filter = Component.filter

-		lastkey = ''

-		while True:

-			line = stream.readline()

-			if len(line) == 0:

-				break

-			if line[:1] == '//':

-				continue

-			

-			key, *val = line.split('=', 1)

-			key = key.strip()

-			if key == '{':

-				cls = self.__class__

-				subpart = cls(stream, lastkey, self._depth + 1)

-				if subpart._name == '':

-					subpart._name = subpart._type

-				self._AddChildByIndex(subpart, subpart._name)

-			if key == '}':

-				break

-			lastkey = key

-			if len(val) == 0:

-				continue

-			val = val[0].strip().split(',')

-			if len(val) == 1:

-				val = val[0].strip()

-			if key == "name":

-				self._name = val

-			

-			try:

-				val = float(val)

-			except:

-				pass

-			

-			self._checkFilter(key, val)

-			

-			if key in self._properties:

-				self._properties[key] = [self._properties[key]]

-				self._properties[key].append(val)

-				continue

-			

-			self._properties[key] = val

-	

-	def _checkFilter(self, key, val, filter=None):

-		if filter is None:

-			filter = self.filter

-		if key in filter:

-			for classname, method in filter[key]:

-				func = getattr(globals()[classname], method)

-				if callable(func):

-					func(self, val, self._name)

-			return

-		if self._name in filter:

-			return self._checkFilter(key, val, filter[self._name])

-		if self._type in filter:

-			return self._checkFilter(key, val, filter[self._type])

-	

-	def _AddChildByIndex(self, child, idx=None):

-		if super()._AddChildByIndex(child, idx):

-			for comp in child._components.values():

-				if comp.__classname__ not in self._components:

-					self.addComponent(comp.__class__)

-	

-	def dumpKSPData(self):

-		s = self.__str__() + "\n"

-		for prop, value in self._properties.items():

-			s += '{0: <{w}}'.format('', w=self._depth * 4)

-			s += "{0}: {1}\n".format(prop, repr(value))

-		if len(self._Children) > 0:

-			s += '{0: <{w}}'.format('', w=self._depth * 4)

-			s += 'Subparts\n'

-			for subpart in self._Children.values():

-				s += '{0: <{w}}'.format('', w=self._depth * 4)

-				s += "{0}".format(repr(subpart))

-		return s

-	

-	def getRecursiveString(self):

-		s = self.__str__()

-		if len(self._Children) > 0:

-			s += '{0: <{w}}'.format('', w=self._depth * 4)

-			s += 'Subparts\n'

-			for subpart in self._Children.values():

-				s += '{0: <{w}}'.format('', w=self._depth * 4)

-				s += "{0}".format(subpart.getRecursiveString())

-		return s

-	

-	def __repr__(self):

-		s = self._type

-		if '_name' in self.__dict__:

-			s += " '{0}'".format(self._name)

-		return s

-	

-	def __str__(self):

-		s = self.__repr__()

-		s += ":\n"

-		s += '{0: <{w}}Properties\n'.format('', w=self._depth * 4)

-		for comp in self._components.values():

-			s += '{0: <{w}}'.format('', w=self._depth * 4)

-			s += "{0}\n".format(comp)

-		return s

-

-class Resource(Part):

-	pass

 

 def main():

 	import argparse, os.path


file:b/KSP_Part.py (new)
--- /dev/null
+++ b/KSP_Part.py
@@ -1,1 +1,136 @@
-
+#!/usr/bin/python3

+

+__all__ = ['Part', 'Resource']

+

+from Modular import Node, Component

+

+class Part(Node):

+	@classmethod

+	def createFromFile(cls, filename):

+		with open(filename, 'r', errors="replace") as fileStream:

+			part = cls(fileStream)

+		

+		return part

+	

+	def __init__(self, stream, blocktype='PART', depth = 0):

+		super().__init__()

+		self._type = blocktype

+		self._depth = depth

+		self._properties = {}

+		self._subparts = []

+		self._name = ''

+		self.filter = Component.filter

+		lastkey = ''

+		while True:

+			line = stream.readline()

+			if len(line) == 0:

+				break

+			if line[:1] == '//':

+				continue

+			

+			key, *val = line.split('=', 1)

+			key = key.strip()

+			if key == '{':

+				cls = self.__class__

+				subpart = cls(stream, lastkey, self._depth + 1)

+				if subpart._name == '':

+					subpart._name = subpart._type

+				self._AddChildByIndex(subpart, subpart._name)

+			if key == '}':

+				break

+			lastkey = key

+			if len(val) == 0:

+				continue

+			val = val[0].strip().split(',')

+			if len(val) == 1:

+				val = val[0].strip()

+			if key == "name":

+				self._name = val

+			

+			try:

+				val = float(val)

+			except:

+				pass

+			

+			self._checkFilter(key, val)

+			

+			if key in self._properties:

+				self._properties[key] = [self._properties[key]]

+				self._properties[key].append(val)

+				continue

+			

+			self._properties[key] = val

+	

+	def _checkFilter(self, key, val, componentFilter=None):

+		if componentFilter is None:

+			componentFilter = self.filter

+		if key in componentFilter:

+			for classname, method in componentFilter[key]:

+				func = getattr(Component.getSubclass(classname), method)

+				if callable(func):

+					func(self, val, self._name)

+			return

+		if self._name in componentFilter:

+			return self._checkFilter(key, val, componentFilter[self._name])

+		if self._type in componentFilter:

+			return self._checkFilter(key, val, componentFilter[self._type])

+	

+	def _AddChildByIndex(self, child, idx=None):

+		if super()._AddChildByIndex(child, idx):

+			for comp in child._components.values():

+				if comp.__classname__ not in self._components:

+					self.addComponent(comp.__class__)

+	

+	def dumpKSPData(self):

+		s = self.__str__() + "\n"

+		for prop, value in self._properties.items():

+			s += '{0: <{w}}'.format('', w=self._depth * 4)

+			s += "{0}: {1}\n".format(prop, repr(value))

+		if len(self._Children) > 0:

+			s += '{0: <{w}}'.format('', w=self._depth * 4)

+			s += 'Subparts\n'

+			for subpart in self._Children.values():

+				s += '{0: <{w}}'.format('', w=self._depth * 4)

+				s += "{0}".format(repr(subpart))

+		return s

+	

+	def getRecursiveString(self):

+		s = self.__str__()

+		if len(self._Children) > 0:

+			s += '{0: <{w}}'.format('', w=self._depth * 4)

+			s += 'Subparts\n'

+			for subpart in self._Children.values():

+				s += '{0: <{w}}'.format('', w=self._depth * 4)

+				s += "{0}".format(subpart.getRecursiveString())

+		return s

+	

+	def __repr__(self):

+		s = self._type

+		if '_name' in self.__dict__:

+			s += " '{0}'".format(self._name)

+		return s

+	

+	def __str__(self):

+		s = self.__repr__()

+		s += ":\n"

+		s += '{0: <{w}}Properties\n'.format('', w=self._depth * 4)

+		for comp in self._components.values():

+			s += '{0: <{w}}'.format('', w=self._depth * 4)

+			s += "{0}\n".format(comp)

+		return s

+

+class Resource(Part):

+	_resource_parent = None

+	def __new__(cls, *args, **kwargs):

+		r = super().__new__(cls, args, kwargs)

+		if cls._resource_parent is None:

+			cls._resource_parent = r

+		return r

+	

+	@classmethod

+	def get(cls, idx):

+		if cls._resource_parent is None:

+			raise AttributeError('Resources have not yet been read.')

+		if idx in cls._resource_parent._Children:

+			return cls._resource_parent._Children[idx]

+		raise KeyError("{0}: No such resource.".format(repr(idx)))

file:b/Modular.py (new)
--- /dev/null
+++ b/Modular.py
@@ -1,1 +1,190 @@
+'''

+Created on Apr 26, 2013

+

+@author: andy

+'''

+

+__all__ = ['Component', 'ModularType', 'Modular', 'Node']

+

+from _tools import merge_dicts

+from sys import modules

+

+class ModularType(type):

+	subclasses = {}

+	DerivedValues = {}

+	filter = {}

+	@classmethod

+	def __prepare__(mcls, name, bases):

+		return {'__classname__': name}

+	

+	def __new__(cls, name, bases, classDict):

+		newClass = type.__new__(cls, name, bases, classDict)

+		if 'moduleDepends' in classDict:

+			for module in classDict['moduleDepends']:

+				if module not in cls.DerivedValues:

+					cls.DerivedValues[module] = {}

+				cls.DerivedValues[module] = merge_dicts(cls.DerivedValues[module], {newClass: classDict['moduleDepends']})

+		if 'filter' in classDict:

+			cls.filter = merge_dicts(cls.filter, classDict['filter'])

+		if name not in cls.subclasses:

+			cls.subclasses[name] = newClass

+		_all = modules[newClass.__module__].__dict__.setdefault('__all__', [])

+		if name not in _all:

+			_all.append(name)

+		return newClass

+	

+	@classmethod

+	def getSubclass(cls, idx):

+		if idx in cls.subclasses:

+			return cls.subclasses[idx]

+		raise KeyError("{0}: No such ModularType class.".format(repr(idx)))

+

+class Modular(metaclass=ModularType):

+	def __init__(self):

+		self._components = {}

+	

+	def addComponent(self, comp):

+		if isinstance(comp, Modular):

+			o = comp

+		elif isinstance(comp, type):

+			o = comp()

+		else:

+			raise TypeError("Got '{0}', expected Component object or class.".format(comp.__class__.__name__))

+		component_name = o.__class__.__name__

+		if component_name in self._components:

+			return self._components[component_name]

+		

+		self._components[component_name] = o

+		o._container = self

+		

+		if o.__class__ in self.__class__.DerivedValues:

+			for mod, depends in self.__class__.DerivedValues[o.__class__].items():

+				dependsMet = True

+				for depend in depends:

+					if depend.__name__ not in self._components:

+						dependsMet = False

+						break

+				if dependsMet:

+					c = mod()

+					if isinstance(c, mod):

+						self.addComponent(mod)

+		

+		return o

+			

+	

+	def getComponent(self):

+		return self

+	

+	def __getattribute__(self, *args):

+		ex = True

+		rval = None

+		try:

+			rval = object.__getattribute__(self, *args)

+			ex = False

+		except AttributeError as e:

+			for component in self._components.values():

+				try:

+					rval = object.__getattribute__(component, *args)

+					ex = False

+				except AttributeError:

+					pass

+				except Exception as f:

+					e = f

+			if ex:

+				raise e

+		

+		return rval

+

+class Node(Modular):

+	id_count = 0

+	@classmethod

+	def next_id(cls):

+		cls.id_count += 1

+		return cls.id_count

+	

+	def __new__(cls, *args, **kwargs):

+		o = super().__new__(cls)

+		o._ID = cls.next_id()

+		

+		return o

+	

+	def __init__(self):

+		super().__init__()

+		self.__setattr__("get{0}Node".format(self.__class__.__name__), self.getComponent)

+		self._Parent = None

+		self._Children = {}

+	

+	@classmethod

+	def _ChangeParent(cls, nod, new_parent):

+		if nod._Parent is not None:

+			nod._Parent._RemoveChildByIndex(nod)

+		new_parent._AddChildByIndex(nod)

+	

+	def _AddChildByIndex(self, child, idx=None):

+		if idx is not None:

+			index = idx

+		else:

+			index = child._ID

+		if child._ID in self._Children:

+			return False

+		if child._Parent is not None:

+			child._Parent._RemoveChildByIndex(idx)

+		child._Parent = self

+		self._Children[index] = child

+		

+		return True

+	

+	def _RemoveChildByIndex(self, Idx):

+		s = "Removing child at index '{0}' from {1}.".format(Idx, self)

+		if Idx in self._Children:

+			child = self._Children[Idx]

+			child._Parent = None

+			del self._Children[Idx]

+			s += "  Done!"

+			return child

+		s += "  Failed. ({0})".format(self._Children)

+		return False

+

+class Component(Node):

+	HumanName = "ComponentName"

+	HumanUnits = "units"

+	def __init__(self):

+		super().__init__()

+		self._identifier = self.__classname__

+		self._value = 0

+	

+	def getValue(self):

+		return self._value

+	

+	def __str__(self):

+		return " ".join((self.HumanName + ":", str(self.getValue()), self.HumanUnits))

+

+class MultiValueComponent(Component):

+	def __init__(self):

+		super().__init__()

+		self._value = {}

+	

+	def getValueByKey(self, key):

+		return self._value[key]

+	

+	def getValue(self):

+		s = self._value

+		for child in self._container._Children.values():

+			if self.__classname__ in child._components:

+				s = merge_dicts(s, child._components[self.__classname__].getValue())

+		return s

+	

+	def __str__(self):

+		s = []

+		for key, value in self.getValue().items():

+			units = self.HumanUnits

+			if key in self.keyNames:

+				key = self.keyNames[key]

+			if key in self.keyUnits:

+				units = self.keyUnits[key]

+			s.append("{0} ({1}): {2} {3}".format(self.HumanName, key, value, units))

+		return "\n{0: <{w}}".format('', w=self._container._depth * 4).join(s)

+	

+	keyNames = {}

+	keyUnits = {}

 

file:b/_tools.py (new)
--- /dev/null
+++ b/_tools.py
@@ -1,1 +1,24 @@
-
+#!/usr/bin/python3

+

+__all__ = ['merge_dicts', 'warn']

+

+from sys import stderr

+

+'''

+Some functional tools for people who need them.

+'''

+

+def merge_dicts(*dicts):

+	r = {}

+	for d in dicts:

+		for k, v in d.items():

+			if k not in r:

+				r[k] = v

+			elif isinstance(r[k], dict) and isinstance(v, dict):

+				r[k] = merge_dicts(r[k], v)

+			else: r[k] += v

+	

+	return r

+

+def warn(text):

+	stderr.write("{0}\n".format(text))