--- /dev/null
+"""
+Base Class for DesktopEntry, IconTheme and IconData
+"""
+
+import re, os, stat, codecs
+from Exceptions import *
+import xdg.Locale
+
+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):
+ # for performance reasons
+ content = self.content
+
+ if not os.path.isfile(filename):
+ raise ParsingError("File not found", filename)
+
+ try:
+ fd = file(filename, 'r')
+ except IOError, 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:
+ index = line.find("=")
+ key = line[0:index].strip()
+ value = line[index+1:].strip()
+ try:
+ if debug and self.hasKey(key, currentGroup):
+ raise DuplicateKeyError(key, currentGroup, filename)
+ else:
+ content[currentGroup][key] = value
+ 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 content.has_key(header):
+ 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 self.content.has_key(group) and self.content[group].has_key(key):
+ if locale:
+ value = self.content[group][self.__addLocale(key, group)]
+ else:
+ value = self.content[group][key]
+ else:
+ if debug:
+ if not self.content.has_key(group):
+ raise NoGroupError(group, self.filename)
+ elif not self.content[group].has_key(key):
+ 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 == "string" and locale == True:
+ value = value.decode("utf-8", "ignore")
+ elif 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":
+ value = value.split(",")
+
+ 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:
+ if self.content[group].has_key(key+'['+lang+']'):
+ return key+'['+lang+']'
+
+ return key
+
+ # start validation stuff
+ def validate(self, report="All"):
+ "validate ... report = 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)
+ 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):
+ # convert to ascii
+ if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value:
+ return 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))
+
+ fp = codecs.open(filename, 'w')
+
+ # 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("#!/usr/bin/env xdg-open\n")
+
+ if self.defaultGroup:
+ fp.write("[%s]\n" % self.defaultGroup)
+ for (key, value) in self.content[self.defaultGroup].items():
+ fp.write("%s=%s\n" % (key, value))
+ fp.write("\n")
+ for (name, group) in self.content.items():
+ if name != self.defaultGroup:
+ fp.write("[%s]\n" % name)
+ for (key, value) in group.items():
+ fp.write("%s=%s\n" % (key, value))
+ fp.write("\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:
+ if isinstance(value, unicode):
+ self.content[group][key] = value.encode("utf-8", "ignore")
+ else:
+ 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:
+ pass
+ 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, value) in self.content[group].items():
+ if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key:
+ value = self.content[group][name]
+ del self.content[group][name]
+ value = self.content[group][key]
+ del self.content[group][key]
+ self.tainted = True
+ return value
+ except KeyError, 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):
+ if self.content.has_key(group):
+ return True
+ else:
+ return False
+
+ def hasKey(self, key, group=None):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ if self.content[group].has_key(key):
+ return True
+ else:
+ return False
+
+ def getFileName(self):
+ return self.filename