iron-extensions 1.1.0 → 1.1.1
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.
- data/History.txt +7 -0
- data/README.rdoc +18 -1
- data/Version.txt +1 -1
- data/lib/iron/extensions/class.rb +10 -0
- data/lib/iron/extensions/dsl_builder.rb +14 -0
- data/lib/iron/extensions/dsl_proxy.rb +98 -34
- data/lib/iron/extensions/symbol.rb +10 -0
- data/spec/extensions/dsl_builder_spec.rb +20 -0
- data/spec/extensions/dsl_proxy_spec.rb +19 -0
- metadata +7 -4
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
== 1.1.1 / 2012-03-13
|
2
|
+
|
3
|
+
* Added DslBuilder base class for additional DSL-building goodness, is basically a better blank-slate starter class than BasicObject for working with our DslProxy class
|
4
|
+
* Added dsl_accessor class method as a way to easily create DSL-style accessors
|
5
|
+
* DslProxy now only copies back instance vars when they change
|
6
|
+
* Symbol now supports #starts_with? and #ends_with?
|
7
|
+
|
1
8
|
== 1.1.0 / 2012-03-06
|
2
9
|
|
3
10
|
* Added DslProxy class and specs, which enables slim and sexy DSL construction
|
data/README.rdoc
CHANGED
@@ -4,7 +4,7 @@ Written by Rob Morris @ Irongaze Consulting LLC (http://irongaze.com)
|
|
4
4
|
|
5
5
|
== DESCRIPTION
|
6
6
|
|
7
|
-
Helpful extensions to core Ruby classes
|
7
|
+
Helpful extensions to core Ruby classes, plus a little sugar for common patterns
|
8
8
|
|
9
9
|
== ADDED EXTENSIONS
|
10
10
|
|
@@ -20,6 +20,19 @@ Helpful extensions to core Ruby classes
|
|
20
20
|
|
21
21
|
[1, 2, nil, '', 'count'].list_join # => '1, 2, count'
|
22
22
|
|
23
|
+
* Class#dsl_accessor - helpful method for defining accessors on DSL builder-style classes
|
24
|
+
|
25
|
+
class MyBuilder
|
26
|
+
dsl_accessor :name
|
27
|
+
end
|
28
|
+
builder = MyBuilder.new
|
29
|
+
builder.name 'ProjectX'
|
30
|
+
builder.name # => 'ProjectX'
|
31
|
+
DslProxy.exec(builder) do
|
32
|
+
name 'Project Omega'
|
33
|
+
end
|
34
|
+
builder.name # => 'Project Omega'
|
35
|
+
|
23
36
|
* Enumerable#to_hash - convert an array or other enumerable to a hash using a block or constant
|
24
37
|
|
25
38
|
[:frog, :pig].to_hash {|n| n.to_s.capitalize} # => {:frog => 'Frog', :pig => 'Pig'}
|
@@ -117,3 +130,7 @@ After that, simply write code to make use of the new extensions and helper class
|
|
117
130
|
To install, simply run:
|
118
131
|
|
119
132
|
sudo gem install iron-extensions
|
133
|
+
|
134
|
+
RVM users should drop the 'sudo':
|
135
|
+
|
136
|
+
gem install iron-extensions
|
data/Version.txt
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.1.
|
1
|
+
1.1.1
|
@@ -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
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Specialty helper class for building elegant DSLs (domain-specific languages)
|
2
2
|
# The purpose of the class is to allow seamless DSL's by allowing execution
|
3
3
|
# of blocks with the instance variables of the calling context preserved, but
|
4
|
-
# all method calls
|
4
|
+
# all method calls proxied to a given receiver. This sounds pretty abstract,
|
5
5
|
# so here's an example:
|
6
6
|
#
|
7
7
|
# class ControlBuilder
|
@@ -26,19 +26,19 @@
|
|
26
26
|
# end
|
27
27
|
#
|
28
28
|
# Notice the lack of explicit builder receiver to the calls to #switch, #knob and #button.
|
29
|
-
# Those calls are automatically proxied to the
|
29
|
+
# Those calls are automatically proxied to the receiver we passed to the DslProxy.
|
30
30
|
#
|
31
31
|
# In quick and dirty DSLs, like Rails' migrations, you end up with a lot of
|
32
32
|
# pointless receiver declarations for each method call, like so:
|
33
33
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
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
|
40
41
|
# end
|
41
|
-
# end
|
42
42
|
#
|
43
43
|
# This is not a big deal if you're using a simple DSL, but when you have multiple nested
|
44
44
|
# builders going on at once, it is ugly, pointless, and can cause bugs when
|
@@ -60,52 +60,116 @@ class DslProxy < BasicObject
|
|
60
60
|
# block, and off you go. The passed block will be executed with all
|
61
61
|
# block-context local and instance variables available, but with all
|
62
62
|
# method calls sent to the receiver you pass in. The block's result will
|
63
|
-
# be returned.
|
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.
|
64
67
|
def self.exec(receiver, &block) # :yields: receiver
|
65
|
-
|
66
|
-
|
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, &block)
|
67
82
|
end
|
68
83
|
|
69
|
-
#
|
70
|
-
def initialize(
|
84
|
+
# Simple state setup
|
85
|
+
def initialize(context)
|
86
|
+
@_receivers = []
|
87
|
+
@_instance_original_values = {}
|
88
|
+
@_context = context
|
89
|
+
end
|
90
|
+
|
91
|
+
def _proxy(receiver, &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.starts_with?('@_')
|
100
|
+
value = @_context.instance_variable_get(var.to_s)
|
101
|
+
@_instance_original_values[var] = value
|
102
|
+
#instance_variable_set(var, value)
|
103
|
+
instance_eval "#{var} = value"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
71
108
|
# Save the dsl target as our receiver for proxying
|
72
|
-
|
109
|
+
_push_receiver(receiver)
|
73
110
|
|
74
|
-
# Find the context within which the block was defined
|
75
|
-
@_context = ::Kernel.eval('self', block.binding)
|
76
|
-
# Run each instance variable, and set it to ourselves so we can proxy it
|
77
|
-
@_context.instance_variables.each do |var|
|
78
|
-
value = @_context.instance_variable_get(var.to_s)
|
79
|
-
instance_eval "#{var} = value"
|
80
|
-
end
|
81
|
-
|
82
111
|
# Run the block with ourselves as the new "self", passing the receiver in case
|
83
112
|
# the code wants to disambiguate for some reason
|
84
|
-
|
113
|
+
result = instance_exec(@_receivers.last, &block)
|
85
114
|
|
86
|
-
#
|
87
|
-
|
88
|
-
|
115
|
+
# Pop the last receiver off the stack
|
116
|
+
_pop_receiver
|
117
|
+
|
118
|
+
if @_receivers.empty?
|
119
|
+
# Run each local instance variable and re-set it back to the context if it has changed during execution
|
120
|
+
#instance_variables.each do |var|
|
121
|
+
@_context.instance_variables.each do |var|
|
122
|
+
unless var.starts_with?('@_')
|
123
|
+
value = instance_eval("#{var}")
|
124
|
+
#value = instance_variable_get("#{var}")
|
125
|
+
if @_instance_original_values[var] != value
|
126
|
+
@_context.instance_variable_set(var.to_s, value)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
89
130
|
end
|
131
|
+
|
132
|
+
return result
|
90
133
|
end
|
91
134
|
|
92
|
-
#
|
93
|
-
def
|
94
|
-
|
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
|
95
148
|
end
|
96
149
|
|
97
150
|
# Proxies all calls to our receiver, or to the block's context
|
98
151
|
# if the receiver doesn't respond_to? it.
|
99
152
|
def method_missing(method, *args, &block)
|
100
|
-
|
101
|
-
|
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)
|
102
157
|
else
|
103
|
-
|
158
|
+
#$stderr.puts "Proxy [#{method}] to context"
|
159
|
+
@_context.__send__(method, *args, &block)
|
104
160
|
end
|
105
161
|
end
|
106
162
|
|
107
|
-
#
|
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!
|
108
171
|
def self.const_missing(name)
|
172
|
+
#$stderr.puts "Constant missing: #{name} - proxy to context"
|
109
173
|
@_context.class.const_get(name)
|
110
174
|
end
|
111
175
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
describe DslBuilder do
|
2
|
+
|
3
|
+
it 'should allow creating DSL-style accessors' do
|
4
|
+
class MyBuilder < DslBuilder
|
5
|
+
dsl_accessor :name
|
6
|
+
end
|
7
|
+
builder = MyBuilder.new
|
8
|
+
|
9
|
+
# Test standalone
|
10
|
+
builder.name 'ProjectX'
|
11
|
+
builder.name.should == 'ProjectX'
|
12
|
+
|
13
|
+
# Test as part of DslProxy usage (common case)
|
14
|
+
DslProxy.exec(builder) do
|
15
|
+
name 'Project Omega'
|
16
|
+
end
|
17
|
+
builder.name.should == 'Project Omega'
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -74,6 +74,25 @@ describe DslProxy do
|
|
74
74
|
end
|
75
75
|
end
|
76
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
|
+
|
77
96
|
it 'should put it all together' do
|
78
97
|
@knob_count = 5
|
79
98
|
controls = ControlBuilder.define do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: iron-extensions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
12
|
+
date: 2012-03-13 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &2164378800 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '2.6'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2164378800
|
25
25
|
description: Adds common extensions to core Ruby classes
|
26
26
|
email:
|
27
27
|
- rob@irongaze.com
|
@@ -30,6 +30,8 @@ extensions: []
|
|
30
30
|
extra_rdoc_files: []
|
31
31
|
files:
|
32
32
|
- lib/iron/extensions/array.rb
|
33
|
+
- lib/iron/extensions/class.rb
|
34
|
+
- lib/iron/extensions/dsl_builder.rb
|
33
35
|
- lib/iron/extensions/dsl_proxy.rb
|
34
36
|
- lib/iron/extensions/enumerable.rb
|
35
37
|
- lib/iron/extensions/file.rb
|
@@ -44,6 +46,7 @@ files:
|
|
44
46
|
- lib/iron/extensions/string.rb
|
45
47
|
- lib/iron/extensions/symbol.rb
|
46
48
|
- lib/iron/extensions.rb
|
49
|
+
- spec/extensions/dsl_builder_spec.rb
|
47
50
|
- spec/extensions/dsl_proxy_spec.rb
|
48
51
|
- spec/extensions/enumerable_spec.rb
|
49
52
|
- spec/extensions/kernel_spec.rb
|