View file File name : IniFile.py Content :""" Base Class for DesktopEntry, IconTheme and IconData """ import re, os, stat, io from xdg.Exceptions import (ParsingError, DuplicateGroupError, NoGroupError, NoKeyError, DuplicateKeyError, ValidationError, debug) import xdg.Locale from xdg.util import u def is_ascii(s): """Return True if a string consists entirely of ASCII characters.""" try: s.encode('ascii', 'strict') return True except UnicodeError: return False class IniFile: defaultGroup = '' fileExtension = '' filename = '' tainted = False def __init__(self, filename=None): self.content = dict() if filename: self.parse(filename) def __cmp__(self, other): return cmp(self.content, other.content) def parse(self, filename, headers=None): '''Parse an INI file. headers -- list of headers the parser will try to select as a default header ''' # for performance reasons content = self.content if not os.path.isfile(filename): raise ParsingError("File not found", filename) try: # The content should be UTF-8, but legacy files can have other # encodings, including mixed encodings in one file. We don't attempt # to decode them, but we silence the errors. fd = io.open(filename, 'r', encoding='utf-8', errors='replace') except IOError as e: if debug: raise e else: return # parse file for line in fd: line = line.strip() # empty line if not line: continue # comment elif line[0] == '#': continue # new group elif line[0] == '[': currentGroup = line.lstrip("[").rstrip("]") if debug and self.hasGroup(currentGroup): raise DuplicateGroupError(currentGroup, filename) else: content[currentGroup] = {} # key else: try: key, value = line.split("=", 1) except ValueError: raise ParsingError("Invalid line: " + line, filename) key = key.strip() # Spaces before/after '=' should be ignored try: if debug and self.hasKey(key, currentGroup): raise DuplicateKeyError(key, currentGroup, filename) else: content[currentGroup][key] = value.strip() except (IndexError, UnboundLocalError): raise ParsingError("Parsing error on key, group missing", filename) fd.close() self.filename = filename self.tainted = False # check header if headers: for header in headers: if header in content: self.defaultGroup = header break else: raise ParsingError("[%s]-Header missing" % headers[0], filename) # start stuff to access the keys def get(self, key, group=None, locale=False, type="string", list=False): # set default group if not group: group = self.defaultGroup # return key (with locale) if (group in self.content) and (key in self.content[group]): if locale: value = self.content[group][self.__addLocale(key, group)] else: value = self.content[group][key] else: if debug: if group not in self.content: raise NoGroupError(group, self.filename) elif key not in self.content[group]: raise NoKeyError(key, group, self.filename) else: value = "" if list == True: values = self.getList(value) result = [] else: values = [value] for value in values: if type == "boolean": value = self.__getBoolean(value) elif type == "integer": try: value = int(value) except ValueError: value = 0 elif type == "numeric": try: value = float(value) except ValueError: value = 0.0 elif type == "regex": value = re.compile(value) elif type == "point": x, y = value.split(",") value = int(x), int(y) if list == True: result.append(value) else: result = value return result # end stuff to access the keys # start subget def getList(self, string): if re.search(r"(?<!\\)\;", string): list = re.split(r"(?<!\\);", string) elif re.search(r"(?<!\\)\|", string): list = re.split(r"(?<!\\)\|", string) elif re.search(r"(?<!\\),", string): list = re.split(r"(?<!\\),", string) else: list = [string] if list[-1] == "": list.pop() return list def __getBoolean(self, boolean): if boolean == 1 or boolean == "true" or boolean == "True": return True elif boolean == 0 or boolean == "false" or boolean == "False": return False return False # end subget def __addLocale(self, key, group=None): "add locale to key according the current lc_messages" # set default group if not group: group = self.defaultGroup for lang in xdg.Locale.langs: langkey = "%s[%s]" % (key, lang) if langkey in self.content[group]: return langkey return key # start validation stuff def validate(self, report="All"): """Validate the contents, raising ``ValidationError`` if there is anything amiss. report can be 'All' / 'Warnings' / 'Errors' """ self.warnings = [] self.errors = [] # get file extension self.fileExtension = os.path.splitext(self.filename)[1] # overwrite this for own checkings self.checkExtras() # check all keys for group in self.content: self.checkGroup(group) for key in self.content[group]: self.checkKey(key, self.content[group][key], group) # check if value is empty if self.content[group][key] == "": self.warnings.append("Value of Key '%s' is empty" % key) # raise Warnings / Errors msg = "" if report == "All" or report == "Warnings": for line in self.warnings: msg += "\n- " + line if report == "All" or report == "Errors": for line in self.errors: msg += "\n- " + line if msg: raise ValidationError(msg, self.filename) # check if group header is valid def checkGroup(self, group): pass # check if key is valid def checkKey(self, key, value, group): pass # check random stuff def checkValue(self, key, value, type="string", list=False): if list == True: values = self.getList(value) else: values = [value] for value in values: if type == "string": code = self.checkString(value) if type == "localestring": continue elif type == "boolean": code = self.checkBoolean(value) elif type == "numeric": code = self.checkNumber(value) elif type == "integer": code = self.checkInteger(value) elif type == "regex": code = self.checkRegex(value) elif type == "point": code = self.checkPoint(value) if code == 1: self.errors.append("'%s' is not a valid %s" % (value, type)) elif code == 2: self.warnings.append("Value of key '%s' is deprecated" % key) def checkExtras(self): pass def checkBoolean(self, value): # 1 or 0 : deprecated if (value == "1" or value == "0"): return 2 # true or false: ok elif not (value == "true" or value == "false"): return 1 def checkNumber(self, value): # float() ValueError try: float(value) except: return 1 def checkInteger(self, value): # int() ValueError try: int(value) except: return 1 def checkPoint(self, value): if not re.match("^[0-9]+,[0-9]+$", value): return 1 def checkString(self, value): return 0 if is_ascii(value) else 1 def checkRegex(self, value): try: re.compile(value) except: return 1 # write support def write(self, filename=None, trusted=False): if not filename and not self.filename: raise ParsingError("File not found", "") if filename: self.filename = filename else: filename = self.filename if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) with io.open(filename, 'w', encoding='utf-8') as fp: # An executable bit signifies that the desktop file is # trusted, but then the file can be executed. Add hashbang to # make sure that the file is opened by something that # understands desktop files. if trusted: fp.write(u("#!/usr/bin/env xdg-open\n")) if self.defaultGroup: fp.write(u("[%s]\n") % self.defaultGroup) for (key, value) in self.content[self.defaultGroup].items(): fp.write(u("%s=%s\n") % (key, value)) fp.write(u("\n")) for (name, group) in self.content.items(): if name != self.defaultGroup: fp.write(u("[%s]\n") % name) for (key, value) in group.items(): fp.write(u("%s=%s\n") % (key, value)) fp.write(u("\n")) # Add executable bits to the file to show that it's trusted. if trusted: oldmode = os.stat(filename).st_mode mode = oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH os.chmod(filename, mode) self.tainted = False def set(self, key, value, group=None, locale=False): # set default group if not group: group = self.defaultGroup if locale == True and len(xdg.Locale.langs) > 0: key = key + "[" + xdg.Locale.langs[0] + "]" try: self.content[group][key] = value except KeyError: raise NoGroupError(group, self.filename) self.tainted = (value == self.get(key, group)) def addGroup(self, group): if self.hasGroup(group): if debug: raise DuplicateGroupError(group, self.filename) else: self.content[group] = {} self.tainted = True def removeGroup(self, group): existed = group in self.content if existed: del self.content[group] self.tainted = True else: if debug: raise NoGroupError(group, self.filename) return existed def removeKey(self, key, group=None, locales=True): # set default group if not group: group = self.defaultGroup try: if locales: for name in list(self.content[group]): if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key: del self.content[group][name] value = self.content[group].pop(key) self.tainted = True return value except KeyError as e: if debug: if e == group: raise NoGroupError(group, self.filename) else: raise NoKeyError(key, group, self.filename) else: return "" # misc def groups(self): return self.content.keys() def hasGroup(self, group): return group in self.content def hasKey(self, key, group=None): # set default group if not group: group = self.defaultGroup return key in self.content[group] def getFileName(self): return self.filename