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"
|
|
|
|
|
2013-04-01 01:04:28 -04:00
|
|
|
## Block parameters
|
|
|
|
|
|
|
|
Parameters can be passed to the DSL block.
|
|
|
|
|
|
|
|
Supposing you want to make some sort of cheap [Sinatra][3] knockoff:
|
|
|
|
```ruby
|
|
|
|
@last_request = nil
|
|
|
|
respond '/path' do |request|
|
|
|
|
puts "Request received: #{request}"
|
|
|
|
@last_request = request
|
|
|
|
end
|
|
|
|
|
|
|
|
def ride bike
|
|
|
|
# Play with your new bike
|
|
|
|
end
|
|
|
|
|
|
|
|
respond '/new_bike' do |bike|
|
|
|
|
ride(bike)
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
|
|
|
You'd put together a dispatcher something like this:
|
|
|
|
```ruby
|
|
|
|
require 'singleton'
|
|
|
|
|
|
|
|
class DispatchScope
|
|
|
|
def a_method_you_can_call_from_inside_the_block
|
|
|
|
:useful_huh?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class MessageDispatch
|
|
|
|
include Singleton
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
@responders = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def add_responder path, &block
|
|
|
|
@responders[path] = block
|
|
|
|
end
|
|
|
|
|
|
|
|
def dispatch path, request
|
|
|
|
Docile.dsl_eval(DispatchScope.new, request, &@responders[path])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def respond path, &handler
|
|
|
|
MessageDispatch.instance.add_responder path, handler
|
|
|
|
end
|
|
|
|
|
|
|
|
def send_request path, request
|
|
|
|
MessageDispatch.instance.dispatch path, request
|
|
|
|
end
|
|
|
|
```
|
|
|
|
|
2013-04-01 01:06:13 -04:00
|
|
|
[3]: http://www.sinatrarb.com "Sinatra"
|
|
|
|
|
2011-12-06 17:58:09 -05:00
|
|
|
## 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.
|