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,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,8 @@
1
+ module DSeL
2
+ module API
3
+
4
+ require_relative 'api/generator'
5
+ require_relative 'api/node'
6
+
7
+ end
8
+ end
@@ -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,18 @@
1
+ module DSeL
2
+ module DSL
3
+ module Nodes
4
+
5
+ class API < Proxy
6
+ require_relative 'api/environment'
7
+
8
+ private
9
+
10
+ def prepare_environment
11
+ @environment ||= Environment.new
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
18
+ 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
@@ -0,0 +1,14 @@
1
+ module DSeL
2
+ module DSL
3
+ module Nodes
4
+ class Direct
5
+
6
+ module Environment
7
+ include Base::Environment
8
+
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end