Spaces:
Runtime error
Runtime error
| # Xlib.rdb -- X resource database implementation | |
| # | |
| # Copyright (C) 2000 Peter Liljenberg <petli@ctrl-c.liu.se> | |
| # | |
| # This library is free software; you can redistribute it and/or | |
| # modify it under the terms of the GNU Lesser General Public License | |
| # as published by the Free Software Foundation; either version 2.1 | |
| # of the License, or (at your option) any later version. | |
| # | |
| # This library is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| # See the GNU Lesser General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU Lesser General Public | |
| # License along with this library; if not, write to the | |
| # Free Software Foundation, Inc., | |
| # 59 Temple Place, | |
| # Suite 330, | |
| # Boston, MA 02111-1307 USA | |
| # See end of file for an explanation of the algorithm and | |
| # data structures used. | |
| # Standard modules | |
| import re | |
| import sys | |
| # Xlib modules | |
| from .support import lock | |
| # Set up a few regexpes for parsing string representation of resources | |
| comment_re = re.compile(r'^\s*!') | |
| resource_spec_re = re.compile(r'^\s*([-_a-zA-Z0-9?.*]+)\s*:\s*(.*)$') | |
| value_escape_re = re.compile('\\\\([ \tn\\\\]|[0-7]{3,3})') | |
| resource_parts_re = re.compile(r'([.*]+)') | |
| # Constants used for determining which match is best | |
| NAME_MATCH = 0 | |
| CLASS_MATCH = 2 | |
| WILD_MATCH = 4 | |
| MATCH_SKIP = 6 | |
| # Option error class | |
| class OptionError(Exception): | |
| pass | |
| class ResourceDB(object): | |
| def __init__(self, file = None, string = None, resources = None): | |
| self.db = {} | |
| self.lock = lock.allocate_lock() | |
| if file is not None: | |
| self.insert_file(file) | |
| if string is not None: | |
| self.insert_string(string) | |
| if resources is not None: | |
| self.insert_resources(resources) | |
| def insert_file(self, file): | |
| """insert_file(file) | |
| Load resources entries from FILE, and insert them into the | |
| database. FILE can be a filename (a string)or a file object. | |
| """ | |
| if type(file) is bytes: | |
| file = open(file, 'r') | |
| self.insert_string(file.read()) | |
| def insert_string(self, data): | |
| """insert_string(data) | |
| Insert the resources entries in the string DATA into the | |
| database. | |
| """ | |
| # First split string into lines | |
| lines = data.split('\n') | |
| while lines: | |
| line = lines[0] | |
| del lines[0] | |
| # Skip empty line | |
| if not line: | |
| continue | |
| # Skip comments | |
| if comment_re.match(line): | |
| continue | |
| # Handle continued lines | |
| while line[-1] == '\\': | |
| if lines: | |
| line = line[:-1] + lines[0] | |
| del lines[0] | |
| else: | |
| line = line[:-1] | |
| break | |
| # Split line into resource and value | |
| m = resource_spec_re.match(line) | |
| # Bad line, just ignore it silently | |
| if not m: | |
| continue | |
| res, value = m.group(1, 2) | |
| # Convert all escape sequences in value | |
| splits = value_escape_re.split(value) | |
| for i in range(1, len(splits), 2): | |
| s = splits[i] | |
| if len(s) == 3: | |
| splits[i] = chr(int(s, 8)) | |
| elif s == 'n': | |
| splits[i] = '\n' | |
| # strip the last value part to get rid of any | |
| # unescaped blanks | |
| splits[-1] = splits[-1].rstrip() | |
| value = ''.join(splits) | |
| self.insert(res, value) | |
| def insert_resources(self, resources): | |
| """insert_resources(resources) | |
| Insert all resources entries in the list RESOURCES into the | |
| database. Each element in RESOURCES should be a tuple: | |
| (resource, value) | |
| Where RESOURCE is a string and VALUE can be any Python value. | |
| """ | |
| for res, value in resources: | |
| self.insert(res, value) | |
| def insert(self, resource, value): | |
| """insert(resource, value) | |
| Insert a resource entry into the database. RESOURCE is a | |
| string and VALUE can be any Python value. | |
| """ | |
| # Split res into components and bindings | |
| parts = resource_parts_re.split(resource) | |
| # If the last part is empty, this is an invalid resource | |
| # which we simply ignore | |
| if parts[-1] == '': | |
| return | |
| self.lock.acquire() | |
| db = self.db | |
| for i in range(1, len(parts), 2): | |
| # Create a new mapping/value group | |
| if parts[i - 1] not in db: | |
| db[parts[i - 1]] = ({}, {}) | |
| # Use second mapping if a loose binding, first otherwise | |
| if '*' in parts[i]: | |
| db = db[parts[i - 1]][1] | |
| else: | |
| db = db[parts[i - 1]][0] | |
| # Insert value into the derived db | |
| if parts[-1] in db: | |
| db[parts[-1]] = db[parts[-1]][:2] + (value, ) | |
| else: | |
| db[parts[-1]] = ({}, {}, value) | |
| self.lock.release() | |
| def __getitem__(self, keys_tuple): | |
| """db[name, class] | |
| Return the value matching the resource identified by NAME and | |
| CLASS. If no match is found, KeyError is raised. | |
| """ | |
| # Split name and class into their parts | |
| name, cls = keys_tuple | |
| namep = name.split('.') | |
| clsp = cls.split('.') | |
| # It is an error for name and class to have different number | |
| # of parts | |
| if len(namep) != len(clsp): | |
| raise ValueError('Different number of parts in resource name/class: %s/%s' % (name, cls)) | |
| complen = len(namep) | |
| matches = [] | |
| # Lock database and wrap the lookup code in a try-finally | |
| # block to make sure that it is unlocked. | |
| self.lock.acquire() | |
| try: | |
| # Precedence order: name -> class -> ? | |
| if namep[0] in self.db: | |
| bin_insert(matches, _Match((NAME_MATCH, ), self.db[namep[0]])) | |
| if clsp[0] in self.db: | |
| bin_insert(matches, _Match((CLASS_MATCH, ), self.db[clsp[0]])) | |
| if '?' in self.db: | |
| bin_insert(matches, _Match((WILD_MATCH, ), self.db['?'])) | |
| # Special case for the unlikely event that the resource | |
| # only has one component | |
| if complen == 1 and matches: | |
| x = matches[0] | |
| if x.final(complen): | |
| return x.value() | |
| else: | |
| raise KeyError((name, cls)) | |
| # Special case for resources which begins with a loose | |
| # binding, e.g. '*foo.bar' | |
| if '' in self.db: | |
| bin_insert(matches, _Match((), self.db[''][1])) | |
| # Now iterate over all components until we find the best match. | |
| # For each component, we choose the best partial match among | |
| # the mappings by applying these rules in order: | |
| # Rule 1: If the current group contains a match for the | |
| # name, class or '?', we drop all previously found loose | |
| # binding mappings. | |
| # Rule 2: A matching name has precedence over a matching | |
| # class, which in turn has precedence over '?'. | |
| # Rule 3: Tight bindings have precedence over loose | |
| # bindings. | |
| while matches: | |
| # Work on the first element == the best current match | |
| x = matches[0] | |
| del matches[0] | |
| # print 'path: ', x.path | |
| # if x.skip: | |
| # print 'skip: ', x.db | |
| # else: | |
| # print 'group: ', x.group | |
| i = x.match_length() | |
| for part, score in ((namep[i], NAME_MATCH), | |
| (clsp[i], CLASS_MATCH), | |
| ('?', WILD_MATCH)): | |
| # Attempt to find a match in x | |
| match = x.match(part, score) | |
| if match: | |
| # Hey, we actually found a value! | |
| if match.final(complen): | |
| return match.value() | |
| # Else just insert the new match | |
| else: | |
| bin_insert(matches, match) | |
| # Generate a new loose match | |
| match = x.skip_match(complen) | |
| if match: | |
| bin_insert(matches, match) | |
| # Oh well, nothing matched | |
| raise KeyError((name, cls)) | |
| finally: | |
| self.lock.release() | |
| def get(self, res, cls, default = None): | |
| """get(name, class [, default]) | |
| Return the value matching the resource identified by NAME and | |
| CLASS. If no match is found, DEFAULT is returned, or None if | |
| DEFAULT isn't specified. | |
| """ | |
| try: | |
| return self[(res, cls)] | |
| except KeyError: | |
| return default | |
| def update(self, db): | |
| """update(db) | |
| Update this database with all resources entries in the resource | |
| database DB. | |
| """ | |
| self.lock.acquire() | |
| update_db(self.db, db.db) | |
| self.lock.release() | |
| def output(self): | |
| """output() | |
| Return the resource database in text representation. | |
| """ | |
| self.lock.acquire() | |
| text = output_db('', self.db) | |
| self.lock.release() | |
| return text | |
| def getopt(self, name, argv, opts): | |
| """getopt(name, argv, opts) | |
| Parse X command line options, inserting the recognised options | |
| into the resource database. | |
| NAME is the application name, and will be prepended to all | |
| specifiers. ARGV is the list of command line arguments, | |
| typically sys.argv[1:]. | |
| OPTS is a mapping of options to resource specifiers. The key is | |
| the option flag (with leading -), and the value is an instance of | |
| some Option subclass: | |
| NoArg(specifier, value): set resource to value. | |
| IsArg(specifier): set resource to option itself | |
| SepArg(specifier): value is next argument | |
| ResArg: resource and value in next argument | |
| SkipArg: ignore this option and next argument | |
| SkipLine: ignore rest of arguments | |
| SkipNArgs(count): ignore this option and count arguments | |
| The remaining, non-option, oparguments is returned. | |
| rdb.OptionError is raised if there is an error in the argument list. | |
| """ | |
| while argv and argv[0] and argv[0][0] == '-': | |
| try: | |
| argv = opts[argv[0]].parse(name, self, argv) | |
| except KeyError: | |
| raise OptionError('unknown option: %s' % argv[0]) | |
| except IndexError: | |
| raise OptionError('missing argument to option: %s' % argv[0]) | |
| return argv | |
| class _Match(object): | |
| def __init__(self, path, dbs): | |
| self.path = path | |
| if type(dbs) is tuple: | |
| self.skip = 0 | |
| self.group = dbs | |
| else: | |
| self.skip = 1 | |
| self.db = dbs | |
| def __lt__(self, other): | |
| return self.path < other.path | |
| def __gt__(self, other): | |
| return self.path > other.path | |
| def __eq__(self, other): | |
| return self.path == other.path | |
| def match_length(self): | |
| return len(self.path) | |
| def match(self, part, score): | |
| if self.skip: | |
| if part in self.db: | |
| return _Match(self.path + (score, ), self.db[part]) | |
| else: | |
| return None | |
| else: | |
| if part in self.group[0]: | |
| return _Match(self.path + (score, ), self.group[0][part]) | |
| elif part in self.group[1]: | |
| return _Match(self.path + (score + 1, ), self.group[1][part]) | |
| else: | |
| return None | |
| def skip_match(self, complen): | |
| # Can't make another skip if we have run out of components | |
| if len(self.path) + 1 >= complen: | |
| return None | |
| # If this already is a skip match, clone a new one | |
| if self.skip: | |
| if self.db: | |
| return _Match(self.path + (MATCH_SKIP, ), self.db) | |
| else: | |
| return None | |
| # Only generate a skip match if the loose binding mapping | |
| # is non-empty | |
| elif self.group[1]: | |
| return _Match(self.path + (MATCH_SKIP, ), self.group[1]) | |
| # This is a dead end match | |
| else: | |
| return None | |
| def final(self, complen): | |
| if not self.skip and len(self.path) == complen and len(self.group) > 2: | |
| return 1 | |
| else: | |
| return 0 | |
| def value(self): | |
| return self.group[2] | |
| # | |
| # Helper function for ResourceDB.__getitem__() | |
| # | |
| def bin_insert(list, element): | |
| """bin_insert(list, element) | |
| Insert ELEMENT into LIST. LIST must be sorted, and ELEMENT will | |
| be inserted to that LIST remains sorted. If LIST already contains | |
| ELEMENT, it will not be duplicated. | |
| """ | |
| if not list: | |
| list.append(element) | |
| return | |
| lower = 0 | |
| upper = len(list) - 1 | |
| while lower <= upper: | |
| center = (lower + upper) // 2 | |
| if element < list[center]: | |
| upper = center - 1 | |
| elif element > list[center]: | |
| lower = center + 1 | |
| elif element == list[center]: | |
| return | |
| if element < list[upper]: | |
| list.insert(upper, element) | |
| elif element > list[upper]: | |
| list.insert(upper + 1, element) | |
| # | |
| # Helper functions for ResourceDB.update() | |
| # | |
| def update_db(dest, src): | |
| for comp, group in src.items(): | |
| # DEST already contains this component, update it | |
| if comp in dest: | |
| # Update tight and loose binding databases | |
| update_db(dest[comp][0], group[0]) | |
| update_db(dest[comp][1], group[1]) | |
| # If a value has been set in SRC, update | |
| # value in DEST | |
| if len(group) > 2: | |
| dest[comp] = dest[comp][:2] + group[2:] | |
| # COMP not in src, make a deep copy | |
| else: | |
| dest[comp] = copy_group(group) | |
| def copy_group(group): | |
| return (copy_db(group[0]), copy_db(group[1])) + group[2:] | |
| def copy_db(db): | |
| newdb = {} | |
| for comp, group in db.items(): | |
| newdb[comp] = copy_group(group) | |
| return newdb | |
| # | |
| # Helper functions for output | |
| # | |
| def output_db(prefix, db): | |
| res = '' | |
| for comp, group in db.items(): | |
| # There's a value for this component | |
| if len(group) > 2: | |
| res = res + '%s%s: %s\n' % (prefix, comp, output_escape(group[2])) | |
| # Output tight and loose bindings | |
| res = res + output_db(prefix + comp + '.', group[0]) | |
| res = res + output_db(prefix + comp + '*', group[1]) | |
| return res | |
| def output_escape(value): | |
| value = str(value) | |
| if not value: | |
| return value | |
| for char, esc in (('\\', '\\\\'), | |
| ('\000', '\\000'), | |
| ('\n', '\\n')): | |
| value = value.replace(char, esc) | |
| # If first or last character is space or tab, escape them. | |
| if value[0] in ' \t': | |
| value = '\\' + value | |
| if value[-1] in ' \t' and value[-2:-1] != '\\': | |
| value = value[:-1] + '\\' + value[-1] | |
| return value | |
| # | |
| # Option type definitions | |
| # | |
| class Option(object): | |
| def __init__(self): | |
| pass | |
| def parse(self, name, db, args): | |
| pass | |
| class NoArg(Option): | |
| """Value is provided to constructor.""" | |
| def __init__(self, specifier, value): | |
| self.specifier = specifier | |
| self.value = value | |
| def parse(self, name, db, args): | |
| db.insert(name + self.specifier, self.value) | |
| return args[1:] | |
| class IsArg(Option): | |
| """Value is the option string itself.""" | |
| def __init__(self, specifier): | |
| self.specifier = specifier | |
| def parse(self, name, db, args): | |
| db.insert(name + self.specifier, args[0]) | |
| return args[1:] | |
| class SepArg(Option): | |
| """Value is the next argument.""" | |
| def __init__(self, specifier): | |
| self.specifier = specifier | |
| def parse(self, name, db, args): | |
| db.insert(name + self.specifier, args[1]) | |
| return args[2:] | |
| class ResArgClass(Option): | |
| """Resource and value in the next argument.""" | |
| def parse(self, name, db, args): | |
| db.insert_string(args[1]) | |
| return args[2:] | |
| ResArg = ResArgClass() | |
| class SkipArgClass(Option): | |
| """Ignore this option and next argument.""" | |
| def parse(self, name, db, args): | |
| return args[2:] | |
| SkipArg = SkipArgClass() | |
| class SkipLineClass(Option): | |
| """Ignore rest of the arguments.""" | |
| def parse(self, name, db, args): | |
| return [] | |
| SkipLine = SkipLineClass() | |
| class SkipNArgs(Option): | |
| """Ignore this option and the next COUNT arguments.""" | |
| def __init__(self, count): | |
| self.count = count | |
| def parse(self, name, db, args): | |
| return args[1 + self.count:] | |
| def get_display_opts(options, argv = sys.argv): | |
| """display, name, db, args = get_display_opts(options, [argv]) | |
| Parse X OPTIONS from ARGV (or sys.argv if not provided). | |
| Connect to the display specified by a *.display resource if one is | |
| set, or to the default X display otherwise. Extract the | |
| RESOURCE_MANAGER property and insert all resources from ARGV. | |
| The four return values are: | |
| DISPLAY -- the display object | |
| NAME -- the application name (the filname of ARGV[0]) | |
| DB -- the created resource database | |
| ARGS -- any remaining arguments | |
| """ | |
| from Xlib import display, Xatom | |
| import os | |
| name = os.path.splitext(os.path.basename(argv[0]))[0] | |
| optdb = ResourceDB() | |
| leftargv = optdb.getopt(name, argv[1:], options) | |
| dname = optdb.get(name + '.display', name + '.Display', None) | |
| d = display.Display(dname) | |
| rdbstring = d.screen(0).root.get_full_property(Xatom.RESOURCE_MANAGER, | |
| Xatom.STRING) | |
| if rdbstring: | |
| data = rdbstring.value | |
| else: | |
| data = None | |
| db = ResourceDB(string = data) | |
| db.update(optdb) | |
| return d, name, db, leftargv | |
| # Common X options | |
| stdopts = {'-bg': SepArg('*background'), | |
| '-background': SepArg('*background'), | |
| '-fg': SepArg('*foreground'), | |
| '-foreground': SepArg('*foreground'), | |
| '-fn': SepArg('*font'), | |
| '-font': SepArg('*font'), | |
| '-name': SepArg('.name'), | |
| '-title': SepArg('.title'), | |
| '-synchronous': NoArg('*synchronous', 'on'), | |
| '-xrm': ResArg, | |
| '-display': SepArg('.display'), | |
| '-d': SepArg('.display'), | |
| } | |
| # Notes on the implementation: | |
| # Resource names are split into their components, and each component | |
| # is stored in a mapping. The value for a component is a tuple of two | |
| # or three elements: | |
| # (tightmapping, loosemapping [, value]) | |
| # tightmapping contains the next components which are connected with a | |
| # tight binding (.). loosemapping contains the ones connected with | |
| # loose binding (*). If value is present, then this component is the | |
| # last component for some resource which that value. | |
| # The top level components are stored in the mapping r.db, where r is | |
| # the resource object. | |
| # Example: Inserting "foo.bar*gazonk: yep" into an otherwise empty | |
| # resource database would give the following structure: | |
| # { 'foo': ( { 'bar': ( { }, | |
| # { 'gazonk': ( { }, | |
| # { }, | |
| # 'yep') | |
| # } | |
| # ) | |
| # }, | |
| # {}) | |
| # } | |