Ruby as a configuration language

Discussion in 'Ruby' started by Tapio Kelloniemi, Feb 8, 2007.

  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!
     
    Tapio Kelloniemi, Feb 8, 2007
    #1
    1. Advertisements

  2. Tapio Kelloniemi

    Phrogz Guest

    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.
    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, :eek:riginalname, :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
     
    Phrogz, Feb 8, 2007
    #2
    1. Advertisements

  3. [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/
     
    Simon Strandgaard, Feb 8, 2007
    #3
  4. Tapio Kelloniemi

    Phrogz Guest

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

    class Archive
    attr_accessor :localname, :eek:riginalname, :convert
    def initialize
    @baseurls = []
    end
    def baseurl( url )
    @baseurls << url
    end
    end
     
    Phrogz, Feb 8, 2007
    #4
  5. 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.
     
    Suraj Kurapati, Feb 8, 2007
    #5
  6. Tapio Kelloniemi

    Peter Booth Guest

    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.=20

    -----Original Message-----
    From: Suraj Kurapati [mailto:]=20
    Sent: Thursday, February 08, 2007 12:31 PM
    To:
    Subject: Re: Ruby as a configuration language

    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.

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

    ------------------------------------------------------------------------
    -----------------------------------------------------------.
    =2EThe information contained in and accompanying this communication is
    strictly confidential and intended solely for the use of the intended
    recipient(s).
    If you have received it by mistake please let us know by reply and then
    delete it from your system; you should not copy the message or disclose
    its content to anyone.
    MarketAxess reserves the right to monitor the content of emails sent to
    or from its systems.
    Any comments or statements made are not necessarily those of
    MarketAxess. For more information, please visit www.marketaxess.com.
    MarketAxess Europe Limited is regulated in the UK by the FSA, registered
    in England no. 4017610, registered office at 71 Fenchurch Street,
    London, EC3M 4BS. Telephone (020) 7709 3100.
    MarketAxess Corporation is regulated in the USA by the SEC and the NASD,
    incorporated in Delaware, executive offices at 140 Broadway, New York,
    NY 10005. Telephone (1) 212 813 6000.
    ----------------------------------------------------------
    The information contained in and accompanying this communication is stric=
    tly confidential and intended solely for the use of the intended recipien=
    t(s).

    If you have received it by mistake please let us know by reply and then d=
    elete it from your system; you should not copy the message or disclose it=
    s content to anyone.

    MarketAxess reserves the right to monitor the content of emails sent to o=
    r from its systems.

    Any comments or statements made are not necessarily those of MarketAxess.=
    For more information, please visit www.marketaxess.com. MarketAxess Euro=
    pe Limited is regulated in the UK by the FSA, registered in England no. 4=
    017610, registered office at 71 Fenchurch Street, London, EC3M 4BS. Telep=
    hone (020) 7709 3100.

    MarketAxess Corporation is regulated in the USA by the SEC and the NASD, =
    incorporated in Delaware, executive offices at 140 Broadway, New York, NY=
    10005. Telephone (1) 212 813 6000.
     
    Peter Booth, Feb 8, 2007
    #6
  7. 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.
     
    Brian Candler, Feb 8, 2007
    #7
  8. I'm writing an automated software building framework in Ruby. The software
    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)!
     
    Tapio Kelloniemi, Feb 8, 2007
    #8
  9. 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
    #<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.
     
    Joel VanderWerf, Feb 8, 2007
    #9
    1. Advertisements

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 (here). After that, you can post your question and our members will help you out.