#!/usr/bin/env python import xmllib, traceback, re, string, os, cPickle, sys from types import * import Tkinter import connection PYTHON_VERSION = string.split(sys.version)[0] try: import gzip except: HAVE_GZIP = 0 else: # Gzip only works correctly with cPickle in Python >= 1.5.2 if PYTHON_VERSION >= '1.5.2': HAVE_GZIP = 1 else: HAVE_GZIP = 0 if PYTHON_VERSION >= '1.6': USE_BINARY = 1 else: USE_BINARY = 0 # Configuration variable Config = None def escapeXML(s): try: import xml.sax.saxutils except ImportError: import re s = re.sub('&', '&', s) s = re.sub('<', '<', s) s = re.sub('>', '>', s) s = re.sub('"', '"', s) s = re.sub("'", ''', s) else: s = xml.sax.saxutils.escape(s) return s class Element: def __init__(self, name='', value=None, attribs=None): # We expose the instance variables: name, parent, attribs, # children self.children = [] self.name = name self.parent = None if value is not None: self.setvalue(value) if attribs is not None: self.attribs = attribs else: self.attribs = {} def close(self): self.parent = None for c in self.children: c.close() def __del__(self): # print 'Deleting element', self, self.name self.children = [] # self.parent = None # Append a child to my list of nodes def add_child(self, node): node.parent = self self.children.append(node) #------------------------------------------------------------ # Getting and setting element values def value(self): s = '' for c in self.children: if c.__class__ == CData: s = s + c.text return s def setvalue(self, val): for c in self.children: if c.__class__ != CData: raise TypeError,\ 'cannot set value of element with children' node = CData(val) self.children = [node] def setattribbool(self, name, val): if val: self.attribs[name] = 'yes' else: self.attribs[name] = 'no' def setattrib(self, name, val): self.attribs[name] = val #------------------------------------------------------------ # Getting the ith element node with this particular name # If value is a string, then fetch the first # Otherwise, it should be a tuple of (name, index) in which case we # return the indexth node with this name # Use like: node['nodename', 3] to get the third nodename node def __getitem__(self, value): if type(value) is TupleType: if len(value) < 2: raise TypeError, 'Must specify two arguments to getitem' name = value[0] index = value[1] else: name = value index = 0 count = 0 for c in self.children: if c.__class__ == Element: if c.name == name: if count == index: return c count = count + 1 return None #------------------------------------------------------------ # Getting and setting boolean values as nodes with an attribute # uncommented elements def getboolean(self, path, default=None): if not path: if not self.attribs.has_key('on'): # For compatibility, default to 'yes' if the node is # present but does not have the 'on' attribute attrib = 'yes' else: attrib = self.attribs.get('on') return attrib == 'yes' if type(path) is StringType: components = string.split(path, '/') else: components = path try: node = self[components[0]] except: traceback.print_exc(1) return default newpath = components[1:] if node is None: return default else: return node.getboolean(newpath, default) def setbool(self, value): self.setattribbool('on', value) # tagtext = '<%s/>' % name # for pos in range(len(self.children)): # c = self.children[pos] # if c.__class__ == Element and c.name == name: # # It's already on # if not value: # # If we have to turn it off # node = Comment(' ' + tagtext + ' ', self) # self.children[pos] = node # return # elif c.__class__ == Comment and\ # string.strip(c.text) == tagtext: # # It's already commented out # if value: # node = Element(name) # node.parent = self # self.children[pos] = node #------------------------------------------------------------ # Getting and setting list structures def getlist(self, itemname): out = [] for i in self.children: if i.__class__ == Element and i.name == itemname: out.append(i.value()) return out # TODO: fix this def setlist(self, itemname, values): node = self[listname] if node is None: node = Element(listname) node.parent = self first_whitespace = [] last_whitespace = [] else: # Attempt to preserve whitespace textnodes = filter(lambda x: x.__class__ == CData, node.children) first_whitespace = [textnodes[0]] last_whitespace = [textnodes[-1]] newkids = [] for val in values: k = Element(itemname) k.parent = node k.setvalue(val) newkids = newkids + first_whitespace + [k] self.children = newkids + last_whitespace #------------------------------------------------------------ # Counting the number of nodes with this tag name def getcount(self, name): count = 0 for i in self.children: if i.__class__ == Element and i.name == name: count = count + 1 return count #------------------------------------------------------------ # Return an XML representation of myself def toXML(self, indent=0): attribstr = '' myindentstr = '\n' + (' ' * indent) indent = indent + 2 for key in self.attribs.keys(): attribstr = attribstr + ' %s="%s"' % (key, self.attribs[key]) if attribstr: opentag = '<%s%s>' % (self.name, attribstr) else: opentag = '<%s>' % self.name closetag = ('' % self.name) if self.children: value = myindentstr + opentag for c in self.children: value = value + c.toXML(indent) if filter(lambda x: x.__class__ == Element, self.children): # Subelemnts; format myself with embedded newlines value = value + myindentstr + closetag else: value = value + closetag else: if attribstr: value = myindentstr + '<%s%s/>' % (self.name, attribstr) else: value = myindentstr + '<%s/>' % self.name return value class CData: def __init__(self, text): if type(text) is not StringType: print 'Constructing CData with non-string argument', text self.text = text self.parent = None def toXML(self, indent=0): return escapeXML(self.text) def close(self): self.parent = None class Comment: def __init__(self, text, value=None): self.text = text self.parent = None def toXML(self, indent): indentstr = '\n' + (' ' * indent) return indentstr + '' % self.text def close(self): self.parent = None class Boolean: def __init__(self, name, value): self.name = name self.value = value def toXML(self, indent): indentstr = '\n' + (' ' * indent) tag = '<%s on="%s"/>' % (self.name, ['no', 'yes'][self.value]) return indentstr + tag def close(self): self.parent = None class List: def __init__(self, name, valuelist): self.root = Element('%s-list' % name) for val in valuelist: node = Element(name) node.setvalue(val) self.root.add_child(node) def toXML(self, indent): return self.root.toXML(indent) def close(self): self.parent = None self.root.close() class Tree: def __init__(self): self.root = None self.curnode = None def close(self): if self.root is not None: self.root.close() def addtag(self, name, attribs): if self.curnode is None: self.root = Element(name, None, attribs) self.curnode = self.root else: node = Element(name, None, attribs) node.parent = self.curnode self.curnode.add_child(node) self.curnode = node def addcontent(self, content): if self.curnode is None: return node = CData(content) self.curnode.add_child(node) def endtag(self): self.curnode = self.curnode.parent def addcomment(self, comment): node = Comment(comment, self) self.curnode.add_child(node) def toXML(self): return '\n' + self.root.toXML(0) # Returns default if the path does not exist or there is otherwise # some problem def getvalue(self, path, default=None): components = string.split(path, '/') node = self.root while components: try: node = node[components[0]] except: traceback.print_exc(1) return default components = components[1:] if node is None: return default else: return node.value() def getboolean(self, path, default=None): return self.root.getboolean(path, default) def getlist(self, path, nodename): components = string.split(path, '/') node = self.root while components: try: node = node[components[0]] except: traceback.print_exc(1) return [] components = components[1:] return node.getlist(nodename) def settag(*args): print 'Starting configuration:', args # global Config # Config = ConfigClass() def endtag(*args): print 'End configuration:', args class FuguConfParser(xmllib.XMLParser): def __init__(self): xmllib.XMLParser.__init__(self) self.tree = Tree() self.elements = {} for i in ['fuguconfig', 'parameters']: self.elements[i] = (self.tree.addtag, self.tree.endtag) def __del__(self): self.tree.close() def handle_starttag(self, tag, method, attribs): method(tag, attribs) def unknown_starttag(self, tag, attribs): self.tree.addtag(tag, attribs) def unknown_endtag(self, tag): self.tree.endtag() def handle_data(self, data): self.tree.addcontent(data) def handle_cdata(self, data): self.tree.addcontent(data) def handle_comment(self, comment): self.tree.addcomment(comment) def toXML(self): return self.tree.toXML() class ConfigClass: def toInt(self, str): try: return int(str) except: print 'Unable to convert value %s to integer' % `str` return -1 # To add a new config variable, edit this function as well as # load_vars. def toXML(self): "Create an XML tree, and return its string representation" root = Element('fuguconfig') root.add_child( Comment('Warning: this file is automatically generated')) prefs = Element('preferences') root.add_child(prefs) for (nodetype, name, value) in [ (Element, 'buffersize', `self.SCROLLBACK`), (Element, 'signer', self.SIGNER), (Element, 'sender', self.SENDER), (Comment, 'Window sizes', ''), (Element, 'pane-height', `self.PANE_HEIGHT`), (Element, 'pane-width', `self.PANE_WIDTH`), (Element, 'pane-top', `self.PANE_TOP`), (Element, 'pane-bottom', `self.PANE_BOTTOM`), (Boolean, 'save-pane-width', self.SAVE_PANE_WIDTH), (Boolean, 'buttonbar', self.MAP_PUFFBUTTONS), (Boolean, 'thumbnails', self.SHOW_THUMBS), (Boolean, 'autocomplete', self.AUTOCOMPLETE), (Boolean, 'ccself', self.CC_SELF), (Boolean, 'broadcast-presence', self.BROADCAST_PRESENCE), (Boolean, 'keyword-prompt', self.KEYWORD_PROMPT), (Element, 'urlcommand', self.URLLOAD), (Element, 'thumbnail-url', self.THUMBNAIL_URL), (Element, 'cursorcolor', self.HIGHLIGHT), (List, 'presence', self.PRESENCE_STRINGS), (Element, 'default-presence', self.DEFAULT_PRESENCE)]: prefs.add_child(nodetype(name, value)) node = Element('persist-pufflog') node.setattribbool('on', self.PERSIST_INTERVAL > 0) node.setattrib('interval', self.PERSIST_INTERVAL) prefs.add_child(node) # Screen nodes for conn in self.SUBLIST: screennode = Element('screen') root.add_child(screennode) screennode.add_child(Element('name', value=conn['name'])) screennode.add_child(Element('subscription', value=conn['sub'])) screennode.add_child(Boolean('savehistory', conn['savepuffs'])) # Workaround because send_receipts has been in the wrong # place in some versions of the config file, so it may have # the value None screennode.add_child(Boolean('send-receipts', conn['send_receipts'])) screennode.add_child(Element('logsent', str(conn['logsent']))) onpuff = Element('onpuff') screennode.add_child(onpuff) onpuff.add_child(Boolean('beep', conn['beep'])) onpuff.add_child(Boolean('highlight-button', conn['highlight'])) onpuff.add_child(Boolean('lift-screen', conn['lift'])) onpuff.add_child(Boolean('deiconify', conn['deiconify'])) if conn['command'] is None: attribs = {'on': 'no'} else: attribs = {'on': 'yes'} cmdnode = Element('command', value=conn['command'], attribs=attribs) onpuff.add_child(cmdnode) ret = '\n' + root.toXML() root.close() return ret def load_vars(self, p): val = p.getvalue('preferences/buffersize') if val is not None: self.SCROLLBACK = self.toInt(val) val = p.getvalue('preferences/signer') if val is not None: self.SIGNER = val val = p.getvalue('preferences/sender') if val is not None: self.SENDER = val val = p.getvalue('preferences/pane-height') if val is not None: self.PANE_HEIGHT = self.toInt(val) val = p.getvalue('preferences/pane-width') if val is not None: self.PANE_WIDTH = self.toInt(val) val = p.getvalue('preferences/pane-top') if val is not None: self.PANE_TOP = self.toInt(val) val = p.getvalue('preferences/pane-bottom') if val is not None: self.PANE_BOTTOM = self.toInt(val) val = p.getboolean('preferences/save-pane-width') if val is not None: self.SAVE_PANE_WIDTH = val val = p.getboolean('preferences/buttonbar') if val is not None: self.MAP_PUFFBUTTONS = val val = p.getboolean('preferences/thumbnails') if val is not None: self.SHOW_THUMBS = val val = p.getvalue('preferences/thumbnail-url') if val is not None: self.THUMBNAIL_URL = val val = p.getboolean('preferences/autocomplete') if val is not None: self.AUTOCOMPLETE = val val = p.getboolean('preferences/ccself') if val is not None: self.CC_SELF = val val = p.getboolean('preferences/keyword-prompt') if val is not None: self.KEYWORD_PROMPT = val val = p.getboolean( 'preferences/broadcast-presence') if val is not None: self.BROADCAST_PRESENCE = val val = p.getvalue('preferences/default-presence') if val is not None: self.DEFAULT_PRESENCE = val val = p.getvalue('preferences/cursorcolor', '#00ffff') if val is not None: self.HIGHLIGHT = val val = p.getvalue('preferences/urlcommand') if val is not None: self.URLLOAD = val self.PRESENCE_STRINGS = p.getlist('preferences/presence-list', 'presence') if self.DEFAULT_PRESENCE not in self.PRESENCE_STRINGS: self.PRESENCE_STRINGS.insert(0, self.DEFAULT_PRESENCE) self.PERSIST_INTERVAL = 300 try: persist_node = p.root['preferences']['persist-pufflog'] except AttributeError: pass else: if persist_node is not None: if persist_node.attribs['on'] == 'no': self.PERSIST_INTERVAL = 0 else: self.PERSIST_INTERVAL =\ int(persist_node.attribs['interval']) # Rather than relying on global defaults for each screen, we have # to specify them here, because the user may have any number of # screens, but those screens may not have the new variables # configured yet. self.SUBLIST = [] for i in range(p.root.getcount('screen')): screennode = p.root['screen', i] if screennode is None: print 'Weird error: screen node #%i is NULL' % i break namenode = screennode['name'] if namenode is None: name = ('Screen %i' % i) else: name = namenode.value() sub = screennode['subscription'] if sub is None: subscription = 'Unconfigured subscription' else: subscription = sub.value() conn = connection.Connection(name, subscription) conn['savepuffs'] = screennode.getboolean('savehistory', 0) try: conn['logsent'] = self.toInt(screennode['logsent'].value() or '0') except: traceback.print_exc(1) conn['send_receipts'] = screennode.getboolean('send-receipts', 1) onpuff = screennode['onpuff'] if onpuff is not None: conn['beep'] = onpuff.getboolean('beep', 0) conn['highlight'] = onpuff.getboolean('highlight-button', 1) conn['lift'] = onpuff.getboolean('lift-screen', 0) conn['deiconify'] = onpuff.getboolean('deiconify', 0) cmdnode = onpuff['command'] if cmdnode and cmdnode.attribs['on'] == 'yes': conn['command'] = cmdnode.value() else: conn['command'] = None self.SUBLIST.append(conn) def load_config(self, fname): try: data = open(fname, 'r').read() parser = FuguConfParser() parser.feed(data) if parser.tree.root is None: print 'Error loading empty or ill-formed config file',\ fname return self.load_vars(parser.tree) except KeyboardInterrupt: print 'Keyboard interrupt caught; exiting...' sys.exit(0) except: traceback.print_exc() print 'Unable to load configuration from', fname def load_default_config(self, data): try: parser = FuguConfParser() parser.feed(data) self.load_vars(parser.tree) except KeyboardInterrupt: print 'Keyboard interrupt caught; exiting...' sys.exit(0) except: traceback.print_exc() print 'Unable to load default configuration' def load_oldconf(self, fname): import cPickle try: f = open(fname, 'r') newconf = cPickle.load(f) f.close() except: traceback.print_exc() print 'Error reading old-style config file.' return # Workarounds for wonky config files for key in dir(newconf): if key == 'PRESENCE_STRINGS': self.__dict__[key] = list(newconf.__dict__[key]) elif key == 'SUBLIST': # Convert from old-style Connection to new-style self.SUBLIST = [] for c in newconf.SUBLIST: conn = connection.Connection( c['name'], c['sub']) conn['savepuffs'] = c['savepuffs'] conn['logsent'] = c['logsent'] conn['beep'] = c['beep'] conn['highlight'] = c['highlight'] conn['lift'] = c['lift'] conn['deiconify'] = c['deiconify'] conn['command'] = c['command'] try: conn['pufflist'] = c['pufflist'] except: pass self.SUBLIST.append(conn) else: self.__dict__[key] = newconf.__dict__[key] if hasattr(self, 'PRESENCE') and\ self.PRESENCE not in self.PRESENCE_STRINGS: self.PRESENCE_STRINGS.insert(0, self.PRESENCE) def load(self, fugudir): # Load default config first for defaults import DefaultConfig self.load_default_config(DefaultConfig.getXML()) # Load user-specific config file fname = os.path.join(fugudir, 'fuguconfig.xml') if not os.path.exists(fname): # If the config file doesn't exist fname = os.path.join(fugudir, 'fugu.conf') # Check for old-style config if os.path.exists(fname): print 'Converting old-style config file to new format.' self.load_oldconf(fname) self.save(fugudir) else: print 'No Fugu configuration found. Using defaults.' else: self.load_config(fname) # Load sender coloring info self.COLORS = {} colorfn = os.path.join(fugudir, 'fugu-conf.py') if not os.path.exists(colorfn): return d = {} try: execfile(colorfn, {}, d) except: print 'ERROR parsing', colorfn traceback.print_exc() else: if d.has_key('sender') and type(d['sender']) is DictType: self.COLORS = d['sender'] def save(self, fugudir): fname = os.path.join(fugudir, 'fuguconfig.xml') try: f = open(fname, 'w') f.write(self.toXML()) f.close() except: traceback.print_exc() print 'Unable to save configuration to', fname def load_pufflog(self, fugudir): # Load puff history, assuming SUBLIST has already been # initialized fname = os.path.join(fugudir, 'fugustate.pck') gzfname = fname + '.gz' if not (os.path.exists(fname) or os.path.exists(gzfname)): # No file found return if os.path.exists(gzfname) and not HAVE_GZIP and not\ os.path.exists(fname): print 'Compressed puff history exists, but can\'t be decoded!' return try: if HAVE_GZIP and os.path.exists(gzfname): f = gzip.GzipFile(gzfname, 'rb') else: f = open(fname, 'rb') pufflog = cPickle.load(f) f.close() except KeyboardInterrupt: print 'Keyboard interrupt caught; exiting...' sys.exit(0) except: traceback.print_exc() print 'Puff history exists, but can\'t be loaded!' return for conn in self.SUBLIST: if pufflog.has_key(conn['name']): conn['pufflist'] = pufflog[conn['name']] def persist_pufflog(self, fugudir): # Save puff history to disk pufflog = {} for c in self.SUBLIST: if c['savepuffs']: pufflog[c['name']] = c['pufflist'] if not pufflog: return fname = os.path.join(fugudir, 'fugustate.pck') gzfname = fname + '.gz' try: if HAVE_GZIP: f = gzip.GzipFile(gzfname, 'wb') os.chmod(gzfname, 0600) else: f = open(fname, 'wb') os.chmod(fname, 0600) cPickle.dump(pufflog, f, USE_BINARY) f.close() except: traceback.print_exc(1) print 'Unable to save puff histories to', fname def main(): c = ConfigClass() c.load('.') print c.toXML() if __name__ == '__main__': from pygale import * import sys pygale.init() sys.exitfunc = pygale.shutdown main()