diff --git a/lib/puppet/settings/ini_file.rb b/lib/puppet/settings/ini_file.rb index 0ef4d0843..51ecaa1bc 100644 --- a/lib/puppet/settings/ini_file.rb +++ b/lib/puppet/settings/ini_file.rb @@ -1,95 +1,96 @@ # @api private class Puppet::Settings::IniFile def self.update(config_fh, &block) config = parse(config_fh) manipulator = Manipulator.new(config) yield manipulator config.write(config_fh) end def self.parse(config_fh) config = new line_number = 1 config_fh.each_line do |line| case line when /^(\s*)\[(\w+)\](\s*)$/ config << SectionLine.new(line_number, $1, $2, $3) when /^(\s*)(\w+)(\s*=\s*)(.*?)(\s*)$/ config << SettingLine.new(line_number, $1, $2, $3, $4, $5) else config << Line.new(line_number, line) end + line_number += 1 end config end def initialize @lines = [] end def <<(line) @lines << line end def each(&block) @lines.each(&block) end def setting(name) @lines.find do |line| line.is_a?(SettingLine) && line.name == name end end def write(fh) fh.truncate(0) fh.rewind @lines.each do |line| line.write(fh) end fh.flush end class Manipulator def initialize(config) @config = config end def set(name, value) setting = @config.setting(name) if setting setting.value = value else @config << SettingLine.new("", name, "=", value, "") end end end Line = Struct.new(:line, :text) do def write(fh) fh.puts(text) end end SettingLine = Struct.new(:line, :prefix, :name, :infix, :value, :suffix) do def write(fh) fh.write(prefix) fh.write(name) fh.write(infix) fh.write(value) fh.puts(suffix) end end SectionLine = Struct.new(:line, :prefix, :name, :suffix) do def write(fh) fh.write(prefix) fh.write("[") fh.write(name) fh.write("]") fh.puts(suffix) end end end diff --git a/spec/unit/settings/config_file_spec.rb b/spec/unit/settings/config_file_spec.rb index ec9f33bbf..1733ced2c 100644 --- a/spec/unit/settings/config_file_spec.rb +++ b/spec/unit/settings/config_file_spec.rb @@ -1,100 +1,120 @@ #! /usr/bin/env ruby -S rspec require 'spec_helper' require 'puppet/settings/config_file' describe Puppet::Settings::ConfigFile do NOTHING = {} def section_containing(data) meta = data[:meta] || {} values = data.reject { |key, _| key == :meta } values.merge({ :_meta => Hash[values.keys.collect { |key| [key, meta[key] || {}] }] }) end def the_parse_of(*lines) config.parse_file(filename, lines.join("\n")) end let(:identity_transformer) { Proc.new { |value| value } } let(:config) { Puppet::Settings::ConfigFile.new(identity_transformer) } let(:filename) { "a/fake/filename.conf" } it "interprets an empty file to contain a main section with no entries" do the_parse_of("").should == { :main => section_containing(NOTHING) } end it "interprets an empty main section the same as an empty file" do the_parse_of("").should == config.parse_file(filename, "[main]") end it "places an entry in no section in main" do the_parse_of("var = value").should == { :main => section_containing(:var => "value") } end it "places an entry after a section header in that section" do the_parse_of("[section]", "var = value").should == { :main => section_containing(NOTHING), :section => section_containing(:var => "value") } end it "does not include trailing whitespace in the value" do the_parse_of("var = value\t ").should == { :main => section_containing(:var => "value") } end it "does not include leading whitespace in the name" do the_parse_of(" \t var=value").should == { :main => section_containing(:var => "value") } end it "skips lines that are commented out" do the_parse_of("#var = value").should == { :main => section_containing(NOTHING) } end it "skips lines that are entirely whitespace" do the_parse_of(" \t ").should == { :main => section_containing(NOTHING) } end it "errors when a line is not a known form" do expect { the_parse_of("unknown") }.to raise_error Puppet::Settings::ParseError, /Could not match line/ end + it "errors providing correct line number when line is not a known form" do + multi_line_config = <<-EOF +[main] +foo=bar +badline + EOF + expect { the_parse_of(multi_line_config) } + .to( + raise_error(Puppet::Settings::ParseError, /Could not match line/) do |exception| + expect(exception.line).to eq(3) + end + ) + end + it "stores file meta information in the _meta section" do the_parse_of("var = value { owner = me, group = you, mode = 0666 }").should == { :main => section_containing(:var => "value", :meta => { :var => { :owner => "me", :group => "you", :mode => "0666" } }) } end it "errors when there is unknown meta information" do expect { the_parse_of("var = value { unknown = no }") }. to raise_error ArgumentError, /Invalid file option 'unknown'/ end it "errors when the mode is not numeric" do expect { the_parse_of("var = value { mode = no }") }. to raise_error ArgumentError, "File modes must be numbers" end it "errors when the options are not key-value pairs" do expect { the_parse_of("var = value { mode }") }. to raise_error ArgumentError, "Could not parse 'value { mode }'" end it "errors when an application_defaults section is created" do expect { the_parse_of("[application_defaults]") }. to raise_error Puppet::Error, "Illegal section 'application_defaults' in config file #{filename} at line 1" end + it "errors when a global_defaults section is created" do + expect { the_parse_of("[main]\n[global_defaults]") }. + to raise_error Puppet::Error, + "Illegal section 'global_defaults' in config file #{filename} at line 2" + end + it "transforms values with the given function" do config = Puppet::Settings::ConfigFile.new(Proc.new { |value| value + " changed" }) config.parse_file(filename, "var = value").should == { :main => section_containing(:var => "value changed") } end it "does not try to transform an entry named 'mode'" do config = Puppet::Settings::ConfigFile.new(Proc.new { raise "Should not transform" }) config.parse_file(filename, "mode = value").should == { :main => section_containing(:mode => "value") } end end