[master 1/2] Add simple_replace config file function

dashea installerbot-noreply at redhat.com
Fri Jul 17 17:57:40 UTC 2015


From: "Brian C. Lane" <bcl at redhat.com>

When modifying values in config files we want to preserve the rest of
the file as is. SimpleConfig does this for shell-like files using
upper-case KEYS, but other files (eg. INI style) don't use the same
format.

simple_replace takes a list of tuples (key, string) and replaces every
line in the file that starts with the key with the string. It is case
sensitive and the string must be the whole line including key.

eg.

keys=[("switch_1", "switch_1=On")]
simple_replace("/etc/control.conf", keys)

By default if a key isn't replaced in the existing file its string will
be appended to the end of the file. Set add=False to disable this behavior.

(cherry picked from commit f5acc57fda7994ec8168d2027adfa2049bdffbf6)
---
 pyanaconda/simpleconfig.py                  | 75 +++++++++++++++++++++--------
 tests/pyanaconda_tests/simpleconfig_test.py | 45 ++++++++++++++++-
 2 files changed, 100 insertions(+), 20 deletions(-)

diff --git a/pyanaconda/simpleconfig.py b/pyanaconda/simpleconfig.py
index f30258f..01cc20c 100644
--- a/pyanaconda/simpleconfig.py
+++ b/pyanaconda/simpleconfig.py
@@ -1,7 +1,7 @@
 #
 # simpleconifg.py - representation of a simple configuration file (sh-like)
 #
-# Copyright (C) 1999-2014 Red Hat, Inc.
+# Copyright (C) 1999-2015 Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -69,6 +69,24 @@ def find_comment(s):
     return None
 
 
+def write_tmpfile(filename, data):
+    # Create a temporary in the same directory as the target file to ensure
+    # the new file is on the same filesystem
+    tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False,
+            dir=os.path.dirname(filename) or '.', prefix="." + os.path.basename(filename))
+    tmpf.write(data)
+    tmpf.close()
+
+    # Change the permissions (currently 0600) to match the original file
+    if os.path.exists(filename):
+        m = os.stat(filename).st_mode
+    else:
+        m = 0o0644
+    eintr_retry_call(os.chmod, filename, m)
+
+    # Move the temporary file over the top of the original
+    os.rename(tmpf.name, filename)
+
 class SimpleConfigFile(object):
     """ Edit values in a configuration file without changing comments.
         Supports KEY=VALUE lines and ignores everything else.
@@ -111,24 +129,7 @@ def write(self, filename=None, use_tmp=True):
             return None
 
         if use_tmp:
-            filename = os.path.realpath(filename)
-
-            # Create a temporary in the same directory as the target file to ensure
-            # the new file is on the same filesystem
-            tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False,
-                    dir=os.path.dirname(filename), prefix="." + os.path.basename(filename))
-            tmpf.write(str(self))
-            tmpf.close()
-
-            # Change the permissions (currently 0600) to match the original file
-            if os.path.exists(filename):
-                m = os.stat(filename).st_mode
-            else:
-                m = 0o0644
-            eintr_retry_call(os.chmod, filename, m)
-
-            # Move the temporary file  over the top of the original
-            os.rename(tmpf.name, filename)
+            write_tmpfile(filename, str(self))
         else:
             # write directly to the file
             with open(filename, "w") as fobj:
@@ -205,3 +206,39 @@ def __str__(self):
                 s += self._kvpair(key)
 
         return s
+
+
+def simple_replace(fname, keys, add=True, add_comment="# Added by Anaconda"):
+    """ Replace lines in a file, optionally adding if missing.
+
+    :param str fname: Filename to operate on
+    :param list keys: List of (key, string) tuples to search and replace
+    :param bool add: When True add strings that were not replaced
+
+    This will read all the lines in a file, looking for ones that start
+    with keys and replacing the line with the associated string. The string
+    should be a COMPLETE replacement for the line, not just a value.
+
+    When add is True any keys that haven't been found will be appended
+    to the end of the file along with the add_comment.
+    """
+    # Helper to return the line or the first matching key's string
+    def _replace(l):
+        r = [s for k,s in keys if l.startswith(k)]
+        if r:
+            return r[0]
+        else:
+            return l
+
+    # Replace lines that match any of the keys
+    with open(fname, "r") as f:
+        lines = [_replace(l.strip()) for l in f]
+
+    # Add any strings that weren't already in the file
+    if add:
+        append = [s for k,s in keys if not any(l.startswith(k) for l in lines)]
+        if append:
+            lines += [add_comment]
+            lines += append
+
+    write_tmpfile(fname, "\n".join(lines)+"\n")
diff --git a/tests/pyanaconda_tests/simpleconfig_test.py b/tests/pyanaconda_tests/simpleconfig_test.py
index 4d1879a..4c4fa8a 100644
--- a/tests/pyanaconda_tests/simpleconfig_test.py
+++ b/tests/pyanaconda_tests/simpleconfig_test.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright (C) 2014  Red Hat, Inc.
+# Copyright (C) 2014-2015 Red Hat, Inc.
 #
 # This copyrighted material is made available to anyone wishing to use,
 # modify, copy, or redistribute it subject to the terms and conditions of
@@ -22,6 +22,7 @@
 # pylint: disable=interruptible-system-call
 
 from pyanaconda.simpleconfig import SimpleConfigFile
+from pyanaconda.simpleconfig import simple_replace
 from pyanaconda import simpleconfig
 import unittest
 import tempfile
@@ -189,3 +190,45 @@ def no_use_tmp_test(self):
 
             # Check that the original file handle points to the replaced contents
             self.assertEqual(testconfig.read(), 'KEY1=value2\n')
+
+class SimpleReplaceTests(unittest.TestCase):
+    TEST_CONFIG = """#SKIP=Skip this commented line
+BOOT=always
+"""
+
+    def replace_test(self):
+        with tempfile.NamedTemporaryFile(mode="wt") as testconfig:
+            testconfig.write(self.TEST_CONFIG)
+            testconfig.flush()
+
+            keys = [("BOOT", "BOOT=never")]
+            simple_replace(testconfig.name, keys)
+
+            config = SimpleConfigFile(testconfig.name)
+            config.read()
+            self.assertEqual(config.get("BOOT"), "never")
+
+    def append_test(self):
+        with tempfile.NamedTemporaryFile(mode="wt") as testconfig:
+            testconfig.write(self.TEST_CONFIG)
+            testconfig.flush()
+
+            keys = [("NEWKEY", "NEWKEY=froboz")]
+            simple_replace(testconfig.name, keys)
+
+            config = SimpleConfigFile(testconfig.name)
+            config.read()
+            self.assertEqual(config.get("NEWKEY"), "froboz")
+
+    def no_append_test(self):
+        with tempfile.NamedTemporaryFile(mode="wt") as testconfig:
+            testconfig.write(self.TEST_CONFIG)
+            testconfig.flush()
+
+            keys = [("BOOT", "BOOT=sometimes"), ("NEWKEY", "NEWKEY=froboz")]
+            simple_replace(testconfig.name, keys, add=False)
+
+            config = SimpleConfigFile(testconfig.name)
+            config.read()
+            self.assertEqual(config.get("BOOT"), "sometimes")
+            self.assertEqual(config.get("NEWKEY"), "")


-- 
To view this commit on github, visit https://github.com/rhinstaller/anaconda/commit/ab553fb9323f1f9fd88d79d09f706b8c5710b0aa


More information about the anaconda-patches mailing list