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,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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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,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
|