module Rex
module Parser

###
#
# This class parses the contents of an INI file.
#
###
class Ini < Hash

	##
	#
	# Factories
	#
	##
	
	#
	# Creates a new class instance and reads in the contents of the supplied
	# file path.
	#
	def self.from_file(path)
		ini = Ini.new(path)
		ini.from_file
		return ini
	end

	#
	# Creates a new class instance from the supplied string.
	#
	def self.from_s(str)
		ini = Ini.new
		ini.from_s(str)
		return ini
	end

	#
	# Initializes an ini instance and tries to read in the groups from the
	# file if it exists.
	#
	def initialize(path = nil)
		self.path = path

		# Try to synchronize ourself with the file if we
		# have one
		begin
			self.from_file if (self.path)
		rescue
		end
	end

	#
	# Enumerates the groups hash keys.
	#
	def each_group(&block) 
		self.keys.each { |k|
			yield
		}
	end

	#
	# Adds a group of the supplied name if it doesn't already exist.
	#
	def add_group(name = 'global', reset = true)
		self[name] = {} if (reset == true)
		self[name] = {} if (!self[name])

		return self[name]
	end

	#
	# Checks to see if name is a valid group.
	#
	def group?(name)
		return (self[name] != nil)
	end

	##
	#
	# Serializers
	#
	##

	#
	# Reads in the groups from the supplied file path or the instance's file
	# path.
	#
	def from_file(fpath = nil)
		fpath = path if (!fpath)
	
		read_groups(fpath)
	end

	#
	# Reads in the groups from the supplied string.
	#
	def from_s(str)
		read_groups_string(str.split("\n"))
	end

	#
	# Writes the group settings to a file.
	#
	def to_file(tpath = nil)
		tpath = path if (!tpath)

		f = File.new(tpath, "w")
		f.write(to_s)
		f.close
	end

	#
	# Converts the groups to a string.
	#
	def to_s
		str = ''
		keys.sort.each { |k|
			str << "[#{k}]\n"

			self[k].each_pair { |var, val|
				str << "#{var}=#{val}\n"
			}

			str << "\n";
		}

		return str
	end

	attr_reader :path

protected

	#
	# Reads in the groups and their attributes from the supplied file
	# path or from the instance's file path if one was set.
	#
	def read_groups(fpath) # :nodoc:
		if (!fpath)
			raise ArgumentError, "No file path specified.",
				caller
		end

		# Read in the contents of the file
		lines = ::IO.readlines(fpath)

		# Now read the contents from the supplied string
		read_groups_string(lines)
	end

	#
	# Reads groups from the supplied string
	#
	def read_groups_string(str) # :nodoc:
		# Reset the groups hash
		self.clear

		# The active group
		active_group = nil

		# Walk each line initializing the groups
		str.each { |line|
			next if (line.match(/^;/))

			# Eliminate cr/lf
			line.gsub!(/(\n|\r)/, '')

			# Is it a group [bob]?
			if (md = line.match(/^\[(.+?)\]/))
				active_group = md[1]	
				self[md[1]]  = {}
			# Is it a VAR=VAL?
			elsif (md = line.match(/^(.+?)=(.*)$/))
				if (active_group)
					self[active_group][md[1]] = md[2]
				end
			end
		}
	end

	attr_writer :path # :nodoc:

end

end
end
