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