dsel 0.1.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.
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