# Module Puppet::IniConfig
# A generic way to parse .ini style files and manipulate them in memory
# One 'file' can be made up of several physical files. Changes to sections
# on the file are tracked so that only the physical files in which
# something has changed are written back to disk
# Great care is taken to preserve comments and blank lines from the original
# files
#
# The parsing tries to stay close to python's ConfigParser
require 'puppet/util/filetype'
module Puppet::Util::IniConfig
# A section in a .ini file
class Section
attr_reader :name, :file
def initialize(name, file)
@name = name
@file = file
@dirty = false
@entries = []
end
# Has this section been modified since it's been read in
# or written back to disk
def dirty?
@dirty
end
# Should only be used internally
def mark_clean
@dirty = false
end
# Add a line of text (e.g., a comment) Such lines
# will be written back out in exactly the same
# place they were read in
def add_line(line)
@entries << line
end
# Set the entry 'key=value'. If no entry with the
# given key exists, one is appended to teh end of the section
def []=(key, value)
entry = find_entry(key)
@dirty = true
if entry.nil?
@entries << [key, value]
else
entry[1] = value
end
end
# Return the value associated with KEY. If no such entry
# exists, return nil
def [](key)
entry = find_entry(key)
if entry.nil?
return nil
end
return entry[1]
end
# Format the section as text in the way it should be
# written to file
def format
text = "[#{name}]\n"
@entries.each do |entry|
if entry.is_a?(Array)
key, value = entry
unless value.nil?
text << "#{key}=#{value}\n"
end
else
text << entry
end
end
return text
end
private
def find_entry(key)
@entries.each do |entry|
if entry.is_a?(Array) && entry[0] == key
return entry
end
end
return nil
end
end
# A logical .ini-file that can be spread across several physical
# files. For each physical file, call #read with the filename
class File
def initialize
@files = {}
end
# Add the contents of the file with name FILE to the
# already existing sections
def read(file)
text = Puppet::Util::FileType.filetype(:flat).new(file).read
if text.nil?
raise "Could not find #{file}"
end
section = nil # The name of the current section
optname = nil # The name of the last option in section
line = 0
@files[file] = []
text.each_line do |l|
line += 1
if l.strip.empty? || "#;".include?(l[0,1]) ||
(l.split(nil, 2)[0].downcase == "rem" &&
l[0,1].downcase == "r")
# Whitespace or comment
if section.nil?
@files[file] << l
else
section.add_line(l)
end
elsif " \t\r\n\f".include?(l[0,1]) && section && optname
# continuation line
section[optname] += "\n" + l.chomp
elsif l =~ /^\[([^\]]+)\]/
# section heading
section.mark_clean unless section.nil?
section = add_section($1, file)
optname = nil
elsif l =~ /^\s*([^\s=]+)\s*\=(.*)$/
# We allow space around the keys, but not the values
# For the values, we don't know if space is significant
if section.nil?
raise "#{file}:#{line}:Key/value pair outside of a section for key #{$1}"
else
section[$1] = $2
optname = $1
end
else
raise "#{file}:#{line}: Can't parse '#{l.chomp}'"
end
end
section.mark_clean unless section.nil?
end
# Store all modifications made to sections in this file back
# to the physical files. If no modifications were made to
# a physical file, nothing is written
def store
@files.each do |file, lines|
text = ""
dirty = false
lines.each do |l|
if l.is_a?(Section)
dirty ||= l.dirty?
text << l.format
l.mark_clean
else
text << l
end
end
if dirty
Puppet::Util::FileType.filetype(:flat).new(file).write(text)
end
end
end
# Execute BLOCK, passing each section in this file
# as an argument
def each_section(&block)
@files.each do |file, list|
list.each do |entry|
if entry.is_a?(Section)
yield(entry)
end
end
end
end
# Return the Section with the given name or nil
def [](name)
name = name.to_s
each_section do |section|
return section if section.name == name
end
return nil
end
# Return true if the file contains a section with name NAME
def include?(name)
return ! self[name].nil?
end
# Add a section to be stored in FILE when store is called
def add_section(name, file)
if include?(name)
raise "A section with name #{name} already exists"
end
result = Section.new(name, file)
@files[file] ||= []
@files[file] << result
return result
end
end
end
# $Id: inifile.rb 2218 2007-02-21 17:14:26Z lutter $
syntax highlighted by Code2HTML, v. 0.9.1