# Ruby as a configuration language

**URL:** <https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912>\
**Category:** Ruby\
**Created:** [February 8, 2007, 5:00pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912 "2007-02-08T17:00:34Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 5:00pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/1 "2007-02-08T17:00:34Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 5:35pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/2 "2007-02-08T17:35:41Z")

</div>

On 2/8/07, Tapio K. [removed\_email\_address@domain.invalid](mailto: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/](http://rake.rubyforge.org/)

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 5:36pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/3 "2007-02-08T17:36:42Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 5:40pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/4 "2007-02-08T17:40:42Z")

</div>

On Feb 8, 9:32 am, “Phrogz” [removed\_email\_address@domain.invalid](mailto: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

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 6:31pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/5 "2007-02-08T18:31:11Z")

</div>

Simon S. wrote:

> On 2/8/07, Tapio K. [removed\_email\_address@domain.invalid](mailto: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/](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.

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 9:23pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/6 "2007-02-08T21:23:07Z")

</div>

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/](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.

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 10:56pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/7 "2007-02-08T22:56:19Z")

</div>

> 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)!

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 6:56pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/8 "2007-02-08T18:56:49Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Tapio\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/t/6f9a4e/32.png) [@Tapio\_K](https://www.ruby-forum.com/u/Tapio_K)\
**Post date:** [February 8, 2007, 11:31pm UTC](https://www.ruby-forum.com/t/ruby-as-a-configuration-language/84912/9 "2007-02-08T23:31:22Z")

</div>

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/](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”  
#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”  
}

$ 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(c))  
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.
