Ruby as a configuration language


#1

Hi

I’m writing an automated software building framework in Ruby. The
software
compiles software packages from source code using human-written
profiles. The
profiles should be extremely easy to read and write by humans (and easy
to
parse by a program). I wish I could write the profiles in Ruby to avoid
creating yet another advanced configuration language. But… it seems
that I
might not be able to do that. What I want is something like this:

An example package description file. The actual building commands are

written in a bash script.

name = “gcc”
version = “4.1.1”
title = " “GNU Compiler Collection”
description = “…”

archive {
localname = “#{name}-#{version}.tar.bz2”
originalname = “#{name}-#{version}.tar.gz”
baseurl “ftp://ftp.gnu.org/gnu/gcc/#{version}/
baseurl “ftp.mirror.site/gnu/gcc/#{version}/”
convert = “gz-bz2”
}

Optional components

feature “gfortran” {
title = “Fortran compiler”
depend “gmp”
}

END example

So my question is: is there a way to do this so that I would eval this
package
description file and appropriate fields of internal objects would be
filled
by the evaluated script?

Thanks in advance!


#2

On 2/8/07, Tapio K. removed_email_address@domain.invalid wrote:

I’m writing an automated software building framework in Ruby. The software
compiles software packages from source code using human-written profiles. The
profiles should be extremely easy to read and write by humans (and easy to
[snip]

There is a Ruby replacement of make, that can give you some
inspiration on how to do it. Perhaps rake can do all what you want?
http://rake.rubyforge.org/


#3

Tapio K. wrote:

I wish I could write the profiles in Ruby to avoid
creating yet another advanced configuration language.

Some people will suggest YAML. That’s certainly a reasonable way to
go. Don’t create your own config language when a simple markup exists
that translates painlessly into Ruby data structures.

baseurl "ftp://ftp.gnu.org/gnu/gcc/#{version}/"

END example

If you want a true DSL, here’s some untested code pointing you in the
right direction. I have no idea if you can have more than one archive,
or more than one feature, or more than one baseurl per Archive…but
you can do it.

class Archive
attr_accessor :localname, :originalname, :baseurl, :convert
end

class Feature
attr_accessor :title, :dependency
def depend( depends_on )
@dependency = depends_on
end
end

def archive( &block )
$archive = Archive.new
$archive.instance_eval( &block )
end

$features = []
def feature( &block )
f = Feature.new
$features << f
f.instance_eval( &block )
end


#4

On Feb 8, 9:32 am, “Phrogz” removed_email_address@domain.invalid wrote:

end
Oops, I missed that baseurl was a method call there. Then you might
want something like:

class Archive
attr_accessor :localname, :originalname, :convert
def initialize
@baseurls = []
end
def baseurl( url )
@baseurls << url
end
end


#5

Simon S. wrote:

On 2/8/07, Tapio K. removed_email_address@domain.invalid wrote:

I’m writing an automated software building framework in Ruby. The software
compiles software packages from source code using human-written profiles. The
profiles should be extremely easy to read and write by humans (and easy to
[snip]

There is a Ruby replacement of make, that can give you some
inspiration on how to do it. Perhaps rake can do all what you want?
http://rake.rubyforge.org/

I agree. If you’re going to build a DSL for your particular domain, I
suggest building it atop Rake. Stand on the shoulders of this jolly Red
giant.


#6

On Fri, Feb 09, 2007 at 01:00:09AM +0900, Tapio K. wrote:

baseurl "ftp://ftp.gnu.org/gnu/gcc/#{version}/"

END example

I did something a bit like this, using Rake, for building mail software.
You
can download the source, which is basically just a bunch of package
building
scripts, from http://linnet.rubyforge.org/ (take the linnet-src package,
or
check out from cvs)

You might be able to steal some ideas. It’s not as abstracted as the way
you’ve described above, but I found in practice that the software
packages I
was building didn’t all fit into identical moulds.


#7

I’m writing an automated software building framework in Ruby. The software
compiles software packages from source code using human-written profiles. The
profiles should be extremely easy to read and write by humans (and easy to
parse by a program). I wish I could write the profiles in Ruby to avoid
creating yet another advanced configuration language.

Thanks to all of you who responded. I thought that instance_eval would
be the
solution, but I somehow overlooked it. Now I have to simulate scoping so
that
code in archive and feature blocks may refer to things declared in the
top-level of the profile (which will not be real top-level, since the
profile
scripts get inctance_evaled, to avoid pollution of Object). Overriding
method_missing probably solves this nicely.

Ruby is a great thing (although it lacks multiple dispatching and
multiple inheritance)!


#8

You should take a look at Capistrano - its pretty amazing what it does
out of the box, is base don Rake, and makes it easy to build, configure,
and deploy applications.


#9

Tapio K. wrote:

scripts get inctance_evaled, to avoid pollution of Object). Overriding
method_missing probably solves this nicely.

Ruby is a great thing (although it lacks multiple dispatching and
multiple inheritance)!

You could evaluate the whole config file inside of a scope (for example,
a module), and then pull objects out of this module as you need them.

There’s one way to do this in:

http://redshift.sourceforge.net/script/

You can make methods like “archive” and “feature” available by
subclassing the Script module and defining them as module methods.

$ cat config-script.rb
name = “gcc”
version = “4.1.1”
title = “GNU Compiler Collection”
description = “…”

archive {
localname “#{name}-#{version}.tar.bz2”
#originalname “#{name}-#{version}.tar.gz”
#baseurlftp://ftp.gnu.org/gnu/gcc/#{version}/
#baseurl “ftp.mirror.site/gnu/gcc/#{version}/”
#convert “gz-bz2”
}

Optional components

feature(“gfortran”) {
title “Fortran compiler”
#depend “gmp”
}

$ cat parser.rb
require ‘script’

class Archive
def localname n; @localname = n; end
# etc.
end

class Feature
def initialize arg; @arg = arg; end
def title t; @title = t; end
# etc.
end

class ConfigScript < Script
def archive(&bl)
@archive = Archive.new
@archive.instance_eval(&bl)
end

 def feature(arg, &bl)
   @feature = Feature.new(arg)
   @feature.instance_eval(&bl)
 end

 def inspect
   super + [@archive, @feature].inspect
 end

end

config_script = ConfigScript.load(“config-script.rb”)

p config_script

$ ruby parser.rb
#ConfigScript:/home/vjoel/tmp/scr/config-script.rb[#<Archive:0xb7cb5ff4
@localname=“gcc-4.1.1.tar.bz2”>, #<Feature:0xb7cb5f7c @arg=“gfortran”,
@title=“Fortran compiler”>]

This doesn’t really try to solve your parsing questions. It’s just an
example of how to eval the script inside a scope and not let that leak
out into the global scope.

It might be less work if you can accept a config file of the form:

$ cat config-script2.rb
NAME = “gcc”
VERSION = “4.1.1”
TITLE = “GNU Compiler Collection”
DESCRIPTION = “…”

ARCHIVE = {
:localname => “#{NAME}-#{VERSION}.tar.bz2”
}

module Features
GFORTRAN = {
:title => “Fortran compiler”
}
end

Then the parser is just:

$ cat parser2.rb
require ‘script’

config_script = Script.load(“config-script2.rb”)

p config_script

p config_script::NAME

p config_script::ARCHIVE

config_script::Features.constants.each do |c|
p(c=>config_script::Features.const_get©)
end

$ ruby parser2.rb
#Script:/home/vjoel/tmp/scr/config-script2.rb
“gcc”
{:localname=>“gcc-4.1.1.tar.bz2”}
{“GFORTRAN”=>{:title=>“Fortran compiler”}}

Anyway, I hope this suggests some of the possibilities for scoping your
DSL. There are lots of possibilities for sweetening up the syntax.