mrv.util
Covered: 541 lines
Missed: 117 lines
Skipped 270 lines
Percent: 82 %
  2
"""All kinds of utility methods and classes that are used in more than one modules """
  3
import networkx as nx
  4
from collections import deque as Deque
  5
import weakref
  6
import inspect
  7
import itertools
  8
from interface import iDuplicatable
 10
from path import make_path
 12
import os
 13
import logging
 14
log = logging.getLogger("mrv.maya.ui.util")
 16
__docformat__ = "restructuredtext"
 17
__all__ = ("decodeString", "decodeStringOrList", "capitalize", "uncapitalize", 
 18
	"pythonIndex", "copyClsMembers", "packageClasses", "iterNetworkxGraph", 
 19
           "Call", "CallAdv", "WeakInstFunction", "Event", "EventSender", 
 20
           "InterfaceMaster", "Singleton", "CallOnDeletion", 
 21
           "DAGTree", "PipeSeparatedFile", "MetaCopyClsMembers", "And", "Or", 
 22
           "list_submodules", "list_subpackages") 
 25
def decodeString(valuestr):
 26
	""" :return: int,float or str from string valuestr - a string that encodes a
 27
		numeric value or a string
 29
	:raise TypeError: if the type could not be determined"""
 31
	if not isinstance(valuestr, basestring):
 32
		raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr)
 34
	types = (long, float)
 35
	for numtype in types:
 36
		try:
 37
			val = numtype(valuestr)
 40
			if val != float(valuestr):
 41
				continue
 43
			return val
 44
		except (ValueError,TypeError):
 45
			continue
 49
	return valuestr
 51
def decodeStringOrList(valuestrOrList):
 52
	"""
 53
	:return: as `decodeString`, but returns a list of appropriate values if
 54
		the input argument is a list or tuple type"""
 55
	if isinstance(valuestrOrList , (list, tuple)):
 56
		return [decodeString(valuestr) for valuestr in valuestrOrList]
 58
	return decodeString(valuestrOrList)
 60
def capitalize(s):
 61
	""":return: s with first letter capitalized"""
 62
	return s[0].upper() + s[1:]
 64
def uncapitalize(s, preserveAcronymns=False):
 65
	""":return: ``s`` with first letter lower case
 66
	:param preserveAcronymns: enabled ensures that 'NTSC' does not become 'nTSC'
 67
	:note: from pymel
 68
	"""
 69
	try:
 70
		if preserveAcronymns and s[0:2].isupper():
 71
			return s
 72
	except IndexError: pass
 74
	return s[0].lower() + s[1:]
 76
def pythonIndex(index, length):
 77
	"""Compute the actual index based on the given index and array length, thus
 78
	-1 will result in the last array element's index"""
 79
	if index > -1: return index
 80
	return length + index			# yes, length be better 1 or more ;)
 82
def copyClsMembers(sourcecls, destcls, overwritePrefix = None, forbiddenMembers = list(), copyNamespaceGlobally=None):
 83
	"""Copy the members or sourcecls to destcls while ignoring member names in forbiddenMembers
 84
	It will only copy mebers of this class, not its base classes
 86
	:param sourcecls: class whose members should be copied
 87
	:param destcls: class to receive members from sourcecls
 88
	:param overwritePrefix: if None, existing members on destcls will not be overwritten, if string,
 89
		the original method will be stored in a name like prefix+originalname (allowing you to access the
 90
		original method lateron)
 91
	:param copyNamespaceGlobally: if not None, the variable contains the name of the namespace as string 
 92
		whose methods should also be copied into the global namespace, possibly overwriting existing ones.
 93
		For instance, 'nsmethod' will be available as obj.nsmethod and as obj.method if the namespace value was 'ns.
 94
		The forbiddenMembers list is applied to the original as well as the global name
 95
	:note: this can be useful if you cannot inherit from a class directly because you would get
 96
		method resolution order problems
 97
	:note: see also the `MetaCopyClsMembers` meta class"""
 98
	for orig_name,member in sourcecls.__dict__.iteritems():
 99
		names = [orig_name]
100
		if copyNamespaceGlobally is not None and orig_name.startswith(copyNamespaceGlobally):
101
			names.append(orig_name[len(copyNamespaceGlobally):])	# truncate namespace
104
		for name in names:
105
			if name in forbiddenMembers:
106
				continue
107
			try:
109
				if hasattr(destcls, name):
110
					if not overwritePrefix:
111
						continue
112
					morig = getattr(destcls, name)
113
					type.__setattr__(destcls, overwritePrefix+name, morig)
114
				type.__setattr__(destcls, name, member)
115
			except TypeError:
116
				pass
120
def packageClasses(importBase, packageFile, predicate = lambda x: True):
121
	"""
122
	:return: all classes of modules of the given package file that additionally
123
		match given predicate
124
	:param importBase: longest import base path whose submodules contain the classes to import
125
	:param packageFile: the filepath to the package, as given in your __file__ variables
126
	:param predicate: receives the class and returns True if it is a class you are looking for"""
127
	from glob import glob
128
	import os
130
	packageDir = os.path.dirname(packageFile)
133
	basenameNoExt = lambda n: os.path.splitext(os.path.split(n)[1])[0]
134
	pymodules = itertools.chain(glob(os.path.join(packageDir, "*.py")), glob(os.path.join(packageDir, "*.pyc"))) 
135
	pymodules = [basenameNoExt(m) for m in pymodules
136
							if not os.path.basename(m).startswith('_')]
138
	outclasses = []
139
	classAndCustom = lambda x: inspect.isclass(x) and predicate(x)
140
	for modulename in pymodules:
141
		modobj = __import__("%s.%s" % (importBase, modulename), globals(), locals(), [''])
142
		for name,obj in inspect.getmembers(modobj, predicate = classAndCustom):
143
			outclasses.append(obj)
145
	return outclasses
147
def iterNetworkxGraph(graph, startItem, direction = 0, prune = lambda i,g: False,
148
					   stop = lambda i,g: False, depth = -1, branch_first=True,
149
					   visit_once = True, ignore_startitem=1):
150
	""":return: iterator yielding pairs of depth, item 
151
	:param direction: specifies search direction, either :
152
		0 = items being successors of startItem
153
		1 = items being predecessors of startItem
154
	:param prune: return True if item d,i in graph g should be pruned from result.
155
		d is the depth of item i
156
	:param stop: return True if item d,i in graph g, d is the depth of item i
157
		stop the search in that direction. It will not be returned.
158
	:param depth: define at which level the iteration should not go deeper
159
		if -1, there is no limit
160
		if 0, you would only get startitem.
161
		i.e. if 1, you would only get the startitem and the first level of predessessors/successors
162
	:param branch_first: if True, items will be returned branch first, otherwise depth first
163
	:param visit_once: if True, items will only be returned once, although they might be encountered
164
		several times
165
	:param ignore_startitem: if True, the startItem will be ignored and automatically pruned from
166
		the result
167
	:note: this is an adjusted version of `dge.iterShells`"""
168
	visited = set()
169
	stack = Deque()
170
	stack.append((0 , startItem))		# startitem is always depth level 0
172
	def addToStack(stack, lst, branch_first, dpth):
173
		if branch_first:
174
			reviter = ((dpth , lst[i]) for i in range(len(lst)-1,-1,-1))
175
			stack.extendleft(reviter)
176
		else:
177
			stack.extend((dpth,item) for item in lst)
181
	directionfunc = graph.successors
182
	if direction == 1:
183
		directionfunc = graph.predecessors
185
	while stack:
186
		d, item = stack.pop()			# depth of item, item
188
		if item in visited:
189
			continue
191
		if visit_once:
192
			visited.add(item)
194
		oitem = (d, item)
195
		if stop(oitem, graph):
196
			continue
198
		skipStartItem = ignore_startitem and (item == startItem)
199
		if not skipStartItem and not prune(oitem, graph):
200
			yield oitem
203
		nd = d + 1
204
		if depth > -1 and nd > depth:
205
			continue
207
		addToStack(stack, directionfunc(item), branch_first, nd)
212
class Call(object):
213
	"""Call object encapsulating any code, thus providing a simple facade for it
214
	:note: derive from it if a more complex call is required"""
215
	__slots__ = ("func", "args", "kwargs")
217
	def __init__(self, func, *args,**kwargs):
218
		"""Initialize object with function to call once this object is called"""
219
		self.func = func
220
		self.args = args
221
		self.kwargs = kwargs
223
	def __call__(self, *args, **kwargs):
224
		"""Execute the stored function on call
225
		:note: having ``args`` and ``kwargs`` set makes it more versatile"""
226
		return self.func(*self.args, **self.kwargs)
229
class CallAdv(Call):
230
	"""Advanced call class providing additional options:
232
	merge_args: 
233
		if True, default True, incoming arguments will be prepended before the static ones
234
	merge_kwargs: 
235
		if True, default True, incoming kwargs will be merged into the static ones """
236
	__slots__ = ("merge_args", "merge_kwargs")
238
	def __init__(self, func, *args, **kwargs):
239
		self.merge_args = kwargs.pop("merge_args", True)
240
		self.merge_kwargs = kwargs.pop("merge_kwargs", True)
242
		super(CallAdv, self).__init__(func, *args, **kwargs)
244
	def __call__(self, *inargs, **inkwargs):
245
		"""Call with merge support"""
246
		args = self.args
247
		if self.merge_args:
248
			args = list(inargs)
249
			args.extend(self.args)
251
		if self.merge_kwargs:
252
			self.kwargs.update(inkwargs)
254
		return self.func(*args, **self.kwargs)
257
class WeakInstFunction(object):
258
	"""Create a proper weak instance to an instance function by weakly binding
259
	the instance, not the bound function object.
260
	When called, the weakreferenced instance pointer will be retrieved, if possible,
261
	to finally make the call. If it could not be retrieved, the call
262
	will do nothing."""
263
	__slots__ = ("_weakinst", "_clsfunc")
265
	def __init__(self, instancefunction):
266
		self._weakinst = weakref.ref(instancefunction.im_self)
267
		self._clsfunc = instancefunction.im_func
269
	def __eq__(self, other):
270
		return hash(self) == hash(other)
272
	def __hash__(self):
273
		return hash((self._clsfunc,  self._weakinst()))
275
	def __call__(self, *args, **kwargs):
276
		"""
277
		:raise LookupError: if the instance referred to by the instance method
278
			does not exist anymore"""
279
		inst = self._weakinst()
280
		if inst is None:	# went out of scope
281
			raise LookupError("Instance for call to %s has been deleted as it is weakly bound" % self._clsfunc.__name__)
283
		return self._clsfunc(inst, *args, **kwargs)
286
class Event(object):
287
	"""Descriptor allowing to easily setup callbacks for classes derived from
288
	EventSender"""
289
	_inst_event_attr = '__events__'	# dict with event -> set() relation
294
	use_weakref = True
298
	remove_on_error = False
302
	sender_as_argument = None
307
	_curSender = None
309
	def __init__(self, **kwargs):
310
		"""
311
		:param kwargs:
312
			 * weak: if True, default class configuration use_weakref, weak
313
				references will be created for event handlers, if False it will be strong
314
				references
315
			 * remove_failed: if True, defailt False, failed callback handlers
316
			 	will be removed silently
317
			 * sender_as_argument - see class member"""
318
		self.use_weakref = kwargs.get("weak", self.__class__.use_weakref)
319
		self.remove_on_error = kwargs.get("remove_failed", self.__class__.remove_on_error)
320
		self.sender_as_argument = kwargs.get("sender_as_argument", self.__class__.sender_as_argument)
321
		self._last_inst_ref = None
323
	def _func_to_key(self, eventfunc):
324
		"""
325
		:return: an eventfunction suitable to be used as key in our instance
326
			event set"""
327
		if self.use_weakref:
328
			if inspect.ismethod(eventfunc):
329
				eventfunc = WeakInstFunction(eventfunc)
330
			else:
331
				eventfunc = weakref.ref(eventfunc)
334
		return eventfunc
336
	def _key_to_func(self, eventkey):
337
		""":return: event function from the given eventkey as stored in our events set.
338
		:note: this is required as the event might be weakreffed or not"""
339
		if self.use_weakref:
340
			if isinstance(eventkey, WeakInstFunction):
341
				return eventkey
342
			else:
343
				return eventkey()
346
		return eventkey
348
	def _get_last_instance(self):
349
		""":return: The last instance that retrieved us"""
350
		if self._last_inst_ref is None or self._last_inst_ref() is None:
351
			raise TypeError("Cannot send events through class descriptor")
352
		return self._last_inst_ref()
354
	def __set__(self, inst, eventfunc):
355
		"""Set a new event to our object"""
356
		self._getFunctionSet(inst).add(self._func_to_key(eventfunc))
358
	def __get__(self, inst, cls = None):
359
		"""Always return self, but keep the instance in case
360
		we someone wants to send an event."""
361
		if inst is not None:
362
			inst = weakref.ref(inst)
364
		self._last_inst_ref = inst  
365
		return self
367
	def _getFunctionSet(self, inst):
368
		""":return: function set of the given instance containing functions of our event"""
369
		if not hasattr(inst, self._inst_event_attr):
370
			setattr(inst, self._inst_event_attr, dict())
373
		ed = getattr(inst, self._inst_event_attr)
374
		try:
375
			return ed[self]
376
		except KeyError:
377
			return ed.setdefault(self, set())
381
	def send(self, *args, **kwargs):
382
		"""Send our event using the given args
384
		:note: if an event listener is weak referenced and goes out of scope
385
		:note: will catch all event exceptions trown by the methods called
386
		:return: False if at least one event call threw an exception, true otherwise"""
387
		inst = self._get_last_instance()
388
		callbackset = self._getFunctionSet(inst)
389
		success = True
390
		failed_callbacks = list()
392
		sas = inst.sender_as_argument
393
		if self.sender_as_argument is not None:
394
			sas = self.sender_as_argument
398
		Event._curSender = inst
402
		for function in callbackset.copy():
403
			try:
404
				func = self._key_to_func(function)
405
				if func is None:
406
					log.warn("Listener for callback of %s was not available anymore" % self)
407
					failed_callbacks.append(function)
408
					continue
411
				try:
412
					if sas:
413
						func(inst, *args, **kwargs)
414
					else:
415
						func(*args, **kwargs)
416
				except LookupError, e:
418
					if inst.reraise_on_error:
419
						raise 
420
					log.error(str(e))
421
					failed_callbacks.append(function)
423
			except Exception, e :
424
				if self.remove_on_error:
425
					failed_callbacks.append(function)
427
				if inst.reraise_on_error:
428
					raise 
429
				log.error(str(e))
430
				success = False
434
		for function in failed_callbacks:
435
			callbackset.remove(function)
437
		Event._curSender = None
438
		return success
441
	__call__ = send
443
	def remove(self, eventfunc):
444
		"""remove the given function from this event
445
		:note: will not raise if eventfunc does not exist"""
446
		inst = self._get_last_instance()
447
		eventfunc = self._func_to_key(eventfunc)
448
		try:
449
			self._getFunctionSet(inst).remove(eventfunc)
450
		except KeyError:
451
			pass
453
	def duplicate(self):
454
		inst = self.__class__("")
455
		inst._name = self._name	
456
		inst.use_weakref = self.use_weakref
457
		inst.remove_on_error = self.remove_on_error
458
		inst.sender_as_argument = self.sender_as_argument
459
		return inst
465
class EventSender(object):
466
	"""Base class for all classes that want to provide a common callback interface
467
	to supply event information to clients.
469
	**Usage**:
470
	Derive from this class and define your callbacks like:
472
		>>> event = Event()
473
		>>> # Call it using
474
		>>> self.event.send([*args][,**kwargs]])
476
		>>> # Users register using
477
		>>> yourinstance.event = callable
479
		>>> # and deregister using
480
		>>> yourinstance.event.remove(callable)
482
	:note: if use_weakref is True, we will weakref the eventfunction, and deal
483
		properly with instance methods which would go out of scope immediatly otherwise
485
	:note: using weak-references to ensure one does not keep objects alive,
486
		see `Event.use_weakref`"""
487
	__slots__ = tuple()
492
	sender_as_argument = False
496
	reraise_on_error = False
499
	@classmethod
500
	def listEventNames(cls):
501
		""":return: list of event ids that exist on our class"""
502
		return [name for name,member in inspect.getmembers(cls, lambda m: isinstance(m, Event))]
504
	def clearAllEvents(self):
505
		"""Remove all event receivers for all events registered in this instance.
507
		:note: This usually doesn't need to be called directly, but might be useful 
508
			in conjunction with other system that do not release your strongly bound 
509
			instance"""
511
		for en in self.listEventNames():
512
			event = getattr(self, en)
513
			for key in event._getFunctionSet(self).copy():
514
				event.remove(event._key_to_func(key))
518
	def sender(self):
519
		""":return: instance which sent the event you are currently processing
520
		:raise ValueError: if no event is currently in progress"""
521
		if Event._curSender is None:
522
			raise ValueError("Cannot return sender as no event is being sent")
523
		return Event._curSender
526
class InterfaceMaster(iDuplicatable):
527
	"""Base class making the derived class an interface provider, allowing interfaces
528
	to be set, queried and used including build-in use"""
529
	__slots__ = ("_idict",)
531
	im_provide_on_instance = True			 # if true, interfaces are available directly through the class using descriptors
535
	class InterfaceDescriptor(object):
536
		"""Descriptor handing out interfaces from our interface dict
537
		They allow access to interfaces directly through the InterfaceMaster without calling
538
		extra functions"""
540
		def __init__(self, interfacename):
541
			self.iname = interfacename			# keep name of our interface
543
		def __get__(self, inst, cls = None):
545
			if inst is None:
546
				return self
548
			try:
549
				return inst.interface(self.iname)
550
			except KeyError:
551
				raise AttributeError("Interface %s does not exist" % self.iname)
553
		def __set__(self, value):
554
			raise ValueError("Cannot set interfaces through the instance - use the setInterface method instead")
557
	class _InterfaceHandler(object):
558
		"""Utility class passing all calls to the stored InterfaceBase, updating the
559
		internal caller-id"""
560
		def __init__(self, ibase):
561
			self.__ibase = ibase
562
			self.__callerid = ibase._num_callers
563
			ibase._num_callers += 1
565
			ibase._current_caller_id = self.__callerid		# assure the callback finds the right one
566
			ibase.givenToCaller()
568
		def __getattr__(self, attr):
569
			self.__ibase._current_caller_id = self.__callerid 	# set our caller
570
			return getattr(self.__ibase, attr)
572
		def __del__(self):
573
			self.__ibase.aboutToRemoveFromCaller()
574
			self.__ibase._num_callers -= 1
575
			self.__ibase._current_caller_id = -1
578
	class InterfaceBase(object):
579
		"""If your interface class is derived from this base, you get access to
580
		access to call to the number of your current caller.
582
		:note: You can register an InterfaceBase with several InterfaceMasters and
583
			share the caller count respectively"""
584
		__slots__ = ("_current_caller_id", "_num_callers")
585
		def __init__(self):
586
			self._current_caller_id	 = -1 # id of the caller currently operating on us
587
			self._num_callers = 0		# the amount of possible callers, ids range from 0 to (num_callers-1)
589
		def numCallers(self):
590
			""":return: number possible callers"""
591
			return self._num_callers
593
		def callerId(self):
594
			"""Return the number of the caller that called your interface method
596
			:note: the return value of this method is undefined if called if the
597
				method has been called by someone not being an official caller (like yourself)"""
598
			return self._current_caller_id
600
		def givenToCaller(self):
601
			"""Called once our interface has been given to a new caller.
602
			The caller has not made a call yet, but its id can be queried"""
603
			pass
605
		def aboutToRemoveFromCaller(self):
606
			"""Called once our interface is about to be removed from the current
607
			caller - you will not receive a call from it anymore """
608
			pass
613
	def __init__(self):
614
		"""Initialize the interface base with some tracking variables"""
615
		self._idict = dict()			# keep interfacename->interfaceinstance relations
619
	def copyFrom(self, other, *args, **kwargs):
620
		"""Copy all interface from other to self, use they duplciate method if
621
		possibly """
622
		for ifname, ifinst in other._idict.iteritems():
623
			myinst = ifinst
624
			if hasattr(ifinst, "duplicate"):
625
				myinst = ifinst.duplicate()
627
			self.setInterface(ifname, myinst)
632
	def setInterface(self, interfaceName, interfaceInstance):
633
		"""Set the given interfaceInstance to be handed out once an interface
634
		with interfaceName is requested from the provider base
636
		:param interfaceName: should start with i..., i.e. names would be iInterface
637
			The name can be used to refer to the interface later on
638
		:param interfaceInstance: instance to be handed out once an interface with the
639
			given name is requested by the InterfaceMaster or None
640
			if None, the interface will effectively be deleted
641
		:raise ValueError: if given InterfaceBase has a master already """
642
		if interfaceInstance is None:			# delete interface ?
644
			try:
645
				del(self._idict[interfaceName])
646
			except KeyError:
647
				pass
650
			if self.im_provide_on_instance:
651
				try:
652
					delattr(self.__class__, interfaceName)
653
				except AttributeError:
654
					pass
658
		else:
659
			self._idict[interfaceName] = interfaceInstance
662
			if self.im_provide_on_instance:
663
				setattr(self.__class__, interfaceName, self.InterfaceDescriptor(interfaceName))
667
	def interface(self, interfaceName):
668
		""":return: an interface registered with interfaceName
669
		:raise ValueError: if no such interface exists"""
670
		try:
671
			iinst = self._idict[interfaceName]
674
			if isinstance(iinst, self.InterfaceBase):
675
				return self._InterfaceHandler(iinst)
676
			else:
677
				return iinst
678
		except KeyError:
679
			raise ValueError("Interface %s does not exist" % interfaceName)
681
	def listInterfaces(self):
682
		""":return: list of names indicating interfaces available at our InterfaceMaster"""
683
		return self._idict.keys()
688
class Singleton(object) :
689
	""" Singleton classes can be derived from this class,
690
		you can derive from other classes as long as Singleton comes first (and class doesn't override __new__) """
691
	def __new__(cls, *p, **k):
694
		if '_the_instance' not in cls.__dict__:
695
			cls._the_instance = super(Singleton, cls).__new__(cls)
696
		return cls._the_instance
699
class CallOnDeletion(object):
700
	"""Call the given callable object once this object is being deleted
701
	Its usefull if you want to assure certain code to run once the parent scope
702
	of this object looses focus"""
703
	__slots__ = "callableobj"
704
	def __init__(self, callableobj):
705
		self.callableobj = callableobj
707
	def __del__(self):
708
		if self.callableobj is not None:
709
			self.callableobj()
712
class DAGTree(nx.DiGraph):
713
	"""Adds utility functions to DirectedTree allowing to handle a directed tree like a dag
714
	:note: currently this tree does not support instancing
715
	:todo: add instancing support"""
717
	def children(self, n):
718
		""" :return: list of children of given node n """
719
		return list(self.children_iter(n))
721
	def children_iter(self, n):
722
		""" :return: iterator with children of given node n"""
723
		return (e[1] for e in self.out_edges_iter(n))
725
	def parent(self, n):
726
		""":return: parent of node n
727
		:note: currently there is only one parent, as instancing is not supported yet"""
728
		for parent in  self.predecessors_iter(n):
729
			return parent
730
		return None
732
	def parent_iter(self, n):
733
		""":return: iterator returning all parents of node n"""
734
		while True:
735
			p = self.parent(n)
736
			if p is None:
737
				raise StopIteration()
738
			yield p
739
			n = p
741
	def get_root(self, startnode = None):
742
		""":return: the root node of this dag tree
743
		:param startnode: if None, the first node will be used to get the root from
744
			(good for single rooted dags), otherwise this node will be used to get the root from
745
			- thus it must exist in the dag tree"""
746
		if startnode is None:
747
			startnode = self.nodes_iter().next()
749
		root = None
750
		for parent in self.parent_iter(startnode):
751
			root = parent
753
		return root
755
	def to_hierarchy_file(self, root, output_path):
756
		"""Write ourselves in hierarchy file format to the given output_path.
758
		:param root: The root of the written file, nodes above it will not be serialized.
759
		:note: Directories are expected to exist
760
		:raise ValueError: If an node's string representation contains a newline or 
761
			starts with a tab
762
		:note: works best with strings as nodes, which may not contain newlines"""
763
		fp = open(output_path, "wb")
764
		for depth, item in iterNetworkxGraph(self, root, branch_first=False, ignore_startitem=False):
765
			itemstr = str(item)
766
			if itemstr.startswith("\t") or "\n" in itemstr:
767
				raise ValueError("Item %r contained characters unsupported by the hierarchy file format")
769
			fp.write("%s%s\n" % ("\t"*depth, itemstr))
771
		fp.close()
774
class PipeSeparatedFile(object):
775
	"""Read and write simple pipe separated files.
777
	The number of column must remain the same per line
778
	**Format**:
780
		val11 | val2 | valn
781
		...
782
	"""
783
	kSeparator = '|'
784
	__slots__ = ("_fileobj", "_columncount", "_formatstr")
786
	def __init__(self, fileobj):
787
		"""Initialize the instance
789
		:param fileobj: fileobject where new lines will be written to or read from
790
			It must already be opened for reading and/or writing respectively"""
791
		self._fileobj = fileobj
792
		self._columncount = None
794
	def beginReading(self):
795
		"""Start reading the file"""
797
	def readColumnLine(self):
798
		"""Generator reading one line after another, returning the stripped columns
800
		:return: tuple of stripped column strings
801
		:raise ValueError: if the column count changes between the lines"""
802
		for line in self._fileobj:
803
			if not len(line.strip()):
804
				continue
806
			tokens = [item.strip() for item in line.split(self.kSeparator)]
807
			if not self._columncount:
808
				self._columncount = len(tokens)
810
			if self._columncount != len(tokens):
811
				raise ValueError("Columncount changed between successive lines")
813
			yield tuple(tokens)
816
	def beginWriting(self, columnSizes):
817
		"""intiialize the writing process
819
		:param columnSizes: list of ints defining the size in characters for each column you plan to feed
820
		:note: When done writing, you have to close the file object yourself (there is no endWriting method here)"""
821
		columnTokens = ["%%-%is" % csize for csize in columnSizes]
822
		self._formatstr = ((self.kSeparator + " ").join(columnTokens)) + "\n"
824
	def writeTokens(self, tokens):
825
		"""Write the list of tokens to the file accordingly
827
		:param tokens: one token per column that you want to write
828
		:raise TypeError: If column count changed between successive calls"""
829
		self._fileobj.write(self._formatstr % tokens)
832
class MetaCopyClsMembers(type):
833
	"""Meta class copying members from given classes onto the type to be created
834
	it will read the following attributes from the class dict:
835
	``forbiddenMembers``, ``overwritePrefix``, ``__virtual_bases__``
837
	The virtual bases are a tuple of base classes whose members you whish to receive
838
	For information on these members, check the docs of `copyClsMembers`"""
839
	def __new__(metacls, name, bases, clsdict):
840
		forbiddenMembers = clsdict.get('forbiddenMembers', [])
841
		overwritePrefix = clsdict.get('overwritePrefix', None)
842
		vbases = clsdict.get('__virtual_bases__', [])
844
		for sourcecls in vbases:
845
			for name,member in sourcecls.__dict__.iteritems():
846
				if name in forbiddenMembers:
847
					continue
850
				if name in clsdict:
851
					if not overwritePrefix:
852
						continue
853
					morig = clsdict[name]
854
					clsdict[overwritePrefix+name] = morig
855
				clsdict[name] = member
859
		return super(MetaCopyClsMembers, metacls).__new__(metacls, name, bases, clsdict)
865
class And(object):
866
	"""For use with python's filter method, simulates logical AND
867
	Usage: filter(And(f1,f2,fn), sequence)"""
868
	__slots__ = "functions"
869
	def __init__(self, *args):
870
		"""args must contain the filter methods to be AND'ed
871
		To append functions after creation, simply access the 'functions' attribute
872
		directly as a list"""
873
		self.functions = list(args)
875
	def __call__(self, *args, **kwargs):
876
		"""Called during filter function, return true if all functions return true"""
877
		val = True
878
		for func in self.functions:
879
			val = val and func(*args, **kwargs)
880
			if not val:
881
				return val
883
		return val
886
class Or(object):
887
	"""For use with python's filter method, simulates logical OR
888
	Usage: filter(Or(f1,f2,fn), sequence) """
889
	__slots__ = "functions"
890
	def __init__(self, *args):
891
		"""args must contain the filter methods to be AND'ed"""
892
		self.functions = args
894
	def __call__(self, *args, **kwargs):
895
		"""Called during filter function, return true if all functions return true"""
896
		val = False
897
		for func in self.functions:
898
			val = val or func(*args, **kwargs)
899
			if val:
900
				return val
902
		return val
908
def list_submodules(path):
909
	"""
910
	:return: set(submodule_name, ...) list of submodule names that could 
911
		be imported using __import__
912
	:param path: module path containing the submodules"""
913
	file_package = make_path(path).dirname()
916
	cut_ext = lambda f: str(os.path.splitext(os.path.basename(f))[0])
917
	modules = set()
918
	for glob in ("*.py", "*.pyc"):
919
		modules.update(set(map(cut_ext, file_package.files(glob))))
921
	return modules
923
def list_subpackages(path):
924
	""":return: list of sub-package names""" 
925
	return [str(p.basename()) for p in make_path(path).dirname().dirs() if p.files("__init__.py?")]