Skip to content

Instantly share code, notes, and snippets.

@kellyredding
Created December 5, 2011 15:47
Show Gist options
  • Save kellyredding/1434021 to your computer and use it in GitHub Desktop.
Save kellyredding/1434021 to your computer and use it in GitHub Desktop.
Git-style namespaced key-value config file parser
class NamespacedKeyValueConfigFile
# This class parses a git-style config file of key=value configs grouped
# by [namespaces]. Each namespace gets a method that returns a hash of
# the key-value configs for the namespace. So given:
# [thing]
# key1 = val1
# key2 = val2
# [other]
# cool = true
# [nums]
# one = 1
# You get:
# NamespacedKeyValueConfigFile.thing #=> {'key1' => 'val1', 'key2' => 'val2'}
# NamespacedKeyValueConfigFile.other #=> {'cool' => 'true'}
# NamespacedKeyValueConfigFile.nums #=> {'one' => '1'}
NAMESPACE_REGEX = /^\[(\w+)\].*/
KEYVAL_REGEX = /^\W*(\w+)\s*=\s*([\w|\.|-]+).*/
VALID_NS_REGEX = /^\w+$/
attr_reader :__file__, :__data__
def initialize(file)
self.__parse_file__(file)
end
def method_missing(meth, *args, &block)
if meth.to_s =~ VALID_NS_REGEX
(self.__data__.keys.include?(meth.to_s) ? self.__data__[meth.to_s] : {}).tap do |v|
block.call v if block
end
else
super
end
end
def respond_to?(*args)
meth = args.first
if meth.to_s =~ VALID_NS_REGEX && self.__data__.keys.include?(meth.to_s)
true
else
super
end
end
protected
# reset and reparse the data when a new file is given
def __parse_file__(file)
@__file__ = file
@__data__ = {}
File.open(self.__file__, "r") do |file|
file.readlines.each { |line| self.__parse_line__(line) }
end if self.__file__ && File.exists?(self.__file__.to_s)
end
def __parse_line__(line)
line.tap do
__set_ns__(line =~ NAMESPACE_REGEX ? $1 : @ns)
if @ns && line =~ KEYVAL_REGEX
self.__data__[@ns][$1] = $2
end
end
end
private
def __set_ns__(ns)
return if ns.nil? || @ns == ns
self.__data__[ns] ||= {}
@ns = ns
end
end
# bundler: gem 'assert', '~> 0.7'
require 'assert'
require 'config'
class NamespacedKeyValueConfigFile
class BasicTests < Assert::Context
desc "the namespaced key-value config file"
before do
@config = NamespacedKeyValueConfigFile.new(File.expand_path("../testcfg", __FILE__))
end
subject { @config }
should have_reader :__file__, :__data__
should "store data as a nested hash of key-values" do
assert_kind_of ::Hash, subject.__data__
assert_kind_of ::Hash, subject.__data__[subject.__data__.keys.first]
end
should "parse out key value data for each config" do
assert_equal 'true', subject.__data__['thing1']['status']
assert_equal 'version1', subject.__data__['thing2']['v1']
assert_equal 'version2', subject.__data__['thing2']['v2']
assert_equal 'ver.sion3', subject.__data__['thing2']['v3']
assert_equal 'ver-s4', subject.__data__['thing2']['v4']
assert_equal 'cmd1', subject.__data__['line']['c1']
end
should "proxy its top-level __data__ hash keys as namespace methods" do
subject.__data__.each do |ns, configs|
assert_respond_to ns, subject
assert_equal configs, subject.send(ns)
end
end
should "return an empty hash for valid namespaces not configured" do
assert_equal Hash.new, subject.not_configured
end
should "raise no method errors when accessing invalid namespaces" do
assert_raises NoMethodError do
subject.send(:'abc%&#')
end
assert_raises NoMethodError do
subject.send(:'meth-with-hash-in-name')
end
end
end
end
[thing1]
status=true
# comment line
[thing2]
v1 = version1
v2 = version2
# comment line
v3 = ver.sion3#comment line
v4 = ver-s4 four
[line]
c1 = cmd1, cmd2 # comment line
[ns]
ns1 = 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment