2011-12-06 17:58:09 -05:00
|
|
|
# Docile
|
|
|
|
|
|
|
|
Definition: *Ready to accept control or instruction; submissive* [[1]]
|
|
|
|
|
|
|
|
Tired of overly complex DSL libraries and hairy meta-programming?
|
2011-12-06 18:41:43 -05:00
|
|
|
|
|
|
|
Let's make our Ruby DSLs more docile...
|
2011-12-06 17:58:09 -05:00
|
|
|
|
|
|
|
[1]: http://www.google.com/search?q=docile+definition "Google"
|
|
|
|
|
2012-10-28 08:26:37 -04:00
|
|
|
[![Build Status](https://travis-ci.org/ms-ati/docile.png)](https://travis-ci.org/ms-ati/docile)
|
2012-10-28 08:39:12 -04:00
|
|
|
[![Dependency Status](https://gemnasium.com/ms-ati/docile.png)](https://gemnasium.com/ms-ati/docile)
|
2012-10-28 08:26:37 -04:00
|
|
|
|
2012-11-26 15:23:32 -05:00
|
|
|
## Basic Usage
|
2011-12-06 17:58:09 -05:00
|
|
|
|
2012-11-26 15:23:32 -05:00
|
|
|
Let's say that we want to make a DSL for modifying Array objects.
|
|
|
|
Wouldn't it be great if we could just treat the methods of Array as a DSL?
|
2011-12-06 17:58:09 -05:00
|
|
|
|
2012-11-26 15:23:32 -05:00
|
|
|
```ruby
|
|
|
|
with_array([]) do
|
2011-12-06 17:58:09 -05:00
|
|
|
push 1
|
|
|
|
push 2
|
2011-12-06 18:41:43 -05:00
|
|
|
pop
|
|
|
|
push 3
|
2011-12-06 17:58:09 -05:00
|
|
|
end
|
2012-11-26 15:23:32 -05:00
|
|
|
# => [1, 3]
|
2011-12-06 17:58:09 -05:00
|
|
|
```
|
|
|
|
|
2012-11-27 08:09:44 -05:00
|
|
|
No problem, just define the method `with_array` like this:
|
2011-12-06 17:58:09 -05:00
|
|
|
``` ruby
|
2012-11-26 15:23:32 -05:00
|
|
|
def with_array(arr=[], &block)
|
|
|
|
Docile.dsl_eval(arr, &block)
|
|
|
|
end
|
2011-12-06 17:58:09 -05:00
|
|
|
```
|
|
|
|
|
2012-11-26 15:23:32 -05:00
|
|
|
Easy!
|
2011-12-06 17:58:09 -05:00
|
|
|
|
2012-11-26 15:23:32 -05:00
|
|
|
## Advanced Usage
|
|
|
|
|
|
|
|
Mutating (changing) an Array instance is fine, but what usually makes a good DSL is a [Builder Pattern][2].
|
|
|
|
|
|
|
|
For example, let's say you want a DSL to specify how you want to build a Pizza:
|
|
|
|
```ruby
|
2011-12-06 17:58:09 -05:00
|
|
|
@sauce_level = :extra
|
2012-11-26 15:23:32 -05:00
|
|
|
|
|
|
|
pizza do
|
2011-12-06 17:58:09 -05:00
|
|
|
cheese
|
|
|
|
pepperoni
|
|
|
|
sauce @sauce_level
|
2012-11-26 15:23:32 -05:00
|
|
|
end
|
|
|
|
# => #<Pizza:0x00001009dc398 @cheese=true, @pepperoni=true, @bacon=false, @sauce=:extra>
|
|
|
|
```
|
|
|
|
|
|
|
|
And let's say we have a PizzaBuilder, which builds a Pizza like this:
|
|
|
|
```ruby
|
|
|
|
Pizza = Struct.new(:cheese, :pepperoni, :bacon, :sauce)
|
|
|
|
|
|
|
|
class PizzaBuilder
|
|
|
|
def cheese(v=true); @cheese = v; end
|
|
|
|
def pepperoni(v=true); @pepperoni = v; end
|
|
|
|
def bacon(v=true); @bacon = v; end
|
|
|
|
def sauce(v=nil); @sauce = v; end
|
|
|
|
def build
|
|
|
|
Pizza.new(!!@cheese, !!@pepperoni, !!@bacon, @sauce)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-26 15:25:20 -05:00
|
|
|
PizzaBuilder.new.cheese.pepperoni.sauce(:extra).build
|
2011-12-06 18:41:43 -05:00
|
|
|
#=> #<Pizza:0x00001009dc398 @cheese=true, @pepperoni=true, @bacon=false, @sauce=:extra>
|
2011-12-06 17:58:09 -05:00
|
|
|
```
|
|
|
|
|
2012-11-26 15:23:32 -05:00
|
|
|
Then implement your DSL like this:
|
|
|
|
|
|
|
|
``` ruby
|
|
|
|
def pizza(&block)
|
|
|
|
Docile.dsl_eval(PizzaBuilder.new, &block).build
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
It's just that easy!
|
2011-12-06 18:41:43 -05:00
|
|
|
|
2011-12-06 17:58:09 -05:00
|
|
|
[2]: http://stackoverflow.com/questions/328496/when-would-you-use-the-builder-pattern "Builder Pattern"
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
1. method lookup falls back from the DSL object to the block's context
|
|
|
|
2. local variable lookup falls back from the DSL object to the block's context
|
|
|
|
3. instance variables are from the block's context only
|
2011-12-06 18:41:43 -05:00
|
|
|
4. nested dsl evaluation
|
2011-12-06 17:58:09 -05:00
|
|
|
|
|
|
|
## Installation
|
|
|
|
|
|
|
|
``` bash
|
|
|
|
$ gem install docile
|
|
|
|
```
|
|
|
|
|
|
|
|
## Documentation
|
|
|
|
|
|
|
|
Documentation hosted on *rubydoc.info*: [Docile Documentation](http://rubydoc.info/gems/docile)
|
2012-10-28 08:26:37 -04:00
|
|
|
|
2011-12-06 17:58:09 -05:00
|
|
|
Or, read the code hosted on *github.com*: [Docile Code](https://github.com/ms-ati/docile)
|
|
|
|
|
2012-10-28 13:23:46 -04:00
|
|
|
## Status
|
|
|
|
|
2012-10-29 16:24:18 -04:00
|
|
|
Version 1.0 works on [all ruby versions](https://github.com/ms-ati/docile/blob/master/.travis.yml).
|
2012-10-28 13:23:46 -04:00
|
|
|
|
2011-12-06 17:58:09 -05:00
|
|
|
## Note on Patches/Pull Requests
|
|
|
|
|
|
|
|
* Fork the project.
|
|
|
|
* Setup your development environment with: gem install bundler; bundle install
|
|
|
|
* Make your feature addition or bug fix.
|
|
|
|
* Add tests for it. This is important so I don't break it in a
|
|
|
|
future version unintentionally.
|
|
|
|
* Commit, do not mess with rakefile, version, or history.
|
|
|
|
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
|
|
|
* Send me a pull request. Bonus points for topic branches.
|
|
|
|
|
|
|
|
## Copyright
|
|
|
|
|
2012-10-29 16:22:27 -04:00
|
|
|
Copyright (c) 2012 Marc Siegel. See LICENSE for details.
|