Using ruby for config files

(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"]
}

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)

···

--
Posted via http://www.ruby-forum.com/.

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

···

On Jan 9, 3:09 am, furtive.cl...@gmail.com 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

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

···

On Jan 9, 9:44 am, Karl von Laudermann <doodpa...@mailinator.com> 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:

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?

···

On Jan 9, 2008 9:00 AM, Marc Heiler <shevegen@linuxmail.org> wrote:

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)

--
Rasputnik :: Jack of All Trades - Master of Nuns
http://number9.hellooperator.net/

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 Gray II

···

On Jan 9, 2008, at 11:35 AM, furtive.clown@gmail.com wrote:

On Jan 9, 9:44 am, Karl von Laudermann <doodpa...@mailinator.com> > 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.

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 Davies 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

···

--
Posted via http://www.ruby-forum.com/.