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,148 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::DSL::Nodes::APIBuilder do
4
+ include_examples DSeL::DSL::Nodes::Base
5
+
6
+ let(:name) { "MyAPI#{rand(999)}".to_sym }
7
+ let(:klass) { Object.const_get( name ) }
8
+
9
+ let(:child_name) { "MyAPIChild#{rand(999)}".to_sym }
10
+
11
+ let(:clean_api_spec) { Factory[:clean_api_spec] }
12
+ let(:context) { Factory[:clean_api_spec] }
13
+ let(:other_context) { Factory[:clean_api_spec] }
14
+
15
+ it 'uses the context as the environment' do
16
+ subject.run{}
17
+ expect(subject.environment).to be subject.subject
18
+ end
19
+
20
+ describe '.build' do
21
+ it 'runs the given block' do
22
+ described_class.build( name ){}
23
+ expect(klass).to be < DSeL::API::Node
24
+ end
25
+
26
+ it 'returns the API node' do
27
+ api = described_class.build( name ){}
28
+ expect(klass).to be api
29
+ end
30
+
31
+ context 'when no block has been given' do
32
+ it 'raises ArgumentError' do
33
+ expect do
34
+ described_class.build( name )
35
+ end.to raise_error ArgumentError
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#initialize' do
41
+ describe 'node' do
42
+ context 'when given a' do
43
+ context 'Symbol' do
44
+ let(:context){ name }
45
+
46
+ it 'creates an API node by the same name' do
47
+ subject
48
+ expect(klass).to be < DSeL::API::Node
49
+ end
50
+
51
+ it 'uses it as a context' do
52
+ subject
53
+ expect(subject.subject).to be klass
54
+ end
55
+
56
+ context 'when the name has already been taken' do
57
+ it 'raises ArgumentError' do
58
+ subject
59
+
60
+ expect do
61
+ described_class.new( name )
62
+ end.to raise_error ArgumentError
63
+ end
64
+ end
65
+ end
66
+
67
+ context 'DSeL::API::Node' do
68
+ let(:context){ clean_api_spec }
69
+
70
+ it 'uses it as a context' do
71
+ expect(subject.subject).to be clean_api_spec
72
+ end
73
+ end
74
+
75
+ context 'other' do
76
+ let(:context){ '' }
77
+
78
+ it 'raises ArgumentError' do
79
+ expect do
80
+ subject
81
+ end.to raise_error ArgumentError
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ describe 'options' do
88
+ describe 'when creating a new API node' do
89
+ let(:context) do
90
+ name
91
+ end
92
+
93
+ describe ':namespace' do
94
+ let(:namespace) { module MyNamespace;end; MyNamespace }
95
+ let(:options) do
96
+ { namespace: namespace }
97
+ end
98
+
99
+ it 'is placed under that namespace' do
100
+ subject.subject
101
+ expect(namespace.constants).to include name
102
+ end
103
+ end
104
+
105
+ describe ':superclass' do
106
+ let(:superclass) { other_context }
107
+ let(:options) do
108
+ { superclass: superclass }
109
+ end
110
+
111
+ it 'sets the API node superclass' do
112
+ expect(subject.subject).to be < superclass
113
+ end
114
+
115
+ context "when not an #{DSeL::API::Node} subclass" do
116
+ let(:superclass) { Object }
117
+
118
+ it 'raises ArgumentError' do
119
+ expect do
120
+ subject
121
+ end.to raise_error ArgumentError
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '#node_for' do
130
+ let(:child_node) { subject.node_for( child_name ) }
131
+
132
+ it 'uses the current context as the namespace' do
133
+ expect(child_node.subject).to be subject.subject.const_get( child_name )
134
+ end
135
+
136
+ context 'when a superclass has been set' do
137
+ let(:superclass) { other_context }
138
+ let(:options) do
139
+ { superclass: superclass }
140
+ end
141
+
142
+ it 'uses it as the superclass' do
143
+ expect(child_node.subject).to be < superclass
144
+ end
145
+ end
146
+ end
147
+
148
+ end
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::DSL::Nodes::API do
4
+ include_examples DSeL::DSL::Nodes::Base
5
+
6
+ let(:api_spec) { Factory[:clean_api_spec] }
7
+ let(:other_api_spec) { Factory[:clean_api_spec] }
8
+ let(:context) { api_spec.new }
9
+
10
+ it "uses #{described_class::Environment}" do
11
+ subject.run{}
12
+ expect(subject.environment).to be_kind_of described_class::Environment
13
+ end
14
+
15
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::DSL::Nodes::Direct::Environment do
4
+ include_examples DSeL::DSL::Nodes::Base::Environment
5
+
6
+ subject do
7
+ def node.cleanup_environment
8
+ end
9
+
10
+ node.run{}
11
+ node.environment
12
+ end
13
+
14
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::DSL::Nodes::Direct do
4
+ include_examples DSeL::DSL::Nodes::Base
5
+
6
+ it 'uses the context as the environment' do
7
+ subject.run{}
8
+ expect(subject.environment).to be subject.subject
9
+ end
10
+
11
+ describe '#run' do
12
+ let(:environment) do
13
+ subject.run{}
14
+ subject.environment
15
+ end
16
+
17
+ it 'removes environment methods from the object' do
18
+ called = true
19
+
20
+ subject.extend_env.each do |mod|
21
+ mod.instance_methods( true ).each do |m|
22
+ next if subject.reset_methods.include? m
23
+
24
+ expect(environment).to_not respond_to m
25
+ end
26
+ end
27
+
28
+ expect(called).to be_truthy
29
+ end
30
+
31
+ it 'resets overridden object methods' do
32
+ called = true
33
+
34
+ subject.reset_methods.each do |m|
35
+ next if !environment.respond_to? m
36
+
37
+ expect(environment.method(m).source_location).to be_nil
38
+ end
39
+
40
+ expect(called).to be_truthy
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::DSL::Nodes::Proxy::Environment do
4
+ include_examples DSeL::DSL::Nodes::Base::Environment
5
+ # include_examples DSeL::DSL::Mixins::Environment::IvarExplorer
6
+
7
+ subject { described_class.new }
8
+ let(:node_context) do
9
+ Class.new do
10
+ def test
11
+ __method__
12
+ end
13
+
14
+ def hash
15
+ 1
16
+ end
17
+ end.new
18
+ end
19
+
20
+ it 'forwards methods to the context' do
21
+ expect(subject.test).to be :test
22
+ end
23
+
24
+ describe "##{described_class::DSEL_NODE_ACCESSOR}=" do
25
+ context 'when a node is given' do
26
+ it 'undefines methods with the same name as the context' do
27
+ expect(subject.hash).to eq node.subject.hash
28
+ end
29
+ end
30
+
31
+ context 'when nil is given' do
32
+ it 'does nothing' do
33
+ subject._dsel_node = nil
34
+ expect(subject._dsel_node).to be_nil
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '#respond_to?' do
40
+ it 'returns false' do
41
+ expect(subject).to_not respond_to :stuff
42
+ end
43
+
44
+ context 'when self responds' do
45
+ it 'returns true' do
46
+ expect(subject).to respond_to :_dsel_variables
47
+ end
48
+ end
49
+
50
+ context 'when the context responds' do
51
+ it 'returns true' do
52
+ expect(subject).to respond_to :test
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DSeL::DSL::Nodes::Proxy do
4
+ include_examples DSeL::DSL::Nodes::Base
5
+
6
+ it "uses #{described_class::Environment}" do
7
+ subject.run{}
8
+ expect(subject.environment).to be_kind_of described_class::Environment
9
+ end
10
+
11
+ end
@@ -0,0 +1,22 @@
1
+ require 'bundler/setup'
2
+ require 'dsel'
3
+
4
+ require_relative 'support/helpers/paths'
5
+
6
+ Dir.glob( "#{support_path}/{lib,fixtures,factories,shared}/**/*.rb" ).each { |f| require f }
7
+
8
+ RSpec.configure do |config|
9
+ # Enable flags like --only-failures and --next-failure
10
+ config.example_status_persistence_file_path = '.rspec_status'
11
+
12
+ # Disable RSpec exposing methods globally on `Module` and `main`
13
+ config.disable_monkey_patching!
14
+
15
+ config.expect_with :rspec do |c|
16
+ c.syntax = :expect
17
+ end
18
+
19
+ config.before :each do
20
+ DSeL::API::Generator.reset
21
+ end
22
+ end
@@ -0,0 +1,6 @@
1
+ Factory.define :clean_api_spec do
2
+ Object.const_set(
3
+ "MockNode#{rand(9999)}".to_sym,
4
+ Class.new( DSeL::API::Node )
5
+ )
6
+ end
@@ -0,0 +1,4 @@
1
+ class MockAPI < DSeL::API::Node
2
+ define :on
3
+ def_on { |*args| }
4
+ end
@@ -0,0 +1,19 @@
1
+ def name_from_filename
2
+ File.basename( caller.first.split( ':' ).first, '_spec.rb' )
3
+ end
4
+
5
+ def spec_path
6
+ File.expand_path( File.dirname( File.absolute_path( __FILE__ ) ) + '/../../' ) << '/'
7
+ end
8
+
9
+ def examples_path
10
+ File.expand_path( "#{spec_path}/../examples" ) << '/'
11
+ end
12
+
13
+ def support_path
14
+ "#{spec_path}support/"
15
+ end
16
+
17
+ def fixtures_path
18
+ "#{support_path}fixtures/"
19
+ end
@@ -0,0 +1,107 @@
1
+ class Factory
2
+ class <<self
3
+
4
+ # Clears all instructions and aliases.
5
+ def reset
6
+ @instructions = {}
7
+ @aliases = {}
8
+ end
9
+
10
+ # Defines instruction on how to create a given object.
11
+ #
12
+ # There are 2 ways to define a factory instruction:
13
+ #
14
+ # * Using a `generator` block to return an object.
15
+ # * Using the `options` to generate an object.
16
+ #
17
+ # @param [Symbol] object_name Name of the object.
18
+ # @param [Hash] options Generation options.
19
+ # @option options [Class] :class
20
+ # Class to instantiate.
21
+ # @option options [Hash] :options
22
+ # Options to use to instantiate the `:class`.
23
+ def define( object_name, options = {}, &generator )
24
+ @instructions[object_name] = {
25
+ options: options.dup,
26
+ generator: generator
27
+ }
28
+ end
29
+
30
+ # @param [Symbol] object_name Name of the object.
31
+ # @param [] args
32
+ # If a `generator` block has been provided via {#define}, those arguments
33
+ # will be passed to it.
34
+ #
35
+ # If `:options` have been passed to {#define}, the `args` will be merged
36
+ # with them before instantiating the object.
37
+ def create( object_name, *args )
38
+ ensure_defined( object_name )
39
+
40
+ instructions = instructions_for( object_name )
41
+
42
+ if instructions[:generator]
43
+ instructions[:generator].call( *args )
44
+ elsif (options = instructions[:options]).include? :class
45
+ options[:class].new( options[:options].merge( args.first || {} ) )
46
+ end
47
+ end
48
+
49
+ # @param [Symbol] object_name Name of the object.
50
+ # @return [Hash] Instantiation options passed to {#define}.
51
+ def options_for( object_name )
52
+ instructions_for( object_name )[:options][:options].freeze
53
+ end
54
+
55
+ # @note {#create} helper.
56
+ #
57
+ # @param [Symbol] object_name Name of the object.
58
+ # @return [Object] Instantiated object.
59
+ def []( object_name )
60
+ create object_name
61
+ end
62
+
63
+ # @param [Symbol] object_name
64
+ # Name of the new object.
65
+ # @param [Symbol] existing_object_name
66
+ # Name of the existing object.
67
+ def alias( object_name, existing_object_name )
68
+ ensure_defined( existing_object_name )
69
+ @aliases[object_name] = existing_object_name
70
+ end
71
+
72
+ # @param [Symbol] object_name Object to delete.
73
+ def delete( object_name )
74
+ @instructions.delete object_name
75
+ end
76
+
77
+ # @param [Symbol] object_name Alias to delete.
78
+ def unalias( object_name )
79
+ @aliases.delete object_name
80
+ nil
81
+ end
82
+
83
+ # @param [Symbol] object_name
84
+ # Name of the new object.
85
+ # @return [Bool]
86
+ # `true` if instructions have been {#defined} for the `object_name`,
87
+ # `false` otherwise.
88
+ def defined?( object_name )
89
+ !!instructions_for( object_name ) rescue false
90
+ end
91
+
92
+ private
93
+
94
+ def instructions_for( object_name )
95
+ instructions = @instructions[object_name] || @instructions[@aliases[object_name]]
96
+ return instructions if instructions
97
+
98
+ fail ArgumentError, "Factory '#{object_name}' not defined."
99
+ end
100
+
101
+ def ensure_defined( object_name )
102
+ !!instructions_for( object_name )
103
+ end
104
+ end
105
+ reset
106
+
107
+ end
@@ -0,0 +1,104 @@
1
+ require 'tempfile'
2
+
3
+ shared_examples_for DSeL::DSL::Nodes::Base::Environment do
4
+ if !defined? :subject
5
+ subject do
6
+ Class.new( described_class ).new
7
+ end
8
+ end
9
+
10
+ let(:node_class) do
11
+ sep = '::'
12
+ splits = described_class.to_s.split( sep )
13
+ splits.pop
14
+
15
+ Object.const_get( splits.join( sep ) )
16
+ end
17
+ let(:node) { node_class.new( node_context, node_options ) }
18
+ let(:node_options) { {} }
19
+ let(:node_context) { '2' }
20
+
21
+ let(:other_node) { node_class.new( other_node_context, other_node_options ) }
22
+ let(:other_node_options) { {} }
23
+ let(:other_node_context) { '1' }
24
+
25
+ let(:another_node) { node_class.new( another_node_context, another_node_options ) }
26
+ let(:another_node_options) { {} }
27
+ let(:another_node_context) { '3' }
28
+
29
+ before do
30
+ subject.send( "#{described_class::DSEL_NODE_ACCESSOR}=", node )
31
+ end
32
+
33
+ describe '#instance_variables' do
34
+ it "excludes #{described_class::DSEL_NODE_IVAR}" do
35
+ expect(subject.instance_variables).to_not include described_class::DSEL_NODE_IVAR
36
+ end
37
+ end
38
+
39
+ describe '#_dsel_shared_variables' do
40
+ it 'delegates to node' do
41
+ expect(subject._dsel_shared_variables).to be node.shared_variables
42
+ end
43
+ end
44
+
45
+ describe '#_dsel_self' do
46
+ it 'returns the context' do
47
+ expect(subject._dsel_self).to be node.subject
48
+ end
49
+ end
50
+
51
+ describe '#_dsel_variables' do
52
+ it 'returns instance variables' do
53
+ subject.instance_variable_set( :@tmp, 1 )
54
+ expect(subject._dsel_variables).to eq( tmp: 1 )
55
+ end
56
+ end
57
+
58
+ describe '#Parent' do
59
+ context 'when #root?' do
60
+ it 'raises error' do
61
+ expect do
62
+ subject.Parent{}
63
+ end.to raise_error RuntimeError
64
+ end
65
+ end
66
+
67
+ context 'when not #root?' do
68
+ let(:node_options) { { parent: other_node } }
69
+
70
+ it 'runs the block in the parent' do
71
+ node = nil
72
+ p = proc { node = send( DSeL::DSL::Nodes::Base::Environment::DSEL_NODE_ACCESSOR ) }
73
+
74
+ subject.Parent( &p )
75
+
76
+ expect(node).to be other_node
77
+ end
78
+ end
79
+ end
80
+
81
+ describe '#Root' do
82
+ context 'when #root?' do
83
+ it 'raises error' do
84
+ expect do
85
+ subject.Root{}
86
+ end.to raise_error RuntimeError
87
+ end
88
+ end
89
+
90
+ context 'when not #root?' do
91
+ let(:node_options) { { parent: other_node } }
92
+ let(:other_node_options) { { parent: another_node } }
93
+
94
+ it 'runs the block in the root' do
95
+ n = nil
96
+ p = proc { n = send( DSeL::DSL::Nodes::Base::Environment::DSEL_NODE_ACCESSOR ) }
97
+
98
+ subject.Root( &p )
99
+
100
+ expect(n).to be another_node
101
+ end
102
+ end
103
+ end
104
+ end