From a45ab6593035cfbbd4cbcb535a8b8324b212ef4e Mon Sep 17 00:00:00 2001 From: Chris Price Date: Fri, 26 Oct 2012 17:24:37 -0700 Subject: 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. --- lib/puppet/util/ini_file.rb | 63 +++++++++++++++++ lib/puppet/util/ini_file/section.rb | 25 +++++++ spec/unit/puppet/provider/ini_setting/ruby_spec.rb | 80 ++++++++++++++++++++++ 3 files changed, 168 insertions(+) diff --git a/lib/puppet/util/ini_file.rb b/lib/puppet/util/ini_file.rb index 0409db2..90b0c7b 100644 --- a/lib/puppet/util/ini_file.rb +++ b/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) } diff --git a/lib/puppet/util/ini_file/section.rb b/lib/puppet/util/ini_file/section.rb index d7ff159..ba1a783 100644 --- a/lib/puppet/util/ini_file/section.rb +++ b/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 diff --git a/spec/unit/puppet/provider/ini_setting/ruby_spec.rb b/spec/unit/puppet/provider/ini_setting/ruby_spec.rb index 19db4c7..4f8bdc1 100644 --- a/spec/unit/puppet/provider/ini_setting/ruby_spec.rb +++ b/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 -- cgit v1.2.3