From: Joel VanderWerf Date: 2007-02-09T07:30:31+09:00 Subject: Re: Ruby as a configuration language Tapio Kelloniemi 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 >> 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)! 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" #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 #[#, #] 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 # "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. -- vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407