SF.net SVN: fclient: [13] trunk/fclient/fclient_lib
Status: Pre-Alpha
Brought to you by:
jurner
|
From: <jU...@us...> - 2007-10-27 16:57:37
|
Revision: 13
http://fclient.svn.sourceforge.net/fclient/?rev=13&view=rev
Author: jUrner
Date: 2007-10-27 09:57:28 -0700 (Sat, 27 Oct 2007)
Log Message:
-----------
added python extensions module
Added Paths:
-----------
trunk/fclient/fclient_lib/pyex/
trunk/fclient/fclient_lib/pyex/__init__.py
trunk/fclient/fclient_lib/pyex/__init__.pyc
trunk/fclient/fclient_lib/pyex/events.py
trunk/fclient/fclient_lib/pyex/events.pyc
trunk/fclient/fclient_lib/pyex/namespace.py
trunk/fclient/fclient_lib/pyex/numbers.py
trunk/fclient/fclient_lib/pyex/numbers.pyc
trunk/fclient/fclient_lib/pyex/omapping.py
Added: trunk/fclient/fclient_lib/pyex/__init__.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/__init__.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/__init__.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1 @@
+"""Pytrhon extension modules"""
Added: trunk/fclient/fclient_lib/pyex/__init__.pyc
===================================================================
(Binary files differ)
Property changes on: trunk/fclient/fclient_lib/pyex/__init__.pyc
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/fclient/fclient_lib/pyex/events.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/events.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/events.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,125 @@
+"""Signals and events"""
+
+#***********************************************************************
+#
+# event handler. Mostly taken from
+#http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/410686
+#
+#***********************************************************************
+class EventMeta(type):
+ """Metaclass for events"""
+
+ class Event(object):
+ """Event handler"""
+
+ def __init__(self, name):
+ """
+ @param name: name of the event
+ @attr name: name of the event
+ @attr observers: list of observers of the event
+ """
+ self.name = name
+ self.observers = []
+
+ def __call__(self, *args, **kwargs):
+ """Dispatches the event and additional parameters to all observers registerd"""
+ for o in self.observers:
+ o(self, *args, **kwargs)
+
+ def __iadd__(self, observer):
+ """Adds an observer to the event
+ @note: the observer will be called with the event as first paraeter,
+ followed by any number of *args or **kwargs passed by the caller of an event
+ """
+ self.observers.append(observer)
+ return self
+
+ def __isub__(self, observer):
+ """Removes the first occurence of an observer from the event"""
+ self.observers.remove(observer)
+ return self
+
+ def __new__(clss, name, bases, kws):
+ events = kws.get('_events_', None)
+ if events is None:
+ raise ValueError('Event classes must implement an "_event_" attribute')
+ for event_name in events:
+ kws[event_name] = clss.Event(event_name)
+ return type.__new__(clss, name, bases, kws)
+
+
+class Events(object):
+ """Base class for events
+
+ Derrived classes should list events they support in the "_events_" tuple.
+ Each event name is automagically set as attribute of the event
+ class.
+
+ Listeners may register to receiving events by calling __iadd__,
+ unregister by calling __isub__ on these attributes. Callback are
+ always called with the event as first argument, followed by additional
+ arguments, depending on the event.
+
+ Events have the following methods:
+
+ 'name': name of the event
+ 'observers': list of observers listening to the event
+
+
+ Note:
+ Always make shure to disconnnect when event notification is no longer desired
+
+
+ >>> class MyEvents(Events):
+ ... _events_ = ('FooEvent', 'BarEvent')
+ ...
+ >>> events = MyEvents()
+ >>> def cb(event):
+ ... print 'Received: %s' % event.name
+
+ >>> events.FooEvent += cb
+ >>> events.FooEvent()
+ Received: FooEvent
+
+ >>> events.FooEvent -= cb
+ >>> events.FooEvent()
+
+ >>> events += ( (events.FooEvent, cb), (events.BarEvent, cb) )
+ >>> events.FooEvent()
+ Received: FooEvent
+ >>> events.BarEvent()
+ Received: BarEvent
+
+ >>> events -= ( (events.FooEvent, cb), (events.BarEvent, cb) )
+ >>> events.FooEvent()
+
+ >>> events.BarEvent()
+
+ """
+ __metaclass__ = EventMeta
+ _events_ = ()
+
+ def __iadd__(self, events):
+ """Adds one or more events / observers at once
+ @param events: tuple( (event, observer), (event, observer), ...)
+ """
+ for event, observer in events:
+ event += observer
+ return self
+
+ def __isub__(self, events):
+ """Removes one or more events / observers at once
+ @param events: tuple( (event, observer), (event, observer), ...)
+ """
+ for event, observer in events:
+ event -= observer
+ return self
+
+
+#*********************************************************************
+#
+#*********************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
Added: trunk/fclient/fclient_lib/pyex/events.pyc
===================================================================
(Binary files differ)
Property changes on: trunk/fclient/fclient_lib/pyex/events.pyc
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/fclient/fclient_lib/pyex/namespace.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/namespace.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/namespace.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,115 @@
+"""Namespace handling"""
+
+import os
+import re
+#*********************************************************************
+#
+#*********************************************************************
+def unique_filename(fpath, prefix='', names=None):
+ """Creates a filepath with a unique (human readable) filename
+ @param: fpath: filepath to patch
+ @param prefix: prefix to add to the filename or ''
+ @param names: list of filenames to patch agaunst or None to check files in the directory
+ of filepath
+
+ @return: a filepath with one of these 'MyFile (1).txt' patched filenames
+
+ >>> names = ['foo.txt', 'foo (1).txt']
+ >>> unique_filename('foo.txt', names=names)
+ 'foo (2).txt'
+
+ >>> names = ['foo.txt', 'foo (2).txt']
+ >>> unique_filename('foo.txt', names=names)
+ 'foo (1).txt'
+
+ >>> names = ['foo.txt', ]
+ >>> result = unique_filename(os.path.join('myDir', 'foo.txt'), names=names)
+ >>> expected = os.path.join('myDir', 'foo (1).txt')
+ >>> result == expected
+ True
+
+ >>> names = ['foo.txt', ]
+ >>> unique_filename('foo.txt', names=names, prefix="Copy Of")
+ 'Copy Of foo.txt'
+
+ >>> names = ['foo.txt', 'Copy Of foo.txt']
+ >>> unique_filename('foo.txt', names=names, prefix="Copy Of")
+ 'Copy Of foo (1).txt'
+
+ """
+ filename = os.path.basename(fpath)
+ dirname = os.path.dirname(fpath)
+ if not filename:
+ raise ValueError("No filename found: %s" % fpath)
+
+ n = 0
+ tmp_filename = filename
+ while True:
+ if names is not None:
+ if tmp_filename not in names: break
+ else:
+ if not os.path.exists(os.path.join(dirname, tmp_filename)): break
+
+ n += 1
+ tmp_filename, tmp_ext = os.path.splitext(filename)
+ if prefix:
+ if n == 1:
+ tmp_filename = '%s %s' % (prefix, tmp_filename)
+ else:
+ tmp_filename = '%s %s (%s)' % (prefix, tmp_filename, n -1)
+ else:
+ tmp_filename = '%s (%s)' % (tmp_filename, n)
+ tmp_filename = tmp_filename + tmp_ext
+
+ return os.path.join(dirname, tmp_filename)
+
+
+#*********************************************************************
+#
+#*********************************************************************
+def unquote_uri(uri, slash='-'):
+ """Unquotes an uri so it can be used as a name in the filesystem
+ @param uri: (str) uri to unquote
+ @param slasch: (str) substitution string shlashes to use or None to keep slashes
+ @return: (str) uri
+
+ >>> unquote_uri('foo/bar')
+ 'foo-bar'
+ >>> unquote_uri('foo/b%20ar')
+ 'foo-b ar'
+ >>> unquote_uri('foo/b%34ar')
+ 'foo-b%34ar'
+ >>> unquote_uri('foo/bar', slash='77')
+ 'foo77bar'
+ >>> unquote_uri('foo/bar', slash=None)
+ 'foo/bar'
+
+ """
+
+ # impossible to handle all the dos and donts on oses. So do minimum
+ # to enshure user readability
+ if slash is not None:
+ uri = uri.replace('/', slash)
+ uri = uri.replace('%20', ' ')
+ uri = uri.replace('%28', '(')
+ uri = uri.replace('%29', ')')
+ return uri
+
+
+#*********************************************************************
+#
+#*********************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
+#TODO: handle fpath='myFolder/foo (1).txt' ?
+#a = 'foo (123)'
+#p = re.compile('\A(.*)\(([0-9]+)\)\Z')
+
+
+
+
+
+
+
Added: trunk/fclient/fclient_lib/pyex/numbers.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/numbers.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/numbers.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,143 @@
+"""Number crunching and others
+
+"""
+
+#***************************************************************
+#
+#***************************************************************
+def format_num_bytes(num, short=True, conform=True):
+ """Formats a number representing a number of bytes to a human readable string
+ @param num: (int) number to fomat
+ @param short: use short names
+ @param conform: if True factor is 1000, else factor is 1024
+ @return: (str) formated number
+
+ >>> format_num_bytes(100)
+ '100 b'
+
+ >>> format_num_bytes(1000)
+ '1.00 kb'
+
+ >>> format_num_bytes(1024, conform=False)
+ '1.00 kb'
+
+ >>> format_num_bytes(1000, short=False)
+ '1.00 Kilobyte'
+
+ """
+
+ if short:
+ names = ('b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb')
+ else:
+ names = ('Byte',
+ 'Kilobyte',
+ 'Megabyte',
+ 'Gigabyte',
+ 'Terabyte',
+ 'Petabyte',
+ 'Exabyte',
+ 'Zettabyte',
+ 'Yottabyte'
+ )
+ if conform:
+ factor = 1000
+ else:
+ factor = 1024
+
+ num = float(num)
+ name = names[0]
+ if num >= factor:
+ for tmp_name in names[1: ]:
+ num /= factor
+ name = tmp_name
+ if num < factor:
+ break
+ else:
+ return '%i %s' % (num, name)
+
+
+
+ return '%01.2f %s' % (num, name)
+#***************************************************************
+#
+#***************************************************************
+TimeDurationNames = {
+ 'seconds': 's',
+ 'minutes': 'm',
+ 'hours': 'h',
+ 'days': 'd',
+ 'years': 'y'
+ }
+def format_time_delta(t1, t2, names=None):
+ """Pretty prints a time delta
+ @arg t1: duration starting time
+ @arg t2: duration ending time
+ @arg names: (optional) dict mapping names to names as they should be
+ to be printed (see TimeDurationNames)
+
+ >>> import time
+ >>> t0 = time.time()
+
+ >>> format_time_delta(t0, t0 +1)
+ '1s'
+
+ >>> format_time_delta(t0, t0)
+ '0s'
+
+ >>> format_time_delta(t0, t0 +1.4)
+ '1.4s'
+
+ >>> format_time_delta(t0, t0 +60)
+ '1m'
+
+ >>> format_time_delta(t0, t0 +12345)
+ '3.4h'
+
+ >>> format_time_delta(t0, t0 +1234567890)
+ '39.1y'
+
+ >>> format_time_delta(t0, t0 +1234567890, names={'years': 'leapers', 'seconds': 's', 'minutes': 'm', 'hours': 'h', 'days': 'd'})
+ '39.1leapers'
+
+ """
+ mapping = (
+ ('years', 1),
+ ('days', 365),
+ ('hours', 24),
+ ('minutes', 60),
+ ('seconds', 60),
+ )
+ if names is None:
+ names = TimeDurationNames
+
+ delta = t2 - t1
+ if delta < 0:
+ t = n = 0
+ name = 'seconds'
+ else:
+ start = (60 * 60 * 24 * 365)
+ for name, fac in mapping:
+ start = start / fac
+ t = delta / start
+ t = round(t, 1)
+ n = int(t)
+ if n:
+ break
+
+ name = names[name]
+ if t > n:
+ return '%s%s' % (t, name)
+ else:
+ return '%s%s' % (n, name)
+
+
+#*****************************************************************
+#
+#****************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
+
+
+
Added: trunk/fclient/fclient_lib/pyex/numbers.pyc
===================================================================
(Binary files differ)
Property changes on: trunk/fclient/fclient_lib/pyex/numbers.pyc
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/fclient/fclient_lib/pyex/omapping.py
===================================================================
--- trunk/fclient/fclient_lib/pyex/omapping.py (rev 0)
+++ trunk/fclient/fclient_lib/pyex/omapping.py 2007-10-27 16:57:28 UTC (rev 13)
@@ -0,0 +1,345 @@
+"""Ordered mappings"""
+
+#******************************************************************************
+#
+#******************************************************************************
+class StableMapping(dict):
+ """Stable mapping wich keeps insertion order of items (neither complete nor whatever)
+
+ >>> StableMapping()
+ {}
+ >>> o = StableMapping( ((1, 1), (2, 2)) )
+ >>> for i in o: i
+ (1, 1)
+ (2, 2)
+
+ >>> enum = o.__iter__()
+ >>> enum.next()
+ (1, 1)
+
+ >>> o.popitem(-1)
+ (2, 2)
+
+ >>> for i in o: i
+ (1, 1)
+
+ >>> o['a'] = 'aa'
+ >>> for i in o: i
+ (1, 1)
+ ('a', 'aa')
+
+ >>> o['a'] = 'bb'
+ >>> for i in o: i
+ (1, 1)
+ ('a', 'bb')
+
+ >>> o.getitem(-1)
+ ('a', 'bb')
+
+ >>> del o['a']
+ >>> for i in o: i
+ (1, 1)
+
+
+ >>> o.item_order
+ [1]
+
+ >>> o.remove(1)
+ >>> for i in o: i
+
+
+ """
+
+ def __init__(self, items=None):
+ self.item_order = []
+ if items is None:
+ dict.__init__(self)
+ else:
+ dict.__init__(self, items)
+ self.item_order = [i[0] for i in items]
+
+ def __delitem__(self, name):
+ dict.__delitem__(self, name)
+ self.item_order.remove(name)
+
+ def __iter__(self):
+ for i in self.item_order:
+ yield (i, self[i])
+
+ def __setitem__(self, name, value):
+ isnew = name not in self
+ dict.__setitem__(self, name, value)
+ if isnew:
+ self.item_order.append(name)
+
+ def pop(self, name):
+ item = dict.pop(self, name)
+ self.item_order.remove(name)
+ return item
+
+ def popitem(self, i=-1):
+ item = self.item_order.pop(i)
+ return (item, dict.pop(self, item))
+
+ def getitem(self, i):
+ item = self.item_order[i]
+ return (item, self[item])
+
+ def index(self, item):
+ return self.item_order.index(item)
+
+ def remove(self, item):
+ del self[item]
+
+#******************************************************************************
+#
+#******************************************************************************
+class MappedPriorityQueue(object):
+ """Sorted queue supporting priorities and dictionary lookup of items
+
+ >>> PriorityHigh = 2
+ >>> PriorityMiddle = 1
+ >>> PriorityLow = 0
+
+ >>> m = MappedPriorityQueue()
+ >>> for i in range(4): m.push(str(i), 'MyItem-%s' % i, PriorityLow)
+ >>> len(m)
+ 4
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['0', '1', '2', '3']
+
+ >>> m.set_priority('3', PriorityHigh)
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3', '0', '1', '2']
+
+ >>> m.set_priority('2', PriorityMiddle)
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3', '2', '0', '1']
+
+ # iterate over all items with PriorityMiddle
+ >>> for item in m.iter_items(PriorityMiddle, PriorityMiddle): item
+ (1, '2', 'MyItem-2')
+
+ # pop all items with PriorityMiddle and below
+ >>> for item in m.pop_items(PriorityMiddle): item
+ (1, '2', 'MyItem-2')
+ (0, '0', 'MyItem-0')
+ (0, '1', 'MyItem-1')
+
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3']
+
+
+ # items can be popped by name aswell
+ >>> m.push('MyName', 'MyItem', PriorityLow)
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3', 'MyName']
+
+ >>> m.pop('MyName')
+ (0, 'MyName', 'MyItem')
+ >>> [name for (priority, name, item) in m.iter_items()]
+ ['3']
+
+
+ # dictionary lookup
+ >>> m['3']
+ 'MyItem-3'
+
+
+ # check if queue is empty
+ >>> bool(m)
+ True
+
+ >>> m.clear()
+ >>> bool(m)
+ False
+
+ """
+
+
+ class QueueItem(object):
+
+ __slots__ = ('item', 'priority', 'name')
+
+ def __init__(self, priority, name, item):
+ self.item = item
+ self.priority = priority
+ self.name = name
+
+ def __cmp__(self,other):
+ if isinstance(other, self.__class__):
+ return cmp(self.priority, other.priority)
+ return cmp(self.priority, other)
+
+
+ def __init__(self):
+ """
+ @attr queue: (list) [QueueItem, ...]
+ @attr mapping: (dict) name --> QueueItem
+ @note: both mappings should be considered read only
+ """
+ self.queue = []
+ self.mapping = {}
+
+
+ def __contains__(self, name):
+ """Returns True if an item 'name' exists in the queue, False otherwise"""
+ return name in self.mapping
+
+ def __getitem__(self, name):
+ """Returns an item from the queue given its name"""
+ return self.mapping[name].item
+
+ def __setitem__(self, name, item):
+ """Sets the item associated to name"""
+ self.mapping[name].item = item
+
+
+ def __len__(self):
+ """Retuns the number of items in the queue"""
+ return len(self.queue)
+
+ def __nonzero__(self):
+ """Returns False if the queue is empty, True otherwise"""
+ return bool(self.queue)
+
+
+ def clear(self):
+ """Removes all items from the queue"""
+ self.queue = []
+ self.mapping = {}
+
+
+ def get(self, name, default=None):
+ """Returns an item from the queue given its name or default
+ @return: tuple(priority, item)
+ """
+ queue_item = self.mapping.get(name, None)
+ if queue_item is None:
+ return default
+ return (queue_item.priority, queue_item.item)
+
+
+ def index(self, name):
+ """Returns the first index of an item in the queue"""
+ queue_item = self.mapping[name]
+ return self.queue.index(queue_item)
+
+
+ def iter_items(self, priority_start=None, priority_stop=None):
+ """Iterates over all items in the queue
+ @param priority_start: minimu priority to start iterating at
+ or None to start at the first item
+ @param priority_stop: priority to stop iterating at. If None
+ all items below priority_start are returned
+ @return: tuple(priority, name, item) for the next item in turn
+ """
+ for queue_item in self.queue:
+ if priority_stop is not None:
+ if queue_item < priority_stop:
+ raise StopIteration
+
+ if priority_start is not None:
+ if queue_item > priority_start:
+ continue
+ yield (queue_item.priority, queue_item.name, queue_item.item)
+
+
+
+ def peak(self, index):
+ """Returns an item from a specified index
+ @return: tuple(priority, name, item)
+ """
+ queue_item = self.queue[index]
+ return (queue_item.priority, queue_item.name, queue_item.item)
+
+
+ def pop(self, name=None):
+ """Pops an item from the queue
+ @param name: name of the item to pop. If None, the item with the highest priority is popped
+ @return: tuple(priority, name, item)
+ """
+ if name:
+ queue_item = self.mapping[name]
+ else:
+ queue_item = self.queue[0]
+ self.queue.remove(queue_item)
+ self.mapping.pop(name)
+ return (queue_item.priority, queue_item.name, queue_item.item)
+
+
+ def pop_items(self, priority_start=None, priority_stop=None):
+ """Sequentially pops a number of items from the queue
+ @param priority_start: minimu priority to start popping at
+ @param priority_stop: priority to stop popping at. If None
+ all items below priority_start are popped
+ @return: tuple(name, item) for the next item in turn
+ """
+ i = 0
+ while len(self.queue) > i:
+ queue_item = self.queue[i]
+ if priority_stop is not None:
+ if queue_item < priority_stop:
+ raise StopIteration
+
+ if priority_start is None:
+ del self.queue[i]
+ del self.mapping[queue_item.name]
+ yield (queue_item.name, queue_item.item)
+
+ elif queue_item <= priority_start:
+ del self.queue[i]
+ del self.mapping[queue_item.name]
+ yield (queue_item.priority, queue_item.name, queue_item.item)
+
+ else:
+ i += 1
+
+
+ def push(self, name, item, priority, sort=True):
+ """Pushes an item into the queue
+ @param item: any desired item
+ @param priority: (int) priority of the item
+ @param sort: if True the queue is sorted right away, if False, use sort()
+ to trigger sorting manually
+ """
+ if name in self.mapping:
+ raise KeyError('Item already exists: %r' % name)
+
+ queue_item = self.QueueItem(priority, name, item)
+ self.queue.append(queue_item)
+ self.mapping[name] = queue_item
+ if sort:
+ self.sort()
+
+
+ def get_priority(self, name):
+ """Returns the priority of item name"""
+ return self.mapping[name].priority
+
+
+ def set_priority(self, name, priority, sort=True):
+ """Adjusts the priority of an item
+ @param name: name of the item to adjust priority for
+ @param priority: (int) new priority of the item
+ @param sort: if True the queue is sorted right away, if False, use sort()
+ to trigger sorting manually
+ """
+ self.mapping[name].priority = priority
+ if sort:
+ self.sort()
+
+
+ def sort(self):
+ """Sorts the queue"""
+ self.queue.sort(reverse=True)
+
+
+#***************************************************************
+#
+#***************************************************************
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
+
+
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|