Introduce a cut-down version of my .ini parser; this one doesn't do anything special with indents, and just builds a simple dict on parse. It does introduce the same error handling (other than that for indenting), so some previously working (but highly dubious) configs might break.

Hook up a [keybindings] section, to override the default ones now in config.py; teach TerminatorConfig to merge configured keybindings from available sources; gconf support shouldn't be far away.
This commit is contained in:
Thomas Hurst 2008-08-15 04:11:24 +01:00
parent 0c49debaac
commit c0ab0e3f34
4 changed files with 232 additions and 70 deletions

View File

@ -31,13 +31,11 @@ Trying to read a value that doesn't exist will raise an
AttributeError. This is by design. If you want to look something AttributeError. This is by design. If you want to look something
up, set a default for it first.""" up, set a default for it first."""
# import standard python libs
import os, platform, sys, re import os, platform, sys, re
# import unix-lib
import pwd import pwd
# set this to true to enable debugging output # set this to true to enable debugging output
# These should be moved somewhere better.
debug = False debug = False
def dbg (log = ""): def dbg (log = ""):
@ -49,12 +47,15 @@ def err (log = ""):
"""Print an error message""" """Print an error message"""
print >> sys.stderr, log print >> sys.stderr, log
from configfile import ConfigFile, ConfigSyntaxError
class TerminatorConfig: class TerminatorConfig:
"""This class is used as the base point of the config system""" """This class is used as the base point of the config system"""
callback = None callback = None
sources = [] sources = []
def __init__ (self, sources): def __init__ (self, sources):
self._keys = None
for source in sources: for source in sources:
if isinstance(source, TerminatorConfValuestore): if isinstance(source, TerminatorConfValuestore):
self.sources.append (source) self.sources.append (source)
@ -63,6 +64,21 @@ class TerminatorConfig:
source = TerminatorConfValuestoreDefault () source = TerminatorConfValuestoreDefault ()
self.sources.append (source) 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): def __getattr__ (self, keyname):
for source in self.sources: for source in self.sources:
dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type)) dbg ("TConfig: Looking for: '%s' in '%s'"%(keyname, source.type))
@ -133,6 +149,36 @@ class TerminatorConfValuestore:
'close_button_on_tab' : True, 'close_button_on_tab' : True,
'enable_real_transparency' : False, 'enable_real_transparency' : False,
'try_posix_regexp' : platform.system() != 'Linux', 'try_posix_regexp' : platform.system() != 'Linux',
'keybindings' : {
'zoom_in': '<Ctrl>plus',
'zoom_out': '<Ctrl>minus',
'zoom_normal': '<Ctrl>0',
'new_root_tab': '<Ctrl><Shift><Alt>T',
'new_tab': '<Ctrl><Shift>T',
'go_next': '<Ctrl><Shift>N',
'go_prev': '<Ctrl><Shift>P',
'split_horiz': '<Ctrl><Shift>O',
'split_vert': '<Ctrl><Shift>E',
'close_term': '<Ctrl><Shift>W',
'copy': '<Ctrl><Shift>C',
'paste': '<Ctrl><Shift>V',
'toggle_scrollbar': '<Ctrl><Shift>S',
'search': '<Ctrl><Shift>F',
'close_window': '<Ctrl><Shift>Q',
'resize_up': '<Ctrl><Shift>Up',
'resize_down': '<Ctrl><Shift>Down',
'resize_left': '<Ctrl><Shift>Left',
'resize_right': '<Ctrl><Shift>Right',
'move_tab_right': '<Ctrl><Shift>Page_Down',
'move_tab_left': '<Ctrl><Shift>Page_Up',
'toggle_zoom': '<Ctrl><Shift>X',
'scaled_zoom': '<Ctrl><Shift>Z',
'next_tab': '<Ctrl>Page_Down',
'prev_tab': '<Ctrl>Page_Up',
'go_prev': '<Ctrl><Shift>Tab',
'go_next': '<Ctrl>Tab',
'full_screen': 'F11',
}
} }
def __getattr__ (self, keyname): def __getattr__ (self, keyname):
@ -150,11 +196,10 @@ class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
class TerminatorConfValuestoreRC (TerminatorConfValuestore): class TerminatorConfValuestoreRC (TerminatorConfValuestore):
rcfilename = "" rcfilename = ""
splitter = re.compile("\s*=\s*") type = "RCFile"
#FIXME: use inotify to watch the rc, split __init__ into a parsing function #FIXME: use inotify to watch the rc, split __init__ into a parsing function
# that can be re-used when rc changes. # that can be re-used when rc changes.
def __init__ (self): def __init__ (self):
self.type = "RCFile"
try: try:
directory = os.environ['XDG_CONFIG_HOME'] directory = os.environ['XDG_CONFIG_HOME']
except KeyError, e: except KeyError, e:
@ -163,25 +208,27 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore):
self.rcfilename = os.path.join(directory, "terminator/config") self.rcfilename = os.path.join(directory, "terminator/config")
dbg(" VS_RCFile: config file located at %s" % self.rcfilename) dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
if os.path.exists (self.rcfilename): if os.path.exists (self.rcfilename):
rcfile = open (self.rcfilename) ini = ConfigFile(self.rcfilename)
rc = rcfile.readlines ()
rcfile.close ()
for item in rc:
try: try:
item = item.strip () ini.parse()
if item and item[0] != '#': except ConfigSyntaxError, e:
(key, value) = self.splitter.split (item) print "There was an error parsing your configuration"
print str(e)
sys.exit()
for key in ini.settings:
try:
value = ini.settings[key]
# Check if this is actually a key we care about # Check if this is actually a key we care about
if not self.defaults.has_key (key): if not self.defaults.has_key (key):
# We should really mention this to the user
continue continue
deftype = self.defaults[key].__class__.__name__ deftype = self.defaults[key].__class__.__name__
if deftype == 'bool': if deftype == 'bool':
if value.lower () == 'true': if value.lower () in ('true', 'yes', 'on'):
self.values[key] = True self.values[key] = True
elif value.lower () == 'false': elif value.lower () in ('false', 'no', 'off'):
self.values[key] = False self.values[key] = False
else: else:
raise AttributeError raise AttributeError
@ -192,10 +239,14 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore):
elif deftype == 'list': elif deftype == 'list':
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported")) err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
continue continue
elif deftype == 'dict':
if type(value) != dict:
raise AttributeError
self.values[key] = value
else: else:
self.values[key] = value self.values[key] = value
dbg (" VS_RCFile: Set value '%s' to '%s'"%(key, self.values[key])) dbg (" VS_RCFile: Set value '%s' to '%s'" % (key, self.values[key]))
except Exception, e: except Exception, e:
dbg (" VS_RCFile: %s Exception handling: %s" % (type(e), item)) dbg (" VS_RCFile: %s Exception handling: %s" % (type(e), item))
pass pass

133
terminatorlib/configfile.py Normal file
View File

@ -0,0 +1,133 @@
#!/usr/local/bin/python
import re
from terminatorlib.config import dbg, debug
def group(*choices): return '(' + '|'.join(choices) + ')'
def any(*choices): return group(*choices) + '*'
def maybe(*choices): return group(*choices) + '?'
Newline = re.compile(r'[\r\n]+')
Whitespace = r'[ \f\t]*'
Comment = r'#[^\r\n]*'
Ignore = re.compile(Whitespace + maybe(Comment) + maybe(r'[\r\n]+'))
WhitespaceRE = re.compile(Whitespace)
CommentRE = re.compile(Comment)
QuotedStrings = {"'": re.compile(r"'([^'\r\n]*)'"), '"': re.compile(r'"([^"\r\n]*)"')}
Section = re.compile(r"\[([^\r\n\]]+)\][ \f\t]*")
Setting = re.compile(r"(\w+)\s*=\s*")
Colorvalue = re.compile(r'(#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3})' + Whitespace + maybe(Comment))
Barevalue = re.compile(r'([^\r\n#]+)' + Whitespace + maybe(Comment))
Tabsize = 8
class ConfigSyntaxError(Exception):
def __init__(self, message, cf):
self.message = message
self.file = cf.filename
self.lnum = cf._lnum
self.pos = cf._pos
self.line = cf._line
def __str__(self):
return "File %s line %d:\n %s\n %s^\n%s" % (repr(self.file), self.lnum,
self.line.rstrip(),
' ' * self.pos, self.message)
class ConfigFile:
def __init__(self, filename = None):
self.filename = filename
self.settings = {}
def _call_if_match(self, re, callable, group = 0):
if self._pos == self._max:
return False
mo = re.match(self._line, self._pos)
if mo:
if callable:
callable(mo.group(group))
self._pos = mo.end()
return True
else:
return False
def _call_if_quoted_string(self, callable):
if self._pos == self._max:
return False
chr = self._line[self._pos]
if chr in '"\'':
string = ''
while True:
mo = QuotedStrings[chr].match(self._line, self._pos)
if mo is None:
raise ConfigSyntaxError("Unterminated quoted string", self)
self._pos = mo.end()
if self._line[self._pos - 2] == '\\':
string += mo.group(1)[0:-1] + chr
self._pos -= 1
else:
string += mo.group(1)
break
callable(string)
return True
else:
return False
def parse(self):
file = open(self.filename)
rc = file.readlines()
file.close()
self._indents = [0]
self._pos = 0
self._max = 0
self._lnum = 0
self._line = ''
self._currsection = None
self._cursetting = None
for self._line in rc:
self._lnum += 1
self._pos = 0
self._max = len(self._line)
dbg("Line %d: %s" % (self._lnum, repr(self._line)))
self._call_if_match(WhitespaceRE, None)
# [Section]
self._call_if_match(Section, self._section, 1)
# setting =
if self._call_if_match(Setting, self._setting, 1):
# "quoted value"
if not self._call_if_quoted_string(self._value):
# #000000 # colour that would otherwise be a comment
if not self._call_if_match(Colorvalue, self._value, 1):
# bare value
if not self._call_if_match(Barevalue, self._value, 1):
raise ConfigSyntaxError("Setting without a value", self)
self._call_if_match(Ignore, None)
if self._line[self._pos:] != '':
raise ConfigSyntaxError("Unexpected token", self)
def _section(self, section):
dbg("Section %s" % repr(section))
self._currsection = section
def _setting(self, setting):
dbg("Setting %s" % repr(setting))
self._currsetting = setting
def _value(self, value):
dbg("Value %s" % repr(value))
if self._currsection is not None:
self.settings.setdefault(self._currsection, {})[self._currsetting] = value
else:
self.settings[self._currsetting] = value

View File

@ -1,5 +1,6 @@
import re, gtk import re, gtk
import terminatorlib.config
class KeymapError(Exception): class KeymapError(Exception):
def __init__(self, value): def __init__(self, value):
@ -9,38 +10,8 @@ class KeymapError(Exception):
def __str__(self): def __str__(self):
return "Keybinding '%s' invalid: %s" % (self.action, self.value) return "Keybinding '%s' invalid: %s" % (self.action, self.value)
Modifier = re.compile('<([^<]+)>')
class TerminatorKeybindings: class TerminatorKeybindings:
Modifier = re.compile('<([^<]+)>')
keys = {
'zoom_in': '<Ctrl>plus',
'zoom_out': '<Ctrl>minus',
'zoom_normal': '<Ctrl>0',
'new_root_tab': '<Ctrl><Shift><Alt>T',
'new_tab': '<Ctrl><Shift>T',
'go_next': '<Ctrl><Shift>N',
'go_prev': '<Ctrl><Shift>P',
'split_horiz': '<Ctrl><Shift>O',
'split_vert': '<Ctrl><Shift>E',
'close_term': '<Ctrl><Shift>W',
'copy': '<Ctrl><Shift>C',
'paste': '<Ctrl><Shift>V',
'toggle_scrollbar': '<Ctrl><Shift>S',
'search': '<Ctrl><Shift>F',
'close_window': '<Ctrl><Shift>Q',
'resize_up': '<Ctrl><Shift>Up',
'resize_down': '<Ctrl><Shift>Down',
'resize_left': '<Ctrl><Shift>Left',
'resize_right': '<Ctrl><Shift>Right',
'move_tab_right': '<Ctrl><Shift>Page_Down',
'move_tab_left': '<Ctrl><Shift>Page_Up',
'toggle_zoom': '<Ctrl><Shift>X',
'scaled_zoom': '<Ctrl><Shift>Z',
'next_tab': '<Ctrl>Page_Down',
'prev_tab': '<Ctrl>Page_Up',
'go_prev': '<Ctrl><Shift>Tab',
'go_next': '<Ctrl>Tab',
'full_screen': 'F11',
}
modifiers = { modifiers = {
'ctrl': gtk.gdk.CONTROL_MASK, 'ctrl': gtk.gdk.CONTROL_MASK,
@ -51,6 +22,10 @@ class TerminatorKeybindings:
empty = {} empty = {}
def __init__(self): def __init__(self):
self.configure({})
def configure(self, bindings):
self.keys = bindings
self.reload() self.reload()
def reload(self): def reload(self):
@ -74,14 +49,14 @@ class TerminatorKeybindings:
def _parsebinding(self, binding): def _parsebinding(self, binding):
mask = 0 mask = 0
modifiers = re.findall(self.Modifier, binding) modifiers = re.findall(Modifier, binding)
if modifiers: if modifiers:
for modifier in modifiers: for modifier in modifiers:
mask |= self._lookup_modifier(modifier) mask |= self._lookup_modifier(modifier)
key = re.sub(self.Modifier, '', binding) key = re.sub(Modifier, '', binding)
if key == '': if key == '':
raise KeymapError('No key found') raise KeymapError('No key found')
return (mask, re.sub(self.Modifier, '', binding)) return (mask, re.sub(Modifier, '', binding))
def _lookup_modifier(self, modifier): def _lookup_modifier(self, modifier):
try: try:

View File

@ -118,9 +118,12 @@ class Terminator:
self.icon_theme = gtk.IconTheme () self.icon_theme = gtk.IconTheme ()
self.keybindings = TerminatorKeybindings() self.keybindings = TerminatorKeybindings()
self.keybindings.configure(self.conf.keybindings)
if self.conf.f11_modifier: if self.conf.f11_modifier:
self.keybindings.keys['full_screen'] = '<Ctrl><Shift>F11' print "Warning: f11_modifier is no longer supported"
self.keybindings.reload() print "Add the following to the end of your terminator config:"
print "[keybindings]"
print "full_screen = <Ctrl><Shift>F11"
if self.conf.handle_size in xrange (0,6): if self.conf.handle_size in xrange (0,6):
gtk.rc_parse_string(""" gtk.rc_parse_string("""