2 Base Class for DesktopEntry, IconTheme and IconData
5 import re, os, stat, codecs
6 from Exceptions import *
17 def __init__(self, filename=None):
22 def __cmp__(self, other):
23 return cmp(self.content, other.content)
25 def parse(self, filename, headers=None):
26 # for performance reasons
27 content = self.content
29 if not os.path.isfile(filename):
30 raise ParsingError("File not found", filename)
33 fd = file(filename, 'r')
51 currentGroup = line.lstrip("[").rstrip("]")
52 if debug and self.hasGroup(currentGroup):
53 raise DuplicateGroupError(currentGroup, filename)
55 content[currentGroup] = {}
58 index = line.find("=")
59 key = line[0:index].strip()
60 value = line[index+1:].strip()
62 if debug and self.hasKey(key, currentGroup):
63 raise DuplicateKeyError(key, currentGroup, filename)
65 content[currentGroup][key] = value
66 except (IndexError, UnboundLocalError):
67 raise ParsingError("Parsing error on key, group missing", filename)
71 self.filename = filename
76 for header in headers:
77 if content.has_key(header):
78 self.defaultGroup = header
81 raise ParsingError("[%s]-Header missing" % headers[0], filename)
83 # start stuff to access the keys
84 def get(self, key, group=None, locale=False, type="string", list=False):
87 group = self.defaultGroup
89 # return key (with locale)
90 if self.content.has_key(group) and self.content[group].has_key(key):
92 value = self.content[group][self.__addLocale(key, group)]
94 value = self.content[group][key]
97 if not self.content.has_key(group):
98 raise NoGroupError(group, self.filename)
99 elif not self.content[group].has_key(key):
100 raise NoKeyError(key, group, self.filename)
105 values = self.getList(value)
111 if type == "string" and locale == True:
112 value = value.decode("utf-8", "ignore")
113 elif type == "boolean":
114 value = self.__getBoolean(value)
115 elif type == "integer":
120 elif type == "numeric":
125 elif type == "regex":
126 value = re.compile(value)
127 elif type == "point":
128 value = value.split(",")
136 # end stuff to access the keys
139 def getList(self, string):
140 if re.search(r"(?<!\\)\;", string):
141 list = re.split(r"(?<!\\);", string)
142 elif re.search(r"(?<!\\)\|", string):
143 list = re.split(r"(?<!\\)\|", string)
144 elif re.search(r"(?<!\\),", string):
145 list = re.split(r"(?<!\\),", string)
152 def __getBoolean(self, boolean):
153 if boolean == 1 or boolean == "true" or boolean == "True":
155 elif boolean == 0 or boolean == "false" or boolean == "False":
160 def __addLocale(self, key, group=None):
161 "add locale to key according the current lc_messages"
164 group = self.defaultGroup
166 for lang in xdg.Locale.langs:
167 if self.content[group].has_key(key+'['+lang+']'):
168 return key+'['+lang+']'
172 # start validation stuff
173 def validate(self, report="All"):
174 "validate ... report = All / Warnings / Errors"
180 self.fileExtension = os.path.splitext(self.filename)[1]
182 # overwrite this for own checkings
186 for group in self.content:
187 self.checkGroup(group)
188 for key in self.content[group]:
189 self.checkKey(key, self.content[group][key], group)
190 # check if value is empty
191 if self.content[group][key] == "":
192 self.warnings.append("Value of Key '%s' is empty" % key)
194 # raise Warnings / Errors
197 if report == "All" or report == "Warnings":
198 for line in self.warnings:
201 if report == "All" or report == "Errors":
202 for line in self.errors:
206 raise ValidationError(msg, self.filename)
208 # check if group header is valid
209 def checkGroup(self, group):
212 # check if key is valid
213 def checkKey(self, key, value, group):
217 def checkValue(self, key, value, type="string", list=False):
219 values = self.getList(value)
225 code = self.checkString(value)
226 elif type == "boolean":
227 code = self.checkBoolean(value)
228 elif type == "numeric":
229 code = self.checkNumber(value)
230 elif type == "integer":
231 code = self.checkInteger(value)
232 elif type == "regex":
233 code = self.checkRegex(value)
234 elif type == "point":
235 code = self.checkPoint(value)
237 self.errors.append("'%s' is not a valid %s" % (value, type))
239 self.warnings.append("Value of key '%s' is deprecated" % key)
241 def checkExtras(self):
244 def checkBoolean(self, value):
245 # 1 or 0 : deprecated
246 if (value == "1" or value == "0"):
249 elif not (value == "true" or value == "false"):
252 def checkNumber(self, value):
259 def checkInteger(self, value):
266 def checkPoint(self, value):
267 if not re.match("^[0-9]+,[0-9]+$", value):
270 def checkString(self, value):
272 if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value:
275 def checkRegex(self, value):
282 def write(self, filename=None, trusted=False):
283 if not filename and not self.filename:
284 raise ParsingError("File not found", "")
287 self.filename = filename
289 filename = self.filename
291 if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)):
292 os.makedirs(os.path.dirname(filename))
294 fp = codecs.open(filename, 'w')
296 # An executable bit signifies that the desktop file is
297 # trusted, but then the file can be executed. Add hashbang to
298 # make sure that the file is opened by something that
299 # understands desktop files.
301 fp.write("#!/usr/bin/env xdg-open\n")
303 if self.defaultGroup:
304 fp.write("[%s]\n" % self.defaultGroup)
305 for (key, value) in self.content[self.defaultGroup].items():
306 fp.write("%s=%s\n" % (key, value))
308 for (name, group) in self.content.items():
309 if name != self.defaultGroup:
310 fp.write("[%s]\n" % name)
311 for (key, value) in group.items():
312 fp.write("%s=%s\n" % (key, value))
315 # Add executable bits to the file to show that it's trusted.
317 oldmode = os.stat(filename).st_mode
318 mode = oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
319 os.chmod(filename, mode)
323 def set(self, key, value, group=None, locale=False):
326 group = self.defaultGroup
328 if locale == True and len(xdg.Locale.langs) > 0:
329 key = key + "[" + xdg.Locale.langs[0] + "]"
332 if isinstance(value, unicode):
333 self.content[group][key] = value.encode("utf-8", "ignore")
335 self.content[group][key] = value
337 raise NoGroupError(group, self.filename)
339 self.tainted = (value == self.get(key, group))
341 def addGroup(self, group):
342 if self.hasGroup(group):
344 raise DuplicateGroupError(group, self.filename)
348 self.content[group] = {}
351 def removeGroup(self, group):
352 existed = group in self.content
354 del self.content[group]
358 raise NoGroupError(group, self.filename)
361 def removeKey(self, key, group=None, locales=True):
364 group = self.defaultGroup
368 for (name, value) in self.content[group].items():
369 if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key:
370 value = self.content[group][name]
371 del self.content[group][name]
372 value = self.content[group][key]
373 del self.content[group][key]
379 raise NoGroupError(group, self.filename)
381 raise NoKeyError(key, group, self.filename)
387 return self.content.keys()
389 def hasGroup(self, group):
390 if self.content.has_key(group):
395 def hasKey(self, key, group=None):
398 group = self.defaultGroup
400 if self.content[group].has_key(key):
405 def getFileName(self):