1
# -*- coding: utf-8 -*-
3
# File: portato/config_parser.py
4
# This file is part of the Portato-Project, a graphical portage-frontend.
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.
11
# Written by René 'Necoro' Neumann <necoro@necoro.net>
13
"""A simple parser for configuration files in ini-style.
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.
19
@var DELIMITER: list of delimiters allowed
20
@type DELIMITER: string[]
22
@var COMMENT: comment marks allowed
23
@type COMMENT: string[]
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.
32
from __future__ import absolute_import
35
from gettext import lgettext as _
37
from .helper import debug
39
DELIMITER = ["=", ":"]
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*")
49
"""Class defining a value of a key.
51
@ivar value: The specific value. This is a property.
52
@type value: arbitrary
53
@ivar line: The line in the config file.
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"""
62
def __init__ (self, value, line, bool = None):
65
@param value: the value
67
@param line: the line in the config file
69
@param bool: The boolean meaning of the value. Set this to C{None} if this is not a boolean.
70
@type bool: boolean"""
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
79
def set (self, value):
80
"""Sets the value to a new one.
82
@param value: new value
83
@type value: string"""
93
"""Returns the actual value.
95
@returns: the actual value
101
"""Returns whether the actual value has a boolean meaning.
103
@returns: True if the actual value can be interpreted as a boolean
106
return (self.boolean != None)
109
return str(self.__value)
112
return self.__str__()
114
value = property(get,set)
119
@cvar true_false: A mapping from the truth values to their opposits.
120
@type true_false: string -> string
122
@ivar file: the file to scan
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)"""
131
# generates the complementary true-false-pairs
139
true_false.update(zip(true_false.values(), true_false.keys()))
141
def __init__ (self, file):
144
@param file: the configuration file to open
145
@type file: string"""
150
def __initialize (self):
151
"""Private method which initializes our dictionaries."""
153
self.vars = {"MAIN": {}}
154
self.cache = None # file cache
155
self.pos = {} # stores the positions of the matches
157
def _invert (self, val):
158
"""Invertes a given boolean.
160
@param val: value to invert
162
@returns: inverted value
165
return self.true_false[val.lower()]
168
"""Parses the file."""
171
file = open(self.file, "r")
172
self.cache = file.readlines()
175
# implicit first section is main
178
for line in self.cache:
182
if not ls: continue # empty
183
if ls[0] in COMMENT: continue # comment
186
match = SECTION.search(line)
188
sec = match.group(1).upper()
194
# look for an expression
195
match = EXPRESSION.search(line)
199
# find the boolean value
203
elif FALSE.match(val):
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)
213
def get (self, key, section = "MAIN"):
214
"""Returns the value of a given key in a section.
218
@param section: the section
219
@type section: string
224
@raises KeyError: if section or key could not be found"""
226
section = section.upper()
228
return self.vars[section][key].value
230
def get_boolean (self, key, section = "MAIN"):
231
"""Returns the boolean value of a given key in a section.
235
@param section: the section
236
@type section: string
241
@raises KeyError: if section or key could not be found
242
@raises ValueError: if key does not have a boolean value"""
244
section = section.upper()
247
val = self.vars[section][key]
252
raise ValueError, "\"%s\" is not a boolean." % key
254
def set (self, key, value = "", section = "MAIN"):
255
"""Sets a new value of a given key in a section.
259
@param value: the new value
261
@param section: the section
262
@type section: string
264
@raises KeyError: if section or key could not be found"""
266
section = section.upper()
269
self.vars[section][key].value = value
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).
277
@param value: the new value
279
@param section: the section
280
@type section: string
282
@raises KeyError: if section or key could not be found
283
@raises ValueError: if the old/new value is not a boolean"""
285
section = section.upper()
288
if not isinstance(value, bool):
289
raise ValueError, "Passed value must be a boolean."
291
val = self.vars[section][key]
293
if value is not val.boolean:
295
val.value = self._invert(val.value)
297
raise ValueError, "\"%s\" is not a boolean." % key
302
for sec in self.vars:
303
for key in self.vars[sec]:
304
val = self.vars[sec][key]
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
312
f = open(self.file, "w")
313
f.writelines(self.cache)