[prev in list] [next in list] [prev in thread] [next in thread] 

List:       groovy-user
Subject:    [groovy-user] [ANN] Introducing DSLForge, the Groovy DSL on Rails framework
From:       Zsolt Kovacs <zs.myth () gmail ! com>
Date:       2010-04-27 15:28:53
Message-ID: r2g84e2b6661004270828wc36bed7cud9210588c019111c () mail ! gmail ! com
[Download RAW message or body]

Dear All,

I apologise for the unusually long post but I would like to introduce in
details my DSLForge project, which is based on a real work I did to develop
a functional testing framework for an ecommerce application.

http://github.com/kovax/dslforge

The primary goal is to help DSL development by providing a flexible
framework which enable easy extension and modification of DSLs, and also
supports integration of existing classes.

It is based on the approach described in the A Groovy DSL from scratch in 2
hours <http://groovy.dzone.com/news/groovy-dsl-scratch-2-hours> post
by Steven Devijver, in which each new term in the DSL are delegate to a
method of a specific delegate class. I have found this the neat way of
separating concerns, so I have generalised it a bit further, I have added
some conventions inspired by Grails, and with the help of Groovy's fantastic
MOP support, the project offers you the following features:

*1. Configuration entry to specify Delegate classes*

Add the [dslKey: "selenium", clazz:
com.thoughtworks.selenium.DefaultSelenium]specification to the
dls.delegatesentry (it expects a list of classes or maps) in the
config file, and you can
execute the following script:
*
selenium "localhost", 4444, "*firefox", "http://www.google.com", {
    start
    open "/"
    type "q", "selenium rc"
    click "btnG"
    waitForPageToLoad "30000"
}

*
*DSLEngine<http://github.com/kovax/dslforge/blob/master/src/main/groovy/org/beedom/dslforge/DSLEngine.groovy>takes
 the value of
dslKey and registers a method called selenium in the EMC class of the
script. The method takes the 4 parameters, and calls the constructor of
DefaultSelenum with all of them. In general, the parameters passed the EMC
method are used to construct the delegate class. The dslKey can also be
specified in the delegate class itself. Check the
SeleniumDelagateTests<http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/SeleniumDelagateTests.groovy>
 for
more details, for example see that Groovy handles nesting delegates very
nicely.

2. The DSL can be executed as a groovy Script or Closure*

The DSLEngine has 2 *run()* methods, one takes a name of the script file and
the other takes a Closure. Both updates the DSL instances with the enhanced
EMC.

*3. The execution context (or binding) of the DSL is injected into each
delegate class*

The variable called *context*, instance of the Binding class, is injected
into each delegate class. The DSLEngine can be initialised with an existing
Binding instance, so you have all the possible ways to share data between
different parts of the execution environment. As you very likely know
Binding is used in the Script class by default, so to support context
sharing in Closures the DSLEngine assign its *context* property to the
delegate field of Closure instance. Check the
DSLContextTests<http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/DSLContextTests.groovy>
 from
more details.

*4. Configuration entry to specify Category classes*

The dsl.categories  entry in the config file can specify a list of Category
classes, and the DLSEngine will execute the Script or Closure within the
closure of use(categories) method call. Check the
DecoratorTests<http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/DecoratorTests.groovy>
 for
more details.

*5. Method aliases in delegate classes*

Each delegate class may have a static *aliases* property, which holds a map,
in which you can specify the list of aliases for any methods that the
delegate is implemeneting. This way it is easy to implement the same DSL in
different languages or adjust it for different problem domains. Check the
DelegateTests<http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/DelegateTests.groovy>
 for
more details.

*6. EMC method calls processClosure(closure) of the delegate class*

If you require to take the full control on how the closure is executed in
your DSL implementation, you can define a *processClosure(Closure)* method
in your delegate class. DSLEngine looks for that methods, and if it exists,
it will call it instead of setting up the delegation pattern. It was used in
my functional testing project to setup fixture data in a very user friendly
way. Consider the following DSL snippet from the
AllFuntionalityScript<http://github.com/kovax/dslforge/blob/master/src/test/scripts/AllFuntionalityScript.groovy>
                
.

define {
    user {
        kind = "customer"
        userid = "test@d${timeStamp}d.com"
        password = "hellobaby"
        firstName = "Test"
        lastName = "Customer_${timeStamp}"
        title = "Mr."
        sex = "M"
    }
}
assert customer

The define keyword is associated with the
MetaBuilderDelegate<http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/delegates/MetaBuilderDelegate.groovy>class,
 which has the
*processClosure()* method. MetaBuilder provides a very flexible solution to
populate Beans, and as it can use Closure it integrates very well with
DSLEngine. MetaBuilderDelegate initialise itself from a script containing
the schema definition and a map called objectKeys, and using the
BindingConvention<http://github.com/kovax/dslforge/blob/master/src/main/groovy/org/beedom/dslforge/BindingConvention.groovy>
 it
adds the customer property of User class to the context.

*Future work*

Well, it very much depends on the feedback I hope to get from all of you. I
will certainly use it for my future projects, so I am going to add features
that I need. I think some nice DSL aware error handling could be very
useful, specially with localisation support. Also I would be interested in
developing an eclipse editor which can take the content of delegate classes
to provide code completion and syntax highlight. A Grails plugin can be
done, although a lot of testing is needed to check if many instances if the
same DSL script can be run at the same time.

Thank you for your time for reading this Far Far Away ;)!

Zsolt


[Attachment #3 (text/html)]

<div>Dear All,</div><div><br>I apologise for the unusually long post but I would like \
to introduce in details my DSLForge project, which is  based on a real work I did to \
develop a functional testing framework for an ecommerce application.<br>




<br></div><div><a href="http://github.com/kovax/dslforge" \
target="_blank">http://github.com/kovax/dslforge</a><br> <br></div><div>The primary \
goal is to help DSL development by providing a flexible framework which enable easy \
extension and modification of DSLs, and also supports integration of existing \
classes.</div><div><br></div><div>




It is based on the approach described in the <a \
href="http://groovy.dzone.com/news/groovy-dsl-scratch-2-hours" target="_blank">A \
Groovy DSL from scratch in 2 hours</a> post by  Steven Devijver, in which  each new \
term in the DSL are delegate to a method of a specific delegate class. I have found \
this the  neat way of separating concerns, so I have generalised it a bit further, I \
have added some conventions inspired by Grails, and with the help of Groovy&#39;s \
fantastic MOP support, the project offers you the following features:</div>




<div><br></div><div><b>1. Configuration entry to specify Delegate \
classes</b></div><div><br></div><div>Add the<font face="&#39;courier new&#39;, \
monospace">  [dslKey: &quot;selenium&quot;, clazz: \
com.thoughtworks.selenium.DefaultSelenium]</font>specification to the<font \
face="&#39;courier new&#39;, monospace"> dls.delegates</font> entry (it expects a \
list of classes or maps) in the config file, and you can execute the following \
script:<br>


<b><span style="font-weight:normal"><br><font face="&#39;courier new&#39;, \
monospace">selenium &quot;localhost&quot;, 4444, &quot;*firefox&quot;, &quot;<a \
href="http://www.google.com" target="_blank">http://www.google.com</a>&quot;, {<br>


       start<br>       open &quot;/&quot;<br>       type &quot;q&quot;, \
&quot;selenium rc&quot;<br>       click &quot;btnG&quot;<br>       waitForPageToLoad \
&quot;30000&quot;<br>}<br></font><br></span></b></div><div><b><span \
style="font-weight:normal"><a \
href="http://github.com/kovax/dslforge/blob/master/src/main/groovy/org/beedom/dslforge/DSLEngine.groovy" \
target="_blank">DSLEngine</a> takes the value of <i>dslKey</i> and registers a method \
called selenium in the EMC class of the script. The method takes the 4 parameters, \
and calls the constructor of DefaultSelenum with all of them. In general, the \
parameters passed the EMC method are used to construct the delegate class. The dslKey \
can also be specified in the delegate class itself. Check the  <a \
href="http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/SeleniumDelagateTests.groovy" \
target="_blank">SeleniumDelagateTests</a>  for more details, for example see that \
Groovy handles nesting delegates very nicely.<br>


<br><b>2. The DSL can be executed as a groovy Script or \
Clo</b></span>sure</b></div><div><br></div><div>The DSLEngine has 2 <i>run()</i> \
methods, one takes a name of the script file and the other takes a Closure. Both \
updates the DSL instances with the enhanced EMC.</div>




<div><br></div><div><b>3. The execution context (or binding) of the DSL is injected \
into each delegate class</b></div><div><br></div><div>The variable called \
<i>context</i>, instance of the Binding class, is injected into each delegate class. \
The DSLEngine can be initialised with an existing Binding instance, so you have all \
the possible ways to share data between different parts of the execution environment. \
As you very likely know Binding is used in the Script class by default, so to support \
context sharing in Closures the DSLEngine assign its <i>context</i> property to the \
delegate field of Closure instance. Check the  <a \
href="http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/DSLContextTests.groovy" \
target="_blank">DSLContextTests</a>  from more details.</div>




<div><br></div><div><b>4.  <span \
style="font-weight:normal"><b>Configuration</b></span>  entry to specify Category \
classes</b></div><div><br></div><div>The <font face="&#39;courier new&#39;, \
monospace">dsl.categories</font>    entry in the config file can specify a list of \
Category classes, and the DLSEngine will execute the Script or Closure within the \
closure of use(categories) method call. Check the  <a \
href="http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/DecoratorTests.groovy" \
target="_blank">DecoratorTests</a>  for more details.</div>



<div><br></div><div><b>5. Method aliases in delegate \
classes</b></div><div><br></div><div>Each delegate class may have a static \
<i>aliases</i>  property, which holds a map, in which you can specify the list of \
aliases for any methods that the delegate is implemeneting. This way it is easy to \
implement the same DSL in different languages or adjust it for different problem \
domains. Check the <a \
href="http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/DelegateTests.groovy" \
target="_blank">DelegateTests</a>  for more details.  </div>



<div><br></div><div><b>6. EMC method calls processClosure(closure) of the delegate \
class</b></div><div><br></div><div>If you require to take the full control on how the \
closure is executed in your DSL implementation, you can define a \
<i>processClosure(Closure)</i> method in your delegate class. DSLEngine looks for \
that methods, and if it exists, it will call it instead of setting up the delegation \
pattern. It was used in my functional testing project to setup fixture data in a very \
user friendly way. Consider the following DSL snippet from the  <a \
href="http://github.com/kovax/dslforge/blob/master/src/test/scripts/AllFuntionalityScript.groovy" \
target="_blank">AllFuntionalityScript</a>.</div>



<div><br><font face="&#39;courier new&#39;, monospace">define {<br>       user {<br>  \
kind = &quot;customer&quot;<br>             userid = &quot;test@d${timeStamp}<a \
href="http://d.com" target="_blank">d.com</a>&quot;<br>  password = \
&quot;hellobaby&quot;<br>             firstName = &quot;Test&quot;<br>             \
lastName = &quot;Customer_${timeStamp}&quot;<br>             title = \
&quot;Mr.&quot;<br>             sex = &quot;M&quot;<br>       }</font></div>


<div><font face="&#39;courier new&#39;, monospace">}<br>assert \
customer<br></font><br></div><div>The define keyword is associated with the <a \
href="http://github.com/kovax/dslforge/blob/master/src/test/groovy/org/beedom/dslforge/test/delegates/MetaBuilderDelegate.groovy" \
target="_blank">MetaBuilderDelegate</a> class, which has the <i>processClosure()</i> \
method. MetaBuilder provides a very flexible solution to populate Beans, and as it \
can use Closure it integrates very well with DSLEngine. MetaBuilderDelegate \
initialise itself from a script containing the schema definition and a map called \
objectKeys, and using the  <a \
href="http://github.com/kovax/dslforge/blob/master/src/main/groovy/org/beedom/dslforge/BindingConvention.groovy">BindingConvention</a> \
it adds the customer property of User class to the context.</div>

<div><br></div><div><b>Future work</b></div><div><br></div><div>Well, it very much \
depends on the feedback I hope to get from all of you. I will certainly use it for my \
future projects, so I am going to add features that I need. I think some nice DSL \
aware error handling could be very useful, specially with localisation support. Also \
I would be interested in developing an eclipse editor which can take the content of \
delegate classes to provide code completion and syntax highlight. A Grails plugin can \
be done, although a lot of testing is needed to check if many instances if the same \
DSL script can be run at the same time.</div>

<div><br></div><div>Thank you for your time for reading this Far Far Away \
;)!</div><div><br></div><div>Zsolt</div><div><br></div><div><br></div>

<div><br></div><div><br></div>



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic