1
# Xlib.rdb -- X resource database implementation
3
# Copyright (C) 2000 Peter Liljenberg <petli@ctrl-c.liu.se>
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
# See end of file for an explanation of the algorithm and
21
# data structures used.
31
from support import lock
33
# Set up a few regexpes for parsing string representation of resources
35
comment_re = re.compile(r'^\s*!')
36
resource_spec_re = re.compile(r'^\s*([-_a-zA-Z0-9?.*]+)\s*:\s*(.*)$')
37
value_escape_re = re.compile('\\\\([ \tn\\\\]|[0-7]{3,3})')
38
resource_parts_re = re.compile(r'([.*]+)')
40
# Constants used for determining which match is best
48
class OptionError(Exception):
53
def __init__(self, file = None, string = None, resources = None):
55
self.lock = lock.allocate_lock()
58
self.insert_file(file)
59
if string is not None:
60
self.insert_string(string)
61
if resources is not None:
62
self.insert_resources(resources)
64
def insert_file(self, file):
67
Load resources entries from FILE, and insert them into the
68
database. FILE can be a filename (a string)or a file object.
72
if type(file) is types.StringType:
73
file = open(file, 'r')
75
self.insert_string(file.read())
78
def insert_string(self, data):
79
"""insert_string(data)
81
Insert the resources entries in the string DATA into the
86
# First split string into lines
87
lines = string.split(data, '\n')
98
if comment_re.match(line):
101
# Handle continued lines
102
while line[-1] == '\\':
104
line = line[:-1] + lines[0]
110
# Split line into resource and value
111
m = resource_spec_re.match(line)
113
# Bad line, just ignore it silently
117
res, value = m.group(1, 2)
119
# Convert all escape sequences in value
120
splits = value_escape_re.split(value)
122
for i in range(1, len(splits), 2):
125
splits[i] = chr(string.atoi(s, 8))
129
# strip the last value part to get rid of any
131
splits[-1] = string.rstrip(splits[-1])
133
value = string.join(splits, '')
135
self.insert(res, value)
138
def insert_resources(self, resources):
139
"""insert_resources(resources)
141
Insert all resources entries in the list RESOURCES into the
142
database. Each element in RESOURCES should be a tuple:
146
Where RESOURCE is a string and VALUE can be any Python value.
150
for res, value in resources:
151
self.insert(res, value)
153
def insert(self, resource, value):
154
"""insert(resource, value)
156
Insert a resource entry into the database. RESOURCE is a
157
string and VALUE can be any Python value.
161
# Split res into components and bindings
162
parts = resource_parts_re.split(resource)
164
# If the last part is empty, this is an invalid resource
165
# which we simply ignore
172
for i in range(1, len(parts), 2):
174
# Create a new mapping/value group
175
if not db.has_key(parts[i - 1]):
176
db[parts[i - 1]] = ({}, {})
178
# Use second mapping if a loose binding, first otherwise
180
db = db[parts[i - 1]][1]
182
db = db[parts[i - 1]][0]
184
# Insert value into the derived db
185
if db.has_key(parts[-1]):
186
db[parts[-1]] = db[parts[-1]][:2] + (value, )
188
db[parts[-1]] = ({}, {}, value)
192
def __getitem__(self, (name, cls)):
195
Return the value matching the resource identified by NAME and
196
CLASS. If no match is found, KeyError is raised.
199
# Split name and class into their parts
201
namep = string.split(name, '.')
202
clsp = string.split(cls, '.')
204
# It is an error for name and class to have different number
207
if len(namep) != len(clsp):
208
raise ValueError('Different number of parts in resource name/class: %s/%s' % (name, cls))
213
# Lock database and wrap the lookup code in a try-finally
214
# block to make sure that it is unlocked.
219
# Precedence order: name -> class -> ?
221
if self.db.has_key(namep[0]):
222
bin_insert(matches, _Match((NAME_MATCH, ), self.db[namep[0]]))
224
if self.db.has_key(clsp[0]):
225
bin_insert(matches, _Match((CLASS_MATCH, ), self.db[clsp[0]]))
227
if self.db.has_key('?'):
228
bin_insert(matches, _Match((WILD_MATCH, ), self.db['?']))
231
# Special case for the unlikely event that the resource
232
# only has one component
233
if complen == 1 and matches:
238
raise KeyError((name, cls))
241
# Special case for resources which begins with a loose
242
# binding, e.g. '*foo.bar'
243
if self.db.has_key(''):
244
bin_insert(matches, _Match((), self.db[''][1]))
247
# Now iterate over all components until we find the best match.
249
# For each component, we choose the best partial match among
250
# the mappings by applying these rules in order:
252
# Rule 1: If the current group contains a match for the
253
# name, class or '?', we drop all previously found loose
256
# Rule 2: A matching name has precedence over a matching
257
# class, which in turn has precedence over '?'.
259
# Rule 3: Tight bindings have precedence over loose
264
# Work on the first element == the best current match
269
# print 'path: ', x.path
271
# print 'skip: ', x.db
273
# print 'group: ', x.group
278
for part, score in ((namep[i], NAME_MATCH),
279
(clsp[i], CLASS_MATCH),
282
# Attempt to find a match in x
283
match = x.match(part, score)
285
# Hey, we actually found a value!
286
if match.final(complen):
289
# Else just insert the new match
291
bin_insert(matches, match)
293
# Generate a new loose match
294
match = x.skip_match(complen)
296
bin_insert(matches, match)
298
# Oh well, nothing matched
299
raise KeyError((name, cls))
304
def get(self, res, cls, default = None):
305
"""get(name, class [, default])
307
Return the value matching the resource identified by NAME and
308
CLASS. If no match is found, DEFAULT is returned, or None if
309
DEFAULT isn't specified.
314
return self[(res, cls)]
318
def update(self, db):
321
Update this database with all resources entries in the resource
327
update_db(self.db, db.db)
333
Return the resource database in text representation.
337
text = output_db('', self.db)
341
def getopt(self, name, argv, opts):
342
"""getopt(name, argv, opts)
344
Parse X command line options, inserting the recognised options
345
into the resource database.
347
NAME is the application name, and will be prepended to all
348
specifiers. ARGV is the list of command line arguments,
349
typically sys.argv[1:].
351
OPTS is a mapping of options to resource specifiers. The key is
352
the option flag (with leading -), and the value is an instance of
353
some Option subclass:
355
NoArg(specifier, value): set resource to value.
356
IsArg(specifier): set resource to option itself
357
SepArg(specifier): value is next argument
358
ResArg: resource and value in next argument
359
SkipArg: ignore this option and next argument
360
SkipLine: ignore rest of arguments
361
SkipNArgs(count): ignore this option and count arguments
363
The remaining, non-option, oparguments is returned.
365
rdb.OptionError is raised if there is an error in the argument list.
368
while argv and argv[0] and argv[0][0] == '-':
370
argv = opts[argv[0]].parse(name, self, argv)
372
raise OptionError('unknown option: %s' % argv[0])
374
raise OptionError('missing argument to option: %s' % argv[0])
380
def __init__(self, path, dbs):
383
if type(dbs) is types.TupleType:
391
def __cmp__(self, other):
392
return cmp(self.path, other.path)
394
def match_length(self):
395
return len(self.path)
397
def match(self, part, score):
399
if self.db.has_key(part):
400
return _Match(self.path + (score, ), self.db[part])
404
if self.group[0].has_key(part):
405
return _Match(self.path + (score, ), self.group[0][part])
406
elif self.group[1].has_key(part):
407
return _Match(self.path + (score + 1, ), self.group[1][part])
411
def skip_match(self, complen):
412
# Can't make another skip if we have run out of components
413
if len(self.path) + 1 >= complen:
416
# If this already is a skip match, clone a new one
419
return _Match(self.path + (MATCH_SKIP, ), self.db)
423
# Only generate a skip match if the loose binding mapping
426
return _Match(self.path + (MATCH_SKIP, ), self.group[1])
428
# This is a dead end match
432
def final(self, complen):
433
if not self.skip and len(self.path) == complen and len(self.group) > 2:
443
# Helper function for ResourceDB.__getitem__()
446
def bin_insert(list, element):
447
"""bin_insert(list, element)
449
Insert ELEMENT into LIST. LIST must be sorted, and ELEMENT will
450
be inserted to that LIST remains sorted. If LIST already contains
451
ELEMENT, it will not be duplicated.
460
upper = len(list) - 1
462
while lower <= upper:
463
center = (lower + upper) / 2
464
if element < list[center]:
466
elif element > list[center]:
468
elif element == list[center]:
471
if element < list[upper]:
472
list.insert(upper, element)
473
elif element > list[upper]:
474
list.insert(upper + 1, element)
478
# Helper functions for ResourceDB.update()
481
def update_db(dest, src):
482
for comp, group in src.items():
484
# DEST already contains this component, update it
485
if dest.has_key(comp):
487
# Update tight and loose binding databases
488
update_db(dest[comp][0], group[0])
489
update_db(dest[comp][1], group[1])
491
# If a value has been set in SRC, update
495
dest[comp] = dest[comp][:2] + group[2:]
497
# COMP not in src, make a deep copy
499
dest[comp] = copy_group(group)
501
def copy_group(group):
502
return (copy_db(group[0]), copy_db(group[1])) + group[2:]
506
for comp, group in db.items():
507
newdb[comp] = copy_group(group)
513
# Helper functions for output
516
def output_db(prefix, db):
518
for comp, group in db.items():
520
# There's a value for this component
522
res = res + '%s%s: %s\n' % (prefix, comp, output_escape(group[2]))
524
# Output tight and loose bindings
525
res = res + output_db(prefix + comp + '.', group[0])
526
res = res + output_db(prefix + comp + '*', group[1])
530
def output_escape(value):
535
for char, esc in (('\\', '\\\\'),
539
value = string.replace(value, char, esc)
541
# If first or last character is space or tab, escape them.
542
if value[0] in ' \t':
544
if value[-1] in ' \t' and value[-2:-1] != '\\':
545
value = value[:-1] + '\\' + value[-1]
551
# Option type definitions
558
def parse(self, name, db, args):
562
"""Value is provided to constructor."""
563
def __init__(self, specifier, value):
564
self.specifier = specifier
567
def parse(self, name, db, args):
568
db.insert(name + self.specifier, self.value)
572
"""Value is the option string itself."""
573
def __init__(self, specifier):
574
self.specifier = specifier
576
def parse(self, name, db, args):
577
db.insert(name + self.specifier, args[0])
580
class SepArg(Option):
581
"""Value is the next argument."""
582
def __init__(self, specifier):
583
self.specifier = specifier
585
def parse(self, name, db, args):
586
db.insert(name + self.specifier, args[1])
589
class ResArgClass(Option):
590
"""Resource and value in the next argument."""
591
def parse(self, name, db, args):
592
db.insert_string(args[1])
595
ResArg = ResArgClass()
597
class SkipArgClass(Option):
598
"""Ignore this option and next argument."""
599
def parse(self, name, db, args):
602
SkipArg = SkipArgClass()
604
class SkipLineClass(Option):
605
"""Ignore rest of the arguments."""
606
def parse(self, name, db, args):
609
SkipLine = SkipLineClass()
611
class SkipNArgs(Option):
612
"""Ignore this option and the next COUNT arguments."""
613
def __init__(self, count):
616
def parse(self, name, db, args):
617
return args[1 + self.count:]
621
def get_display_opts(options, argv = sys.argv):
622
"""display, name, db, args = get_display_opts(options, [argv])
624
Parse X OPTIONS from ARGV (or sys.argv if not provided).
626
Connect to the display specified by a *.display resource if one is
627
set, or to the default X display otherwise. Extract the
628
RESOURCE_MANAGER property and insert all resources from ARGV.
630
The four return values are:
631
DISPLAY -- the display object
632
NAME -- the application name (the filname of ARGV[0])
633
DB -- the created resource database
634
ARGS -- any remaining arguments
637
from Xlib import display, Xatom
640
name = os.path.splitext(os.path.basename(argv[0]))[0]
643
leftargv = optdb.getopt(name, argv[1:], options)
645
dname = optdb.get(name + '.display', name + '.Display', None)
646
d = display.Display(dname)
648
rdbstring = d.screen(0).root.get_full_property(Xatom.RESOURCE_MANAGER,
651
data = rdbstring.value
655
db = ResourceDB(string = data)
658
return d, name, db, leftargv
662
stdopts = {'-bg': SepArg('*background'),
663
'-background': SepArg('*background'),
664
'-fg': SepArg('*foreground'),
665
'-foreground': SepArg('*foreground'),
666
'-fn': SepArg('*font'),
667
'-font': SepArg('*font'),
668
'-name': SepArg('.name'),
669
'-title': SepArg('.title'),
670
'-synchronous': NoArg('*synchronous', 'on'),
672
'-display': SepArg('.display'),
673
'-d': SepArg('.display'),
677
# Notes on the implementation:
679
# Resource names are split into their components, and each component
680
# is stored in a mapping. The value for a component is a tuple of two
683
# (tightmapping, loosemapping [, value])
685
# tightmapping contains the next components which are connected with a
686
# tight binding (.). loosemapping contains the ones connected with
687
# loose binding (*). If value is present, then this component is the
688
# last component for some resource which that value.
690
# The top level components are stored in the mapping r.db, where r is
691
# the resource object.
693
# Example: Inserting "foo.bar*gazonk: yep" into an otherwise empty
694
# resource database would give the folliwing structure:
696
# { 'foo': ( { 'bar': ( { },