diff --git a/devstack/dsconf.py b/devstack/dsconf.py index b31e221..f9ba066 100644 --- a/devstack/dsconf.py +++ b/devstack/dsconf.py @@ -27,6 +27,21 @@ class IniFile(object): def __init__(self, fname): self.fname = fname + def has(self, section, name): + """Returns True if section has a key that is name""" + + current_section = "" + with open(self.fname) as reader: + for line in reader.readlines(): + m = re.match("\[([^\[\]]+)\]", line) + if m: + current_section = m.group(1) + if current_section == section: + if re.match("%s\s*\=" % name, line): + return True + return False + + def add(self, section, name, value): """add a key / value to an ini file in a section. @@ -49,9 +64,7 @@ class IniFile(object): writer.write("[%s]\n" % section) writer.write("%s = %s\n" % (name, value)) - def remove(self, section, name): - """remove a key / value from an ini file in a section.""" - + def _at_existing_key(self, section, name, func): temp = tempfile.NamedTemporaryFile(mode='r') shutil.copyfile(self.fname, temp.name) current_section = "" @@ -63,8 +76,32 @@ class IniFile(object): current_section = m.group(1) if current_section == section: if re.match("%s\s*\=" % name, line): - continue + # run function with writer and found line + func(writer, line) else: writer.write(line) else: writer.write(line) + + + def remove(self, section, name): + """remove a key / value from an ini file in a section.""" + def _do_remove(writer, line): + pass + + self._at_existing_key(section, name, _do_remove) + + + def comment(self, section, name): + def _do_comment(writer, line): + writer.write("# %s" % line) + + self._at_existing_key(section, name, _do_comment) + + def set(self, section, name, value): + def _do_set(writer, line): + writer.write("%s = %s\n" % (name, value)) + if self.has(section, name): + self._at_existing_key(section, name, _do_set) + else: + self.add(section, name, value) diff --git a/devstack/tests/test_ini_comment.py b/devstack/tests/test_ini_comment.py new file mode 100644 index 0000000..db1a28a --- /dev/null +++ b/devstack/tests/test_ini_comment.py @@ -0,0 +1,101 @@ +# Copyright 2017 IBM +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Implementation of ini add / remove for devstack. We don't use the +# python ConfigFile parser because that ends up rewriting the entire +# file and doesn't ensure comments remain. + +import fixtures +import testtools + +from devstack import dsconf + + +BASIC = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +s = t +""" + +RESULT1 = """[default] +# a = b +c = d +[second] +e = f +g = h +[new] +s = t +""" + +RESULT2 = """[default] +a = b +c = d +[second] +e = f +# g = h +[new] +s = t +""" + +RESULT3 = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +# s = t +""" + + +class TestIniComment(testtools.TestCase): + + def setUp(self): + super(TestIniComment, self).setUp() + self._path = self.useFixture(fixtures.TempDir()).path + self._path += "/test.ini" + with open(self._path, "w") as f: + f.write(BASIC) + + def test_comment_ini_default(self): + conf = dsconf.IniFile(self._path) + conf.comment("default", "a") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT1) + + def test_comment_ini_second(self): + conf = dsconf.IniFile(self._path) + conf.comment("second", "g") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT2) + + def test_comment_ini_new(self): + conf = dsconf.IniFile(self._path) + conf.comment("new", "s") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT3) + + def test_comment_ini_none(self): + conf = dsconf.IniFile(self._path) + conf.comment("default", "s") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, BASIC) diff --git a/devstack/tests/test_ini_set.py b/devstack/tests/test_ini_set.py new file mode 100644 index 0000000..383bfc7 --- /dev/null +++ b/devstack/tests/test_ini_set.py @@ -0,0 +1,112 @@ +# Copyright 2017 IBM +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# Implementation of ini add / remove for devstack. We don't use the +# python ConfigFile parser because that ends up rewriting the entire +# file and doesn't ensure comments remain. + +import fixtures +import testtools + +from devstack import dsconf + + +BASIC = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +s = t +""" + +RESULT1 = """[default] +a = 2 +c = d +[second] +e = f +g = h +[new] +s = t +""" + +RESULT2 = """[default] +a = b +c = d +[second] +e = f +g = 2 +[new] +s = t +""" + +RESULT3 = """[default] +a = b +c = d +[second] +e = f +g = h +[new] +s = 2 +""" + +RESULT4 = """[default] +s = 2 +a = b +c = d +[second] +e = f +g = h +[new] +s = t +""" + + +class TestIniSet(testtools.TestCase): + + def setUp(self): + super(TestIniSet, self).setUp() + self._path = self.useFixture(fixtures.TempDir()).path + self._path += "/test.ini" + with open(self._path, "w") as f: + f.write(BASIC) + + def test_set_ini_default(self): + conf = dsconf.IniFile(self._path) + conf.set("default", "a", "2") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT1) + + def test_set_ini_second(self): + conf = dsconf.IniFile(self._path) + conf.set("second", "g", "2") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT2) + + def test_set_ini_new(self): + conf = dsconf.IniFile(self._path) + conf.set("new", "s", "2") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT3) + + def test_set_ini_none(self): + conf = dsconf.IniFile(self._path) + conf.set("default", "s", "2") + with open(self._path) as f: + content = f.read() + self.assertEqual(content, RESULT4)