/
usr
/
lib
/
python3
/
dist-packages
/
xdg
/
Upload File
HOME
""" 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 import gettext 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 = '' gettext_domain = None 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) # check for gettext domain e = self.content.get('Desktop Entry', {}) self.gettext_domain = e.get('X-GNOME-Gettext-Domain', e.get('X-Ubuntu-Gettext-Domain', None)) # 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: key = self.__addLocale(key, group) if key.endswith(']') or not self.gettext_domain: # inline translations value = self.content[group][key] else: value = gettext.dgettext(self.gettext_domain, self.content[group][key]) 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