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
up, set a default for it first."""
# import standard python libs
import os, platform, sys, re
# import unix-lib
import pwd
# set this to true to enable debugging output
# These should be moved somewhere better.
debug = False
def dbg (log = ""):
@ -49,12 +47,15 @@ def err (log = ""):
"""Print an error message"""
print >> sys.stderr, log
from configfile import ConfigFile, ConfigSyntaxError
class TerminatorConfig:
"""This class is used as the base point of the config system"""
callback = None
sources = []
def __init__ (self, sources):
self._keys = None
for source in sources:
if isinstance(source, TerminatorConfValuestore):
self.sources.append (source)
@ -62,7 +63,22 @@ class TerminatorConfig:
# 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))
@ -133,6 +149,36 @@ class TerminatorConfValuestore:
'close_button_on_tab' : True,
'enable_real_transparency' : False,
'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):
@ -150,11 +196,10 @@ class TerminatorConfValuestoreDefault (TerminatorConfValuestore):
class TerminatorConfValuestoreRC (TerminatorConfValuestore):
rcfilename = ""
splitter = re.compile("\s*=\s*")
type = "RCFile"
#FIXME: use inotify to watch the rc, split __init__ into a parsing function
# that can be re-used when rc changes.
def __init__ (self):
self.type = "RCFile"
try:
directory = os.environ['XDG_CONFIG_HOME']
except KeyError, e:
@ -163,39 +208,45 @@ class TerminatorConfValuestoreRC (TerminatorConfValuestore):
self.rcfilename = os.path.join(directory, "terminator/config")
dbg(" VS_RCFile: config file located at %s" % self.rcfilename)
if os.path.exists (self.rcfilename):
rcfile = open (self.rcfilename)
rc = rcfile.readlines ()
rcfile.close ()
ini = ConfigFile(self.rcfilename)
try:
ini.parse()
except ConfigSyntaxError, e:
print "There was an error parsing your configuration"
print str(e)
sys.exit()
for item in rc:
for key in ini.settings:
try:
item = item.strip ()
if item and item[0] != '#':
(key, value) = self.splitter.split (item)
value = ini.settings[key]
# Check if this is actually a key we care about
if not self.defaults.has_key (key):
# We should really mention this to the user
continue
# Check if this is actually a key we care about
if not self.defaults.has_key (key):
continue
deftype = self.defaults[key].__class__.__name__
if deftype == 'bool':
if value.lower () == 'true':
self.values[key] = True
elif value.lower () == 'false':
self.values[key] = False
else:
raise AttributeError
elif deftype == 'int':
self.values[key] = int (value)
elif deftype == 'float':
self.values[key] = float (value)
elif deftype == 'list':
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
continue
deftype = self.defaults[key].__class__.__name__
if 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:
self.values[key] = value
raise AttributeError
elif deftype == 'int':
self.values[key] = int (value)
elif deftype == 'float':
self.values[key] = float (value)
elif deftype == 'list':
err (_(" VS_RCFile: Reading list values from .config/terminator/config is not currently supported"))
continue
elif deftype == 'dict':
if type(value) != dict:
raise AttributeError
self.values[key] = value
else:
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:
dbg (" VS_RCFile: %s Exception handling: %s" % (type(e), item))
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 terminatorlib.config
class KeymapError(Exception):
def __init__(self, value):
@ -9,38 +10,8 @@ class KeymapError(Exception):
def __str__(self):
return "Keybinding '%s' invalid: %s" % (self.action, self.value)
Modifier = re.compile('<([^<]+)>')
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 = {
'ctrl': gtk.gdk.CONTROL_MASK,
@ -51,6 +22,10 @@ class TerminatorKeybindings:
empty = {}
def __init__(self):
self.configure({})
def configure(self, bindings):
self.keys = bindings
self.reload()
def reload(self):
@ -74,14 +49,14 @@ class TerminatorKeybindings:
def _parsebinding(self, binding):
mask = 0
modifiers = re.findall(self.Modifier, binding)
modifiers = re.findall(Modifier, binding)
if modifiers:
for modifier in modifiers:
mask |= self._lookup_modifier(modifier)
key = re.sub(self.Modifier, '', binding)
key = re.sub(Modifier, '', binding)
if key == '':
raise KeymapError('No key found')
return (mask, re.sub(self.Modifier, '', binding))
return (mask, re.sub(Modifier, '', binding))
def _lookup_modifier(self, modifier):
try:

View File

@ -118,9 +118,12 @@ class Terminator:
self.icon_theme = gtk.IconTheme ()
self.keybindings = TerminatorKeybindings()
self.keybindings.configure(self.conf.keybindings)
if self.conf.f11_modifier:
self.keybindings.keys['full_screen'] = '<Ctrl><Shift>F11'
self.keybindings.reload()
print "Warning: f11_modifier is no longer supported"
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):
gtk.rc_parse_string("""