From 1f156248eee00545b60bda16f8a1c7f7212934e2 Mon Sep 17 00:00:00 2001 From: Chris Jones Date: Tue, 18 Aug 2009 12:46:41 +0100 Subject: [PATCH] Refactor the config. For now it's defaults only --- terminatorlib/config.py | 408 ++-------------------------------------- 1 file changed, 18 insertions(+), 390 deletions(-) diff --git a/terminatorlib/config.py b/terminatorlib/config.py index 10b321ab..e9175c30 100755 --- a/terminatorlib/config.py +++ b/terminatorlib/config.py @@ -15,38 +15,12 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -"""TerminatorConfig by Chris Jones +"""Terminator by Chris Jones -The config scheme works in layers, with defaults at the base, -and a simple/flexible class which can be placed over the top -in multiple layers. This was written for Terminator, but -could be used generically. Its original use is to guarantee -default values for any config item, while allowing them to be -overridden by at least two other stores of configuration values. -Those being gconf and a plain config file. -In addition to the value, the default layer must also provide -the datatype (str, int, float and bool are currently supported). -values are found as attributes of the TerminatorConfig object. -Trying to read a value that doesn't exist will raise an -AttributeError. This is by design. If you want to look something -up, set a default for it first.""" +Classes relating to configuratoin""" -import os import platform -import sys -import re -import pwd -import gtk -import pango - -try: - import gconf -except ImportError: - gconf = None - -from terminatorlib import translation -from terminatorlib.util import dbg, err -from terminatorlib.configfile import ConfigFile, ParsedWithErrors +from borg import Borg DEFAULTS = { 'gt_dir' : '/apps/gnome-terminal', @@ -77,7 +51,12 @@ DEFAULTS = { 'scrollback_lines' : 500, 'focus' : 'click', 'exit_action' : 'close', - 'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:#CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:#0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:#FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:#00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:#FFFFFFFFFFFF', + 'palette' : '#000000000000:#CDCD00000000:#0000CDCD0000:\ + #CDCDCDCD0000:#30BF30BFA38E:#A53C212FA53C:\ + #0000CDCDCDCD:#FAFAEBEBD7D7:#404040404040:\ + #FFFF00000000:#0000FFFF0000:#FFFFFFFF0000:\ + #00000000FFFF:#FFFF0000FFFF:#0000FFFFFFFF:\ + #FFFFFFFFFFFF', 'word_chars' : '-A-Za-z0-9,./?%&#:_', 'mouse_autohide' : True, 'update_records' : True, @@ -166,367 +145,16 @@ DEFAULTS = { } } +class Config(Borg, dict): + """Class to provide access to our user configuration""" -class TerminatorConfig(object): - """This class is used as the base point of the config system""" - callback = None - sources = None - _keys = None + def __init__(self): + """Class initialiser""" - def __init__ (self, sources): - self.sources = [] + Borg.__init__(self) + dict.__init__(self) - for source in sources: - if isinstance(source, TerminatorConfValuestore): - self.sources.append (source) - - # We always add a default valuestore last so no valid config item ever - # goes unset - source = TerminatorConfValuestoreDefault () - self.sources.append (source) - - def _merge_keybindings(self): - if self._keys: - return self._keys - - self._keys = {} - for source in reversed(self.sources): - try: - val = source['keybindings'] - self._keys.update(val) - except: - pass - return self._keys - - keybindings = property(_merge_keybindings) - - def __getattr__ (self, keyname): - for source in self.sources: - dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type)) - try: - val = source[keyname] - dbg (" TConfig: got: '%s' from a '%s'"%(val, source.type)) - return (val) - except KeyError: - pass - - dbg (" TConfig: Out of sources") - raise (AttributeError) - -class TerminatorConfValuestore(object): - type = "Base" - values = None - reconfigure_callback = None - - def __init__ (self): - self.values = {} - - # Our settings - def __getitem__ (self, keyname): - if self.values.has_key (keyname): - value = self.values[keyname] - dbg ("Returning '%s':'%s'"%(keyname, value)) - return value - else: - dbg ("Failed to find '%s'"%keyname) - raise (KeyError) - -class TerminatorConfValuestoreDefault (TerminatorConfValuestore): - def __init__ (self): - TerminatorConfValuestore.__init__ (self) - self.type = "Default" - self.values = DEFAULTS - -class TerminatorConfValuestoreRC (TerminatorConfValuestore): - rcfilename = "" - type = "RCFile" - def __init__ (self): - TerminatorConfValuestore.__init__ (self) - try: - directory = os.environ['XDG_CONFIG_HOME'] - except KeyError: - dbg(" VS_RCFile: XDG_CONFIG_HOME not found. defaulting to ~/.config") - directory = os.path.join (os.path.expanduser("~"), ".config") - self.rcfilename = os.path.join(directory, "terminator/config") - dbg(" VS_RCFile: config file located at %s" % self.rcfilename) - self.call_parser(True) - - def set_reconfigure_callback (self, function): - dbg (" VS_RCFile: setting callback to: %s"%function) - self.reconfigure_callback = function - return (True) - - def call_parser (self, is_init = False): - dbg (" VS_RCFile: parsing config file") - try: - ini = ConfigFile(self.rcfilename, self._rc_set_callback()) - ini.parse() - except IOError, ex: - dbg (" VS_RCFile: unable to open %s (%r)" % (self.rcfilename, ex)) - except ParsedWithErrors, ex: - # We don't really want to produce an error dialog every run - if not is_init: - pass - msg = _("""Configuration error - -Errors were encountered while parsing terminator_config(5) file: - - %s - -%d line(s) have been ignored.""") % (self.rcfilename, len(ex.errors)) - - dialog = gtk.Dialog(_("Configuration error"), None, gtk.DIALOG_MODAL, - (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) - dialog.set_has_separator(False) - dialog.set_resizable(False) - - image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING, - gtk.ICON_SIZE_DIALOG) - image.set_alignment (0.5, 0) - dmsg = gtk.Label(msg) - dmsg.set_use_markup(True) - dmsg.set_alignment(0, 0.5) - - textbuff = gtk.TextBuffer() - textbuff.set_text("\n".join(map(lambda ex: str(ex), ex.errors))) - textview = gtk.TextView(textbuff) - textview.set_editable(False) - - textview.modify_font(pango.FontDescription(DEFAULTS['font'])) - textscroll = gtk.ScrolledWindow() - textscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - textscroll.add(textview) - # This should be scaled with the size of the text and font - textscroll.set_size_request(600, 200) - - root = gtk.VBox() - root.pack_start(dmsg, padding = 6) - root.pack_start(textscroll, padding = 6) - - box = gtk.HBox() - box.pack_start (image, False, False, 6) - box.pack_start (root, False, False, 6) - - vbox = dialog.get_content_area() - vbox.pack_start (box, False, False, 12) - dialog.show_all() - - dialog.run() - dialog.destroy() - - dbg("ConfigFile settings are: %r" % self.values) - - def _rc_set_callback(self): - def callback(sections, key, value): - dbg("Setting: section=%r with %r => %r" % (sections, key, value)) - section = None - if len(sections) > 0: - section = sections[0] - if section is None: - # handle some deprecated configs - if key == 'silent_bell': - err ("silent_bell config option is deprecated, for the new bell related config options, see: man terminator_config") - if value: - self.values['audible_bell'] = False - else: - self.values['audible_bell'] = True - key = 'visible_bell' - - if not DEFAULTS.has_key (key): - raise ValueError("Unknown configuration option %r" % key) - deftype = DEFAULTS[key].__class__.__name__ - if key.endswith('_color'): - try: - gtk.gdk.color_parse(value) - self.values[key] = value - except ValueError: - raise ValueError(_("Setting %r value %r not a valid colour; ignoring") % (key, value)) - elif key == 'tab_position': - if value.lower() in ('top', 'left', 'bottom', 'right'): - self.values[key] = value.lower() - else: - raise ValueError(_("%s must be one of: top, left, right, bottom") % key) - elif deftype == 'bool': - if value.lower () in ('true', 'yes', 'on'): - self.values[key] = True - elif value.lower () in ('false', 'no', 'off'): - self.values[key] = False - else: - raise ValueError(_("Boolean setting %s expecting one of: yes, no, true, false, on, off") % key) - elif deftype == 'int': - self.values[key] = int (value) - elif deftype == 'float': - self.values[key] = float (value) - elif deftype == 'list': - raise ValueError(_("Reading list values from terminator_config(5) is not currently supported")) - elif deftype == 'dict': - if type(value) != dict: - raise ValueError(_("Setting %r should be a section name") % key) - self.values[key] = value - else: - self.values[key] = value - - dbg (" VS_RCFile: Set value %r to %r" % (key, self.values[key])) - elif section == 'keybindings': - self.values.setdefault(section, {}) - if not DEFAULTS[section].has_key(key): - raise ValueError("Keybinding name %r is unknown" % key) - else: - self.values[section][key] = value - else: - raise ValueError("Section name %r is unknown" % section) - return callback - -class TerminatorConfValuestoreGConf (TerminatorConfValuestore): - profile = "" - client = None - cache = None - notifies = None - - def __init__ (self, profileName = None): - TerminatorConfValuestore.__init__ (self) - self.type = "GConf" - self.inactive = False - self.cache = {} - self.notifies = {} - - import gconf - - self.client = gconf.client_get_default () - - # Grab a couple of values from base class to avoid recursing with our __getattr__ - self._gt_dir = DEFAULTS['gt_dir'] - self._profile_dir = DEFAULTS['profile_dir'] - - dbg ('VSGConf: Profile bet on is: "%s"'%profileName) - profiles = self.client.get_list (self._gt_dir + '/global/profile_list','string') - dbg ('VSGConf: Found profiles: "%s"'%profiles) - - dbg ('VSGConf: Profile requested is: "%s"'%profileName) - if not profileName: - profile = self.client.get_string (self._gt_dir + '/global/default_profile') - else: - profile = profileName - # In newer gnome-terminal, the profile keys are named Profile0/1 etc. - # We have to match using visible_name instead - for p in profiles: - profileName2 = self.client.get_string ( - self._profile_dir + '/' + p + '/visible_name') - if profileName == profileName2: - profile = p - - #need to handle the list of Gconf.value - if profile in profiles: - dbg (" VSGConf: Found profile '%s' in profile_list"%profile) - self.profile = '%s/%s' % (self._profile_dir, profile) - elif "Default" in profiles: - dbg (" VSGConf: profile '%s' not found, but 'Default' exists" % profile) - self.profile = '%s/%s'%(self._profile_dir, "Default") - else: - # We're a bit stuck, there is no profile in the list - # FIXME: Find a better way to handle this than setting a non-profile - dbg ("VSGConf: No profile found, marking inactive") - self.inactive = True - return - - #set up the active encoding list - self.active_encodings = self.client.get_list (self._gt_dir + '/global/active_encodings', 'string') - - self.client.add_dir (self.profile, gconf.CLIENT_PRELOAD_RECURSIVE) - if self.on_gconf_notify: - self.client.notify_add (self.profile, self.on_gconf_notify) - - self.client.add_dir ('/apps/metacity/general', gconf.CLIENT_PRELOAD_RECURSIVE) - self.client.notify_add ('/apps/metacity/general/focus_mode', self.on_gconf_notify) - self.client.add_dir ('/desktop/gnome/interface', gconf.CLIENT_PRELOAD_RECURSIVE) - self.client.notify_add ('/desktop/gnome/interface/monospace_font_name', self.on_gconf_notify) - # FIXME: Do we need to watch more non-profile stuff here? - - def set_reconfigure_callback (self, function): - dbg (" VSConf: setting callback to: %s"%function) - self.reconfigure_callback = function - return (True) - - def on_gconf_notify (self, client, cnxn_id, entry, what): - dbg (" VSGConf: invalidating cache") - self.cache = {} - dbg (" VSGConf: gconf changed, may run a callback. %s, %s"%(entry.key, entry.value)) - if entry.key[-12:] == 'visible_name': - dbg (" VSGConf: only a visible_name change, ignoring") - return False - if self.reconfigure_callback: - dbg (" VSGConf: callback is: %s"%self.reconfigure_callback) - self.reconfigure_callback () - - def __getitem__ (self, key = ""): - if self.inactive: - raise KeyError - - if self.cache.has_key (key): - dbg (" VSGConf: returning cached value: %s"%self.cache[key]) - return (self.cache[key]) - - ret = None - value = None - - dbg (' VSGConf: preparing: %s/%s'%(self.profile, key)) - - # FIXME: Ugly special cases we should look to fix in some other way. - if key == 'font' and self['use_system_font']: - value = self.client.get ('/desktop/gnome/interface/monospace_font_name') - elif key == 'focus': - value = self.client.get ('/apps/metacity/general/focus_mode') - elif key == 'http_proxy': - if self.client.get_bool ('/system/http_proxy/use_http_proxy'): - dbg ('HACK: Mangling http_proxy') - - if self.client.get_bool ('/system/http_proxy/use_authentication'): - dbg ('HACK: Using proxy authentication') - value = 'http://%s:%s@%s:%s/' % ( - self.client.get_string ('/system/http_proxy/authentication_user'), - self.client.get_string ('/system/http_proxy/authentication_password'), - self.client.get_string ('/system/http_proxy/host'), - self.client.get_int ('/system/http_proxy/port')) - else: - dbg ('HACK: Not using proxy authentication') - value = 'http://%s:%s/' % ( - self.client.get_string ('/system/http_proxy/host'), - self.client.get_int ('/system/http_proxy/port')) - elif key == 'cursor_blink': - tmp = self.client.get_string('%s/cursor_blink_mode' % self.profile) - if tmp in ['on', 'off'] and self.notifies.has_key ('cursor_blink'): - self.client.notify_remove (self.notifies['cursor_blink']) - del (self.notifies['cursor_blink']) - if tmp == 'on': - value = True - elif tmp == 'off': - value = False - elif tmp == 'system': - value = self.client.get_bool ('/desktop/gnome/interface/cursor_blink') - self.notifies['cursor_blink'] = self.client.notify_add ('/desktop/gnome/interface/cursor_blink', self.on_gconf_notify) - else: - value = self.client.get ('%s/%s'%(self.profile, key)) - else: - value = self.client.get ('%s/%s'%(self.profile, key)) - - if value != None: - from types import StringType, BooleanType - if type(value) in [StringType, BooleanType]: - ret = value - else: - funcname = "get_" + DEFAULTS[key].__class__.__name__ - dbg (' GConf: picked function: %s'%funcname) - # Special case for str - if funcname == "get_str": - funcname = "get_string" - # Special case for strlist - if funcname == "get_strlist": - funcname = "get_list" - typefunc = getattr (value, funcname) - ret = typefunc () - - self.cache[key] = ret - return (ret) - else: - raise (KeyError) + def __getitem__(self, key): + """Look up a configuration item""" + return(DEFAULTS[key])