iron-dsl 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/History.txt +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +149 -0
- data/Version.txt +1 -0
- data/lib/iron/dsl.rb +5 -0
- data/lib/iron/dsl/class.rb +46 -0
- data/lib/iron/dsl/dsl_builder.rb +14 -0
- data/lib/iron/dsl/dsl_proxy.rb +176 -0
- data/spec/dsl/class_spec.rb +33 -0
- data/spec/dsl/dsl_builder_spec.rb +29 -0
- data/spec/dsl/dsl_proxy_spec.rb +110 -0
- data/spec/spec_helper.rb +11 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 58e6a28f277a7fb6305e7ffb268f26d2f737c0e2
|
4
|
+
data.tar.gz: 2e08931bde299f5ac6f84f93f924b3e4beecf85f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 51b0a8cea42976e123c25471d8558cdea7e4f53ecd8d47de187d9551f87ec4f55429087fd9826f893ce18e729cfb3baffe344b6f942772f2d8fe28b8a0748bb4
|
7
|
+
data.tar.gz: 55e093337f7a637640d4d99acb6b4880be3515aabc0f949c23fb8ceaf5ea7a043dbdb62bc4a46145d1e8b4691a3c7e71fd02507da0ce5aea39384abb1c2ba352
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require <%= File.join(File.expand_path(File.dirname(__FILE__)), 'spec', 'spec_helper.rb') %>
|
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Irongaze Consulting LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
'Software'), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
= GEM: iron-dsl
|
2
|
+
|
3
|
+
Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
The iron-dsl gem provides a set of powerful tools for building "domain-specific languages"
|
8
|
+
in Ruby. Ruby's natural DSL construction capabilities (through, e.g. instance_eval) are
|
9
|
+
very solid, but to make truly clean DSLs requires additional magic. This gem provides
|
10
|
+
that magic in a nice self-contained package.
|
11
|
+
|
12
|
+
== USAGE
|
13
|
+
|
14
|
+
There are 3 main pieces to this gem: DslBuilder, a set of accessor helpers, and DslProxy.
|
15
|
+
|
16
|
+
DslBuilder is simply an empty class, suitable for use as a base class for your DSL receiver class.
|
17
|
+
It is similar to BasicObject in the standard library, but has methods such as #respond_to?
|
18
|
+
and #send that are required for any real DSL building effort.
|
19
|
+
|
20
|
+
You can use DslBuilder, or any other class, as the basis for your DSL system. In any case,
|
21
|
+
you want a clean way to set attributes on an instance of that class. For that, we have
|
22
|
+
two class-level methods: #dsl_accessor and #dsl_flag
|
23
|
+
|
24
|
+
The first, #dsl_accessor, is a helpful method for defining accessors on DSL builder-style classes:
|
25
|
+
|
26
|
+
require 'iron/dsl'
|
27
|
+
|
28
|
+
class MyBuilder < DslBuilder
|
29
|
+
# Declare an accessor on this receiver class, just like you'd use attr_accessor
|
30
|
+
dsl_accessor :name
|
31
|
+
end
|
32
|
+
|
33
|
+
# When you create an instance, you have a set of behavior for the #name accessor you declared
|
34
|
+
builder = MyBuilder.new
|
35
|
+
|
36
|
+
# You can set a value by calling #name as a setter, and get the value by using #name as a getter
|
37
|
+
builder.name = 'ProjectX'
|
38
|
+
builder.name # => 'ProjectX'
|
39
|
+
|
40
|
+
# But you can also set the value by simply calling #name with the value to set:
|
41
|
+
builder.name 'ProjectY'
|
42
|
+
builder.name # => 'ProjectY'
|
43
|
+
|
44
|
+
# This makes for a cleaner syntax when using your DSL with DslProxy#exec below..
|
45
|
+
DslProxy.exec(builder) do
|
46
|
+
name 'Project Omega'
|
47
|
+
end
|
48
|
+
builder.name # => 'Project Omega'
|
49
|
+
|
50
|
+
# You can also capture blocks this way, which is often useful in DSL creation for values
|
51
|
+
# that need to be dynamically calculated at run-time
|
52
|
+
builder.name do
|
53
|
+
"Project " + Date.today
|
54
|
+
end
|
55
|
+
|
56
|
+
The second accessor helper is #dsl_flag, which is the same as #dsl_accessor, but designed for
|
57
|
+
boolean values.
|
58
|
+
|
59
|
+
class AnotherBuilder < DslBuilder
|
60
|
+
dsl_flag :awesome
|
61
|
+
end
|
62
|
+
|
63
|
+
builder = AnotherBuilder
|
64
|
+
builder.awesome? # => false on uninitialized value
|
65
|
+
builder.awesome! # => sets @awesome to true
|
66
|
+
# dsl_flags can still be set normally
|
67
|
+
builder.awesome true
|
68
|
+
builder.awesome false
|
69
|
+
|
70
|
+
Bringing it all together, and the key to the whole system, is DslProxy. DslProxy is a more powerful version of
|
71
|
+
#instance_exec that handles instance variable propagation and other nifty tricks like nesting and propagating
|
72
|
+
method references and constant lookups to the calling scope. That all sounds like gibberish, so here's a few
|
73
|
+
hopefully illustrative examples.
|
74
|
+
|
75
|
+
@name = 'Bob'
|
76
|
+
|
77
|
+
# First, how you would traditionally do it:
|
78
|
+
instance_exec(some_receiver) do
|
79
|
+
# This fails - the instance var from the calling context is not defined. Sucks if you're in Rails
|
80
|
+
# and trying to define something in a controller or view, where all the state is typically in
|
81
|
+
# instance vars!
|
82
|
+
self.name = @name
|
83
|
+
end
|
84
|
+
|
85
|
+
# This, however, totally works
|
86
|
+
DslProxy.exec(some_receiver) do
|
87
|
+
# @name has bubbled into our block and can be referenced!
|
88
|
+
self.name = @name
|
89
|
+
# But of course, we'd use a dsl_accessor so we could lose the 'self.' and the '='
|
90
|
+
name @name
|
91
|
+
|
92
|
+
# Having nested DSLs is also supported, all instance vars are available at all levels
|
93
|
+
sub_define do
|
94
|
+
page_title @name + ' Likes Bees'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
In summary, making DSLs is a bit of an art, and what this gem attempts to do is make pretty DSLs like this
|
99
|
+
easier to build:
|
100
|
+
|
101
|
+
grid = Grid.define do
|
102
|
+
url '/orders/grid'
|
103
|
+
souce Order.by_date
|
104
|
+
|
105
|
+
columns do
|
106
|
+
column :id
|
107
|
+
column :customer do
|
108
|
+
no_wrap!
|
109
|
+
column :total do
|
110
|
+
render_as :currency
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
pagination do
|
115
|
+
default 40
|
116
|
+
allow_custom!
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
Using code to configure complex systems (rather than hashes of hashes, or manually constructed settings objects)
|
121
|
+
allows for a much more expressive codebase. At Irongaze, we use this type of builder for grid controls, pagination,
|
122
|
+
filters, forms, fields, and so forth. Places where the MVC system breaks down, and complexity that bridges
|
123
|
+
controller and view needs to be managed.
|
124
|
+
|
125
|
+
== SYNOPSIS
|
126
|
+
|
127
|
+
To use:
|
128
|
+
|
129
|
+
require 'iron/dsl'
|
130
|
+
|
131
|
+
After that, simply write code to make use of the new extensions and helper classes.
|
132
|
+
|
133
|
+
== REQUIREMENTS
|
134
|
+
|
135
|
+
* Ruby 1.9.2 or later
|
136
|
+
|
137
|
+
== INSTALL
|
138
|
+
|
139
|
+
To install, simply run:
|
140
|
+
|
141
|
+
sudo gem install iron-dsl
|
142
|
+
|
143
|
+
RVM users should drop the 'sudo':
|
144
|
+
|
145
|
+
gem install iron-dsl
|
146
|
+
|
147
|
+
Then simply require the library:
|
148
|
+
|
149
|
+
require 'iron/dsl'
|
data/Version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/iron/dsl.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class Class
|
2
|
+
|
3
|
+
# Provides a DSL-friendly way to set values. Similar to attr_accessor, but
|
4
|
+
# supports setting values like so:
|
5
|
+
#
|
6
|
+
# class Widget
|
7
|
+
# dsl_accessor :size
|
8
|
+
# end
|
9
|
+
# @w = Widget.new
|
10
|
+
# @w.size = 5 # normal setter, same as...
|
11
|
+
# @w.size 5 # note the lack of explicit = sign
|
12
|
+
# puts @w.size # still get reader access
|
13
|
+
#
|
14
|
+
# Useful in DslProxy blocks:
|
15
|
+
#
|
16
|
+
# DslProxy.exec(Widget.new) do
|
17
|
+
# size 10 # sets size to 10, as expected
|
18
|
+
# size = 10 # fails, creates local variable 'size' instead of invoking Widget#size
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
def dsl_accessor(*keys)
|
22
|
+
keys.each do |key|
|
23
|
+
class_eval "def #{key}(val = :__UNDEFINED, &block); if val != :__UNDEFINED ; @#{key} = val ; elsif block ; @#{key} = block ; end ; @#{key}; end"
|
24
|
+
class_eval "def #{key}=(val); @#{key} = val; end"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Like #dsl_accessor, but adds imperative and query versions of the keys as well to set the
|
29
|
+
# flag to true and to query the true-ness of the flag.
|
30
|
+
#
|
31
|
+
# class Widget
|
32
|
+
# dsl_flag :heavy
|
33
|
+
# end
|
34
|
+
# @w = Widget.new
|
35
|
+
# @w.heavy? # => false
|
36
|
+
# @w.heavy! # now is true
|
37
|
+
#
|
38
|
+
def dsl_flag(*keys)
|
39
|
+
dsl_accessor(*keys)
|
40
|
+
keys.each do |key|
|
41
|
+
class_eval "def #{key}!; @#{key} = true; end"
|
42
|
+
class_eval "def #{key}?; @#{key} === true; end"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# Provides a base class for building DSL (domain specific language) builder
|
2
|
+
# classes, ie classes that define a minimal subset of methods and act as aggregators
|
3
|
+
# of settings or functionality. Similar to BasicObject in the standard library, but
|
4
|
+
# has methods such as respond_to? and send that are required for any real DSL building
|
5
|
+
# effort.
|
6
|
+
class DslBuilder < Object
|
7
|
+
|
8
|
+
# Remove all methods not explicitly desired
|
9
|
+
instance_methods.each do |m|
|
10
|
+
keepers = [:inspect, :send]
|
11
|
+
undef_method m if m =~ /^[a-z]+[0-9]?$/ && !keepers.include?(m)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
# Specialty helper class for building elegant DSLs (domain-specific languages)
|
2
|
+
# The purpose of the class is to allow seamless DSL's by allowing execution
|
3
|
+
# of blocks with the instance variables of the calling context preserved, but
|
4
|
+
# all method calls proxied to a given receiver. This sounds pretty abstract,
|
5
|
+
# so here's an example:
|
6
|
+
#
|
7
|
+
# class ControlBuilder
|
8
|
+
# def initialize; @controls = []; end
|
9
|
+
# def control_list; @controls; end
|
10
|
+
# def knob; @controls << :knob; end
|
11
|
+
# def button; @controls << :button; end
|
12
|
+
# def switch; @controls << :switch; end
|
13
|
+
# def self.define(&block)
|
14
|
+
# @builder = self.new
|
15
|
+
# DslProxy.exec(@builder, &block)
|
16
|
+
# # Do something here with the builder's list of controls
|
17
|
+
# @builder.control_list
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @knob_count = 5
|
22
|
+
# new_list = ControlBuilder.define do
|
23
|
+
# switch
|
24
|
+
# @knob_count.times { knob }
|
25
|
+
# button
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Notice the lack of explicit builder receiver to the calls to #switch, #knob and #button.
|
29
|
+
# Those calls are automatically proxied to the receiver we passed to the DslProxy.
|
30
|
+
#
|
31
|
+
# In quick and dirty DSLs, like Rails' migrations, you end up with a lot of
|
32
|
+
# pointless receiver declarations for each method call, like so:
|
33
|
+
#
|
34
|
+
# def change
|
35
|
+
# create_table do |t|
|
36
|
+
# t.integer :counter
|
37
|
+
# t.text :title
|
38
|
+
# t.text :desc
|
39
|
+
# # ... tired of typing "t." yet? ...
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# This is not a big deal if you're using a simple DSL, but when you have multiple nested
|
44
|
+
# builders going on at once, it is ugly, pointless, and can cause bugs when
|
45
|
+
# the throwaway arg names you choose (eg 't' above) overlap in scope.
|
46
|
+
#
|
47
|
+
# In addition, simply using a yield statment loses the instance variables set in the calling
|
48
|
+
# context. This is a major pain in eg Rails views, where most of the interesting
|
49
|
+
# data resides in instance variables. You can get around this when #yield-ing by
|
50
|
+
# explicitly creating a local variable to be picked up by the closure created in the
|
51
|
+
# block, but it kind of sucks.
|
52
|
+
#
|
53
|
+
# In summary, DslProxy allows you to keep all the local and instance variable context
|
54
|
+
# from your block declarations, while proxying all method calls to a given
|
55
|
+
# receiver. If you're not building DSLs, this class is not for you, but if you are,
|
56
|
+
# I hope it helps!
|
57
|
+
class DslProxy < BasicObject
|
58
|
+
|
59
|
+
# Pass in a builder-style class, or other receiver you want set as "self" within the
|
60
|
+
# block, and off you go. The passed block will be executed with all
|
61
|
+
# block-context local and instance variables available, but with all
|
62
|
+
# method calls sent to the receiver you pass in. The block's result will
|
63
|
+
# be returned.
|
64
|
+
#
|
65
|
+
# If the receiver doesn't respond_to? a method, any missing methods
|
66
|
+
# will be proxied to the enclosing context.
|
67
|
+
def self.exec(receiver, *to_yield, &block) # :yields: receiver
|
68
|
+
# Find the context within which the block was defined
|
69
|
+
context = ::Kernel.eval('self', block.binding)
|
70
|
+
|
71
|
+
# Create or re-use our proxy object
|
72
|
+
if context.respond_to?(:_to_dsl_proxy)
|
73
|
+
# If we're nested, we don't want/need a new dsl proxy, just re-use the existing one
|
74
|
+
proxy = context._to_dsl_proxy
|
75
|
+
else
|
76
|
+
# Not nested, create a new proxy for our use
|
77
|
+
proxy = DslProxy.new(context)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Exec the block and return the result
|
81
|
+
proxy._proxy(receiver, *to_yield, &block)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Simple state setup
|
85
|
+
def initialize(context)
|
86
|
+
@_receivers = []
|
87
|
+
@_instance_original_values = {}
|
88
|
+
@_context = context
|
89
|
+
end
|
90
|
+
|
91
|
+
def _proxy(receiver, *to_yield, &block) # :yields: receiver
|
92
|
+
# Sanity!
|
93
|
+
raise 'Cannot proxy with a DslProxy as receiver!' if receiver.respond_to?(:_to_dsl_proxy)
|
94
|
+
|
95
|
+
if @_receivers.empty?
|
96
|
+
# On first proxy call, run each context instance variable,
|
97
|
+
# and set it to ourselves so we can proxy it
|
98
|
+
@_context.instance_variables.each do |var|
|
99
|
+
unless var[0...2] == '@_'
|
100
|
+
value = @_context.instance_variable_get(var.to_s)
|
101
|
+
@_instance_original_values[var] = value
|
102
|
+
instance_eval "#{var} = value"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Save the dsl target as our receiver for proxying
|
108
|
+
_push_receiver(receiver)
|
109
|
+
|
110
|
+
# Run the block with ourselves as the new "self", passing the given yieldable(s) or
|
111
|
+
# the receiver in case the code wants to disambiguate for some reason
|
112
|
+
to_yield = [receiver] if to_yield.empty?
|
113
|
+
to_yield = to_yield.first(block.arity)
|
114
|
+
result = instance_exec(*to_yield, &block)
|
115
|
+
|
116
|
+
# Pop the last receiver off the stack
|
117
|
+
_pop_receiver
|
118
|
+
|
119
|
+
if @_receivers.empty?
|
120
|
+
# Run each local instance variable and re-set it back to the context if it has changed during execution
|
121
|
+
#instance_variables.each do |var|
|
122
|
+
@_context.instance_variables.each do |var|
|
123
|
+
unless var[0...2] == '@_'
|
124
|
+
value = instance_eval("#{var}")
|
125
|
+
if @_instance_original_values[var] != value
|
126
|
+
@_context.instance_variable_set(var.to_s, value)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
return result
|
133
|
+
end
|
134
|
+
|
135
|
+
# For nesting multiple proxies
|
136
|
+
def _to_dsl_proxy
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
# Set the currently active receiver
|
141
|
+
def _push_receiver(receiver)
|
142
|
+
@_receivers.push receiver
|
143
|
+
end
|
144
|
+
|
145
|
+
# Remove the currently active receiver, restore old receiver if nested
|
146
|
+
def _pop_receiver
|
147
|
+
@_receivers.pop
|
148
|
+
end
|
149
|
+
|
150
|
+
# Proxies all calls to our receiver, or to the block's context
|
151
|
+
# if the receiver doesn't respond_to? it.
|
152
|
+
def method_missing(method, *args, &block)
|
153
|
+
#$stderr.puts "Method missing: #{method}"
|
154
|
+
if @_receivers.last.respond_to?(method)
|
155
|
+
#$stderr.puts "Proxy [#{method}] to receiver"
|
156
|
+
@_receivers.last.__send__(method, *args, &block)
|
157
|
+
else
|
158
|
+
#$stderr.puts "Proxy [#{method}] to context"
|
159
|
+
@_context.__send__(method, *args, &block)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Let anyone who's interested know what our proxied objects will accept
|
164
|
+
def respond_to?(method, include_private = false)
|
165
|
+
return true if method == :_to_dsl_proxy
|
166
|
+
@_receivers.last.respond_to?(method, include_private) || @_context.respond_to?(method, include_private)
|
167
|
+
end
|
168
|
+
|
169
|
+
# Proxies searching for constants to the context, so that eg Kernel::foo can actually
|
170
|
+
# find Kernel - BasicObject does not partake in the global scope!
|
171
|
+
def self.const_missing(name)
|
172
|
+
#$stderr.puts "Constant missing: #{name} - proxy to context"
|
173
|
+
@_context.class.const_get(name)
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
describe Class do
|
2
|
+
|
3
|
+
context 'when using dsl_accessor' do
|
4
|
+
class MyBuilder < DslBuilder
|
5
|
+
dsl_accessor :process
|
6
|
+
end
|
7
|
+
|
8
|
+
before do
|
9
|
+
@builder = MyBuilder.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should set via =' do
|
13
|
+
@builder.process.should be_nil
|
14
|
+
@builder.process = 5
|
15
|
+
@builder.process.should == 5
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should set via call' do
|
19
|
+
@builder.process.should be_nil
|
20
|
+
@builder.process 5
|
21
|
+
@builder.process.should == 5
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should capture blocks' do
|
25
|
+
@builder.process do
|
26
|
+
puts 'foo'
|
27
|
+
end
|
28
|
+
@builder.process.should be_a Proc
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
describe DslBuilder do
|
2
|
+
|
3
|
+
# TODO: break this out a bit...
|
4
|
+
it 'should allow using DSL-style accessors' do
|
5
|
+
class MyBuilder < DslBuilder
|
6
|
+
dsl_accessor :name
|
7
|
+
dsl_flag :flagged
|
8
|
+
end
|
9
|
+
builder = MyBuilder.new
|
10
|
+
|
11
|
+
# Test standalone
|
12
|
+
builder.name 'ProjectX'
|
13
|
+
builder.name.should == 'ProjectX'
|
14
|
+
|
15
|
+
builder.flagged?.should be_false
|
16
|
+
builder.flagged = true
|
17
|
+
builder.flagged?.should be_true
|
18
|
+
builder.flagged = false
|
19
|
+
|
20
|
+
# Test as part of DslProxy usage (common case)
|
21
|
+
DslProxy.exec(builder) do
|
22
|
+
name 'Project Omega'
|
23
|
+
flagged!
|
24
|
+
end
|
25
|
+
builder.name.should == 'Project Omega'
|
26
|
+
builder.flagged?.should be_true
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
describe DslProxy do
|
2
|
+
|
3
|
+
# Sample DSL builder class for use in testing
|
4
|
+
class ControlBuilder
|
5
|
+
def initialize; @controls = []; end
|
6
|
+
def controls; @controls; end
|
7
|
+
def knob; @controls << :knob; end
|
8
|
+
def button; @controls << :button; end
|
9
|
+
def switch; @controls << :switch; end
|
10
|
+
|
11
|
+
def self.define(&block)
|
12
|
+
@builder = self.new
|
13
|
+
DslProxy.exec(@builder, &block)
|
14
|
+
@builder.controls
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should proxy calls to the receiver' do
|
19
|
+
receiver = Object.new
|
20
|
+
DslProxy.exec(receiver) do
|
21
|
+
self.class.name.should == 'Object'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should proxy respond_to? to the receiver' do
|
26
|
+
receiver = ControlBuilder.new
|
27
|
+
DslProxy.exec(receiver) do
|
28
|
+
respond_to?(:garbaz).should == false
|
29
|
+
respond_to?(:button).should == true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should proxy local variables from the binding context' do
|
34
|
+
@foo = 'bar'
|
35
|
+
DslProxy.exec(Object.new) do
|
36
|
+
@foo.should == 'bar'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should propagate local variable changes back to the binding context' do
|
41
|
+
@foo = 'bar'
|
42
|
+
DslProxy.exec(Object.new) do
|
43
|
+
@foo = 'no bar!'
|
44
|
+
end
|
45
|
+
@foo.should == 'no bar!'
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should proxy missing methods on the receiver to the calling context' do
|
49
|
+
class TestContext
|
50
|
+
def bar
|
51
|
+
'something'
|
52
|
+
end
|
53
|
+
|
54
|
+
def test
|
55
|
+
DslProxy.exec(Object.new) do
|
56
|
+
bar
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
TestContext.new.test.should == 'something'
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should return the result of the block' do
|
65
|
+
res = DslProxy.exec(Object.new) do
|
66
|
+
'foo'
|
67
|
+
end
|
68
|
+
res.should == 'foo'
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should allow access to global constants' do
|
72
|
+
DslProxy.exec(self) do # Use self here, so #be_a is defined. :-)
|
73
|
+
Object.new.should be_a(Object)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should proxy correctly even when nested' do
|
78
|
+
def outerfunc
|
79
|
+
5
|
80
|
+
end
|
81
|
+
@instance_var = nil
|
82
|
+
local_var = nil
|
83
|
+
DslProxy.exec(self) do
|
84
|
+
DslProxy.exec(Object.new) do
|
85
|
+
outerfunc.should == 5
|
86
|
+
@instance_var.should be_nil
|
87
|
+
local_var.should be_nil
|
88
|
+
@instance_var = 10
|
89
|
+
local_var = 11
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@instance_var.should == 10
|
93
|
+
local_var.should == 11
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should pass additional args to block as argument' do
|
97
|
+
l = lambda {|arg1, arg2| arg1 + arg2}
|
98
|
+
DslProxy.exec(Object.new, 5, 1, &l).should == 6
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should put it all together' do
|
102
|
+
@knob_count = 5
|
103
|
+
controls = ControlBuilder.define do
|
104
|
+
switch
|
105
|
+
@knob_count.times { knob }
|
106
|
+
end
|
107
|
+
controls.count.should == 6
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# Require our library
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib', 'iron', 'dsl'))
|
3
|
+
|
4
|
+
# Config RSpec options
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.color = true
|
7
|
+
config.add_formatter 'documentation'
|
8
|
+
config.backtrace_clean_patterns = [/rspec/]
|
9
|
+
end
|
10
|
+
|
11
|
+
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iron-dsl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Rob Morris
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.6'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.6'
|
27
|
+
description: Provides the DslProxy and DslBuilder classes plus DSL-friendly accessor
|
28
|
+
and flag support
|
29
|
+
email:
|
30
|
+
- rob@irongaze.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- ".rspec"
|
36
|
+
- History.txt
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Version.txt
|
40
|
+
- lib/iron/dsl.rb
|
41
|
+
- lib/iron/dsl/class.rb
|
42
|
+
- lib/iron/dsl/dsl_builder.rb
|
43
|
+
- lib/iron/dsl/dsl_proxy.rb
|
44
|
+
- spec/dsl/class_spec.rb
|
45
|
+
- spec/dsl/dsl_builder_spec.rb
|
46
|
+
- spec/dsl/dsl_proxy_spec.rb
|
47
|
+
- spec/spec_helper.rb
|
48
|
+
homepage: https://github.com/irongaze/iron-dsl
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.9.2
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.4.3
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Powerful and concise construction helpers for Domain Specific Languages
|
72
|
+
test_files: []
|