Using ruby for config files

#1

(In reference to
http://groups.google.com/group/ruby-talk-google/browse_thread/thread/1c1fa0bfca68084f/
)

I eventually settled on the below for my config file purposes. I
wanted configuration to be convenient and flexible. Using XML or YAML
robs me of the occasional #map, for example. Using a hash for
configuration is too error-prone; I should assume those doing the
configuring have no knowledge of ruby. That is,

foo = “bar”
size = 44

is a better config file than

{
:foo => “bar”,
:size => 44,
}

So here is what I use:

module ConfigReader_m
module Util
def self.file_contents(filename)
File.open(filename) { |f|
f.read
}
end

  def self.no_verbose
     previous_verbose = $VERBOSE
     begin
        $VERBOSE = nil
        yield
     ensure
        $VERBOSE = previous_verbose
     end
  end

end

def config_each_pair(config_code)
previous_locals = local_variables
config_locals = eval(config_code + “\n” + “local_variables”)
(config_locals - previous_locals).each { |name|
yield(name, eval(name))
}
end

def config_to_hash(config_code)
hash = Hash.new
config_each_pair(config_code) { |name, value|
hash[name] = value
}
hash
end

def config_to_open_struct(config_code)
require ‘ostruct’
OpenStruct.new(config_to_hash(config_code))
end

def config_to_instance_variables(config_code)
config_each_pair(config_code) { |name, value|
ivar = “@” + name
existing_value = Util.no_verbose {
instance_variable_get(ivar)
}
if existing_value
raise “instance variable already set: #{name}”
end
instance_variable_set(ivar, value)
}
end

def config_file_each_pair(config_file)
config_each_pair(Util.file_contents(config_file))
end

def config_file_to_hash(config_file)
config_to_hash(Util.file_contents(config_file))
end

def config_file_to_open_struct(config_file)
config_to_open_struct(Util.file_contents(config_file))
end

def config_file_to_instance_variables(config_file)
config_to_instance_variables(Util.file_contents(config_file))
end
end

class ConfigReader
include ConfigReader_m
end

class Foo
include ConfigReader_m

def initialize(config)
config_to_instance_variables(config)
end

def test_config
print "@version: "
p @version
print "@format_spec: "
p @format_spec
print "@source_files: "
p @source_files
end
end

config = %q{
version = 3
format_spec = “format.xml”
source_files = %w(a b c).map { |base|
base + “.cxx”
}
}

def sep(header)
puts “-”*10 + header
end

sep(“config_each_pair”)
ConfigReader.new.config_each_pair(config) { |key, value|
print "#{key}: "
p value
}

sep(“config_to_hash”)
p ConfigReader.new.config_to_hash(config)

sep(“config_to_open_struct”)
p ConfigReader.new.config_to_open_struct(config)

sep(“config_to_instance_variables”)
Foo.new(config).test_config

output:

----------config_each_pair
version: 3
format_spec: “format.xml”
source_files: [“a.cxx”, “b.cxx”, “c.cxx”]
----------config_to_hash
{“source_files”=>[“a.cxx”, “b.cxx”, “c.cxx”], “version”=>3,
“format_spec”=>“format.xml”}
----------config_to_open_struct
#<OpenStruct version=3, format_spec=“format.xml”,
source_files=[“a.cxx”, “b.cxx”, “c.cxx”]>
----------config_to_instance_variables
@version: 3
@format_spec: “format.xml”
@source_files: [“a.cxx”, “b.cxx”, “c.cxx”]

Since OpenStruct is often the nicest choice, and since ostruct is
underused by newcomers (both my opinion only), I provided it for
convenience.

If you wish to provide a restricted list of local variables which will
become configuration parameters (only), you can do this:

config = %q{
version = 3
format_spec = “format.xml”
source_files = %w(a b c).map { |base|
base + “.cxx”
}
local_variables = [“version”, “format_spec”]
}

#2

Hmm the config style looks nice, but the code looks a bit complex?

I use yaml right now even though it has a few disadvantages (for me in
this regard) because of indent (where a user may get parse errors).
And from this yaml info I generate or build system info, on top of it.

I think what would be cool, would be a minimal “config” interpreter with
only
a very few (but still quite) readable lines of ruby code. (Anyway,
that’s just my opinion)

#3

On Jan 9, 3:09 am, removed_email_address@domain.invalid wrote:

I eventually settled on the below for my config file purposes. I
wanted configuration to be convenient and flexible. Using XML or YAML
robs me of the occasional #map, for example. Using a hash for
configuration is too error-prone; I should assume those doing the
configuring have no knowledge of ruby. That is,

foo = “bar”
size = 44

If you just want to have configuration files that are little more than
key/value pairs, why not use the Windows INI file format? There are
already ruby gems that provide read/write access to INI files:

$ gem list ini -r -d

*** REMOTE GEMS ***

ini (0.1.1)
INI file reader and writer

inifile (0.1.0)
INI file reader and writer

#4

On Jan 9, 9:44 am, Karl von Laudermann removed_email_address@domain.invalid
wrote:

If you just want to have configuration files that are little more than
key/value pairs, why not use the Windows INI file format? There are
already ruby gems that provide read/write access to INI files:

Because using XML, YAML, or INI files robs me of the occasional #map,
for instance, as shown in my example. The whole point is that I
don’t want configuration files that are little more than key/value
pairs. Even if that was the case at the beginning of the project,
eventually it becomes too restrictive as the project grows.

–FC

#5

On Jan 9, 2008, at 11:35 AM, removed_email_address@domain.invalid wrote:

don’t want configuration files that are little more than key/value
pairs. Even if that was the case at the beginning of the project,
eventually it becomes too restrictive as the project grows.

I sometimes use the stupid simple:

#!/usr/bin/env ruby -wKU

require “ostruct”

module Config
module_function

def load_config_file(path)
eval <<-END_CONFIG
config = OpenStruct.new
#{File.read(path)}
config
END_CONFIG
end
end

END

Here are the tests:

#!/usr/bin/env ruby -wKU

require “test/unit”
require “tempfile”

require “config”

class TestConfig < Test::Unit::TestCase
def test_config_returns_a_customized_ostruct
assert_instance_of(OpenStruct, config)
end

def
test_config_object_is_passed_into_the_file_and_used_to_set_options
c = config(<<-END_SETTINGS)
config.string_setting = “just a String”
config.integer_setting = 41
END_SETTINGS
assert_equal(“just a String”, c.string_setting)
assert_equal(41, c.integer_setting)
end

def test_exceptions_bubble_up_to_the_caller
assert_raise(RuntimeError) do
config(<<-END_ERROR)
raise “Oops!”
END_ERROR
end
end

private

def config(content = String.new)
cf = Tempfile.new(“ender_config_test”)
cf << content
cf.flush
Config.load_config_file(cf.path)
end
end

END

James Edward G. II

#6

I used to love YAML but the whitespace / indent issue has bitten me
too many times.

I needed a config file today, and eventually went for a
lib/config.rb like:

CONFIG = {
:servera => {
:hostname => ‘a.com’,
:remote_user => ‘usera’
},
:serverb => {
:hostname => ‘b.org’,
:remote_user => ‘userb’
}
}

I just load it in with ‘require config’.

I know it’s a bit cryptic to non-rubyists (compared to e.g. yaml)
but it has the HUGE advantage of being easy to validate (ruby -wc
lib/config.rb).

Be interested to know how others do it - an include/mixin maybe?

#7

hi,

I am new to Ruby and this thread was one of the solution I was looking
for.

May be my question must be very basic :frowning:

Based on the Dick D. comment I would like to use the config as
described.
But can someone let me know how to use the variables defined in this
config file.

viz., I want to call the value of :hostname from another ruby file, how
shall I do this?

Thanks