dsel 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/Gemfile +15 -0
  4. data/LICENSE.md +23 -0
  5. data/README.md +113 -0
  6. data/Rakefile +6 -0
  7. data/dsel.gemspec +22 -0
  8. data/lib/dsel/api/generator.rb +285 -0
  9. data/lib/dsel/api/node.rb +241 -0
  10. data/lib/dsel/api.rb +8 -0
  11. data/lib/dsel/dsl/mixins/environment/ivar_explorer.rb +34 -0
  12. data/lib/dsel/dsl/nodes/api/environment.rb +49 -0
  13. data/lib/dsel/dsl/nodes/api.rb +18 -0
  14. data/lib/dsel/dsl/nodes/api_builder/environment.rb +56 -0
  15. data/lib/dsel/dsl/nodes/api_builder.rb +71 -0
  16. data/lib/dsel/dsl/nodes/base/environment.rb +50 -0
  17. data/lib/dsel/dsl/nodes/base.rb +110 -0
  18. data/lib/dsel/dsl/nodes/direct/environment.rb +14 -0
  19. data/lib/dsel/dsl/nodes/direct.rb +75 -0
  20. data/lib/dsel/dsl/nodes/proxy/environment.rb +41 -0
  21. data/lib/dsel/dsl/nodes/proxy.rb +20 -0
  22. data/lib/dsel/dsl.rb +10 -0
  23. data/lib/dsel/node.rb +42 -0
  24. data/lib/dsel/ruby/object.rb +53 -0
  25. data/lib/dsel/version.rb +3 -0
  26. data/lib/dsel.rb +10 -0
  27. data/spec/dsel/api/generator_spec.rb +402 -0
  28. data/spec/dsel/api/node_spec.rb +328 -0
  29. data/spec/dsel/dsel_spec.rb +63 -0
  30. data/spec/dsel/dsl/nodes/api/environment.rb +208 -0
  31. data/spec/dsel/dsl/nodes/api_builder/environment_spec.rb +91 -0
  32. data/spec/dsel/dsl/nodes/api_builder_spec.rb +148 -0
  33. data/spec/dsel/dsl/nodes/api_spec.rb +15 -0
  34. data/spec/dsel/dsl/nodes/direct/environment_spec.rb +14 -0
  35. data/spec/dsel/dsl/nodes/direct_spec.rb +43 -0
  36. data/spec/dsel/dsl/nodes/proxy/environment_spec.rb +56 -0
  37. data/spec/dsel/dsl/nodes/proxy_spec.rb +11 -0
  38. data/spec/spec_helper.rb +22 -0
  39. data/spec/support/factories/clean_api_spec.rb +6 -0
  40. data/spec/support/fixtures/mock_api.rb +4 -0
  41. data/spec/support/helpers/paths.rb +19 -0
  42. data/spec/support/lib/factory.rb +107 -0
  43. data/spec/support/shared/dsl/nodes/base/environment.rb +104 -0
  44. data/spec/support/shared/dsl/nodes/base.rb +171 -0
  45. data/spec/support/shared/node.rb +70 -0
  46. metadata +108 -0
@@ -0,0 +1,75 @@
1
+ require_relative 'base'
2
+ require_relative '../mixins/environment/ivar_explorer'
3
+
4
+ module DSeL
5
+ module DSL
6
+ module Nodes
7
+
8
+ class Direct < Base
9
+ require_relative 'direct/environment'
10
+
11
+ def extend_env
12
+ [
13
+ Environment,
14
+ Mixins::Environment::IvarExplorer
15
+ ]
16
+ end
17
+
18
+ def reset_methods
19
+ [
20
+ :instance_variables,
21
+ :method_missing
22
+ ]
23
+ end
24
+
25
+ private
26
+
27
+ def prepare_environment
28
+ capture_subject
29
+ decorate_subject
30
+
31
+ @environment = @subject
32
+ end
33
+
34
+ def cleanup_environment
35
+ restore_subject
36
+ end
37
+
38
+ def capture_subject
39
+ @original_methods = reset_methods.map do |m|
40
+ @subject.instance_eval do
41
+ method( m ) if respond_to? m
42
+ end
43
+ end.compact
44
+ end
45
+
46
+ def decorate_subject
47
+ # We could use @subject.extend but that only works the first time.
48
+ extend_env.each do |mod|
49
+ mod.instance_methods( true ).each do |m|
50
+ @subject.instance_eval do
51
+ define_singleton_method m, mod.instance_method( m )
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def restore_subject
58
+ cmethods = @subject.methods
59
+ extend_env.each do |mod|
60
+ mod.instance_methods( true ).each do |m|
61
+ next if !cmethods.include?( m )
62
+ @subject.instance_eval( "undef :'#{m}'" )
63
+ end
64
+ end
65
+
66
+ @original_methods.each do |m|
67
+ @subject.define_singleton_method m.name, &m
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../../mixins/environment/ivar_explorer'
2
+
3
+ module DSeL
4
+ module DSL
5
+ module Nodes
6
+ class Proxy
7
+
8
+ class Environment
9
+ include Base::Environment
10
+ include Mixins::Environment::IvarExplorer
11
+
12
+ define_method "#{DSEL_NODE_ACCESSOR}=" do |node|
13
+ super( node )
14
+
15
+ if node
16
+ _dsel_node.subject.public_methods( false ).each do |m|
17
+ instance_eval( "undef :'#{m}'" ) rescue nil
18
+ end
19
+ end
20
+
21
+ node
22
+ end
23
+
24
+ def method_missing( name, *args, &block )
25
+ if _dsel_node && _dsel_self.respond_to?( name )
26
+ return _dsel_self.send( name, *args, &block )
27
+ end
28
+
29
+ super( name, *args, &block )
30
+ end
31
+
32
+ def respond_to?( *args )
33
+ super( *args ) || (_dsel_node && _dsel_self.respond_to?( *args ))
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'base'
2
+
3
+ module DSeL
4
+ module DSL
5
+ module Nodes
6
+
7
+ class Proxy < Base
8
+ require_relative 'proxy/environment'
9
+
10
+ private
11
+
12
+ def prepare_environment
13
+ @environment ||= Environment.new
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
20
+ end
data/lib/dsel/dsl.rb ADDED
@@ -0,0 +1,10 @@
1
+ module DSeL
2
+ module DSL
3
+
4
+ require_relative 'dsl/nodes/direct'
5
+ require_relative 'dsl/nodes/proxy'
6
+ require_relative 'dsl/nodes/api'
7
+ require_relative 'dsl/nodes/api_builder'
8
+
9
+ end
10
+ end
data/lib/dsel/node.rb ADDED
@@ -0,0 +1,42 @@
1
+ module DSeL
2
+ class Node
3
+
4
+ # @return [Object]
5
+ attr_reader :subject
6
+
7
+ # @return [Base, nil]
8
+ attr_reader :parent
9
+
10
+ # @return [Base]
11
+ # `self` if {#root?}.
12
+ attr_reader :root
13
+
14
+ # @param [Object] subject
15
+ # @param [Hash] options
16
+ # @option options [Base, nil] :parent (nil)
17
+ def initialize( subject = nil, options = {} )
18
+ @subject = subject
19
+ @parent = options[:parent]
20
+ @root = (@parent ? @parent._dsel_node.root : self)
21
+ end
22
+
23
+ def root?
24
+ @root == self
25
+ end
26
+
27
+ # @private
28
+ def _dsel_node
29
+ self
30
+ end
31
+
32
+ def calc_node_hash( subject )
33
+ "#{self.class}:#{subject.object_id}".hash
34
+ end
35
+
36
+ def hash
37
+ calc_node_hash( @subject )
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,53 @@
1
+ class Object
2
+
3
+ def as_dsel( parent = nil, &block )
4
+ _dsel_determine_node( DSeL::DSL::Nodes::Proxy, parent ).run( &block )
5
+ end
6
+
7
+ def dsel_script( script, parent = nil )
8
+ _dsel_determine_node( DSeL::DSL::Nodes::Proxy, parent ).run( script )
9
+ end
10
+
11
+ def as_direct_dsel( parent = nil, &block )
12
+ _dsel_determine_node( DSeL::DSL::Nodes::Direct, parent ).run( &block )
13
+ end
14
+
15
+ def direct_dsel_script( script, parent = nil )
16
+ _dsel_determine_node( DSeL::DSL::Nodes::Direct, parent ).run( script )
17
+ end
18
+
19
+ def DSeL( object = self, &block )
20
+ object.as_dsel( _dsel_self_if_node, &block )
21
+ end
22
+
23
+ def DSeLScript( script, object = self )
24
+ object.dsel_script( script, _dsel_self_if_node )
25
+ end
26
+
27
+ def DirectDSeL( object = self, &block )
28
+ object.as_direct_dsel( _dsel_self_if_node, &block )
29
+ end
30
+
31
+ def DirectDSeLScript( script, object = self )
32
+ object.direct_dsel_script( script, _dsel_self_if_node )
33
+ end
34
+
35
+ private
36
+
37
+ def _dsel_self_if_node
38
+ respond_to?( DSeL::DSL::Nodes::Base::Environment::DSEL_NODE_ACCESSOR ) ? self : nil
39
+ end
40
+
41
+ def _dsel_determine_node( klass, parent = nil )
42
+ return klass.new( self ) if !parent
43
+
44
+ if parent._dsel_node.is_a?( klass )
45
+ parent._dsel_node.node_for( self )
46
+ else
47
+ node = klass.new( self, parent: parent )
48
+ parent._dsel_node.cache_node node
49
+ node
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,3 @@
1
+ module DSeL
2
+ VERSION = '0.1.0'
3
+ end
data/lib/dsel.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'ap'
2
+
3
+ module DSeL
4
+
5
+ require 'dsel/version'
6
+ require 'dsel/node'
7
+ require 'dsel/api'
8
+ require 'dsel/dsl'
9
+
10
+ end
@@ -0,0 +1,402 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::API::Generator do
4
+ let(:api_spec) { MockAPI }
5
+ let(:api) { api_spec.new }
6
+ let(:clean_api_spec) { Factory[:clean_api_spec] }
7
+ let(:clean_api) { clean_api_spec.new }
8
+ subject { described_class }
9
+
10
+ describe '#last_call' do
11
+ let(:last_call) { subject.last_call }
12
+
13
+ before do
14
+ api.on
15
+ api.on 2
16
+ end
17
+
18
+ it 'returns the last call' do
19
+ expect(last_call[:node]).to be api_spec
20
+ expect(last_call[:type]).to be :on
21
+ expect(last_call[:args]).to eq [2]
22
+ end
23
+
24
+ context '#last_call_with_caller?' do
25
+ context 'true' do
26
+ before do
27
+ subject.last_call_with_caller!
28
+ api.on 3
29
+ end
30
+
31
+ it 'includes caller information' do
32
+ expect(last_call[:caller]).to be_kind_of Array
33
+ end
34
+ end
35
+
36
+ context 'false' do
37
+ before do
38
+ subject.last_call_without_caller!
39
+ api.on 3
40
+ end
41
+
42
+ it 'does not include caller information' do
43
+ expect(last_call).to_not include :caller
44
+ end
45
+ end
46
+
47
+ context 'default' do
48
+ before do
49
+ api.on 3
50
+ end
51
+
52
+ it 'does not include caller information' do
53
+ expect(last_call).to_not include :caller
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ describe '#last_call_with_caller?' do
60
+ it 'returns false' do
61
+ expect(subject).to_not be_last_call_with_caller
62
+ end
63
+
64
+ context 'when #last_call_with_caller! has been called' do
65
+ before do
66
+ subject.last_call_with_caller!
67
+ end
68
+
69
+ it 'returns true' do
70
+ expect(subject).to be_last_call_with_caller
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#last_call_without_caller!' do
76
+ it 'disables tracking of last call callers' do
77
+ subject.last_call_with_caller!
78
+ expect(subject).to be_last_call_with_caller
79
+
80
+ subject.last_call_without_caller!
81
+ expect(subject).to_not be_last_call_with_caller
82
+ end
83
+ end
84
+
85
+ describe '#last_call_witt_caller!' do
86
+ it 'enables tracking of last call callers' do
87
+ subject.last_call_with_caller!
88
+ expect(subject).to be_last_call_with_caller
89
+ end
90
+ end
91
+
92
+ describe '#on_call' do
93
+ it 'sets callbacks for each call' do
94
+ calls = Hash.new
95
+
96
+ subject.on_call do |call|
97
+ (calls[:first] ||= []) << call
98
+ end
99
+
100
+ subject.on_call do |call|
101
+ (calls[:second] ||= []) << call
102
+ end
103
+
104
+ api.on 4
105
+
106
+ expect(calls[:first]).to eq calls[:second]
107
+ {
108
+ node: api_spec,
109
+ type: :on,
110
+ args: [4]
111
+ }.each do |k, v|
112
+ calls[:first].each do |call|
113
+ expect(call[k]).to eq v
114
+ end
115
+ end
116
+ end
117
+
118
+ it 'returns self' do
119
+ expect(subject.on_call{}).to be subject.instance
120
+ end
121
+
122
+ context 'when no block is given' do
123
+ it 'raises ArgumentError' do
124
+ expect do
125
+ subject.on_call
126
+ end.to raise_error ArgumentError
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#call_object_hasher=' do
132
+ before do
133
+ subject.define_definers( clean_api_spec, :on )
134
+ end
135
+
136
+ context 'nil' do
137
+ before do
138
+ subject.call_object_hasher = nil
139
+ end
140
+
141
+ it 'uses #hash' do
142
+ o = Object.new
143
+ expect(o).to receive(:hash).and_return(1)
144
+
145
+ called = false
146
+ subject.define_call_handler clean_api_spec, :on, o do |*a, &block|
147
+ called = true
148
+ end
149
+
150
+ o2 = Object.new
151
+ expect(o2).to receive(:hash).and_return(1)
152
+
153
+ clean_api.on o2
154
+ expect(called).to be_truthy
155
+ end
156
+
157
+ it 'treats strings as case-insensitive' do
158
+ o = 'StFuFf'
159
+
160
+ called = false
161
+ subject.define_call_handler clean_api_spec, :on, o do |*a, &block|
162
+ called = true
163
+ end
164
+
165
+ clean_api.on o.downcase
166
+ expect(called).to be_truthy
167
+ end
168
+ end
169
+
170
+ context 'Symbol' do
171
+ before do
172
+ subject.call_object_hasher = :object_id
173
+ end
174
+
175
+ it 'uses it as a method on the object' do
176
+ o = Object.new
177
+
178
+ called = false
179
+ subject.define_call_handler clean_api_spec, :on, o do |*a, &block|
180
+ called = true
181
+ end
182
+
183
+ expect do
184
+ clean_api.on Object.new
185
+ end.to raise_error NoMethodError
186
+
187
+ clean_api.on o
188
+ expect(called).to be_truthy
189
+ end
190
+ end
191
+
192
+ context '#call' do
193
+ before do
194
+ subject.call_object_hasher = proc do |o|
195
+ o.hash
196
+ end
197
+ end
198
+
199
+ it 'passes the object to it' do
200
+ o = Object.new
201
+ expect(o).to receive(:hash).and_return(1)
202
+
203
+ called = false
204
+ subject.define_call_handler clean_api_spec, :on, o do |*a, &block|
205
+ called = true
206
+ end
207
+
208
+ o2 = Object.new
209
+ expect(o2).to receive(:hash).and_return(1)
210
+
211
+ clean_api.on o2
212
+ expect(called).to be_truthy
213
+ end
214
+ end
215
+
216
+ context 'other' do
217
+ it 'raises ArgumentError' do
218
+ expect do
219
+ subject.call_object_hasher = 'stuff'
220
+ end.to raise_error ArgumentError
221
+ end
222
+ end
223
+
224
+ context 'when the return value is not an Integer' do
225
+ before do
226
+ subject.call_object_hasher = :to_s
227
+ end
228
+
229
+ it 'raises ArgumentError' do
230
+ o = Object.new
231
+
232
+ expect do
233
+ subject.define_call_handler( clean_api_spec, :on, o ){}
234
+ end.to raise_error ArgumentError
235
+ end
236
+ end
237
+ end
238
+
239
+ describe '#define_definers' do
240
+ it 'defines the given definers on the given node' do
241
+ subject.define_definers( clean_api_spec, :on, :after )
242
+
243
+ expect(clean_api_spec.definers).to eq [
244
+ { type: :on, method: :def_on },
245
+ { type: :after, method: :def_after }
246
+ ]
247
+
248
+ expect(clean_api_spec).to respond_to :def_on
249
+ expect(clean_api_spec).to respond_to :def_after
250
+ end
251
+
252
+ context 'when a definer already exists' do
253
+ it 'raises NameError' do
254
+ subject.define_definers( clean_api_spec, :on )
255
+
256
+ expect do
257
+ subject.define_definers( clean_api_spec, :on )
258
+ end.to raise_error NameError
259
+ end
260
+ end
261
+
262
+ context 'when a definer is given more than 2 objects' do
263
+ it 'raises ArgumentError' do
264
+ subject.define_definers( clean_api_spec, :on )
265
+
266
+ expect do
267
+ clean_api_spec.def_on :stuff, :stuff2 do
268
+
269
+ end
270
+ end.to raise_error ArgumentError
271
+ end
272
+ end
273
+ end
274
+
275
+ describe '#define_call_handler' do
276
+ before do
277
+ subject.define_definers( clean_api_spec, :on, :after )
278
+ end
279
+
280
+ it 'defines a handler for the given type and object' do
281
+ on_args = []
282
+ subject.define_call_handler clean_api_spec, :on, :stuff do |*a, &block|
283
+ on_args << [a, block]
284
+ end
285
+
286
+ after_args = []
287
+ subject.define_call_handler clean_api_spec, :after, :stuff do |*a, &block|
288
+ after_args << [a, block]
289
+ end
290
+
291
+ b1 = proc{}
292
+ clean_api.on :stuff, 1, &b1
293
+
294
+ b2 = proc{}
295
+ clean_api.after :stuff, 2, &b2
296
+
297
+ expect(on_args).to eq [[[1], b1]]
298
+ expect(after_args).to eq [[[2], b2]]
299
+ end
300
+
301
+ it 'enforces argument arity' do
302
+ subject.define_call_handler clean_api_spec, :on, :none do
303
+ end
304
+
305
+ expect do
306
+ clean_api.on :none
307
+ end.to_not raise_error
308
+
309
+ expect do
310
+ clean_api.on :none, 1
311
+ end.to raise_error ArgumentError
312
+
313
+
314
+ subject.define_call_handler clean_api_spec, :on, :one do |x|
315
+ end
316
+
317
+ expect do
318
+ clean_api.on :one, 1
319
+ end.to_not raise_error
320
+
321
+ expect do
322
+ clean_api.on :one
323
+ end.to raise_error ArgumentError
324
+
325
+ expect do
326
+ clean_api.on :one, 1, 2
327
+ end.to raise_error ArgumentError
328
+
329
+
330
+ subject.define_call_handler clean_api_spec, :on, :two do |x, y|
331
+ end
332
+
333
+ expect do
334
+ clean_api.on :two, 1, 2
335
+ end.to_not raise_error
336
+
337
+ expect do
338
+ clean_api.on :two
339
+ end.to raise_error ArgumentError
340
+
341
+ expect do
342
+ clean_api.on :two, 1
343
+ end.to raise_error ArgumentError
344
+
345
+ expect do
346
+ clean_api.on :two, 1, 2, 3
347
+ end.to raise_error ArgumentError
348
+
349
+
350
+ subject.define_call_handler clean_api_spec, :on, :var do |*args|
351
+ end
352
+
353
+ expect do
354
+ clean_api.on :var
355
+ end.to_not raise_error
356
+
357
+ expect do
358
+ clean_api.on :var, 1
359
+ end.to_not raise_error
360
+ end
361
+
362
+ context 'when object is' do
363
+ context 'nil' do
364
+ it 'is treated as an object' do
365
+ args = []
366
+ subject.define_call_handler clean_api_spec, :on, nil do |*a|
367
+ args << a
368
+ end
369
+
370
+ expect do
371
+ clean_api.on
372
+ end.to raise_error NoMethodError
373
+
374
+ clean_api.on nil, :stuff
375
+
376
+ expect(args).to eq [[:stuff]]
377
+ end
378
+ end
379
+ end
380
+
381
+ context 'when no object is given' do
382
+ it 'is treated as a catch-call' do
383
+ args = []
384
+ subject.define_call_handler clean_api_spec, :on do |*a|
385
+ args << a
386
+ end
387
+
388
+ subject.define_call_handler clean_api_spec, :on, :something do |*a|
389
+ end
390
+
391
+ clean_api.on
392
+ clean_api.on nil
393
+ clean_api.on [:stuff]
394
+ clean_api.on :stuff2, { mode: :stuff }
395
+
396
+ clean_api.on :something
397
+
398
+ expect(args).to eq [[], [nil], [[:stuff]], [:stuff2, { mode: :stuff }]]
399
+ end
400
+ end
401
+ end
402
+ end