~necoro/portato/0.8.6.2

« back to all changes in this revision

Viewing changes to portato/config_parser.py

  • Committer: Necoro
  • Date: 2008-03-24 22:05:07 UTC
  • Revision ID: svn-v3-trunk0:707e4503-1661-43cb-8fc9-483266be2d6d:tags%2F0.8.6.2:686
Moved versions back to tags

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# File: portato/config_parser.py
 
4
# This file is part of the Portato-Project, a graphical portage-frontend.
 
5
#
 
6
# Copyright (C) 2006-2007 René 'Necoro' Neumann
 
7
# This is free software.  You may redistribute copies of it under the terms of
 
8
# the GNU General Public License version 2.
 
9
# There is NO WARRANTY, to the extent permitted by law.
 
10
#
 
11
# Written by René 'Necoro' Neumann <necoro@necoro.net>
 
12
 
 
13
"""A simple parser for configuration files in ini-style.
 
14
 
 
15
The main difference to other simple ini-parsers is, that it does not
 
16
write the whole structure into the file, but only the changed values.
 
17
Thus it keeps comments and structuring of the file.
 
18
 
 
19
@var DELIMITER: list of delimiters allowed
 
20
@type DELIMITER: string[]
 
21
 
 
22
@var COMMENT: comment marks allowed
 
23
@type COMMENT: string[]
 
24
 
 
25
@var TRUE: Regular expression for all TRUE values allowed.
 
26
Currently supported are the values (case insensitive): true, 1, on, wahr, ja, yes.
 
27
@var FALSE: Regular expression for all FALSE values allowed.
 
28
Currently supported are the values (case insensitive): false, 0, off, falsch, nein, no,
 
29
@var SECTION: Regular expression allowing the recognition of a section header.
 
30
@var EXPRESSION: Regular expression defining a normal option-value pair.
 
31
"""
 
32
from __future__ import absolute_import
 
33
 
 
34
import re
 
35
from gettext import lgettext as _
 
36
 
 
37
from .helper import debug
 
38
 
 
39
DELIMITER = ["=", ":"]
 
40
COMMENT = [";","#"]
 
41
 
 
42
# precompiled expressions
 
43
TRUE = re.compile("((true)|(1)|(on)|(wahr)|(ja)|(yes))", re.I)
 
44
FALSE = re.compile("((false)|(0)|(off)|(falsch)|(nein)|(no))", re.I)
 
45
SECTION = re.compile("\s*\[(\w+)\]\s*")
 
46
EXPRESSION = re.compile(r"\s*(\w+)\s*[:=]\s*(.*)\s*")
 
47
 
 
48
class Value (object):
 
49
        """Class defining a value of a key.
 
50
        
 
51
        @ivar value: The specific value. This is a property.
 
52
        @type value: arbitrary
 
53
        @ivar line: The line in the config file.
 
54
        @type line: int
 
55
        @ivar boolean: The boolean meaning of this value. Set this to C{None} if this is not a boolean.
 
56
        @type boolean: boolean
 
57
        @ivar changed: Set to True if the value has been changed.
 
58
        @type changed: boolean
 
59
        @ivar old: The old value.
 
60
        @type old: arbitrary"""
 
61
 
 
62
        def __init__ (self, value, line, bool = None):
 
63
                """Constructor.
 
64
 
 
65
                @param value: the value
 
66
                @type value: string
 
67
                @param line: the line in the config file
 
68
                @type line: int
 
69
                @param bool: The boolean meaning of the value. Set this to C{None} if this is not a boolean.
 
70
                @type bool: boolean"""
 
71
 
 
72
                self.__value = value
 
73
                self.line = line
 
74
                self.boolean = bool
 
75
                
 
76
                self.changed = False # true if we changed it
 
77
                self.old = value # keep the original one ... so if we change it back to this one, we do not have to write
 
78
 
 
79
        def set (self, value):
 
80
                """Sets the value to a new one.
 
81
                
 
82
                @param value: new value
 
83
                @type value: string"""
 
84
 
 
85
                self.__value = value
 
86
                
 
87
                if value != self.old:
 
88
                        self.changed = True
 
89
                else:
 
90
                        self.changed = False
 
91
 
 
92
        def get (self):
 
93
                """Returns the actual value.
 
94
                
 
95
                @returns: the actual value
 
96
                @rtype: string"""
 
97
 
 
98
                return self.__value
 
99
        
 
100
        def is_bool (self):
 
101
                """Returns whether the actual value has a boolean meaning.
 
102
                
 
103
                @returns: True if the actual value can be interpreted as a boolean
 
104
                @rtype: boolean"""
 
105
 
 
106
                return (self.boolean != None)
 
107
 
 
108
        def __str__ (self):
 
109
                return str(self.__value)
 
110
 
 
111
        def __repr__ (self):
 
112
                return self.__str__()
 
113
        
 
114
        value = property(get,set)
 
115
        
 
116
class ConfigParser:
 
117
        """The parser class.
 
118
 
 
119
        @cvar true_false: A mapping from the truth values to their opposits.
 
120
        @type true_false: string -> string
 
121
        
 
122
        @ivar file: the file to scan
 
123
        @type file: string
 
124
        @ivar cache: caches the content of the file
 
125
        @type cache: string[]
 
126
        @ivar vars: the found options grouped by section
 
127
        @type vars: string -> (string -> L{Value})
 
128
        @ivar pos: the positions of the values grouped by lineno
 
129
        @type pos: int -> (int, int)"""
 
130
 
 
131
        # generates the complementary true-false-pairs
 
132
        true_false = {
 
133
                                "true"  : "false",
 
134
                                "1"             : "0",
 
135
                                "on"    : "off",
 
136
                                "yes"   : "no",
 
137
                                "ja"    : "nein",
 
138
                                "wahr"  : "falsch"}
 
139
        true_false.update(zip(true_false.values(), true_false.keys()))
 
140
 
 
141
        def __init__ (self, file):
 
142
                """Constructor.
 
143
 
 
144
                @param file: the configuration file to open
 
145
                @type file: string"""
 
146
 
 
147
                self.file = file
 
148
                self.__initialize()
 
149
 
 
150
        def __initialize (self):
 
151
                """Private method which initializes our dictionaries."""
 
152
 
 
153
                self.vars = {"MAIN": {}}
 
154
                self.cache = None # file cache
 
155
                self.pos = {} # stores the positions of the matches
 
156
 
 
157
        def _invert (self, val):
 
158
                """Invertes a given boolean.
 
159
 
 
160
                @param val: value to invert
 
161
                @type val: string
 
162
                @returns: inverted value
 
163
                @rtype: string"""
 
164
 
 
165
                return self.true_false[val.lower()]
 
166
 
 
167
        def parse (self):
 
168
                """Parses the file."""
 
169
 
 
170
                # read into cache
 
171
                file = open(self.file, "r")
 
172
                self.cache = file.readlines()
 
173
                file.close()
 
174
 
 
175
                # implicit first section is main
 
176
                section = "MAIN"
 
177
                count = -1
 
178
                for line in self.cache:
 
179
                        count += 1
 
180
 
 
181
                        ls = line.strip()
 
182
                        if not ls: continue # empty
 
183
                        if ls[0] in COMMENT: continue # comment
 
184
                        
 
185
                        # look for a section
 
186
                        match = SECTION.search(line)
 
187
                        if match:
 
188
                                sec = match.group(1).upper()
 
189
                                if sec != section:
 
190
                                        self.vars[sec] = {}
 
191
                                        section = sec
 
192
                                continue
 
193
 
 
194
                        # look for an expression
 
195
                        match = EXPRESSION.search(line)
 
196
                        if match: 
 
197
                                val = match.group(2)
 
198
                                
 
199
                                # find the boolean value
 
200
                                bool = None
 
201
                                if TRUE.match(val):
 
202
                                        bool = True
 
203
                                elif FALSE.match(val):
 
204
                                        bool = False
 
205
                                
 
206
                                # insert
 
207
                                key = match.group(1).lower()
 
208
                                self.vars[section][key] = Value(val, count, bool = bool)
 
209
                                self.pos[count] = match.span(2)
 
210
                        else: # neither comment nor empty nor expression nor section => error
 
211
                                error(_("Unrecognized line in configuration: %s"), line)
 
212
 
 
213
        def get (self, key, section = "MAIN"):
 
214
                """Returns the value of a given key in a section.
 
215
 
 
216
                @param key: the key
 
217
                @type key: string
 
218
                @param section: the section
 
219
                @type section: string
 
220
                
 
221
                @returns: value
 
222
                @rtype: string
 
223
                
 
224
                @raises KeyError: if section or key could not be found"""
 
225
 
 
226
                section = section.upper()
 
227
                key = key.lower()
 
228
                return self.vars[section][key].value
 
229
 
 
230
        def get_boolean (self, key, section = "MAIN"):
 
231
                """Returns the boolean value of a given key in a section.
 
232
 
 
233
                @param key: the key
 
234
                @type key: string
 
235
                @param section: the section
 
236
                @type section: string
 
237
                
 
238
                @returns: value
 
239
                @rtype: boolean
 
240
                
 
241
                @raises KeyError: if section or key could not be found
 
242
                @raises ValueError: if key does not have a boolean value"""
 
243
                
 
244
                section = section.upper()
 
245
                key = key.lower()
 
246
 
 
247
                val = self.vars[section][key]
 
248
 
 
249
                if val.is_bool():
 
250
                        return val.boolean
 
251
 
 
252
                raise ValueError, "\"%s\" is not a boolean." % key
 
253
 
 
254
        def set (self, key, value = "", section = "MAIN"):
 
255
                """Sets a new value of a given key in a section.
 
256
 
 
257
                @param key: the key
 
258
                @type key: string
 
259
                @param value: the new value
 
260
                @type value: string
 
261
                @param section: the section
 
262
                @type section: string
 
263
                
 
264
                @raises KeyError: if section or key could not be found"""
 
265
                
 
266
                section = section.upper()
 
267
                key = key.lower()
 
268
 
 
269
                self.vars[section][key].value = value
 
270
 
 
271
        def set_boolean (self, key, value, section = "MAIN"):
 
272
                """Sets a new boolean value of a given key in a section.
 
273
                Therefore it invertes the string representation of the boolean (in lowercase).
 
274
 
 
275
                @param key: the key
 
276
                @type key: string
 
277
                @param value: the new value
 
278
                @type value: boolean
 
279
                @param section: the section
 
280
                @type section: string
 
281
                
 
282
                @raises KeyError: if section or key could not be found
 
283
                @raises ValueError: if the old/new value is not a boolean"""
 
284
                
 
285
                section = section.upper()
 
286
                key = key.lower()
 
287
                
 
288
                if not isinstance(value, bool):
 
289
                        raise ValueError, "Passed value must be a boolean."
 
290
 
 
291
                val = self.vars[section][key]
 
292
                if val.is_bool():
 
293
                        if value is not val.boolean:
 
294
                                val.boolean = value
 
295
                                val.value = self._invert(val.value)
 
296
                else:
 
297
                        raise ValueError, "\"%s\" is not a boolean." % key
 
298
 
 
299
        def write (self):
 
300
                """Writes file."""
 
301
 
 
302
                for sec in self.vars:
 
303
                        for key in self.vars[sec]:
 
304
                                val = self.vars[sec][key]
 
305
                                if val.changed:
 
306
                                        part1 = self.cache[val.line][:self.pos[val.line][0]]    # key+DELIMITER
 
307
                                        part2 = val.value                                                                               # value
 
308
                                        part3 = self.cache[val.line][self.pos[val.line][1]:]    # everything behind the vale (\n in normal cases)
 
309
                                        self.cache[val.line] = part1 + part2 + part3
 
310
                
 
311
                # write
 
312
                f = open(self.file, "w")
 
313
                f.writelines(self.cache)
 
314
                f.close()
 
315
 
 
316
                # reload
 
317
                self.__initialize()
 
318
                self.parse()