dsl_block 1.0.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/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +16 -0
- data/dsl_block.gemspec +29 -0
- data/lib/dsl_block/executor.rb +37 -0
- data/lib/dsl_block/version.rb +10 -0
- data/lib/dsl_block.rb +159 -0
- data/spec/lib/dsl_block/dsl_executor_spec.rb +61 -0
- data/spec/lib/dsl_block_spec.rb +241 -0
- data/spec/spec_helper.rb +114 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6c00534daad8cd4a27d4f3855c4d9a03fad82595
|
4
|
+
data.tar.gz: 4dfb593b99f4653aecd81b661e4a04533068a87e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bb9421a382aca361db5ff31e801d5f064a5789077d68b994aee65a92d87e4385c26631da68466203c5030f6271371db9444a59c9da991e46fcd41f111da462bf
|
7
|
+
data.tar.gz: 227ebf931eca9fdb584fc51dea8a422a74bfa9fad5823af7127937ef69f0c277efc3c152ebd781f19f4a75cd76c5e0e802d65cfbd5e3783f0215868a965a4376
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Frank Hall
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# DslBlock
|
2
|
+
|
3
|
+
DslBlock allows you to use classes to define blocks with commands for a Domain Specific Language. The commands are automatically relayed to your instance method.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'dsl_block'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install dsl_block
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
class Foo < DslBlock
|
22
|
+
commands :show_foo
|
23
|
+
def show_foo(x)
|
24
|
+
puts "Mr. T says you are a foo times #{x.to_i}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Bar < DslBlock
|
29
|
+
commands :show_bar
|
30
|
+
def show_bar(x)
|
31
|
+
puts "Ordering #{x.to_i} Shirley Temples from the bar"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Baz < DslBlock
|
36
|
+
commands :show_baz
|
37
|
+
def show_baz(x)
|
38
|
+
puts "Baz spaz #{x.inspect}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Baz.add_command_to(Bar)
|
43
|
+
Bar.add_command_to(Foo, true)
|
44
|
+
Foo.add_command_to(self)
|
45
|
+
|
46
|
+
|
47
|
+
foo do
|
48
|
+
puts self.inspect # => #<Foo:0x007f98f187e240 @block=#<Proc:0x...>, @parent=nil>
|
49
|
+
x = 10/10
|
50
|
+
show_foo x # => Mr. T says you are a foo times 1
|
51
|
+
|
52
|
+
bar do
|
53
|
+
x *= 2
|
54
|
+
show_bar x # => Ordering 2 Shirley Temples from the bar
|
55
|
+
x += 1
|
56
|
+
show_foo x # => Mr. T says you are a foo times 3
|
57
|
+
|
58
|
+
baz do
|
59
|
+
x *= 4
|
60
|
+
x /= 3
|
61
|
+
show_baz x # => Baz spaz 4
|
62
|
+
begin
|
63
|
+
x += 1
|
64
|
+
show_bar 5 # This will throw a NameError
|
65
|
+
rescue NameError
|
66
|
+
puts 'No bar for us'
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rdoc/task'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
require 'rdoc/task'
|
5
|
+
|
6
|
+
RDoc::Task.new do |rdoc|
|
7
|
+
rdoc.rdoc_dir = 'doc'
|
8
|
+
rdoc.main = 'DslBlock.html'
|
9
|
+
rdoc.rdoc_files.include 'lib'
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
RSpec::Core::RakeTask.new
|
14
|
+
|
15
|
+
task :default => :spec
|
16
|
+
task :test => :spec
|
data/dsl_block.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dsl_block/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'dsl_block'
|
8
|
+
spec.version = DslBlock::VERSION
|
9
|
+
spec.authors = ['Frank Hall']
|
10
|
+
spec.email = ['ChapterHouse.Dune@gmail.com']
|
11
|
+
spec.summary = %q{Quick and simple DSL creator.}
|
12
|
+
spec.description = %q{DslBlock is a quick and simple DSL creator.}
|
13
|
+
spec.homepage = 'http://chapterhouse.github.io/dsl_block'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'activesupport', '~> 4.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 2.14.1'
|
26
|
+
spec.add_development_dependency 'simplecov', '~> 0.7.1'
|
27
|
+
spec.add_development_dependency 'rdoc', '~> 4.0.0'
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class DslBlock
|
2
|
+
|
3
|
+
# The Executor class is designed to run a block of code in isolation
|
4
|
+
# for the DslBlock class. By running it in a 'sandbox', the block
|
5
|
+
# of code cannot inadvertently access protected and private methods
|
6
|
+
# within the DslBlock without explicate declaration by the DslBlock.
|
7
|
+
# To the user, it will appear that the block runs directly in the
|
8
|
+
# DslBlock but in a partly restricted manner if they care to investigate.
|
9
|
+
# In this fashion, executor is effectively a transparent proxy.
|
10
|
+
class Executor < BasicObject
|
11
|
+
|
12
|
+
def initialize(dsl_block)
|
13
|
+
@dsl_block = dsl_block
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args, &block)
|
17
|
+
# If the dsl block lists the method as a callable command
|
18
|
+
if @dsl_block._commands.include?(method)
|
19
|
+
# Attempt to call it
|
20
|
+
begin
|
21
|
+
@dsl_block.send(method, *args, &block)
|
22
|
+
rescue => e
|
23
|
+
# If there is any type of error, remove ourselves from the callstack to reduce confusion.
|
24
|
+
e.set_backtrace(::Kernel.caller.select { |x| !x.include?(__FILE__)})
|
25
|
+
::Kernel.raise e
|
26
|
+
end
|
27
|
+
else
|
28
|
+
# Otherwise raise a no method error as if the method does not really exist, regardless of reality.
|
29
|
+
name_error = ::NameError.new("undefined local variable or method `#{method}' for #{self.inspect}")
|
30
|
+
name_error.set_backtrace(::Kernel.caller.select { |x| !x.include?(__FILE__)})
|
31
|
+
::Kernel::raise name_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/dsl_block.rb
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'dsl_block/version'
|
2
|
+
require 'dsl_block/executor'
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
4
|
+
|
5
|
+
# DslBlock is a base class for defining a Domain Specific Language. Subclasses of DslBlock define the desired dsl.
|
6
|
+
# These methods become available to ruby code running in the context of the subclass.
|
7
|
+
# The block execution is automatically isolated to prevent the called block from accessing instance methods unless
|
8
|
+
# specifically designated as callable. DslBlocks can be nested and parent blocks can allow their methods to be exposed to child block.
|
9
|
+
#
|
10
|
+
# ==== Example
|
11
|
+
# # Define three DslBlocks each with at least one command in each block
|
12
|
+
# class Foo < DslBlock
|
13
|
+
# commands :show_foo
|
14
|
+
# def show_foo(x)
|
15
|
+
# "Mr. T says you are a foo times #{x.to_i}"
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# class Bar < DslBlock
|
20
|
+
# commands :show_bar
|
21
|
+
# def show_bar(x)
|
22
|
+
# "Ordering #{x.to_i} Shirley Temples from the bar"
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# class Baz < DslBlock
|
27
|
+
# commands :show_baz
|
28
|
+
# def show_baz(x)
|
29
|
+
# "Baz spaz #{x.inspect}"
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# # Connect the blocks to each other so they can be easily nested
|
34
|
+
# Baz.add_command_to(Bar)
|
35
|
+
# Bar.add_command_to(Foo, true) # Let Bar blocks also respond to foo methods
|
36
|
+
# Foo.add_command_to(self)
|
37
|
+
#
|
38
|
+
# # Use the new DSL
|
39
|
+
# foo do
|
40
|
+
# self.inspect # => #<Foo:0x007fdbd52b54e0 @block=#<Proc:0x007fdbd52b5530@/home/fhall/wonderland/alice.rb:29>, @parent=nil>
|
41
|
+
# x = 10/10
|
42
|
+
# show_foo x # => Mr. T says you are a foo times 1
|
43
|
+
#
|
44
|
+
# bar do
|
45
|
+
# x *= 2
|
46
|
+
# show_bar x # => Ordering 2 Shirley Temples from the bar
|
47
|
+
#
|
48
|
+
# x += 1
|
49
|
+
# show_foo x # => Mr. T says you are a foo times 3
|
50
|
+
#
|
51
|
+
# baz do
|
52
|
+
#
|
53
|
+
# x *= 4
|
54
|
+
# x /= 3
|
55
|
+
# show_baz x # => Baz spaz 4
|
56
|
+
#
|
57
|
+
# begin
|
58
|
+
# x += 1
|
59
|
+
# show_bar x # => NameError
|
60
|
+
# rescue NameError
|
61
|
+
# 'No bar for us'
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# end
|
66
|
+
#
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
class DslBlock
|
70
|
+
# Parent object providing additional commands to the block.
|
71
|
+
attr_accessor :parent
|
72
|
+
# Block of code that will be executed upon yield.
|
73
|
+
attr_accessor :block
|
74
|
+
|
75
|
+
# With no arguments, returns an array of command names that this DslBlock makes available to blocks either directly or indirectly.
|
76
|
+
# With arguments, adds new names to the array of command names, then returns the new array.
|
77
|
+
def self.commands(*args)
|
78
|
+
@commands ||= []
|
79
|
+
@commands = (@commands + args.map(&:to_sym)).uniq
|
80
|
+
@commands
|
81
|
+
end
|
82
|
+
|
83
|
+
# This is a convenience command that allows this DslBlock to inject itself as a method into another DslBlock or Object.
|
84
|
+
# If the parent is also a DslBlock, the new method will automatically be added to the available commands.
|
85
|
+
#
|
86
|
+
# Params:
|
87
|
+
# +destination+:: The object to receive the new method
|
88
|
+
# +propigate_local_commands+:: Allow methods in the destination to be called by the block. (default: false)
|
89
|
+
# +command_name+:: The name of the method to be created or nil to use the default which is based off of the class name. (default: nil)
|
90
|
+
def self.add_command_to(destination, propigate_local_commands=false, command_name=nil)
|
91
|
+
# Determine the name of the method to create
|
92
|
+
command_name = (command_name || name).to_s.underscore.to_sym
|
93
|
+
# Save a reference to our self so we will have something to call in a bit when self will refer to someone else.
|
94
|
+
this_class = self
|
95
|
+
# Define the command in the destination.
|
96
|
+
destination.send(:define_method, command_name) do |&block|
|
97
|
+
# Create a new instance of our self with the callers 'self' passed in as an optional parent.
|
98
|
+
# Immediately after initialization, yield the block.
|
99
|
+
this_class.new(propigate_local_commands ? self : nil, &block).yield
|
100
|
+
end
|
101
|
+
# Add the new command to the parent if it is a DslBlock.
|
102
|
+
destination.commands << command_name if destination.is_a?(Class) && destination < DslBlock
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create a new DslBlock instance.
|
106
|
+
# +parent+:: Optional parent DslBlock or Object that is providing additional commands to the block. (default: nil)
|
107
|
+
# +block+:: Required block of code that will be executed when yield is called on the new DslBlock instance.
|
108
|
+
def initialize(parent = nil, &block)
|
109
|
+
raise ArgumentError, 'block must be provided' unless block_given?
|
110
|
+
@block = block
|
111
|
+
@parent = parent
|
112
|
+
end
|
113
|
+
|
114
|
+
# This is the entire list of commands that this instance makes available to the block of code to be run.
|
115
|
+
# It is a combination of three distinct sources.
|
116
|
+
# 1. The class's declared commands
|
117
|
+
# 2. If there is a parent of this DslBock instance...
|
118
|
+
# * The parents declared commands if it is a DslBlock
|
119
|
+
# * The parents public_methods if it is any other type of object
|
120
|
+
# 3. Kernel.methods
|
121
|
+
#
|
122
|
+
# This method is prefixed with an underscore in an attempt to avoid collisions with commands in the given block.
|
123
|
+
def _commands
|
124
|
+
cmds = self.class.commands.dup
|
125
|
+
if @parent
|
126
|
+
if @parent.is_a?(DslBlock)
|
127
|
+
cmds += @parent._commands
|
128
|
+
else
|
129
|
+
cmds += @parent.public_methods
|
130
|
+
end
|
131
|
+
end
|
132
|
+
(cmds + Kernel.methods).uniq
|
133
|
+
end
|
134
|
+
|
135
|
+
# Yield the block given.
|
136
|
+
def yield
|
137
|
+
begin
|
138
|
+
# Evaluate the block in an executor to provide isolation
|
139
|
+
# and prevent accidental interference with ourselves.
|
140
|
+
Executor.new(self).instance_eval(&@block)
|
141
|
+
rescue Exception => e
|
142
|
+
e.set_backtrace(caller.select { |x| !x.include?(__FILE__)})
|
143
|
+
raise e
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# :nodoc:
|
148
|
+
def respond_to_missing?(method, include_all)
|
149
|
+
@parent && @parent.respond_to?(method, include_all) || super
|
150
|
+
end
|
151
|
+
|
152
|
+
def method_missing(name, *args, &block)
|
153
|
+
if @parent && @parent.respond_to?(name)
|
154
|
+
@parent.send(name, *args, &block)
|
155
|
+
else
|
156
|
+
super
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DslBlock::Executor do
|
4
|
+
|
5
|
+
context '.new' do
|
6
|
+
|
7
|
+
it 'inherits from BasicObject to constrain the block' do
|
8
|
+
expect(DslBlock::Executor.superclass).to equal(BasicObject)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
context '#method_missing' do
|
14
|
+
|
15
|
+
it 'calls the method on the dsl_block if it contains the method in its #_commands' do
|
16
|
+
dsl_block = Object.new
|
17
|
+
dsl_block.stub(:foo).and_return('bar')
|
18
|
+
dsl_block.stub(:_commands).and_return([:foo, :inspect])
|
19
|
+
executor = DslBlock::Executor.new(dsl_block)
|
20
|
+
expect(executor.foo).to eql('bar')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises NameError if the dsl_block does not contain the method in its #_commands' do
|
24
|
+
dsl_block = Object.new
|
25
|
+
dsl_block.stub(:foo).and_return('bar')
|
26
|
+
dsl_block.stub(:_commands).and_return([:inspect])
|
27
|
+
executor = DslBlock::Executor.new(dsl_block)
|
28
|
+
expect { executor.bar }.to raise_error(NameError)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'any exception' do
|
34
|
+
|
35
|
+
it 'removes itself from the backtrace to make it easier to understand' do
|
36
|
+
begin
|
37
|
+
dsl_block = Object.new
|
38
|
+
dsl_block.stub(:_commands).and_return([:inspect])
|
39
|
+
executor = DslBlock::Executor.new(dsl_block)
|
40
|
+
executor.bar
|
41
|
+
rescue => e
|
42
|
+
expect(e.backtrace.any? { |x| x.include?('dsl_block/lib/dsl_block/executor.rb')} ).to be_false
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
dsl_block = Object.new
|
47
|
+
dsl_block.stub(:foo) { raise 'Kaboom' }
|
48
|
+
dsl_block.stub(:_commands).and_return([:inspect, :foo])
|
49
|
+
executor = DslBlock::Executor.new(dsl_block)
|
50
|
+
executor.foo
|
51
|
+
rescue => e
|
52
|
+
expect(e.message).to eql('Kaboom')
|
53
|
+
expect(e.backtrace.any? { |x| x.include?('dsl_block/lib/dsl_block/executor.rb')} ).to be_false
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DslBlock do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
dsl_reset
|
7
|
+
end
|
8
|
+
|
9
|
+
context '.commands' do
|
10
|
+
|
11
|
+
it 'starts as an empty array' do
|
12
|
+
expect(dsl_class1.commands).to eql([])
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'accepts multiple method names' do
|
16
|
+
dsl_class1.commands :a, :b, :c
|
17
|
+
expect(dsl_class1.commands).to eql([:a, :b, :c])
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'appends the method names to the existing list' do
|
21
|
+
dsl_class1.commands :a, :b, :c
|
22
|
+
dsl_class1.commands :d, :e, :f
|
23
|
+
expect(dsl_class1.commands).to eql([:a, :b, :c, :d, :e, :f])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns the current method names when setting' do
|
27
|
+
expect(dsl_class1.commands(*[:a, :b, :c])).to eql([:a, :b, :c])
|
28
|
+
expect(dsl_class1.commands(*[:d, :e, :f])).to eql([:a, :b, :c, :d, :e, :f])
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'removes duplicates' do
|
32
|
+
dsl_class1.commands :a, :b, :a
|
33
|
+
dsl_class1.commands :c, :b, :d
|
34
|
+
expect(dsl_class1.commands).to eql([:a, :b, :c, :d])
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
context '.add_command_to' do
|
40
|
+
|
41
|
+
it 'defines a method in the destination' do
|
42
|
+
dsl_class2.add_command_to(dsl_class1)
|
43
|
+
expect(dsl_class1.instance_methods.include?(dsl_class2_command)).to be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'allows for the name of the method to be chosen' do
|
47
|
+
dsl_class2.add_command_to(dsl_class1, nil, :foo)
|
48
|
+
expect(dsl_class1.instance_methods.include?(:foo)).to be_true
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'adds to the commands in the destination if it is a DslBlock' do
|
52
|
+
dsl_class2.add_command_to(dsl_class1)
|
53
|
+
expect(dsl_class1.commands.include?(dsl_class2_command)).to be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'allows a non DslBlock destination' do
|
57
|
+
generic_class = Class.new
|
58
|
+
dsl_class2.add_command_to(generic_class)
|
59
|
+
expect(generic_class.instance_methods.include?(dsl_class2_command)).to be_true
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'the method created' do
|
63
|
+
|
64
|
+
it 'creates a new instance of the target' do
|
65
|
+
dsl_class2.should_receive(:new).and_call_original
|
66
|
+
dsl12 {}
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'calls the block given' do
|
70
|
+
expect(dsl12 { 1 }).to equal(1)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'executes the block in the context of the target' do
|
74
|
+
expect(dsl12 { self }).to be_instance_of(dsl_class2)
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'propagate_commands' do
|
80
|
+
|
81
|
+
it 'by default is false and does not propagate parent block commands' do
|
82
|
+
dsl_class1.send(:define_method, :foo) { |x| 'foo' * x }
|
83
|
+
dsl_class1.commands :foo
|
84
|
+
|
85
|
+
dsl = dsl12(false) { foo(2) }
|
86
|
+
|
87
|
+
expect { dsl.yield }.to raise_error(NameError)
|
88
|
+
# Prove we can call it normally
|
89
|
+
expect( dsl.foo(1)).to eql('foo')
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'can be true to propagate parent block commands' do
|
93
|
+
target = nil
|
94
|
+
|
95
|
+
dsl_class1.send(:define_method, :foo) { |x| 'foo' * x }
|
96
|
+
dsl_class1.commands :foo
|
97
|
+
|
98
|
+
dsl = dsl12(false, true) { foo(2) }
|
99
|
+
|
100
|
+
expect(dsl.yield).to eql('foofoo')
|
101
|
+
# Prove we can call it normally
|
102
|
+
expect(dsl.foo(1)).to eql('foo')
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'will not propagate parent block commands that aren\'t marked as commands' do
|
106
|
+
dsl_class1.send(:define_method, :foo) { |x| 'foo' * x }
|
107
|
+
# Unlike above, :foo will not added to the list of commands at this point.
|
108
|
+
|
109
|
+
dsl = dsl12(false) { foo(2) }
|
110
|
+
|
111
|
+
expect { dsl.yield }.to raise_error(NameError)
|
112
|
+
# Prove we can call it normally
|
113
|
+
expect(dsl.foo(1)).to eql('foo')
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
context '.new' do
|
122
|
+
|
123
|
+
it 'requires a block' do
|
124
|
+
expect{dsl_class1.new}.to raise_error(ArgumentError, 'block must be provided')
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'stores the block for later execution' do
|
128
|
+
block = Proc.new {}
|
129
|
+
dsl = dsl_class1.new(&block)
|
130
|
+
expect(dsl.instance_variable_get(:@block)).to equal(block)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'can also take a parent object' do
|
134
|
+
object = Object.new
|
135
|
+
dsl = dsl_class1.new(object) {}
|
136
|
+
expect(dsl.instance_variable_get(:@parent)).to equal(object)
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
context '#_commands' do
|
142
|
+
|
143
|
+
context 'without a parent object' do
|
144
|
+
|
145
|
+
it 'shows only the dsl class commands and the Kernel.methods available to the block passed' do
|
146
|
+
dsl_class1.commands :foo, :bar
|
147
|
+
dsl = dsl1(false) {}
|
148
|
+
expect(dsl._commands.sort).to eql((dsl_class1.commands + Kernel.methods).uniq.sort)
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'with a DslBlock parent' do
|
154
|
+
|
155
|
+
it 'shows the dls class commands, the parent._commands, and the Kernel.methods available to the block passed' do
|
156
|
+
dsl_class2.send(:define_method, :true_self) { self }
|
157
|
+
dsl_class2.commands :true_self
|
158
|
+
dsl_class1.commands :foo, :bar
|
159
|
+
dsl2_instance = dsl12(true, true) { true_self }
|
160
|
+
|
161
|
+
expect(dsl2_instance._commands.sort).to eql((dsl_class1.commands + dsl_class2.commands + Kernel.methods).sort)
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
context 'with a generic Object parent' do
|
167
|
+
|
168
|
+
it 'shows the dls class commands, the object.public_methods, and the Kernel.methods available to the block passed' do
|
169
|
+
array = Array.new
|
170
|
+
dsl1_instance = dsl_class1.new(array) {}
|
171
|
+
expect(dsl1_instance._commands.sort).to eql((Kernel.methods + dsl_class1.commands + array.public_methods).sort.uniq)
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
context '#yield' do
|
179
|
+
|
180
|
+
it 'yields the block given at instantiation' do
|
181
|
+
dsl = dsl_class1.new { 3 }
|
182
|
+
expect(dsl.yield).to equal(3)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'creates an executor to evaluate the block' do
|
186
|
+
dsl = dsl_class1.new {}
|
187
|
+
DslBlock::Executor.should_receive(:new).with(dsl).and_call_original
|
188
|
+
dsl.yield
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'isolates the block by evaluating it in the context of the executor' do
|
192
|
+
block = Proc.new {}
|
193
|
+
dsl = dsl_class1.new(&block)
|
194
|
+
executor = DslBlock::Executor.new(dsl)
|
195
|
+
executor.should_receive(:instance_eval).with(&block)
|
196
|
+
DslBlock::Executor.stub(:new).and_return(executor)
|
197
|
+
dsl.yield
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'cleans up any backtraces by removing itself from the call stack' do
|
201
|
+
begin
|
202
|
+
dsl123 { raise 'Kaboom' }
|
203
|
+
rescue => e
|
204
|
+
expect(e.message).to eql('Kaboom')
|
205
|
+
expect(e.backtrace.any? { |x| x.include?('dsl_block/lib/dsl_block.rb')} ).to be_false
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context '#respond_to_missing?' do
|
211
|
+
|
212
|
+
it 'behaves as normal if no parent is set' do
|
213
|
+
dsl = dsl_class1.new {}
|
214
|
+
expect(dsl.respond_to?(:each)).to equal(false)
|
215
|
+
expect(dsl.respond_to?(:to_s)).to equal(true)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'also checks with the parent if it is set' do
|
219
|
+
dsl = dsl_class1.new(Array.new) {}
|
220
|
+
expect(dsl.respond_to?(:each)).to equal(true)
|
221
|
+
expect(dsl.respond_to?(:to_s)).to equal(true)
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
context '#method_missing?' do
|
227
|
+
|
228
|
+
it 'behaves as normal if no parent is set' do
|
229
|
+
dsl = dsl_class1.new {}
|
230
|
+
expect { dsl.each }.to raise_error(NoMethodError)
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'relays the call to the parent if it is set' do
|
234
|
+
dsl = dsl_class1.new(Array.new) {}
|
235
|
+
expect { dsl.each }.not_to raise_error
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
SimpleCov.start('test_frameworks')
|
3
|
+
require 'rspec/autorun'
|
4
|
+
require 'dsl_block'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.order = 'random'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Due to the nature of these tests, the number of discreete classes involved, and scoping issues
|
11
|
+
# the memoizaton will occur here instead of the standard rspec 'let' statements. Feel free to
|
12
|
+
# move these back to standard let statements if the specs can be kept as clean or cleaner
|
13
|
+
|
14
|
+
|
15
|
+
# Generic dsl class. Used in the specs as the outermost block
|
16
|
+
def dsl_class1
|
17
|
+
@dsl_class1||= Class.new(DslBlock).tap do |klass|
|
18
|
+
def klass.name
|
19
|
+
'Dsl1'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generic dsl class. Used in the specs as the middle block
|
25
|
+
def dsl_class2
|
26
|
+
@dsl_class2 ||= Class.new(DslBlock).tap do |klass|
|
27
|
+
def klass.name
|
28
|
+
'Dsl2'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generic dsl class. Used in the specs as the innermst block
|
34
|
+
def dsl_class3
|
35
|
+
@dsl_class3 ||= Class.new(DslBlock).tap do |klass|
|
36
|
+
def klass.name
|
37
|
+
'Dsl3'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# The command the outermost dsl block is usually called by
|
43
|
+
def dsl_class1_command
|
44
|
+
dsl_class1.name.underscore.to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
# The command the middle dsl block is usually called by
|
48
|
+
def dsl_class2_command
|
49
|
+
dsl_class2.name.underscore.to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
# The command the innermost dsl block is usually called by
|
53
|
+
def dsl_class3_command
|
54
|
+
dsl_class3.name.underscore.to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
# Reset all of the classes for a new test
|
58
|
+
def dsl_reset
|
59
|
+
@dsl_class1 = nil
|
60
|
+
@dsl_class2 = nil
|
61
|
+
@dsl_class3 = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
# The following are helpers to DRY up the tests.
|
68
|
+
# The numbers after 'dsl' are references to the order the dsl blocks are nested.
|
69
|
+
|
70
|
+
|
71
|
+
# No nested dsl. Block given is run at the ellipsis
|
72
|
+
# dsl_class1 do
|
73
|
+
# ...
|
74
|
+
# end
|
75
|
+
def dsl1(auto_yield=true, &block)
|
76
|
+
dsl = dsl_class1.new(&block)
|
77
|
+
auto_yield ? dsl.yield : dsl
|
78
|
+
end
|
79
|
+
|
80
|
+
# Nested dsl. Block given is run at the ellipsis
|
81
|
+
# dsl_class1 do
|
82
|
+
# dsl_class2 do
|
83
|
+
# ...
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
def dsl12(auto_yield=true, propigate12=false,&block)
|
87
|
+
dsl_class2.add_command_to(dsl_class1, propigate12)
|
88
|
+
command = dsl_class2_command
|
89
|
+
dsl = dsl_class1.new do
|
90
|
+
self.send(command, &block)
|
91
|
+
end
|
92
|
+
auto_yield ? dsl.yield : dsl
|
93
|
+
end
|
94
|
+
|
95
|
+
# Double nested dsl. Block given is run at the ellipsis
|
96
|
+
# dsl_class1 do
|
97
|
+
# dsl_class2 do
|
98
|
+
# dsl_class3 do
|
99
|
+
# ...
|
100
|
+
# end
|
101
|
+
# end
|
102
|
+
# end
|
103
|
+
def dsl123(auto_yield=true, propigate12=false, propigate23=false, &block)
|
104
|
+
dsl_class2.add_command_to(dsl_class1, propigate12)
|
105
|
+
dsl_class3.add_command_to(dsl_class2, propigate23)
|
106
|
+
command2 = dsl_class2_command
|
107
|
+
command3 = dsl_class3_command
|
108
|
+
dsl = dsl_class1.new do
|
109
|
+
self.send(command2) do
|
110
|
+
self.send(command3, &block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
auto_yield ? dsl.yield : dsl
|
114
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dsl_block
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Frank Hall
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-12-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.14.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.14.1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: simplecov
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.7.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.7.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rdoc
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.0.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.0.0
|
97
|
+
description: DslBlock is a quick and simple DSL creator.
|
98
|
+
email:
|
99
|
+
- ChapterHouse.Dune@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- .rspec
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- dsl_block.gemspec
|
111
|
+
- lib/dsl_block.rb
|
112
|
+
- lib/dsl_block/executor.rb
|
113
|
+
- lib/dsl_block/version.rb
|
114
|
+
- spec/lib/dsl_block/dsl_executor_spec.rb
|
115
|
+
- spec/lib/dsl_block_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
homepage: http://chapterhouse.github.io/dsl_block
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.0.3
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Quick and simple DSL creator.
|
141
|
+
test_files:
|
142
|
+
- spec/lib/dsl_block/dsl_executor_spec.rb
|
143
|
+
- spec/lib/dsl_block_spec.rb
|
144
|
+
- spec/spec_helper.rb
|
145
|
+
has_rdoc:
|