ソースを参照

Add detection for commented versions of settings

This commit adds support for detecting commented versions of
settings in an existing version of an inifile.  If you are
setting a value for a setting that isn't currently set
in the file, but a commented version is found, then we
add the new setting immediately following the commented
version, rather than at the end of the section.
Chris Price 11 年 前
コミット
a45ab65

+ 63 - 0
lib/puppet/util/ini_file.rb

@@ -7,6 +7,7 @@ module Util
 
     SECTION_REGEX = /^\s*\[([\w\d\.\\\/\-\:]+)\]\s*$/
     SETTING_REGEX = /^(\s*)([\w\d\.\\\/\-]+)(\s*=\s*)([\S\s]*\S)\s*$/
+    COMMENTED_SETTING_REGEX = /^(\s*)[#;]+(\s*)([\w\d\.\\\/\-]+)(\s*=\s*)([\S\s]*\S)\s*$/
 
     def initialize(path, key_val_separator = ' = ')
       @path = path
@@ -34,9 +35,31 @@ module Util
       end
 
       section = @sections_hash[section_name]
+
       if (section.has_existing_setting?(setting))
         update_line(section, setting, value)
         section.update_existing_setting(setting, value)
+      elsif result = find_commented_setting(section, setting)
+        # So, this stanza is a bit of a hack.  What we're trying
+        # to do here is this: for settings that don't already
+        # exist, we want to take a quick peek to see if there
+        # is a commented-out version of them in the section.
+        # If so, we'd prefer to add the setting directly after
+        # the commented line, rather than at the end of the section.
+
+        # If we get here then we found a commented line, so we
+        # call "insert_inline_setting_line" to update the lines array
+        insert_inline_setting_line(result, section, setting, value)
+
+        # Then, we need to tell the setting object that we hacked
+        # in an inline setting
+        section.insert_inline_setting(setting, value)
+
+        # Finally, we need to update all of the start/end line
+        # numbers for all of the sections *after* the one that
+        # was modified.
+        section_index = @section_names.index(section_name)
+        increment_section_line_numbers(section_index + 1)
       else
         section.set_additional_setting(setting, value)
       end
@@ -206,6 +229,35 @@ module Util
         File.readlines(path)
     end
 
+    # This utility method scans through the lines for a section looking for
+    # commented-out versions of a setting.  It returns `nil` if it doesn't
+    # find one.  If it does find one, then it returns a hash containing
+    # two keys:
+    #
+    #   :line_num - the line number that contains the commented version
+    #               of the setting
+    #   :match    - the ruby regular expression match object, which can
+    #               be used to mimic the whitespace from the comment line
+    def find_commented_setting(section, setting)
+      return nil if section.is_new_section?
+      (section.start_line..section.end_line).each do |line_num|
+        if (match = COMMENTED_SETTING_REGEX.match(lines[line_num]))
+          if (match[3] == setting)
+            return { :match => match, :line_num => line_num }
+          end
+        end
+      end
+      nil
+    end
+
+    # This utility method is for inserting a line into the existing
+    # lines array.  The `result` argument is expected to be in the
+    # format of the return value of `find_commented_setting`.
+    def insert_inline_setting_line(result, section, setting, value)
+      line_num = result[:line_num]
+      match = result[:match]
+      lines.insert(line_num + 1, "#{' ' * section.indentation}#{setting}#{match[4]}#{value}")
+    end
 
     # Utility method; given a section index (index into the @section_names
     # array), decrement the start/end line numbers for that section and all
@@ -217,6 +269,17 @@ module Util
       end
     end
 
+    # Utility method; given a section index (index into the @section_names
+    # array), increment the start/end line numbers for that section and all
+    # all of the other sections that appear *after* the specified section.
+    def increment_section_line_numbers(section_index)
+      @section_names[section_index..(@section_names.length - 1)].each do |name|
+        section = @sections_hash[name]
+        section.increment_line_nums
+      end
+    end
+
+
     def flush_buffer_to_file(buffer, fh)
       if buffer.length > 0
         buffer.each { |l| fh.puts(l) }

+ 25 - 0
lib/puppet/util/ini_file/section.rb

@@ -52,6 +52,19 @@ class IniFile
       end
     end
 
+    # This is a hacky method; it's basically called when we need to insert
+    # a new setting but we don't want it to appear at the very end of the
+    # section.  Instead we hack it into the existing settings list and
+    # increment our end_line number--this assumes that the caller (`ini_file`)
+    # is doing some babysitting w/rt the other sections and the actual data
+    # of the lines.
+    def insert_inline_setting(setting_name, value)
+      @existing_settings[setting_name] = value
+      if @end_line
+        @end_line = @end_line + 1
+      end
+    end
+
     def set_additional_setting(setting_name, value)
       @additional_settings[setting_name] = value
     end
@@ -68,6 +81,18 @@ class IniFile
       end
     end
 
+    # Increment the start and end line numbers for the section (if they are
+    # defined); this is intended to be called when an inline setting is added
+    # to a section that comes before this section in the ini file.
+    def increment_line_nums()
+      if @start_line
+        @start_line = @start_line + 1
+      end
+      if @end_line
+        @end_line = @end_line + 1
+      end
+    end
+
   end
 end
 end

+ 80 - 0
spec/unit/puppet/provider/ini_setting/ruby_spec.rb

@@ -731,4 +731,84 @@ subby=bar
 
   end
 
+
+  context "when dealing settings that have a commented version present" do
+    let(:orig_content) {
+      <<-EOS
+     [section1]
+     # foo=foovalue
+     bar=barvalue
+     foo = foovalue2
+
+[section2]
+# foo = foovalue
+;bar=barvalue
+blah = blah
+      EOS
+    }
+
+    it "should add a new setting below a commented version of that setting" do
+      resource = Puppet::Type::Ini_setting.new(
+          common_params.merge(:section => 'section2', :setting => 'foo', :value => 'foo3'))
+      provider = described_class.new(resource)
+      provider.exists?.should be_false
+      provider.create
+      validate_file(<<-EOS
+     [section1]
+     # foo=foovalue
+     bar=barvalue
+     foo = foovalue2
+
+[section2]
+# foo = foovalue
+foo = foo3
+;bar=barvalue
+blah = blah
+      EOS
+      )
+    end
+
+    it "should update an existing setting in place, even if there is a commented version of that setting" do
+      resource = Puppet::Type::Ini_setting.new(
+          common_params.merge(:section => 'section1', :setting => 'foo', :value => 'foo3'))
+      provider = described_class.new(resource)
+      provider.exists?.should be_true
+      provider.create
+      validate_file(<<-EOS
+     [section1]
+     # foo=foovalue
+     bar=barvalue
+     foo = foo3
+
+[section2]
+# foo = foovalue
+;bar=barvalue
+blah = blah
+      EOS
+      )
+    end
+
+    it "should add a new setting below a commented version of that setting, respecting semicolons as comments" do
+      resource = Puppet::Type::Ini_setting.new(
+          common_params.merge(:section => 'section2', :setting => 'bar', :value => 'bar3'))
+      provider = described_class.new(resource)
+      provider.exists?.should be_false
+      provider.create
+      validate_file(<<-EOS
+     [section1]
+     # foo=foovalue
+     bar=barvalue
+     foo = foovalue2
+
+[section2]
+# foo = foovalue
+;bar=barvalue
+bar=bar3
+blah = blah
+      EOS
+      )
+    end
+
+  end
+
 end