Configuration Files

J

John W. Long

------=_NextPart_000_0020_01C3C4E8.FCD59B60
Content-Type: text/plain;
charset="Windows-1252"
Content-Transfer-Encoding: 7bit

A while back someone submitted some code to this list for evaluating
Configuration files in the style of:

% cat test.config
editor.spacespertab = 4
toolbar.visible = true
statusbar.visible = false
. . .

One of the great things about their code was that it enabled you to use ruby
code within your configuration file so you could do things like this:

%cat test2.config
timer.alarm = 30 * 60 # set the timer to go off in 30 min

Their implementation was pretty simple. Mainly they created a hash,
splitting each line on /=/, and evaled the right side and stored the result
in the hash with a key of the left side.

I like this concept, but I wanted a better implementation. So I set to work
and created the attached files. The attached now enables you to do this:

%cat myprog.rb
require 'config'

$conf = Config.new

# set up the default values and configuration structure:
$conf.define %{
editor.spacespertab = 8
toolbar.visible = false
toolbar.caption = "Tools"
statusbar.visible = true
copyright = "Copyright © 2003, John W. Long"
}

$conf.read File.new('test.config', 'r').read

# you can even read multi line ruby snippets:
$conf.read %{
copyright = "Copyright © 2003, John W. Long
All Rights Reserved"
alarm = 30 * 60 #seconds
}

# now for getting the values, note the object hierarchy
toolbar.caption = $conf.toolbar.caption.value
toolbar.show if $conf.toolbar.visible.value
aboutbox.copyright = $conf.copyright.value
. . .

You get the idea.

A couple of advantages that this has over the previous concept:
-- You define the default values and create the config structure at the same
time
-- Because you define a config structure if someone makes a typo in the
config file misspelling "toolbar" as "tolbar" an error will be thrown
(eventually it will even tell you what line the error was on)
-- You access the values from the config file in almost the same way that
they are written into the config file on your Config object

I didn't attach the unit tests of this version because the code still
relatively crude. Once I get things worked out a bit I may post it on
RubyForge.

Feedback is appreciated.

___________________
John Long
www.wiseheartdesign.com

------=_NextPart_000_0020_01C3C4E8.FCD59B60
Content-Type: application/octet-stream;
name="cleanobject.rb"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="cleanobject.rb"

class CleanObject=0A=
alias :__extend__ :extend=0A=
alias :__instance_eval__ :instance_eval=0A=
def initialize=0A=
methods.each { |s|=0A=
symbol =3D s.intern=0A=
next if s =3D~ /^__.*__$/=0A=
next if [:instance_eval].include?(symbol)=0A=
instance_eval %{ undef :#{symbol.to_s} }=0A=
}=0A=
instance_eval %{ undef :instance_eval }=0A=
end=0A=
end=0A=

------=_NextPart_000_0020_01C3C4E8.FCD59B60
Content-Type: application/octet-stream;
name="config.rb"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="config.rb"

require "cleanobject"=0A=
=0A=
class Config=0A=
module Node=0A=
attr_accessor :__defining__, :__children__, :value=0A=
def __defining__=3D(value)=0A=
@__children__ ||=3D Hash.new=0A=
@__children__.each{ |s, v|=0A=
v.__defining__ =3D value=0A=
}=0A=
@__defining__ =3D value=0A=
end=0A=
def method_missing(symbol, *args)=0A=
@__children__ ||=3D Hash.new=0A=
if @__defining__ =0A=
s =3D symbol.to_s=0A=
s =3D $1 if s =3D~ /(.*)=3D$/=0A=
__instance_eval__ <<-CODE=0A=
def #{s}=3D(value)=0A=
unless @__children__.has_key?:)#{s})=0A=
n =3D CleanObject.new=0A=
n.__extend__(Node)=0A=
n.__defining__ =3D true=0A=
@__children__[:#{s}] =3D n=0A=
end=0A=
@__children__[:#{s}].value =3D value=0A=
end=0A=
def #{s}=0A=
unless @__children__.has_key?:)#{s})=0A=
n =3D CleanObject.new=0A=
n.__extend__(Node)=0A=
n.__defining__ =3D true=0A=
@__children__[:#{s}] =3D n=0A=
end=0A=
@__children__[:#{s}]=0A=
end=0A=
CODE=0A=
__send__ symbol, *args=0A=
else=0A=
raise NoMethodError, "undefined method `#{symbol}'"=0A=
end=0A=
end=0A=
end=0A=
=0A=
def initialize=0A=
@root =3D CleanObject.new=0A=
@root.__extend__(Node)=0A=
end=0A=
=0A=
def define(object)=0A=
@root.__defining__ =3D true=0A=
@root.__instance_eval__(prep(object.to_s))=0A=
@root.__defining__ =3D false=0A=
end=0A=
=0A=
def read(object)=0A=
@root.__instance_eval__(prep(object.to_s))=0A=
end=0A=
=0A=
def prep(string)=0A=
code =3D ""=0A=
string.each { |line|=0A=
c =3D line.strip=0A=
c =3D "self.#{c}" unless c =3D~ /^self\./ or c !~ /=3D/=0A=
code << "#{c}\n"=0A=
}=0A=
code=0A=
end=0A=
=0A=
def method_missing(symbol, *args)=0A=
@root.__send__ symbol, *args=0A=
end=0A=
end=0A=

------=_NextPart_000_0020_01C3C4E8.FCD59B60--
 
H

Hal Fulton

Gavin said:
I have a really cool way of specifying config files. It goes like this:

plugin:
player:
class: CommandPlayer
command: mpg123

However, the code for parsing this is a secret :)

Why, you lucky stiff...

Hal
 
N

NAKAMURA, Hiroshi

Hi,
From: "John W. Long" <[email protected]>
Sent: Thursday, December 18, 2003 1:01 PM
I like this concept, but I wanted a better implementation. So I set to work
and created the attached files. The attached now enables you to do this:
Interesting.

You get the idea.

A couple of advantages that this has over the previous concept:
-- You define the default values and create the config structure at the same
time
-- Because you define a config structure if someone makes a typo in the
config file misspelling "toolbar" as "tolbar" an error will be thrown
(eventually it will even tell you what line the error was on)
-- You access the values from the config file in almost the same way that
they are written into the config file on your Config object

Sorry for not related to your great work, but let me introduce mine.
From the similar point of view, I wrote my own version for soap4r, too.

http://www.ruby-lang.org/cgi-bin/cvsweb.cgi/lib/soap4r/lib/soap/property.rb
http://www.ruby-lang.org/cgi-bin/cvsweb.cgi/lib/soap4r/test/soap/test_property.rb

My concept is; define property tree and lock to avoid typo.
And I want to "hook" property changes to reconfigure something
and value-space checking.


require 'soap/property'

###
## property definition
#
prop = SOAP::property.new
prop.add_hook("protocol.http.proxy") do |name, value|
# check value here...
puts "(proxy) #{name}: #{value}"
end

sitebag = prop["protocol.http.sites"] = SOAP::property.new

prop.lock(true) # true: locks properties cascading.
sitebag.unlock # unlock sitebag property node.

###
## assigning values
#
prop["protocol.http.proxy"] = "myproxy:8080"
prop["protocol.http.sites"] << "foo"
prop["protocol.http.sites"] << "bar"

p prop["protocol.http.sites.0"] # => "foo"
p prop["protocol.http.sites.1"] # => "bar"
p prop["protocol.http.sites"].values # => ["foo", "bar"]

# typo
prop["protocooool.http.proxy"] = "foo" #=> error
prop["protocol.htttttp.proxy"] = "foo" #=> error
prop["protocol.http.proxxxxy"] = "foo" #=> error



But mine cannot read property file after property definition for now
though it may be able to do easily.

How do you think hooking feature of configuration files?

Regards,
// NaHi
 
A

Ara.T.Howard

Date: Thu, 18 Dec 2003 13:51:45 +0900
From: Gavin Sinclair <[email protected]>
Newsgroups: comp.lang.ruby
Subject: Re: Configuration Files
That sounds like a great config file system. I rolled my own basic one
not too long ago, but having seen this, I'm thinking I'd replace it. :)

One thing I did in mine which I kind of like, but has drawbacks, is a
sort of grouping, where:
[plugin.player]
class=CommandPlayer
command=mpg123

is the same as:
plugin.player.class=CommandPlayer
plugin.player.command=mpg123

I have a really cool way of specifying config files. It goes like this:

plugin:
player:
class: CommandPlayer
command: mpg123

However, the code for parsing this is a secret :)

i'm with you (and use it myself), but the above is problematic if you actually
want non-programmers to be able to _configure_ a system. for example, how long
do you think it would take a non-programmer to debug this?

file config.rb:
========
require 'yaml'
config = <<-txt
plugin:
player:
class: CommandPlayer
command: mpg123
txt
YAML::load(config)


~/eg/ruby > ruby config.rb
/data/ruby-1.8.0//lib/ruby/1.8/yaml.rb:39:in `load': parse error on line 3, col 12: ` command: mpg123' (ArgumentError)
from /data/ruby-1.8.0//lib/ruby/1.8/yaml.rb:39:in `load'
from config.rb:8

-a
--

ATTN: please update your address books with address below!

===============================================================================
| EMAIL :: Ara [dot] T [dot] Howard [at] noaa [dot] gov
| PHONE :: 303.497.6469
| ADDRESS :: E/GC2 325 Broadway, Boulder, CO 80305-3328
| STP :: http://www.ngdc.noaa.gov/stp/
| NGDC :: http://www.ngdc.noaa.gov/
| NESDIS :: http://www.nesdis.noaa.gov/
| NOAA :: http://www.noaa.gov/
| US DOC :: http://www.commerce.gov/
|
| The difference between art and science is that science is what we
| understand well enough to explain to a computer.
| Art is everything else.
| -- Donald Knuth, "Discover"
|
| /bin/sh -c 'for l in ruby perl;do $l -e "print \"\x3a\x2d\x29\x0a\"";done'
===============================================================================
 
W

why the lucky stiff

i'm with you (and use it myself), but the above is problematic if you
actually want non-programmers to be able to _configure_ a system. for
example, how long do you think it would take a non-programmer to debug
this?

file config.rb:
========
require 'yaml'
config = <<-txt
plugin:
player:
class: CommandPlayer
command: mpg123
txt
YAML::load(config)


~/eg/ruby > ruby config.rb
/data/ruby-1.8.0//lib/ruby/1.8/yaml.rb:39:in `load': parse error on line 3,
col 12: ` command: mpg123' (ArgumentError) from
/data/ruby-1.8.0//lib/ruby/1.8/yaml.rb:39:in `load'
from config.rb:8

-a

The above throws an error for you? Man, a lot has been fixed since 1.8.0.

Anyhow, I'm with ya. So, let's solve the problem. Would it help if you could
hook the parse error handler to generate your own nice errors? We can also
start to provide more specific parse error messages, though this can be
needly on some edges.

~/eg/ruby > ruby config.rb
Error in configuration file. See line 4.

2 player:
3 class: CommandPlayer
Here!! -> 4 command: mpg123

Yeah, the indentation on that line needs to be trimmed
a bit. Just line it up cozy with the line above it.

_why
 
G

Gennady

why said:
The above throws an error for you? Man, a lot has been fixed since 1.8.0.


I use YAML form the latest ruby-1.8.1preview3, and when I
copy-and-pasted the code from Ara's post, I got the same error. The
problem was that there was a TAB character after "player:" (was it
deliberately implanted to prove the point ;-) ?), and it caused the
problem. If you remove TAB or convert it to spaces, everything starts
working fine.

_why, could you just ignore trailing spaces and tabs?

Gennady.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top