# -*- coding: latin-1 -*- # Created by Leo from: C:\Development\Python22\Lib\site-packages\vb2py\vb2py.leo """ Classes which mimic the behaviour of VB classes - Collection """ import vbfunctions # << VB Classes >> (1 of 12) class Collection(dict): """A Collection Class An implementation of Visual Basic Collections. This implementation assumes that indexing by integers is rare and that memory is a less scarce resource than CPU time. Based on original code submitted by Jacob Hallén. """ # << Collection Methods >> (1 of 12) def __init__(self): dict.__init__(self) # self.insertOrder is used as the relative index when the collection # is treated as an array. # It is also used as the dictionary key for entries that have no # assigned key. This always works because VB keys can only be strings. self.insertOrder = 1 # << Collection Methods >> (2 of 12) def __setitem__(self, Key, Item): if isinstance(Key, int): raise TypeError("Index must be a non-numeric string") if Key is None: Key = self.insertOrder dict.__setitem__(self, Key, (Item, self.insertOrder, Key)) self.insertOrder += 1 # << Collection Methods >> (3 of 12) def __getitem__(self, Key): try: Key = int(Key) if Key < 1: raise IndexError list = self.values() list.sort(self._order) return list[Key-1][0] except ValueError: return dict.__getitem__(self, Key)[0] # << Collection Methods >> (4 of 12) def __delitem__(self, Key): try: key = int(Key) list = self.values() list.sort(self._order) _, _, key = list[Key-1] except ValueError: pass dict.__delitem__(self, Key) # << Collection Methods >> (5 of 12) def __call__(self, Key): return self.Item(Key) # << Collection Methods >> (6 of 12) def __iter__(self): lst = self.values() lst.sort(self._order) return iter([val[0] for val in lst]) # << Collection Methods >> (7 of 12) def _getElement(self, Key): if isinstance(Key, int): list = self.values() list.sort(self._order) return list[Key-1] else: return dict.__getitem__(self, Key) # << Collection Methods >> (8 of 12) def _order(self, a, b): # Equality is impossible if a[1] < b[1]: return -1 else: return 1 # << Collection Methods >> (9 of 12) def Add(self, Item, Key=None, Before=None, After=None): """ Add's an item with an optional key. The item can also be added before or after an existing item. The before/after parameters can either be integer indices or keys. **kw can contain - key - before - after before and after exclude each other """ if Before is None and After is None: self[Key] = Item elif Before is not None and After is None: _, order, _ = self._getElement(Before) for k, entry in self.iteritems(): if entry[1] >= order: dict.__setitem__(self, k, (entry[0], entry[1]+1, k)) if not isinstance(Key, str): Key = self.insertOrder dict.__setitem__(self, Key, (Item, order, Key)) self.insertOrder += 1 elif After is not None and Before is None: _, order, _ = self._getElement(After) for k, entry in self.iteritems(): if entry[1] > order: dict.__setitem__(self, k, (entry[0], entry[1]+1, k)) if not isinstance(Key, str): Key = self.insertOrder dict.__setitem__(self, Key, (Item, order+1, Key)) self.insertOrder += 1 else: raise VB2PYCodeError("Can't specify both 'before' and 'after' parameters to Collection.Add") # << Collection Methods >> (10 of 12) def Count(self): """Return the length of the collection""" return len(self) # << Collection Methods >> (11 of 12) def Remove(self, Key): """Remove an item from the collection""" self.__delitem__(Key) # << Collection Methods >> (12 of 12) def Item(self, Key): """Get an item from the collection""" return self.__getitem__(Key) # -- end -- << Collection Methods >> if __name__ == '__main__': # Tests c = Collection() c['a'] = 'va' c['b'] = 'vb' print c['a'] print c[2] del c[1] print c[1] # << VB Classes >> (2 of 12) class Integer(int): """Python version of VB's integer""" # << VB Classes >> (3 of 12) class Single(float): """Python version of VB's Single""" # << VB Classes >> (4 of 12) class Double(float): """Python version of VB's Double""" # << VB Classes >> (5 of 12) class Long(int): """Python version of VB's Long""" # << VB Classes >> (6 of 12) class Byte(int): """Python version of VB's Byte""" # << VB Classes >> (7 of 12) class Object(object): """Python version of VB's Object""" # << VB Classes >> (8 of 12) class Variant(float): """Python version of VB's Variant""" # << VB Classes >> (9 of 12) class FixedString(str): """Python version of VB's fixed length string""" def __new__(cls, length): """Initialize the string""" return " "*length # << VB Classes >> (10 of 12) def IsMissing(argument): """Check if an argument was omitted from a function call Missing arguments default to the VBMissingArgument class so we just check if the argument is an instance of this type and return true if this is the case. """ try: return argument._missing except AttributeError: return 0 # << VB Classes >> (11 of 12) class VBArray(list): """Represents an array in VB This is basically a list but we use the __call__ syntax to access indexes of the array """ # << VBArray methods >> (1 of 8) def __init__(self, size, init_type=None): """Initialize with a size or a low and upper bound""" if not isinstance(size, tuple) == 1: size = (0, size) self._min, self._max = size if init_type: for i in range(size[0], size[1]+1): self.append(init_type()) # << VBArray methods >> (2 of 8) def __call__(self, *args): """Index the array""" return self.__getitem__(args) # << VBArray methods >> (3 of 8) def __setitem__(self, index, value): """Set an item in the array""" if isinstance(index, tuple): if len(index) == 1: myindex, rest = index[0], () else: myindex, rest = index[0], index[1:] else: myindex, rest = index, () if rest: list.__getitem__(self, myindex-self._min).__setitem__(rest, value) else: list.__setitem__(self, myindex-self._min, value) # << VBArray methods >> (4 of 8) def __getitem__(self, args): """Get an item from the array""" if isinstance(args, tuple): if len(args) == 1: myindex, rest = args[0], () else: myindex, rest = args[0], args[1:] else: myindex, rest = args, () if self._min <= myindex <= self._max: if rest: return list.__getitem__(self, myindex-self._min).__getitem__(rest) else: return list.__getitem__(self, myindex-self._min) else: raise IndexError("Index '%d' is out of range (%d, %d)" % (myindex, self._min, self._max)) # << VBArray methods >> (5 of 8) def __ubound__(self, dimension=1): """Return the upper bound""" if dimension <= 0: raise ValueError("Invalid dimension for UBound: %s" % dimension) elif dimension == 1: return self._max else: return self[self._min].__ubound__(dimension-1) # << VBArray methods >> (6 of 8) def __lbound__(self, dimension=1): """Return the lower bound""" if dimension <= 0: raise ValueError("Invalid dimension for LBound: %s" % dimension) elif dimension == 1: return self._min else: return self[self._min].__lbound__(dimension-1) # << VBArray methods >> (7 of 8) def __contents__(self, pre=()): """Iterate over the contents of the array""" idx = 0 ret = [] for item in self: if isinstance(item, VBArray): ret.extend(item.__contents__(pre + (idx,))) else: ret.append((pre+(idx,), item)) idx +=1 return ret # << VBArray methods >> (8 of 8) def __copyto__(self, other): """Copy our values to another array""" for index, value in self.__contents__(): try: other.__setitem__(index, value) except IndexError: pass # Throw away values which aren't in the new range # -- end -- << VBArray methods >> # << VB Classes >> (12 of 12) import threading class _VBFiles: """A class to control all interfaces to the file system This is required since VB accesses files through channel numbers rather than file objects. Since a channel number might be an expression that is evaluated at runtime we can't do a static conversion. The solution used here is to have a global object which everyone interfaces to when doing reading and writing to files. This object maintains the list of open channels and marshalls all read and write operations. """ # << VBFiles methods >> (1 of 9) def __init__(self): """Initialize the file interface""" self._channels = {} self._lock = threading.Lock() # << VBFiles methods >> (2 of 9) def openFile(self, channelid, filename, mode): """Open a file If the channel is already one then close it. There are likely to be some race conditions here in multithreaded applications so we use a lock to make this entire process atomic. """ self._lock.acquire() try: try: old_file = self._channels[channelid] except KeyError: pass else: old_file.close() # self._channels[channelid] = open(filename, mode) finally: self._lock.release() # << VBFiles methods >> (3 of 9) def closeFile(self, channelid=None): """Close a file TODO - what should the error be if there is no file open? """ if channelid is None: for channel in self._channels.keys(): self.closeFile(channel) else: self._channels[channelid].close() del(self._channels[channelid]) # << VBFiles methods >> (4 of 9) def getInput(self, channelid, number, separators=None, evaloutput=1): """Get data from a file VB nicely parses the input from files into variables so we have to mimic this here. For safety sake we go one character at a time here. TODO: find out how VB does this and what the implications of chunking would be in a multithreaded app. We use the lock to prevent multiple reads. """ if separators is None: separators = ("\n", ",", "\t", "") # self._lock.acquire() try: vars = [] f = self._channels[channelid] buffer = "" while len(vars) < number: char = f.read(1) if char in separators: if evaloutput: # Try to eval it - if we get a syntax error then assume it is a string try: vars.append(eval(buffer)) except SyntaxError: vars.append(buffer) else: vars.append(buffer) buffer = "" else: buffer += char finally: self._lock.release() # if number == 1: return vars[0] else: return vars # << VBFiles methods >> (5 of 9) def getLineInput(self, channelid, number=1): """Get data from a file one line at a time with no parsing""" return self.getInput(channelid, number, separators=("\n", ""), evaloutput=0) # << VBFiles methods >> (6 of 9) def writeText(self, channelid, *args): """Write data to the file We write with tabs separating the variables that are given in the *args parameter. We use the lock to protect this section in multithreaded environments. """ self._lock.acquire() try: if args: self._channels[channelid].write("".join([str(arg) for arg in args])) else: self._channels[channelid].write("\n") finally: self._lock.release() # << VBFiles methods >> (7 of 9) def seekFile(self, channelid, position): """Move to the specified point in the given channel""" self._channels[channelid].seek(position-1) # VB starts at 1 # << VBFiles methods >> (8 of 9) def getFile(self, channelid): """Return the underlying file link to a channel""" return self._channels[channelid] # << VBFiles methods >> (9 of 9) def getChars(self, channelid, length): """Return the specified number of characters from a file""" return self._channels[channelid].read(length) # -- end -- << VBFiles methods >> VBFiles = _VBFiles() # -- end -- << VB Classes >>