diff options
author | Brian C. Lane <bcl@redhat.com> | 2012-08-07 17:32:28 -0700 |
---|---|---|
committer | Brian C. Lane <bcl@redhat.com> | 2012-08-09 08:22:27 -0700 |
commit | 16a7c6e7af3df4816323a007f785155d307869bf (patch) | |
tree | 17c754675b3e20fa982a9710e247f601db86a374 | |
parent | e7f28ffbb3f85bf72ac828e5838d99714ccc997f (diff) | |
download | anaconda-16a7c6e7af3df4816323a007f785155d307869bf.tar.gz anaconda-16a7c6e7af3df4816323a007f785155d307869bf.tar.xz anaconda-16a7c6e7af3df4816323a007f785155d307869bf.zip |
simpleconfig: rewrite to better support commented config files
This takes some of my code, and some of wwoods' code and rewrites
SimpleConfigFile to support a wider range of uses. It can now
preserve comments and whitespaces in config files and can quote
(or not) values when reading and writing them.
-rw-r--r-- | pyanaconda/simpleconfig.py | 222 |
1 files changed, 143 insertions, 79 deletions
diff --git a/pyanaconda/simpleconfig.py b/pyanaconda/simpleconfig.py index 4bd665ac8..db0dea22a 100644 --- a/pyanaconda/simpleconfig.py +++ b/pyanaconda/simpleconfig.py @@ -3,8 +3,10 @@ # # Matt Wilson <msw@redhat.com> # Jeremy Katz <katzj@redhat.com> +# Will Woods <wwoods@redhat.com> +# Brian C. Lane <bcl@redhat.com> # -# Copyright 1999-2002 Red Hat, Inc. +# Copyright 1999-2012 Red Hat, Inc. # # This software may be freely redistributed under the terms of the GNU # library public license. @@ -13,81 +15,157 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # - -import string import os import shutil +import string +import shlex +from pipes import _safechars +import tempfile # use our own ASCII only uppercase function to avoid locale issues # not going to be fast but not important -def uppercase_ASCII_string(str): +def uppercase_ASCII_string(s): newstr = "" - for i in range(0,len(str)): - if str[i] in string.lowercase: - newstr += chr(ord(str[i])-32) - else: - newstr += str[i] - + for c in s: + if c in string.lowercase: + newstr += chr(ord(c)-32) + else: + newstr += c return newstr -class SimpleConfigFile: - def __str__ (self): - s = "" - keys = self.info.keys () - keys.sort () - for key in keys: - # FIXME - use proper escaping - if type (self.info[key]) == type(""): - s = s + key + "=\"" + self.info[key] + "\"\n" - return s +def unquote(s): + return ' '.join(shlex.split(s)) - def __init__ (self): +def quote(s, always=False): + """ If always is set it returns a quoted value + """ + if not always: + for c in s: + if c not in _safechars: + break + else: + return s + return '"'+s.replace('"', '\\"')+'"' + +class SimpleConfigFile(object): + """ Edit values in a configuration file without changing comments. + Supports KEY=VALUE lines and ignores everything else. + Supports adding new keys. + Supports deleting keys. + Preserves comment, blank lines and comments on KEY lines + Does not support duplicate key entries. + """ + def __init__(self, filename=None, read_unquote=True, write_quote=True, + always_quote=False): + self.filename = filename + self.read_unquote = read_unquote + self.write_quote = write_quote + self.always_quote = always_quote + self.reset() + + def reset(self): + self._lines = [] self.info = {} - def write(self, file): - f = open(file, "w") - f.write(self.__str__()) - f.close() - - def read(self, file): - if not os.access(file, os.R_OK): - return - - f = open(file, "r") - lines = f.readlines() - f.close() - - for line in lines: - fields = line[:-1].split('=', 2) - if len(fields) < 2: - # how am I supposed to know what to do here? - continue - key = uppercase_ASCII_string(fields[0]) - value = fields[1] - # XXX hack - value = value.replace('"', '') - value = value.replace("'", '') - self.info[key] = value - - def set (self, *args): - for (key, data) in args: - self.info[uppercase_ASCII_string(key)] = data - - def unset (self, *keys): - for key in keys: - key = uppercase_ASCII_string(key) - if self.info.has_key (key): + def read(self, filename=None): + """ passing filename will override the filename passed to init. + + save the lines into self._lines and the key/value pairs into + self.info + """ + filename = filename or self.filename + with open(filename) as f: + for line in f: + self._lines.append(line) + key, value = self._parseline(line) + if key: + self.info[key] = value + + def write(self, filename=None): + """ passing filename will override the filename passed to init. + """ + filename = filename or self.filename + if not filename: + return None + + tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False) + tmpf.write(str(self)) + tmpf.close() + + # Move the temporary file (with 0600 permissions) over the top of the + # original and preserve the original's permissions + filename = os.path.realpath(filename) + m = os.stat(filename) + shutil.move(tmpf.name, filename) + os.chmod(filename, m.st_mode) + + def set(self, *args): + for key, value in args: + self.info[uppercase_ASCII_string(key)] = value + + def unset(self, *keys): + for key in [uppercase_ASCII_string(k) for k in keys]: + if key in self.info: del self.info[key] - def get (self, key): - key = uppercase_ASCII_string(key) - return self.info.get(key, "") + def get(self, key): + return self.info.get(uppercase_ASCII_string(key), "") + + def _parseline(self, line): + """ parse a line into a key, value pair + Handle comments and optionally unquote quoted strings + Returns (key, value) or (None, None) + key is always UPPERCASE + """ + s = line.strip() + if '#' in s: + s = s[:s.find('#')] # remove from comment to EOL + s = s.strip() # and any unnecessary whitespace + key, eq, val = s.partition('=') + if self.read_unquote: + val = unquote(val) + if key != '' and eq == '=': + return (uppercase_ASCII_string(key), val) + else: + return (None, None) + def _kvpair(self, key, comment=""): + value = self.info[key] + if self.write_quote or self.always_quote: + value = quote(value, self.always_quote) + return key + '=' + value + comment + "\n" + + def __str__(self): + """ Return the file that was read, replacing existing keys with new values + removing keys that have been deleted and adding new keys. + """ + oldkeys = [] + s = "" + for line in self._lines: + key, val = self._parseline(line) + if key is None: + s += line + else: + if key not in self.info: + continue + oldkeys.append(key) + if "#" in line: + comment = " " + line[line.find("#"):] + else: + comment = "" + s += self._kvpair(key, comment) + + # Add new keys + for key in self.info: + if key not in oldkeys: + s += self._kvpair(key) + + return s -class IfcfgFile(SimpleConfigFile): +class IfcfgFile(SimpleConfigFile): def __init__(self, dir, iface): - SimpleConfigFile.__init__(self) + SimpleConfigFile.__init__(self, always_quote=True) self.iface = iface self.dir = dir @@ -96,35 +174,21 @@ class IfcfgFile(SimpleConfigFile): return os.path.join(self.dir, "ifcfg-%s" % self.iface) def clear(self): - self.info = {} + SimpleConfigFile.reset(self) def read(self): - """Reads values from ifcfg file. + """ Reads values from ifcfg file. - returns: number of values read + returns: number of values read """ - f = open(self.path, "r") - lines = f.readlines() - f.close() - - for line in lines: - line = line.strip() - if line.startswith("#") or line == '': - continue - fields = line.split('=', 1) - key = uppercase_ASCII_string(fields[0]) - value = fields[1] - # XXX hack - value = value.replace('"', '') - value = value.replace("'", '') - self.info[key] = value - + SimpleConfigFile.read(self, self.path) return len(self.info) # ifcfg-rh is using inotify IN_CLOSE_WRITE event # so we don't use temporary file for new configuration. def write(self, dir=None): - """Writes values into ifcfg file.""" + """ Writes values into ifcfg file. + """ if not dir: path = self.path |