domain-specific language

Discussion in 'Ruby' started by Joe Van Dyk, Aug 18, 2005.

  1. Joe Van Dyk

    Joe Van Dyk Guest

    I'm writing an application that controls a group/cluster of linux
    computers/nodes. I need to have a configuration file that lists the
    nodes in the cluster.

    It would be neat if the configuration file was in Ruby. If I had
    nodes named node1 and node3, the configuration file could look a
    little like:

    node :node1 do
    ip 192.whatever
    title "Node 1"
    end

    node :node2 do
    ip 192.whatever
    title "Node 2"
    end

    So, is there some standard Ruby idiom for how to read a file and
    execute the code? Just load the file and instance_eval it?
    Joe Van Dyk, Aug 18, 2005
    #1
    1. Advertising

  2. Joe Van Dyk

    Jamis Buck Guest

    On Aug 18, 2005, at 3:40 PM, Joe Van Dyk wrote:

    > I'm writing an application that controls a group/cluster of linux
    > computers/nodes. I need to have a configuration file that lists the
    > nodes in the cluster.
    >
    > It would be neat if the configuration file was in Ruby. If I had
    > nodes named node1 and node3, the configuration file could look a
    > little like:
    >
    > node :node1 do
    > ip 192.whatever
    > title "Node 1"
    > end
    >
    > node :node2 do
    > ip 192.whatever
    > title "Node 2"
    > end
    >
    > So, is there some standard Ruby idiom for how to read a file and
    > execute the code? Just load the file and instance_eval it?


    That's what SwitchTower does. I'm sure there's many other ways to do
    it, but it works well enough.

    - Jamis
    Jamis Buck, Aug 18, 2005
    #2
    1. Advertising

  3. Joe Van Dyk

    Joe Van Dyk Guest

    On 8/18/05, Jamis Buck <> wrote:
    > On Aug 18, 2005, at 3:40 PM, Joe Van Dyk wrote:
    >=20
    > > I'm writing an application that controls a group/cluster of linux
    > > computers/nodes. I need to have a configuration file that lists the
    > > nodes in the cluster.
    > >
    > > It would be neat if the configuration file was in Ruby. If I had
    > > nodes named node1 and node3, the configuration file could look a
    > > little like:
    > >
    > > node :node1 do
    > > ip 192.whatever
    > > title "Node 1"
    > > end
    > >
    > > node :node2 do
    > > ip 192.whatever
    > > title "Node 2"
    > > end
    > >
    > > So, is there some standard Ruby idiom for how to read a file and
    > > execute the code? Just load the file and instance_eval it?

    >=20
    > That's what SwitchTower does. I'm sure there's many other ways to do
    > it, but it works well enough.


    Thanks! I'll check it out.

    So, say I have the following class

    class ClusterManager

    def load_config_file config_file
    instance_eval File.read(config_file)
    end

    def node node_id, &block
    # What goes here?
    end

    end

    Or should I set it up differently?
    Joe Van Dyk, Aug 19, 2005
    #3
  4. Joe Van Dyk

    Ara.T.Howard Guest

    On Fri, 19 Aug 2005, Joe Van Dyk wrote:

    > I'm writing an application that controls a group/cluster of linux
    > computers/nodes. I need to have a configuration file that lists the
    > nodes in the cluster.
    >
    > It would be neat if the configuration file was in Ruby. If I had
    > nodes named node1 and node3, the configuration file could look a
    > little like:
    >
    > node :node1 do
    > ip 192.whatever
    > title "Node 1"
    > end
    >
    > node :node2 do
    > ip 192.whatever
    > title "Node 2"
    > end
    >
    > So, is there some standard Ruby idiom for how to read a file and
    > execute the code? Just load the file and instance_eval it?



    harp:~ > cat a.rb
    config = <<-config
    nodes :
    1 :
    ip : 192.whatever
    title : node 1
    2 :
    ip : 192.whatever
    title : node 2
    config

    require 'yaml'

    config = YAML::load config

    config['nodes'].each do |nid, node|
    puts "node <#{ nid }> => <#{ node.inspect }>"
    end


    harp:~ > ruby a.rb
    node <1> => <{"title"=>"node 1", "ip"=>"192.whatever"}>
    node <2> => <{"title"=>"node 2", "ip"=>"192.whatever"}>

    so all you have to do is 'YAML::load(IO::read(configfile))'.

    what kind of clustering are you working with?

    you may, or may not, find this useful:

    http://raa.ruby-lang.org/project/rq/
    http://www.linuxjournal.com/article/7922

    cheers.

    -a
    --
    ===============================================================================
    | email :: ara [dot] t [dot] howard [at] noaa [dot] gov
    | phone :: 303.497.6469
    | Your life dwells amoung the causes of death
    | Like a lamp standing in a strong breeze. --Nagarjuna
    ===============================================================================
    Ara.T.Howard, Aug 19, 2005
    #4
  5. Joe Van Dyk

    Jacob Fugal Guest

    > node :node1 do
    > ip 192.whatever
    > title "Node 1"
    > end
    >
    > node :node2 do
    > ip 192.whatever
    > title "Node 2"
    > end


    <snip>

    > class ClusterManager
    >=20
    > def load_config_file config_file
    > instance_eval File.read(config_file)
    > end
    >=20
    > def node node_id, &block
    > # What goes here?
    > end
    >=20
    > end


    I'd think similarly, but a bit different in that
    ClusterManager::Builder should be the one doing the instance eval:

    class ClusterManager
    attr_accessor :nodes

    def initialize
    @nodes =3D []
    end

    def describe
    @nodes.collect{ |node| node.describe }.join("\n")
    end

    class Node
    attr_accessor :id, :ip, :title

    def initialize( id )
    @id =3D id
    end

    def describe
    "#@id -> #@ip \"#@title\""
    end

    class Builder
    def process( node, dsl )
    @node =3D node
    instance_eval &dsl
    end

    def ip( value )
    @node.ip =3D value
    end

    def title( value )
    @node.title =3D value
    end
    end
    end

    class Builder
    def process( manager, dsl )
    @manager =3D manager
    @node_builder =3D Node::Builder.new
    instance_eval &dsl
    end

    def node( node_id, &block )
    node =3D Node.new( node_id )
    @node_builder.process( node, block )
    @manager.nodes << node
    end
    end
    end

    dsl =3D lambda{
    node :node1 do
    ip '192.whatever'
    title 'Node 1'
    end

    node :node2 do
    ip '192.whatever'
    title 'Node 2'
    end
    }

    manager =3D ClusterManager.new
    builder =3D ClusterManager::Builder.new
    builder.process( manager, dsl )

    puts manager.describe

    ###############

    A few caveats about the above:

    1) You'll notice I changed the DSL a little (quoted the 192.whatever
    values). That was simply for brevity in this illustration.

    2) My Builders require the argument to process be lambdas (Procs) not
    strings. This was because I didn't want to complicate things with
    switches on whether to use the prefix & or not. But it would be as
    simple as an if/else to hide the lambda/string distinction from the
    "user" (which will be yourself, not the person writing in the DSL)
    inside the Builder. Alternatively, you can take a string and build it
    into a lambda for the Builder via eval("lambda{ #{dsl} }"). I wouldn't
    necessarily recommend that though (with all the evils of eval).

    Jacob Fugal
    Jacob Fugal, Aug 19, 2005
    #5
  6. Joe Van Dyk wrote:
    > On 8/18/05, Jamis Buck <> wrote:
    >> On Aug 18, 2005, at 3:40 PM, Joe Van Dyk wrote:
    >>
    >>> I'm writing an application that controls a group/cluster of linux
    >>> computers/nodes. I need to have a configuration file that lists the
    >>> nodes in the cluster.
    >>>
    >>> It would be neat if the configuration file was in Ruby. If I had
    >>> nodes named node1 and node3, the configuration file could look a
    >>> little like:
    >>>
    >>> node :node1 do
    >>> ip 192.whatever
    >>> title "Node 1"
    >>> end
    >>>
    >>> node :node2 do
    >>> ip 192.whatever
    >>> title "Node 2"
    >>> end
    >>>
    >>> So, is there some standard Ruby idiom for how to read a file and
    >>> execute the code? Just load the file and instance_eval it?

    >>
    >> That's what SwitchTower does. I'm sure there's many other ways to do
    >> it, but it works well enough.

    >
    > Thanks! I'll check it out.
    >
    > So, say I have the following class
    >
    > class ClusterManager
    >
    > def load_config_file config_file
    > instance_eval File.read(config_file)
    > end
    >
    > def node node_id, &block
    > # What goes here?
    > end
    >
    > end
    >
    > Or should I set it up differently?


    Here's how I'd do it;

    class ClusterManager

    def self.load_config_file config_file
    cm = new
    cm.instance_eval File.read(config_file)
    cm
    end

    def initialize ...
    # and other CM methods

    def node node_id, &block
    # What goes here?
    end

    class Node
    ...
    end
    end

    Then you can do

    cm = ClusterManager.load_config_file "cluster.conf"

    Instead of class Node you could also use Hashes. Then you can have

    node(
    :name => "foo",
    :size => 10,
    ...
    )

    Kind regards

    robert
    Robert Klemme, Aug 19, 2005
    #6
  7. Joe Van Dyk

    Joe Van Dyk Guest

    On 8/18/05, Ara.T.Howard <> wrote:
    > On Fri, 19 Aug 2005, Joe Van Dyk wrote:
    >=20
    > > I'm writing an application that controls a group/cluster of linux
    > > computers/nodes. I need to have a configuration file that lists the
    > > nodes in the cluster.
    > >
    > > It would be neat if the configuration file was in Ruby. If I had
    > > nodes named node1 and node3, the configuration file could look a
    > > little like:
    > >
    > > node :node1 do
    > > ip 192.whatever
    > > title "Node 1"
    > > end
    > >
    > > node :node2 do
    > > ip 192.whatever
    > > title "Node 2"
    > > end
    > >
    > > So, is there some standard Ruby idiom for how to read a file and
    > > execute the code? Just load the file and instance_eval it?

    >=20
    >=20
    > harp:~ > cat a.rb
    > config =3D <<-config
    > nodes :
    > 1 :
    > ip : 192.whatever
    > title : node 1
    > 2 :
    > ip : 192.whatever
    > title : node 2
    > config
    >=20
    > require 'yaml'
    >=20
    > config =3D YAML::load config
    >=20
    > config['nodes'].each do |nid, node|
    > puts "node <#{ nid }> =3D> <#{ node.inspect }>"
    > end
    >=20
    >=20
    > harp:~ > ruby a.rb
    > node <1> =3D> <{"title"=3D>"node 1", "ip"=3D>"192.whatever"}>
    > node <2> =3D> <{"title"=3D>"node 2", "ip"=3D>"192.whatever"}>
    >=20
    > so all you have to do is 'YAML::load(IO::read(configfile))'.
    >=20
    > what kind of clustering are you working with?
    >=20
    > you may, or may not, find this useful:
    >=20
    > http://raa.ruby-lang.org/project/rq/
    > http://www.linuxjournal.com/article/7922


    Thanks for the pointers.

    Essentially, there's a bunch of applications that need to be started
    on a few machines.

    Imagine you have 20 applications. You need to start those
    applications on three different machines (nodes in a cluster). Some
    applications will run on all machines, some applications will only run
    on some machines. All the applications take many (20 or so)
    environment options and command-line arguments and a user needs to be
    able to change those options/arguments.

    The users need to be able to select what application will get run on
    what computer. They need to be able to make sure that the
    applications haven't died, and if they have, to restart the
    application. They need to be able to view log files for the
    applications. Since these are real-time applications, the "cluster
    manager application" that takes requests from the user's GUI and
    distributes them to the computers in the cluster will probably need to
    enforce certain restrictions on the user (i.e. can't run too many
    cpu-intensive applications on one computer).

    And because the applications and their env options and command-line
    args change frequently, depending on what the user's objectives are,
    it needs to be heavily driven by configuration files (that hopefully
    are easy to change). I figure Ruby's syntax would be a good fit for
    the configuration files.

    The current design:

    The GUI. The GUI is populated primarily from configuration files that
    detail the available applications, their environment options and
    command-line arguments.

    The Cluster Manager. This connects the Node Managers to the GUIs.
    Sends start and kill application requests from the GUIs to the Node
    Managers, and sends node status updates from the nodes to the GUIs (so
    the GUIs can see what's going on with each node and their
    applications). Uses XML-RPC for communication (non-ruby clients will
    need to access this).

    The Node Manager. Starts and Kills applications on a node and sends
    status updates (what applications are running, the node load average,
    stuff like that) to the Cluster Manager. Uses DRb for communication
    with the Cluster Manager.
    Joe Van Dyk, Aug 22, 2005
    #7
  8. Joe Van Dyk

    Joe Van Dyk Guest

    On 8/18/05, Jacob Fugal <> wrote:
    > > node :node1 do
    > > ip 192.whatever
    > > title "Node 1"
    > > end
    > >
    > > node :node2 do
    > > ip 192.whatever
    > > title "Node 2"
    > > end

    >=20
    > <snip>
    >=20
    > > class ClusterManager
    > >
    > > def load_config_file config_file
    > > instance_eval File.read(config_file)
    > > end
    > >
    > > def node node_id, &block
    > > # What goes here?
    > > end
    > >
    > > end

    >=20
    > I'd think similarly, but a bit different in that
    > ClusterManager::Builder should be the one doing the instance eval:
    >=20
    > class ClusterManager
    > attr_accessor :nodes
    >=20
    > def initialize
    > @nodes =3D []
    > end
    >=20
    > def describe
    > @nodes.collect{ |node| node.describe }.join("\n")
    > end
    >=20
    > class Node
    > attr_accessor :id, :ip, :title
    >=20
    > def initialize( id )
    > @id =3D id
    > end
    >=20
    > def describe
    > "#@id -> #@ip \"#@title\""
    > end
    >=20
    > class Builder
    > def process( node, dsl )
    > @node =3D node
    > instance_eval &dsl
    > end
    >=20
    > def ip( value )
    > @node.ip =3D value
    > end
    >=20
    > def title( value )
    > @node.title =3D value
    > end
    > end
    > end
    >=20
    > class Builder
    > def process( manager, dsl )
    > @manager =3D manager
    > @node_builder =3D Node::Builder.new
    > instance_eval &dsl
    > end
    >=20
    > def node( node_id, &block )
    > node =3D Node.new( node_id )
    > @node_builder.process( node, block )
    > @manager.nodes << node
    > end
    > end
    > end
    >=20
    > dsl =3D lambda{
    > node :node1 do
    > ip '192.whatever'
    > title 'Node 1'
    > end
    >=20
    > node :node2 do
    > ip '192.whatever'
    > title 'Node 2'
    > end
    > }
    >=20
    > manager =3D ClusterManager.new
    > builder =3D ClusterManager::Builder.new
    > builder.process( manager, dsl )
    >=20
    > puts manager.describe
    >=20
    > ###############
    >=20
    > A few caveats about the above:
    >=20
    > 1) You'll notice I changed the DSL a little (quoted the 192.whatever
    > values). That was simply for brevity in this illustration.
    >=20
    > 2) My Builders require the argument to process be lambdas (Procs) not
    > strings. This was because I didn't want to complicate things with
    > switches on whether to use the prefix & or not. But it would be as
    > simple as an if/else to hide the lambda/string distinction from the
    > "user" (which will be yourself, not the person writing in the DSL)
    > inside the Builder. Alternatively, you can take a string and build it
    > into a lambda for the Builder via eval("lambda{ #{dsl} }"). I wouldn't
    > necessarily recommend that though (with all the evils of eval).


    Thank you for your ideas. I've adapted it somewhat for parts of my
    application, but I'm running into problems.

    I've got the following configuration file:

    option :display do
    display :text, :size =3D> 20, :title =3D> "DISPLAY"
    value :DISPLAY, :default =3D> ENV['DISPLAY'] || 'localhost:0'
    end

    argument :xterm_title do
    display :text, :size =3D> 20, :title =3D> "Xterm Title"
    value "-T", :default =3D> "You are on a xterm!"
    end

    argument :xterm_text_color do
    display :text, :size =3D> 20, :title =3D> "Xterm Font Color"
    value "-fg", :default =3D> "red"
    end


    application :xterm do=20
    executable "/usr/X11R6/bin/xterm"
    title :Xterm
    node :fatire
    options :display
    arguments :xterm_text_color, :xterm_title
    end

    application :xeyes do
    executable "/usr/X11R6/bin/xeyes"
    title :Xeyes
    node :fatire
    options :display
    end


    Should be self-explanatory. I have some applications that take env
    options and command-line arguments, and I want to have a very readable
    and configurable file for defining those applications, options, and
    arguments (and some other things). The 'node' option for the
    application is the machine that the application should be started on.=20
    The 'display' option for the options/arguments state how the GUI
    should display the option/argument.

    My problem is trying to adapt your solution to fit something like
    this. There would be a lot of duplication if I had Builders for
    applications, nodes, options, and arguments. And I'm also having some
    difficulties getting each Application object to know what options and
    arguments it should have.

    I considered using YAML for the configuration file format, but I'm
    still leaning towards having a pure Ruby file.

    Thoughts are greatly appreciated! This is my first time trying to do
    this type of programming, so it's a little weird.
    Joe Van Dyk, Sep 7, 2005
    #8
  9. Joe Van Dyk

    Joe Van Dyk Guest

    On 9/7/05, Joe Van Dyk <> wrote:
    > On 8/18/05, Jacob Fugal <> wrote:
    > > > node :node1 do
    > > > ip 192.whatever
    > > > title "Node 1"
    > > > end
    > > >
    > > > node :node2 do
    > > > ip 192.whatever
    > > > title "Node 2"
    > > > end

    > >
    > > <snip>
    > >
    > > > class ClusterManager
    > > >
    > > > def load_config_file config_file
    > > > instance_eval File.read(config_file)
    > > > end
    > > >
    > > > def node node_id, &block
    > > > # What goes here?
    > > > end
    > > >
    > > > end

    > >
    > > I'd think similarly, but a bit different in that
    > > ClusterManager::Builder should be the one doing the instance eval:
    > >
    > > class ClusterManager
    > > attr_accessor :nodes
    > >
    > > def initialize
    > > @nodes =3D []
    > > end
    > >
    > > def describe
    > > @nodes.collect{ |node| node.describe }.join("\n")
    > > end
    > >
    > > class Node
    > > attr_accessor :id, :ip, :title
    > >
    > > def initialize( id )
    > > @id =3D id
    > > end
    > >
    > > def describe
    > > "#@id -> #@ip \"#@title\""
    > > end
    > >
    > > class Builder
    > > def process( node, dsl )
    > > @node =3D node
    > > instance_eval &dsl
    > > end
    > >
    > > def ip( value )
    > > @node.ip =3D value
    > > end
    > >
    > > def title( value )
    > > @node.title =3D value
    > > end
    > > end
    > > end
    > >
    > > class Builder
    > > def process( manager, dsl )
    > > @manager =3D manager
    > > @node_builder =3D Node::Builder.new
    > > instance_eval &dsl
    > > end
    > >
    > > def node( node_id, &block )
    > > node =3D Node.new( node_id )
    > > @node_builder.process( node, block )
    > > @manager.nodes << node
    > > end
    > > end
    > > end
    > >
    > > dsl =3D lambda{
    > > node :node1 do
    > > ip '192.whatever'
    > > title 'Node 1'
    > > end
    > >
    > > node :node2 do
    > > ip '192.whatever'
    > > title 'Node 2'
    > > end
    > > }
    > >
    > > manager =3D ClusterManager.new
    > > builder =3D ClusterManager::Builder.new
    > > builder.process( manager, dsl )
    > >
    > > puts manager.describe
    > >
    > > ###############
    > >
    > > A few caveats about the above:
    > >
    > > 1) You'll notice I changed the DSL a little (quoted the 192.whatever
    > > values). That was simply for brevity in this illustration.
    > >
    > > 2) My Builders require the argument to process be lambdas (Procs) not
    > > strings. This was because I didn't want to complicate things with
    > > switches on whether to use the prefix & or not. But it would be as
    > > simple as an if/else to hide the lambda/string distinction from the
    > > "user" (which will be yourself, not the person writing in the DSL)
    > > inside the Builder. Alternatively, you can take a string and build it
    > > into a lambda for the Builder via eval("lambda{ #{dsl} }"). I wouldn't
    > > necessarily recommend that though (with all the evils of eval).

    >=20
    > Thank you for your ideas. I've adapted it somewhat for parts of my
    > application, but I'm running into problems.
    >=20
    > I've got the following configuration file:
    >=20
    > option :display do
    > display :text, :size =3D> 20, :title =3D> "DISPLAY"
    > value :DISPLAY, :default =3D> ENV['DISPLAY'] || 'localhost:0'
    > end
    >=20
    > argument :xterm_title do
    > display :text, :size =3D> 20, :title =3D> "Xterm Title"
    > value "-T", :default =3D> "You are on a xterm!"
    > end
    >=20
    > argument :xterm_text_color do
    > display :text, :size =3D> 20, :title =3D> "Xterm Font Color"
    > value "-fg", :default =3D> "red"
    > end
    >=20
    >=20
    > application :xterm do
    > executable "/usr/X11R6/bin/xterm"
    > title :Xterm
    > node :fatire
    > options :display
    > arguments :xterm_text_color, :xterm_title
    > end
    >=20
    > application :xeyes do
    > executable "/usr/X11R6/bin/xeyes"
    > title :Xeyes
    > node :fatire
    > options :display
    > end
    >=20
    >=20
    > Should be self-explanatory. I have some applications that take env
    > options and command-line arguments, and I want to have a very readable
    > and configurable file for defining those applications, options, and
    > arguments (and some other things). The 'node' option for the
    > application is the machine that the application should be started on.
    > The 'display' option for the options/arguments state how the GUI
    > should display the option/argument.
    >=20
    > My problem is trying to adapt your solution to fit something like
    > this. There would be a lot of duplication if I had Builders for
    > applications, nodes, options, and arguments. And I'm also having some
    > difficulties getting each Application object to know what options and
    > arguments it should have.
    >=20
    > I considered using YAML for the configuration file format, but I'm
    > still leaning towards having a pure Ruby file.
    >=20
    > Thoughts are greatly appreciated! This is my first time trying to do
    > this type of programming, so it's a little weird.
    >=20



    I would also like to be able to 'group' things together, like

    group :xterm_options_arguments do
    arguments :xterm_title, :xterm_text_color
    options :display
    end

    application :xterm do
    group :xterm_options_arguments
    title :Xterm
    ...=20
    end

    So that applications with common options and arguments can specify a groupi=
    ng.

    (btw, an 'argument' is a command-line argument, like 'ls -F'... the -F
    is an argument. An option is something like "DISPLAY=3Dmy_machine:1
    xeyes".. the DISPLAY is an option).
    Joe Van Dyk, Sep 7, 2005
    #9
  10. Joe Van Dyk

    Joe Van Dyk Guest

    On 9/7/05, Joe Van Dyk <> wrote:
    > On 9/7/05, Joe Van Dyk <> wrote:
    > > On 8/18/05, Jacob Fugal <> wrote:
    > > > > node :node1 do
    > > > > ip 192.whatever
    > > > > title "Node 1"
    > > > > end
    > > > >
    > > > > node :node2 do
    > > > > ip 192.whatever
    > > > > title "Node 2"
    > > > > end
    > > >
    > > > <snip>
    > > >
    > > > > class ClusterManager
    > > > >
    > > > > def load_config_file config_file
    > > > > instance_eval File.read(config_file)
    > > > > end
    > > > >
    > > > > def node node_id, &block
    > > > > # What goes here?
    > > > > end
    > > > >
    > > > > end
    > > >
    > > > I'd think similarly, but a bit different in that
    > > > ClusterManager::Builder should be the one doing the instance eval:
    > > >
    > > > class ClusterManager
    > > > attr_accessor :nodes
    > > >
    > > > def initialize
    > > > @nodes =3D []
    > > > end
    > > >
    > > > def describe
    > > > @nodes.collect{ |node| node.describe }.join("\n")
    > > > end
    > > >
    > > > class Node
    > > > attr_accessor :id, :ip, :title
    > > >
    > > > def initialize( id )
    > > > @id =3D id
    > > > end
    > > >
    > > > def describe
    > > > "#@id -> #@ip \"#@title\""
    > > > end
    > > >
    > > > class Builder
    > > > def process( node, dsl )
    > > > @node =3D node
    > > > instance_eval &dsl
    > > > end
    > > >
    > > > def ip( value )
    > > > @node.ip =3D value
    > > > end
    > > >
    > > > def title( value )
    > > > @node.title =3D value
    > > > end
    > > > end
    > > > end
    > > >
    > > > class Builder
    > > > def process( manager, dsl )
    > > > @manager =3D manager
    > > > @node_builder =3D Node::Builder.new
    > > > instance_eval &dsl
    > > > end
    > > >
    > > > def node( node_id, &block )
    > > > node =3D Node.new( node_id )
    > > > @node_builder.process( node, block )
    > > > @manager.nodes << node
    > > > end
    > > > end
    > > > end
    > > >
    > > > dsl =3D lambda{
    > > > node :node1 do
    > > > ip '192.whatever'
    > > > title 'Node 1'
    > > > end
    > > >
    > > > node :node2 do
    > > > ip '192.whatever'
    > > > title 'Node 2'
    > > > end
    > > > }
    > > >
    > > > manager =3D ClusterManager.new
    > > > builder =3D ClusterManager::Builder.new
    > > > builder.process( manager, dsl )
    > > >
    > > > puts manager.describe
    > > >
    > > > ###############
    > > >
    > > > A few caveats about the above:
    > > >
    > > > 1) You'll notice I changed the DSL a little (quoted the 192.whatever
    > > > values). That was simply for brevity in this illustration.
    > > >
    > > > 2) My Builders require the argument to process be lambdas (Procs) not
    > > > strings. This was because I didn't want to complicate things with
    > > > switches on whether to use the prefix & or not. But it would be as
    > > > simple as an if/else to hide the lambda/string distinction from the
    > > > "user" (which will be yourself, not the person writing in the DSL)
    > > > inside the Builder. Alternatively, you can take a string and build it
    > > > into a lambda for the Builder via eval("lambda{ #{dsl} }"). I wouldn'=

    t
    > > > necessarily recommend that though (with all the evils of eval).

    > >
    > > Thank you for your ideas. I've adapted it somewhat for parts of my
    > > application, but I'm running into problems.
    > >
    > > I've got the following configuration file:
    > >
    > > option :display do
    > > display :text, :size =3D> 20, :title =3D> "DISPLAY"
    > > value :DISPLAY, :default =3D> ENV['DISPLAY'] || 'localhost:0'
    > > end
    > >
    > > argument :xterm_title do
    > > display :text, :size =3D> 20, :title =3D> "Xterm Title"
    > > value "-T", :default =3D> "You are on a xterm!"
    > > end
    > >
    > > argument :xterm_text_color do
    > > display :text, :size =3D> 20, :title =3D> "Xterm Font Color"
    > > value "-fg", :default =3D> "red"
    > > end
    > >
    > >
    > > application :xterm do
    > > executable "/usr/X11R6/bin/xterm"
    > > title :Xterm
    > > node :fatire
    > > options :display
    > > arguments :xterm_text_color, :xterm_title
    > > end
    > >
    > > application :xeyes do
    > > executable "/usr/X11R6/bin/xeyes"
    > > title :Xeyes
    > > node :fatire
    > > options :display
    > > end
    > >
    > >
    > > Should be self-explanatory. I have some applications that take env
    > > options and command-line arguments, and I want to have a very readable
    > > and configurable file for defining those applications, options, and
    > > arguments (and some other things). The 'node' option for the
    > > application is the machine that the application should be started on.
    > > The 'display' option for the options/arguments state how the GUI
    > > should display the option/argument.
    > >
    > > My problem is trying to adapt your solution to fit something like
    > > this. There would be a lot of duplication if I had Builders for
    > > applications, nodes, options, and arguments. And I'm also having some
    > > difficulties getting each Application object to know what options and
    > > arguments it should have.
    > >
    > > I considered using YAML for the configuration file format, but I'm
    > > still leaning towards having a pure Ruby file.
    > >
    > > Thoughts are greatly appreciated! This is my first time trying to do
    > > this type of programming, so it's a little weird.
    > >

    >=20
    >=20
    > I would also like to be able to 'group' things together, like
    >=20
    > group :xterm_options_arguments do
    > arguments :xterm_title, :xterm_text_color
    > options :display
    > end
    >=20
    > application :xterm do
    > group :xterm_options_arguments
    > title :Xterm
    > ...
    > end
    >=20
    > So that applications with common options and arguments can specify a grou=

    ping.
    >=20
    > (btw, an 'argument' is a command-line argument, like 'ls -F'... the -F
    > is an argument. An option is something like "DISPLAY=3Dmy_machine:1
    > xeyes".. the DISPLAY is an option).


    And grouping of applications would also be nice:

    application_group :all_x_apps
    applications :xterm, :xeyes
    display :checkbox, :title =3D> "Start all X applications"
    end

    So, in the GUI, there would be a checkbox titled "Start all
    applications" that would start the xterm and xeyes applications.

    In other words, I'd like to be able to do grouping of various things
    in this configuration file.
    Joe Van Dyk, Sep 7, 2005
    #10
  11. Joe Van Dyk

    Joe Van Dyk Guest

    > > > Thank you for your ideas. I've adapted it somewhat for parts of my
    > > > application, but I'm running into problems.
    > > >
    > > > I've got the following configuration file:
    > > >
    > > > option :display do
    > > > display :text, :size =3D> 20, :title =3D> "DISPLAY"
    > > > value :DISPLAY, :default =3D> ENV['DISPLAY'] || 'localhost:0'
    > > > end
    > > >
    > > > argument :xterm_title do
    > > > display :text, :size =3D> 20, :title =3D> "Xterm Title"
    > > > value "-T", :default =3D> "You are on a xterm!"
    > > > end
    > > >
    > > > argument :xterm_text_color do
    > > > display :text, :size =3D> 20, :title =3D> "Xterm Font Color"
    > > > value "-fg", :default =3D> "red"
    > > > end
    > > >
    > > >
    > > > application :xterm do
    > > > executable "/usr/X11R6/bin/xterm"
    > > > title :Xterm
    > > > node :fatire
    > > > options :display
    > > > arguments :xterm_text_color, :xterm_title
    > > > end
    > > >
    > > > application :xeyes do
    > > > executable "/usr/X11R6/bin/xeyes"
    > > > title :Xeyes
    > > > node :fatire
    > > > options :display
    > > > end
    > > >
    > > >
    > > > Should be self-explanatory. I have some applications that take env
    > > > options and command-line arguments, and I want to have a very readabl=

    e
    > > > and configurable file for defining those applications, options, and
    > > > arguments (and some other things). The 'node' option for the
    > > > application is the machine that the application should be started on.
    > > > The 'display' option for the options/arguments state how the GUI
    > > > should display the option/argument.
    > > >
    > > > My problem is trying to adapt your solution to fit something like
    > > > this. There would be a lot of duplication if I had Builders for
    > > > applications, nodes, options, and arguments. And I'm also having som=

    e
    > > > difficulties getting each Application object to know what options and
    > > > arguments it should have.
    > > >
    > > > I considered using YAML for the configuration file format, but I'm
    > > > still leaning towards having a pure Ruby file.
    > > >
    > > > Thoughts are greatly appreciated! This is my first time trying to do
    > > > this type of programming, so it's a little weird.
    > > >

    > >
    > >
    > > I would also like to be able to 'group' things together, like
    > >
    > > group :xterm_options_arguments do
    > > arguments :xterm_title, :xterm_text_color
    > > options :display
    > > end
    > >
    > > application :xterm do
    > > group :xterm_options_arguments
    > > title :Xterm
    > > ...
    > > end
    > >
    > > So that applications with common options and arguments can specify a gr=

    ouping.
    > >
    > > (btw, an 'argument' is a command-line argument, like 'ls -F'... the -F
    > > is an argument. An option is something like "DISPLAY=3Dmy_machine:1
    > > xeyes".. the DISPLAY is an option).

    >=20
    > And grouping of applications would also be nice:
    >=20
    > application_group :all_x_apps
    > applications :xterm, :xeyes
    > display :checkbox, :title =3D> "Start all X applications"
    > end
    >=20
    > So, in the GUI, there would be a checkbox titled "Start all
    > applications" that would start the xterm and xeyes applications.
    >=20
    > In other words, I'd like to be able to do grouping of various things
    > in this configuration file.



    Here's the start of what I have so far. How's it look?

    require 'test/unit'

    class ConfigLoader
    attr_accessor :configuration, :applications, :eek:ptions

    def initialize
    @applications =3D Hash.new
    @options =3D Hash.new
    end

    def process configuration
    instance_eval configuration
    end

    def get_application_options application_id
    @options.values.find_all do |option|=20
    if option.option_type =3D=3D :env_option
    @applications[application_id].options.include? option.option_id
    end
    end
    end

    def get_application_arguments application_id
    @options.values.find_all do |option|=20
    if option.option_type =3D=3D :command_line_argument
    @applications[application_id].options.include? option.option_id
    end
    end
    end

    def application application_id, &block
    application =3D Application.new application_id
    @applications[application_id] =3D application=20
    application.instance_eval &block
    end

    def option option_id, &block
    option =3D Option.new option_id, :env_option
    @options[option_id] =3D option
    option.instance_eval &block
    end

    def argument option_id, &block
    option =3D Option.new option_id, :command_line_argument
    @options[option_id] =3D option
    option.instance_eval &block
    end
    class Option
    attr_accessor :name, :value, :default, :eek:ption_id, :eek:ption_type
    def initialize option_id, option_type
    @option_id =3D option_id
    @option_type =3D option_type
    @name =3D nil
    @value =3D nil
    end

    def set name, args
    @name =3D name
    @value =3D @default =3D args[:default]
    end
    end

    class Application
    attr_accessor :executable, :application_id, :title, :node, :eek:ptions
    def initialize application_id
    @application_id =3D application_id
    @executable =3D nil
    @title =3D nil
    @node =3D nil
    @options =3D []
    end

    def executable value=3Dnil
    @executable =3D value if value
    @executable
    end

    def title value=3Dnil
    @title =3D value if value
    @title
    end

    def node value=3Dnil
    @node =3D value if value
    @node
    end

    def options *args
    @options =3D args if args.size > 0
    @options
    end

    end
    end

    class TestConfigLoader < Test::Unit::TestCase

    def test_process
    a =3D ConfigLoader.new
    a.process sample_configuration

    # Test to see that the application stuff was set ok.
    assert_equal "/usr/X11R6/bin/xeyes", a.applications[:xeyes].executable
    assert_equal [:display], a.applications[:xeyes].options
    assert_equal :fatire, a.applications[:xeyes].node
    assert_equal :Xeyes, a.applications[:xeyes].title

    # Test to see that the options were set ok.
    assert_equal :DISPLAY, a.options[:display].name
    # Not sure how to properly test these... they depend on your env=20
    # and if your DISPLAY is not set, then it should be something else=20
    # ('localhost:0'). So I'd be duplicating logic in the tests.
    #assert_equal "vandyk-j:0", a.options[:display].default
    #assert_equal "vandyk-j:0", a.options[:display].value

    # Test to see that the env options are ok.
    assert_equal [a.options[:display]], a.get_application_options:)xeyes)
    assert_equal [a.options[:display]], a.get_application_options:)xterm)

    # Test to see that the command line arguments are ok.
    assert_equal [a.options[:xterm_font_color], a.options[:xterm_title]],=
    =20
    a.get_application_arguments:)xterm)
    end


    # A sample configuration file
    def sample_configuration
    <<-EOF
    option :display do
    #display :text, :size =3D> 20, :title =3D> "DISPLAY"
    set :DISPLAY, :default =3D> ENV['DISPLAY'] || 'localhost:0'
    end

    argument :xterm_title do
    #display :text, :size =3D> 20, :title =3D> "Xterm Title"
    set "-T", :default =3D> "You are on a xterm!"
    end

    argument :xterm_font_color do
    #display :text, :size =3D> 20, :title =3D> "Xterm Font Color"
    set "-fg", :default =3D> "red"
    end

    application :xterm do=20
    executable "/usr/X11R6/bin/xterm"
    title :Xterm
    node :fatire
    options :display, :xterm_font_color, :xterm_title
    end

    application :xeyes do
    executable "/usr/X11R6/bin/xeyes"
    title :Xeyes
    node :fatire
    options :display
    end
    EOF
    end
    end
    Joe Van Dyk, Sep 7, 2005
    #11
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    8
    Views:
    144
    Michael Neumann
    Feb 16, 2005
  2. Replies:
    1
    Views:
    199
    James Britt
    Oct 31, 2005
  3. John Miller
    Replies:
    0
    Views:
    104
    John Miller
    Sep 8, 2006
  4. Rustem Zhunusov

    Ruby 2.0 and Domain-Specific language

    Rustem Zhunusov, Feb 14, 2008, in forum: Ruby
    Replies:
    2
    Views:
    118
    Etienne Vallette d'Osia
    Feb 14, 2008
  5. Zd Yu
    Replies:
    6
    Views:
    105
    Brian Candler
    Dec 20, 2010
Loading...

Share This Page