dsel 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +15 -0
- data/LICENSE.md +23 -0
- data/README.md +113 -0
- data/Rakefile +6 -0
- data/dsel.gemspec +22 -0
- data/lib/dsel/api/generator.rb +285 -0
- data/lib/dsel/api/node.rb +241 -0
- data/lib/dsel/api.rb +8 -0
- data/lib/dsel/dsl/mixins/environment/ivar_explorer.rb +34 -0
- data/lib/dsel/dsl/nodes/api/environment.rb +49 -0
- data/lib/dsel/dsl/nodes/api.rb +18 -0
- data/lib/dsel/dsl/nodes/api_builder/environment.rb +56 -0
- data/lib/dsel/dsl/nodes/api_builder.rb +71 -0
- data/lib/dsel/dsl/nodes/base/environment.rb +50 -0
- data/lib/dsel/dsl/nodes/base.rb +110 -0
- data/lib/dsel/dsl/nodes/direct/environment.rb +14 -0
- data/lib/dsel/dsl/nodes/direct.rb +75 -0
- data/lib/dsel/dsl/nodes/proxy/environment.rb +41 -0
- data/lib/dsel/dsl/nodes/proxy.rb +20 -0
- data/lib/dsel/dsl.rb +10 -0
- data/lib/dsel/node.rb +42 -0
- data/lib/dsel/ruby/object.rb +53 -0
- data/lib/dsel/version.rb +3 -0
- data/lib/dsel.rb +10 -0
- data/spec/dsel/api/generator_spec.rb +402 -0
- data/spec/dsel/api/node_spec.rb +328 -0
- data/spec/dsel/dsel_spec.rb +63 -0
- data/spec/dsel/dsl/nodes/api/environment.rb +208 -0
- data/spec/dsel/dsl/nodes/api_builder/environment_spec.rb +91 -0
- data/spec/dsel/dsl/nodes/api_builder_spec.rb +148 -0
- data/spec/dsel/dsl/nodes/api_spec.rb +15 -0
- data/spec/dsel/dsl/nodes/direct/environment_spec.rb +14 -0
- data/spec/dsel/dsl/nodes/direct_spec.rb +43 -0
- data/spec/dsel/dsl/nodes/proxy/environment_spec.rb +56 -0
- data/spec/dsel/dsl/nodes/proxy_spec.rb +11 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/factories/clean_api_spec.rb +6 -0
- data/spec/support/fixtures/mock_api.rb +4 -0
- data/spec/support/helpers/paths.rb +19 -0
- data/spec/support/lib/factory.rb +107 -0
- data/spec/support/shared/dsl/nodes/base/environment.rb +104 -0
- data/spec/support/shared/dsl/nodes/base.rb +171 -0
- data/spec/support/shared/node.rb +70 -0
- metadata +108 -0
@@ -0,0 +1,241 @@
|
|
1
|
+
module DSeL
|
2
|
+
module API
|
3
|
+
|
4
|
+
class Node < Node
|
5
|
+
|
6
|
+
def self.inherited( base )
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def run( *args, &block )
|
12
|
+
DSL::Nodes::API.new( new ).run( *args, &block )
|
13
|
+
end
|
14
|
+
|
15
|
+
def define( *types )
|
16
|
+
if types.size > 1
|
17
|
+
if has_options?
|
18
|
+
fail ArgumentError,
|
19
|
+
"Cannot set options for multiple types: #{types}"
|
20
|
+
end
|
21
|
+
|
22
|
+
if has_description?
|
23
|
+
fail ArgumentError,
|
24
|
+
"Cannot set description for multiple types: #{types}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Generator.define_definers( self, *types )
|
29
|
+
end
|
30
|
+
|
31
|
+
def has_options?
|
32
|
+
!!@last_options
|
33
|
+
end
|
34
|
+
|
35
|
+
def has_description?
|
36
|
+
!!@last_description
|
37
|
+
end
|
38
|
+
|
39
|
+
def describe( description )
|
40
|
+
@last_description = description
|
41
|
+
end
|
42
|
+
|
43
|
+
def configure( options, &block )
|
44
|
+
@last_options = [options, block].compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def definers
|
48
|
+
@definers ||= []
|
49
|
+
end
|
50
|
+
|
51
|
+
def has_call_handler?( type, *possible_object )
|
52
|
+
method_defined? Generator.call_handler_name( type, *possible_object )
|
53
|
+
end
|
54
|
+
|
55
|
+
def call_handlers
|
56
|
+
@call_handlers ||= []
|
57
|
+
end
|
58
|
+
|
59
|
+
def root?
|
60
|
+
!parent
|
61
|
+
end
|
62
|
+
|
63
|
+
def child?
|
64
|
+
!root?
|
65
|
+
end
|
66
|
+
|
67
|
+
def root
|
68
|
+
return self if root?
|
69
|
+
|
70
|
+
@root ||= begin
|
71
|
+
p = @parent
|
72
|
+
while p.parent
|
73
|
+
p = p.parent
|
74
|
+
end
|
75
|
+
p
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def parent
|
80
|
+
@parent
|
81
|
+
end
|
82
|
+
|
83
|
+
def push_children( c )
|
84
|
+
c.each do |name, (klass, *args)|
|
85
|
+
push_child( name, klass, *args )
|
86
|
+
end
|
87
|
+
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def push_child( name, node, s = nil )
|
92
|
+
if s && !s.is_a?( Symbol ) && !s.respond_to?( :call )
|
93
|
+
fail ArgumentError, 'Subject not Symbol nor responds to #call.'
|
94
|
+
end
|
95
|
+
|
96
|
+
node.set_parent( self )
|
97
|
+
|
98
|
+
child = {
|
99
|
+
name: name.to_sym,
|
100
|
+
node: node
|
101
|
+
}
|
102
|
+
|
103
|
+
if (options = self.flush_options)
|
104
|
+
child.merge!( options: options )
|
105
|
+
end
|
106
|
+
|
107
|
+
if (description = self.flush_description)
|
108
|
+
child.merge!( description: description )
|
109
|
+
end
|
110
|
+
|
111
|
+
children[name] = child
|
112
|
+
|
113
|
+
define_method name do
|
114
|
+
ivar = "@#{name}"
|
115
|
+
|
116
|
+
v = instance_variable_get( ivar )
|
117
|
+
return v if v
|
118
|
+
|
119
|
+
sub = @subject
|
120
|
+
if s.is_a?( Symbol )
|
121
|
+
sub = sub.send( s )
|
122
|
+
end
|
123
|
+
|
124
|
+
if s.respond_to?( :call )
|
125
|
+
sub = s.call( sub )
|
126
|
+
end
|
127
|
+
|
128
|
+
instance_variable_set( ivar, node.new( sub, parent: self ) )
|
129
|
+
end
|
130
|
+
|
131
|
+
child
|
132
|
+
end
|
133
|
+
|
134
|
+
def children
|
135
|
+
@children ||= {}
|
136
|
+
end
|
137
|
+
|
138
|
+
def tree
|
139
|
+
root.branch
|
140
|
+
end
|
141
|
+
|
142
|
+
def branch
|
143
|
+
t = {
|
144
|
+
definers: self.definers,
|
145
|
+
call_handlers: self.call_handlers.map { |h| c = h.dup; c.delete( :method ); c },
|
146
|
+
children: {}
|
147
|
+
}
|
148
|
+
|
149
|
+
self.children.each do |name, child|
|
150
|
+
t[:children][name] = child.merge( child[:node].branch )
|
151
|
+
end
|
152
|
+
|
153
|
+
t
|
154
|
+
end
|
155
|
+
|
156
|
+
# @private
|
157
|
+
def flush_description
|
158
|
+
d = @last_description
|
159
|
+
@last_description = nil
|
160
|
+
d
|
161
|
+
end
|
162
|
+
|
163
|
+
# @private
|
164
|
+
def flush_options
|
165
|
+
o = @last_options
|
166
|
+
@last_options = nil
|
167
|
+
o
|
168
|
+
end
|
169
|
+
|
170
|
+
# @private
|
171
|
+
def push_call_handler( type, method, *possible_object )
|
172
|
+
handler = {
|
173
|
+
type: type.to_sym
|
174
|
+
}
|
175
|
+
|
176
|
+
if !possible_object.empty?
|
177
|
+
handler.merge!( object: possible_object.first )
|
178
|
+
end
|
179
|
+
|
180
|
+
if (options = self.flush_options)
|
181
|
+
handler.merge!( options: options )
|
182
|
+
end
|
183
|
+
|
184
|
+
if (description = self.flush_description)
|
185
|
+
handler.merge!( description: description )
|
186
|
+
end
|
187
|
+
|
188
|
+
handler.merge!(
|
189
|
+
method: method.to_sym
|
190
|
+
)
|
191
|
+
|
192
|
+
call_handlers << handler
|
193
|
+
handler
|
194
|
+
end
|
195
|
+
|
196
|
+
# @private
|
197
|
+
def set_parent( node )
|
198
|
+
fail if @parent
|
199
|
+
@parent = node
|
200
|
+
end
|
201
|
+
|
202
|
+
# @private
|
203
|
+
def push_definer( type, method )
|
204
|
+
definer = {
|
205
|
+
type: type.to_sym
|
206
|
+
}
|
207
|
+
|
208
|
+
if (options = self.flush_options)
|
209
|
+
definer.merge!( options: options )
|
210
|
+
end
|
211
|
+
|
212
|
+
if (description = self.flush_description)
|
213
|
+
definer.merge!( description: description )
|
214
|
+
end
|
215
|
+
|
216
|
+
definer.merge!(
|
217
|
+
method: method.to_sym
|
218
|
+
)
|
219
|
+
|
220
|
+
definers << definer
|
221
|
+
definer
|
222
|
+
end
|
223
|
+
|
224
|
+
def method_missing( m, *args, &block )
|
225
|
+
ms = m.to_s
|
226
|
+
if ms.start_with? 'def_'
|
227
|
+
to_define = ms.split( 'def_', 2 ).last
|
228
|
+
define to_define
|
229
|
+
|
230
|
+
send m, *args, &block
|
231
|
+
else
|
232
|
+
super( m, *args, &block )
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
end
|
data/lib/dsel/api.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
module DSeL
|
2
|
+
module DSL
|
3
|
+
module Mixins
|
4
|
+
module Environment
|
5
|
+
|
6
|
+
module IvarExplorer
|
7
|
+
|
8
|
+
def method_missing( name, *args, &block )
|
9
|
+
first_letter = name[0...1]
|
10
|
+
|
11
|
+
if block && first_letter == first_letter.capitalize
|
12
|
+
ivar = "@#{name}".to_sym
|
13
|
+
return _dsel_node_for_ivar( ivar, &block )
|
14
|
+
end
|
15
|
+
|
16
|
+
super( name, *args, &block )
|
17
|
+
end
|
18
|
+
|
19
|
+
# @private
|
20
|
+
def _dsel_node_for_ivar( ivar, &block )
|
21
|
+
ivar = ivar.downcase
|
22
|
+
if !_dsel_node.subject.instance_variable_defined?( ivar )
|
23
|
+
fail ArgumentError, "Instance variable not defined: #{ivar}"
|
24
|
+
end
|
25
|
+
|
26
|
+
_dsel_node.node_for( _dsel_node.subject.instance_variable_get( ivar ) ).run( &block )
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../proxy/environment'
|
2
|
+
|
3
|
+
module DSeL
|
4
|
+
module DSL
|
5
|
+
module Nodes
|
6
|
+
class API
|
7
|
+
|
8
|
+
class Environment < Proxy::Environment
|
9
|
+
|
10
|
+
define_method "#{DSEL_NODE_ACCESSOR}=" do |node|
|
11
|
+
super( node )
|
12
|
+
|
13
|
+
if node
|
14
|
+
node.subject.class.children.keys.each do |name|
|
15
|
+
define_singleton_method name.capitalize do |&b|
|
16
|
+
node.node_for( send( name ) ).run( &b )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
node
|
22
|
+
end
|
23
|
+
|
24
|
+
def also( *args, &block )
|
25
|
+
# TODO: Store #last_call on Node at the instance level,
|
26
|
+
# this global state can be interfered with by other DSLs.
|
27
|
+
last_call = DSeL::API::Generator.last_call
|
28
|
+
type = last_call[:type]
|
29
|
+
|
30
|
+
# Check to see if there is a handler that matches our possible object.
|
31
|
+
# If so, treat it as object.
|
32
|
+
# If not, use the last object and assume arguments.
|
33
|
+
if last_call.include?( :object ) &&
|
34
|
+
!_dsel_self.class.has_call_handler?( type, args.first )
|
35
|
+
|
36
|
+
args.unshift last_call[:object]
|
37
|
+
end
|
38
|
+
|
39
|
+
send( type, *args, &block )
|
40
|
+
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module DSeL
|
2
|
+
module DSL
|
3
|
+
module Nodes
|
4
|
+
class APIBuilder
|
5
|
+
|
6
|
+
module Environment
|
7
|
+
include Base::Environment
|
8
|
+
|
9
|
+
def import( file )
|
10
|
+
f = file.dup
|
11
|
+
f << '.rb' if !file.end_with?( '.rb' )
|
12
|
+
|
13
|
+
_dsel_import f
|
14
|
+
end
|
15
|
+
|
16
|
+
def import_many( glob )
|
17
|
+
Dir["#{glob}.rb"].each { |file| _dsel_import( file ) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def import_relative( file )
|
21
|
+
f = _dsel_caller_dir
|
22
|
+
f << file
|
23
|
+
f << '.rb' if !file.end_with?( '.rb' )
|
24
|
+
|
25
|
+
_dsel_import f
|
26
|
+
end
|
27
|
+
|
28
|
+
def import_relative_many( glob )
|
29
|
+
Dir["#{_dsel_caller_dir}#{glob}.rb"].each { |file| _dsel_import( file ) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def child( method_name, class_name, *args, &block )
|
33
|
+
node = _dsel_node.node_for( class_name )
|
34
|
+
node.run( &block )
|
35
|
+
|
36
|
+
_dsel_node.subject.push_child(
|
37
|
+
method_name,
|
38
|
+
node.subject,
|
39
|
+
*args
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def _dsel_import( file )
|
44
|
+
_dsel_node.subject.instance_eval( IO.read( file ) )
|
45
|
+
end
|
46
|
+
|
47
|
+
def _dsel_caller_dir( offset = 1 )
|
48
|
+
File.dirname( caller[offset].split( ':', 2 ).first ) << '/'
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative 'direct'
|
2
|
+
|
3
|
+
module DSeL
|
4
|
+
module DSL
|
5
|
+
module Nodes
|
6
|
+
|
7
|
+
class APIBuilder < Nodes::Direct
|
8
|
+
require_relative 'api_builder/environment'
|
9
|
+
|
10
|
+
def self.build( *args, &block )
|
11
|
+
fail ArgumentError, 'Missing block.' if !block
|
12
|
+
|
13
|
+
node = new( *args )
|
14
|
+
node.run( &block )
|
15
|
+
node.subject
|
16
|
+
end
|
17
|
+
|
18
|
+
API_NODE = DSeL::API::Node
|
19
|
+
|
20
|
+
def initialize( node, options = {} )
|
21
|
+
@superclass = options[:superclass] || API_NODE
|
22
|
+
if !(@superclass <= API_NODE)
|
23
|
+
fail ArgumentError, "Superclass not subclass of #{API_NODE}."
|
24
|
+
end
|
25
|
+
|
26
|
+
if node.is_a?( Symbol )
|
27
|
+
namespace = options[:namespace] || Object
|
28
|
+
|
29
|
+
if namespace.constants.include?( node )
|
30
|
+
fail ArgumentError, "Node name taken: #{c.inspect}"
|
31
|
+
end
|
32
|
+
|
33
|
+
subject = namespace.const_set( node, Class.new( @superclass ) )
|
34
|
+
|
35
|
+
elsif node.is_a?( Class ) && node < DSeL::API::Node
|
36
|
+
subject = node
|
37
|
+
|
38
|
+
else
|
39
|
+
fail ArgumentError,
|
40
|
+
"Expected #{Symbol} or #{DSeL::API::Node}, got: #{node.inspect}"
|
41
|
+
end
|
42
|
+
|
43
|
+
super( subject, options )
|
44
|
+
end
|
45
|
+
|
46
|
+
# @private
|
47
|
+
def node_for( subject, options = {} )
|
48
|
+
super( subject, options.merge(
|
49
|
+
namespace: @subject,
|
50
|
+
superclass: @superclass
|
51
|
+
))
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset_methods
|
55
|
+
[
|
56
|
+
:instance_variables,
|
57
|
+
:method_missing
|
58
|
+
]
|
59
|
+
end
|
60
|
+
|
61
|
+
def extend_env
|
62
|
+
[
|
63
|
+
Environment
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module DSeL
|
2
|
+
module DSL
|
3
|
+
module Nodes
|
4
|
+
class Base
|
5
|
+
|
6
|
+
module Environment
|
7
|
+
|
8
|
+
DSEL_NODE_ACCESSOR = :_dsel_node
|
9
|
+
DSEL_NODE_IVAR = "@#{DSEL_NODE_ACCESSOR}".to_sym
|
10
|
+
|
11
|
+
# @private
|
12
|
+
attr_accessor DSEL_NODE_ACCESSOR
|
13
|
+
|
14
|
+
def instance_variables
|
15
|
+
super.tap { |ivars| ivars.delete DSEL_NODE_IVAR }
|
16
|
+
end
|
17
|
+
|
18
|
+
def _dsel_shared_variables
|
19
|
+
_dsel_node.shared_variables
|
20
|
+
end
|
21
|
+
|
22
|
+
def _dsel_self
|
23
|
+
_dsel_node.subject
|
24
|
+
end
|
25
|
+
|
26
|
+
def _dsel_variables
|
27
|
+
s = {}
|
28
|
+
instance_variables.each do |ivar|
|
29
|
+
s[ivar.to_s.sub( '@', '' ).to_sym] = instance_variable_get( ivar )
|
30
|
+
end
|
31
|
+
s.freeze
|
32
|
+
end
|
33
|
+
|
34
|
+
def Parent( &block )
|
35
|
+
fail 'Already root.' if _dsel_node.root?
|
36
|
+
|
37
|
+
_dsel_node.parent.run( &block )
|
38
|
+
end
|
39
|
+
|
40
|
+
def Root( &block )
|
41
|
+
fail 'Already root.' if _dsel_node.root?
|
42
|
+
_dsel_node.root.run( &block )
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module DSeL
|
2
|
+
module DSL
|
3
|
+
module Nodes
|
4
|
+
|
5
|
+
class Base < Node
|
6
|
+
require_relative 'base/environment'
|
7
|
+
|
8
|
+
# @return [Environment]
|
9
|
+
attr_reader :environment
|
10
|
+
|
11
|
+
def initialize(*)
|
12
|
+
super
|
13
|
+
|
14
|
+
@shared_variables = {}
|
15
|
+
@nodes = {}
|
16
|
+
|
17
|
+
cache_node( self )
|
18
|
+
end
|
19
|
+
|
20
|
+
# @private
|
21
|
+
def nodes
|
22
|
+
root? ? @nodes : @root.nodes
|
23
|
+
end
|
24
|
+
|
25
|
+
def shared_variables
|
26
|
+
root? ? @shared_variables : @root.shared_variables
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
def cache_node( node )
|
31
|
+
nodes[node.hash] ||= node
|
32
|
+
end
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def node_for( subject, options = {} )
|
36
|
+
nodes[calc_node_hash( subject )] ||=
|
37
|
+
self.class.new( subject, options.merge( parent: self ) )
|
38
|
+
end
|
39
|
+
|
40
|
+
def run( script = nil, &block )
|
41
|
+
if script && block
|
42
|
+
fail ArgumentError, 'Cannot use both script and &block.'
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
prepare
|
47
|
+
|
48
|
+
calling do
|
49
|
+
if block
|
50
|
+
return @environment.instance_eval( &block )
|
51
|
+
end
|
52
|
+
|
53
|
+
if script
|
54
|
+
@environment.instance_eval do
|
55
|
+
return eval( IO.read( script ) )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
ensure
|
60
|
+
# Re-entry, don't touch anything.
|
61
|
+
return if calling?
|
62
|
+
|
63
|
+
# May not have been prepared yet.
|
64
|
+
return if !@environment.respond_to?( Environment::DSEL_NODE_ACCESSOR )
|
65
|
+
|
66
|
+
cleanup
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def prepare
|
73
|
+
prepare_environment
|
74
|
+
@environment._dsel_node = self
|
75
|
+
end
|
76
|
+
|
77
|
+
def cleanup
|
78
|
+
@environment._dsel_node = nil
|
79
|
+
cleanup_environment
|
80
|
+
end
|
81
|
+
|
82
|
+
# @abstract
|
83
|
+
def cleanup_environment
|
84
|
+
end
|
85
|
+
|
86
|
+
# @abstract
|
87
|
+
def prepare_environment
|
88
|
+
fail 'Not implemented.'
|
89
|
+
end
|
90
|
+
|
91
|
+
def calling( &block )
|
92
|
+
return block.call if @calling
|
93
|
+
|
94
|
+
@calling = true
|
95
|
+
begin
|
96
|
+
block.call
|
97
|
+
ensure
|
98
|
+
@calling = false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def calling?
|
103
|
+
@calling
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|