vim line
[drlaunch] / src / xdg / IniFile.py
1 """
2 Base Class for DesktopEntry, IconTheme and IconData
3 """
4
5 import re, os, stat, codecs
6 from Exceptions import *
7 import xdg.Locale
8
9 class IniFile:
10     defaultGroup = ''
11     fileExtension = ''
12
13     filename = ''
14
15     tainted = False
16
17     def __init__(self, filename=None):
18         self.content = dict()
19         if filename:
20             self.parse(filename)
21
22     def __cmp__(self, other):
23         return cmp(self.content, other.content)
24
25     def parse(self, filename, headers=None):
26         # for performance reasons
27         content = self.content
28
29         if not os.path.isfile(filename):
30             raise ParsingError("File not found", filename)
31
32         try:
33             fd = file(filename, 'r')
34         except IOError, e:
35             if debug:
36                 raise e
37             else:
38                 return
39
40         # parse file
41         for line in fd:
42             line = line.strip()
43             # empty line
44             if not line:
45                 continue
46             # comment
47             elif line[0] == '#':
48                 continue
49             # new group
50             elif line[0] == '[':
51                 currentGroup = line.lstrip("[").rstrip("]")
52                 if debug and self.hasGroup(currentGroup):
53                     raise DuplicateGroupError(currentGroup, filename)
54                 else:
55                     content[currentGroup] = {}
56             # key
57             else:
58                 index = line.find("=")
59                 key = line[0:index].strip()
60                 value = line[index+1:].strip()
61                 try:
62                     if debug and self.hasKey(key, currentGroup):
63                         raise DuplicateKeyError(key, currentGroup, filename)
64                     else:
65                         content[currentGroup][key] = value
66                 except (IndexError, UnboundLocalError):
67                     raise ParsingError("Parsing error on key, group missing", filename)
68
69         fd.close()
70
71         self.filename = filename
72         self.tainted = False
73
74         # check header
75         if headers:
76             for header in headers:
77                 if content.has_key(header):
78                     self.defaultGroup = header
79                     break
80             else:
81                 raise ParsingError("[%s]-Header missing" % headers[0], filename)
82
83     # start stuff to access the keys
84     def get(self, key, group=None, locale=False, type="string", list=False):
85         # set default group
86         if not group:
87             group = self.defaultGroup
88
89         # return key (with locale)
90         if self.content.has_key(group) and self.content[group].has_key(key):
91             if locale:
92                 value = self.content[group][self.__addLocale(key, group)]
93             else:
94                 value = self.content[group][key]
95         else:
96             if debug:
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)
101             else:
102                 value = ""
103
104         if list == True:
105             values = self.getList(value)
106             result = []
107         else:
108             values = [value]
109
110         for value in values:
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":
116                 try:
117                     value = int(value)
118                 except ValueError:
119                     value = 0
120             elif type == "numeric":
121                 try:
122                     value = float(value)
123                 except ValueError:
124                     value = 0.0
125             elif type == "regex":
126                 value = re.compile(value)
127             elif type == "point":
128                 value = value.split(",")
129
130             if list == True:
131                 result.append(value)
132             else:
133                 result = value
134
135         return result
136     # end stuff to access the keys
137
138     # start subget
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)
146         else:
147             list = [string]
148         if list[-1] == "":
149             list.pop()
150         return list
151
152     def __getBoolean(self, boolean):
153         if boolean == 1 or boolean == "true" or boolean == "True":
154             return True
155         elif boolean == 0 or boolean == "false" or boolean == "False":
156             return False
157         return False
158     # end subget
159
160     def __addLocale(self, key, group=None):
161         "add locale to key according the current lc_messages"
162         # set default group
163         if not group:
164             group = self.defaultGroup
165
166         for lang in xdg.Locale.langs:
167             if self.content[group].has_key(key+'['+lang+']'):
168                 return key+'['+lang+']'
169
170         return key
171
172     # start validation stuff
173     def validate(self, report="All"):
174         "validate ... report = All / Warnings / Errors"
175
176         self.warnings = []
177         self.errors = []
178
179         # get file extension
180         self.fileExtension = os.path.splitext(self.filename)[1]
181
182         # overwrite this for own checkings
183         self.checkExtras()
184
185         # check all keys
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)
193
194         # raise Warnings / Errors
195         msg = ""
196
197         if report == "All" or report == "Warnings":
198             for line in self.warnings:
199                 msg += "\n- " + line
200
201         if report == "All" or report == "Errors":
202             for line in self.errors:
203                 msg += "\n- " + line
204
205         if msg:
206             raise ValidationError(msg, self.filename)
207
208     # check if group header is valid
209     def checkGroup(self, group):
210         pass
211
212     # check if key is valid
213     def checkKey(self, key, value, group):
214         pass
215
216     # check random stuff
217     def checkValue(self, key, value, type="string", list=False):
218         if list == True:
219             values = self.getList(value)
220         else:
221             values = [value]
222
223         for value in values:
224             if type == "string":
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)
236             if code == 1:
237                 self.errors.append("'%s' is not a valid %s" % (value, type))
238             elif code == 2:
239                 self.warnings.append("Value of key '%s' is deprecated" % key)
240
241     def checkExtras(self):
242         pass
243
244     def checkBoolean(self, value):
245         # 1 or 0 : deprecated
246         if (value == "1" or value == "0"):
247             return 2
248         # true or false: ok
249         elif not (value == "true" or value == "false"):
250             return 1
251
252     def checkNumber(self, value):
253         # float() ValueError
254         try:
255             float(value)
256         except:
257             return 1
258
259     def checkInteger(self, value):
260         # int() ValueError
261         try:
262             int(value)
263         except:
264             return 1
265
266     def checkPoint(self, value):
267         if not re.match("^[0-9]+,[0-9]+$", value):
268             return 1
269
270     def checkString(self, value):
271         # convert to ascii
272         if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value:
273             return 1
274
275     def checkRegex(self, value):
276         try:
277             re.compile(value)
278         except:
279             return 1
280
281     # write support
282     def write(self, filename=None, trusted=False):
283         if not filename and not self.filename:
284             raise ParsingError("File not found", "")
285
286         if filename:
287             self.filename = filename
288         else:
289             filename = self.filename
290
291         if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)):
292             os.makedirs(os.path.dirname(filename))
293
294         fp = codecs.open(filename, 'w')
295
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.
300         if trusted:
301             fp.write("#!/usr/bin/env xdg-open\n")
302
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))
307             fp.write("\n")
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))
313                 fp.write("\n")
314
315         # Add executable bits to the file to show that it's trusted.
316         if 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)
320
321         self.tainted = False
322
323     def set(self, key, value, group=None, locale=False):
324         # set default group
325         if not group:
326             group = self.defaultGroup
327
328         if locale == True and len(xdg.Locale.langs) > 0:
329             key = key + "[" + xdg.Locale.langs[0] + "]"
330
331         try:
332             if isinstance(value, unicode):
333                 self.content[group][key] = value.encode("utf-8", "ignore")
334             else:
335                 self.content[group][key] = value
336         except KeyError:
337             raise NoGroupError(group, self.filename)
338             
339         self.tainted = (value == self.get(key, group))
340
341     def addGroup(self, group):
342         if self.hasGroup(group):
343             if debug:
344                 raise DuplicateGroupError(group, self.filename)
345             else:
346                 pass
347         else:
348             self.content[group] = {}
349             self.tainted = True
350
351     def removeGroup(self, group):
352         existed = group in self.content
353         if existed:
354             del self.content[group]
355             self.tainted = True
356         else:
357             if debug:
358                 raise NoGroupError(group, self.filename)
359         return existed
360
361     def removeKey(self, key, group=None, locales=True):
362         # set default group
363         if not group:
364             group = self.defaultGroup
365
366         try:
367             if locales:
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]
374             self.tainted = True
375             return value
376         except KeyError, e:
377             if debug:
378                 if e == group:
379                     raise NoGroupError(group, self.filename)
380                 else:
381                     raise NoKeyError(key, group, self.filename)
382             else:
383                 return ""
384
385     # misc
386     def groups(self):
387         return self.content.keys()
388
389     def hasGroup(self, group):
390         if self.content.has_key(group):
391             return True
392         else:
393             return False
394
395     def hasKey(self, key, group=None):
396         # set default group
397         if not group:
398             group = self.defaultGroup
399
400         if self.content[group].has_key(key):
401             return True
402         else:
403             return False
404
405     def getFileName(self):
406         return self.filename