dynamic_variable 0.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.
- data/.gitignore +1 -0
- data/README.md +113 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/doc/readme_code/01example_at_start.rb +14 -0
- data/doc/readme_code/02example_with_debug.rb +21 -0
- data/doc/readme_code/03simple_with.rb +20 -0
- data/dynamic_variable.gemspec +49 -0
- data/lib/dynamic_variable.rb +176 -0
- data/spec/dynamic_variable_spec.rb +580 -0
- metadata +76 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg/
|
data/README.md
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# Introduction
|
2
|
+
|
3
|
+
Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations.
|
4
|
+
|
5
|
+
DynamicVariable is similar to [dynamic scope](http://c2.com/cgi/wiki?DynamicScoping), [fluid-let](http://www.megasolutions.net/scheme/fluid-binding-25366.aspx), [SRFI 39 parameters](http://srfi.schemers.org/srfi-39/srfi-39.html), [cflow pointcuts](http://www.eclipse.org/aspectj/doc/released/progguide/semantics-pointcuts.html#d0e5410), even [Scala has its own DynamicVariable](http://www.scala-lang.org/api/current/scala/util/DynamicVariable.html).
|
6
|
+
|
7
|
+
# Example: Context Dependent Debugging
|
8
|
+
|
9
|
+
Suppose we have a method which is `frequently_called`. It works fine except when called from `source_of_the_trouble`. Suppose further that `source_of_the_trouble` doesn't directly invoke `frequently_called` instead there's a method `in_the_middle`. Our setup looks like this:
|
10
|
+
|
11
|
+
def source_of_the_trouble
|
12
|
+
in_the_middle
|
13
|
+
end
|
14
|
+
|
15
|
+
def in_the_middle
|
16
|
+
frequently_called
|
17
|
+
end
|
18
|
+
|
19
|
+
def frequently_called
|
20
|
+
puts "I'm called all the time."
|
21
|
+
end
|
22
|
+
|
23
|
+
1000.times { in_the_middle }
|
24
|
+
source_of_the_trouble
|
25
|
+
|
26
|
+
With DynamicVariable, we can easily change `frequently_called` so that it only brings up the `debugger` when called from `source_of_the_trouble`:
|
27
|
+
|
28
|
+
require 'rubygems'
|
29
|
+
require 'ruby-debug'
|
30
|
+
require 'dynamic_variable'
|
31
|
+
|
32
|
+
@troubled = DynamicVariable.new(false)
|
33
|
+
|
34
|
+
def source_of_the_trouble
|
35
|
+
@troubled.with(true) { in_the_middle }
|
36
|
+
end
|
37
|
+
|
38
|
+
def in_the_middle
|
39
|
+
frequently_called
|
40
|
+
end
|
41
|
+
|
42
|
+
def frequently_called
|
43
|
+
debugger if @troubled.value
|
44
|
+
puts "I'm called all the time."
|
45
|
+
end
|
46
|
+
|
47
|
+
1000.times { in_the_middle }
|
48
|
+
source_of_the_trouble
|
49
|
+
|
50
|
+
# Typical Usage
|
51
|
+
|
52
|
+
A DynamicVariable storing one `:value` and then `:another`:
|
53
|
+
|
54
|
+
DynamicVariable.new(1) do |dv|
|
55
|
+
dv.value.should == 1
|
56
|
+
dv.value = 2
|
57
|
+
dv.value.should == 2
|
58
|
+
|
59
|
+
dv.with(:another, :middle, 3) do
|
60
|
+
dv.another.should == :middle
|
61
|
+
dv.value.should == 3
|
62
|
+
|
63
|
+
dv.with(:another, :inner, 4) do
|
64
|
+
dv.another.should == :inner
|
65
|
+
dv.value.should == 4
|
66
|
+
end
|
67
|
+
|
68
|
+
dv.another.should == :middle
|
69
|
+
dv.value.should == 3
|
70
|
+
end
|
71
|
+
|
72
|
+
expect do
|
73
|
+
dv.another
|
74
|
+
end.should raise_error(ArgumentError, "unbound variable :another")
|
75
|
+
dv.value.should == 2
|
76
|
+
end
|
77
|
+
|
78
|
+
# Why have a library?
|
79
|
+
|
80
|
+
Isn't it easy to set and reset a flag as the context changes? Sure, just watch out for raise, throw, and nesting:
|
81
|
+
|
82
|
+
def simple_with(value)
|
83
|
+
old_value = $value
|
84
|
+
$value = value
|
85
|
+
yield
|
86
|
+
ensure
|
87
|
+
$value = old_value
|
88
|
+
end
|
89
|
+
|
90
|
+
DynamicVariable adds the ability to reflect on your bindings. You can inspect them, and you can tinker with them if you feel the need:
|
91
|
+
|
92
|
+
dv = DynamicVariable.new(:v, 1, :w, 2) do |dv|
|
93
|
+
dv.with(:w, 3) do
|
94
|
+
dv.with(:v, 4) do
|
95
|
+
dv.bindings.should == [[:v, 1], [:w, 2], [:w, 3], [:v, 4]]
|
96
|
+
dv.bindings(:v).should == [1, 4]
|
97
|
+
dv.bindings(:w).should == [2, 3]
|
98
|
+
|
99
|
+
dv.set_bindings(:v, [-1, -2, -3])
|
100
|
+
dv.set_bindings(:w, [-4])
|
101
|
+
dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2], [:v, -3]]
|
102
|
+
end
|
103
|
+
|
104
|
+
dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2]]
|
105
|
+
end
|
106
|
+
|
107
|
+
dv.bindings.should == [[:v, -1], [:v, -2]]
|
108
|
+
end
|
109
|
+
|
110
|
+
dv.bindings.should == [[:v, -1]]
|
111
|
+
|
112
|
+
dv.bindings = []
|
113
|
+
dv.bindings.should == []
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "dynamic_variable"
|
5
|
+
gemspec.summary = "Provides dynamically scoped variables."
|
6
|
+
gemspec.description = "Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations."
|
7
|
+
gemspec.email = "wtaysom@gmail.com"
|
8
|
+
gemspec.homepage = "http://github.com/wtaysom/Ruby-DynamicVariable"
|
9
|
+
gemspec.authors = ["William Taysom"]
|
10
|
+
end
|
11
|
+
Jeweler::GemcutterTasks.new
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
14
|
+
end
|
15
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.0
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'ruby-debug'
|
3
|
+
require 'dynamic_variable'
|
4
|
+
|
5
|
+
@troubled = DynamicVariable.new(false)
|
6
|
+
|
7
|
+
def source_of_the_trouble
|
8
|
+
@troubled.with(true) { in_the_middle }
|
9
|
+
end
|
10
|
+
|
11
|
+
def in_the_middle
|
12
|
+
frequently_called
|
13
|
+
end
|
14
|
+
|
15
|
+
def frequently_called
|
16
|
+
debugger if @troubled.value
|
17
|
+
puts "I'm called all the time."
|
18
|
+
end
|
19
|
+
|
20
|
+
1000.times { in_the_middle }
|
21
|
+
source_of_the_trouble
|
@@ -0,0 +1,20 @@
|
|
1
|
+
def simple_with(value)
|
2
|
+
old_value = $value
|
3
|
+
$value = value
|
4
|
+
yield
|
5
|
+
ensure
|
6
|
+
$value = old_value
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
describe '#simple_with' do it 'should behave' do
|
11
|
+
|
12
|
+
simple_with(3) do
|
13
|
+
simple_with(4) do
|
14
|
+
$value.should == 4
|
15
|
+
end
|
16
|
+
$value.should == 3
|
17
|
+
end
|
18
|
+
$value.should == nil
|
19
|
+
|
20
|
+
end end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{dynamic_variable}
|
8
|
+
s.version = "0.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["William Taysom"]
|
12
|
+
s.date = %q{2010-09-09}
|
13
|
+
s.description = %q{Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations.}
|
14
|
+
s.email = %q{wtaysom@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"README.md",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"doc/readme_code/01example_at_start.rb",
|
24
|
+
"doc/readme_code/02example_with_debug.rb",
|
25
|
+
"doc/readme_code/03simple_with.rb",
|
26
|
+
"dynamic_variable.gemspec",
|
27
|
+
"lib/dynamic_variable.rb",
|
28
|
+
"spec/dynamic_variable_spec.rb"
|
29
|
+
]
|
30
|
+
s.homepage = %q{http://github.com/wtaysom/Ruby-DynamicVariable}
|
31
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
32
|
+
s.require_paths = ["lib"]
|
33
|
+
s.rubygems_version = %q{1.3.7}
|
34
|
+
s.summary = %q{Provides dynamically scoped variables.}
|
35
|
+
s.test_files = [
|
36
|
+
"spec/dynamic_variable_spec.rb"
|
37
|
+
]
|
38
|
+
|
39
|
+
if s.respond_to? :specification_version then
|
40
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
41
|
+
s.specification_version = 3
|
42
|
+
|
43
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
44
|
+
else
|
45
|
+
end
|
46
|
+
else
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,176 @@
|
|
1
|
+
class DynamicVariable
|
2
|
+
@@un = Object.new
|
3
|
+
|
4
|
+
def initialize(*pairs, &block)
|
5
|
+
@bindings = []
|
6
|
+
if block_given?
|
7
|
+
with(*pairs, &block)
|
8
|
+
else
|
9
|
+
push_pairs(pairs) unless pairs.empty?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(variable, value)
|
14
|
+
pair = [variable, value]
|
15
|
+
@bindings << pair
|
16
|
+
pair
|
17
|
+
end
|
18
|
+
|
19
|
+
def pop(variable)
|
20
|
+
index = rindex(variable)
|
21
|
+
@bindings.slice!(index)[1] if index
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# with {}
|
26
|
+
# varible defaults to :value
|
27
|
+
# value defaults to nil
|
28
|
+
#
|
29
|
+
# with(value) { block }
|
30
|
+
# variable defaults to :value
|
31
|
+
#
|
32
|
+
# with(variable, value) { block }
|
33
|
+
#
|
34
|
+
# with(variable_1, value_1, ..., variable_n_1, value_n_1, value_n) { block }
|
35
|
+
# variable_n defaults to :value
|
36
|
+
#
|
37
|
+
# with(variable_1, value_1, ..., variable_n, value_n) { block }
|
38
|
+
#
|
39
|
+
def with(*pairs)
|
40
|
+
pairs = [nil] if pairs.empty?
|
41
|
+
push_pairs(pairs)
|
42
|
+
begin
|
43
|
+
yield(self)
|
44
|
+
ensure
|
45
|
+
pop_pairs(pairs)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_module
|
50
|
+
this = self
|
51
|
+
mod = Module.new
|
52
|
+
mod.send(:define_method, :with) do |*args, &block|
|
53
|
+
this.with(*args, &block)
|
54
|
+
end
|
55
|
+
mod
|
56
|
+
end
|
57
|
+
|
58
|
+
def [](variable)
|
59
|
+
find_binding(variable)[1]
|
60
|
+
end
|
61
|
+
|
62
|
+
def []=(variable, value)
|
63
|
+
find_binding(variable)[1] = value
|
64
|
+
end
|
65
|
+
|
66
|
+
def method_missing(variable, *args)
|
67
|
+
if args.empty?
|
68
|
+
value = self[variable]
|
69
|
+
instance_eval %Q{def #{variable}; self[:#{variable}] end}
|
70
|
+
else
|
71
|
+
writer = variable
|
72
|
+
unless args.size == 1 and writer.to_s =~ /(.*)=/
|
73
|
+
raise NoMethodError, "undefined method `#{writer}' for #{self.inspect}"
|
74
|
+
end
|
75
|
+
variable = $1.to_sym
|
76
|
+
self[variable] = args[0]
|
77
|
+
instance_eval %Q{def #{writer}(v); self[:#{variable}] = v end}
|
78
|
+
end
|
79
|
+
value
|
80
|
+
end
|
81
|
+
|
82
|
+
def variables
|
83
|
+
variables = {}
|
84
|
+
@bindings.each{|variable, value| variables[variable] = value}
|
85
|
+
variables
|
86
|
+
end
|
87
|
+
|
88
|
+
def variables=(variables)
|
89
|
+
variables.each{|variable, value| self[variable] = value}
|
90
|
+
end
|
91
|
+
|
92
|
+
def bindings(variable = @@un)
|
93
|
+
if variable == @@un
|
94
|
+
@bindings.map{|pair| pair.clone}
|
95
|
+
else
|
96
|
+
@bindings.select{|var, value| var == variable}.map!{|var, value| value}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def bindings=(bindings)
|
101
|
+
set_bindings(bindings)
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_bindings(variable, bindings = @@un)
|
105
|
+
if bindings == @@un
|
106
|
+
bindings = variable
|
107
|
+
@bindings = bindings.map do |pair|
|
108
|
+
unless pair.is_a? Array and pair.size == 2
|
109
|
+
raise ArgumentError,
|
110
|
+
"expected [variable, value] pair, got #{pair.inspect}"
|
111
|
+
end
|
112
|
+
pair.clone
|
113
|
+
end
|
114
|
+
else
|
115
|
+
unless bindings.is_a? Array
|
116
|
+
raise ArgumentError,
|
117
|
+
"expected bindings to be Array, got a #{bindings.class}"
|
118
|
+
end
|
119
|
+
index = 0
|
120
|
+
old_bindings = @bindings
|
121
|
+
@bindings = []
|
122
|
+
old_bindings.each do |var, value|
|
123
|
+
if var == variable
|
124
|
+
unless index < bindings.size
|
125
|
+
next
|
126
|
+
end
|
127
|
+
value = bindings[index]
|
128
|
+
index += 1
|
129
|
+
end
|
130
|
+
push(var, value)
|
131
|
+
end
|
132
|
+
while index < bindings.size
|
133
|
+
value = bindings[index]
|
134
|
+
push(variable, value)
|
135
|
+
index += 1
|
136
|
+
end
|
137
|
+
end
|
138
|
+
bindings
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
def find_binding(variable)
|
144
|
+
index = rindex(variable)
|
145
|
+
unless index
|
146
|
+
raise ArgumentError, "unbound variable #{variable.inspect}"
|
147
|
+
end
|
148
|
+
@bindings[index]
|
149
|
+
end
|
150
|
+
|
151
|
+
def rindex(variable)
|
152
|
+
@bindings.rindex{|pair| pair[0] == variable}
|
153
|
+
end
|
154
|
+
|
155
|
+
def push_pairs(pairs)
|
156
|
+
pairs.insert(-2, :value) if pairs.size.odd?
|
157
|
+
each_pair(pairs) {|variable, value| push(variable, value)}
|
158
|
+
end
|
159
|
+
|
160
|
+
def pop_pairs(pairs)
|
161
|
+
each_pair(pairs) {|variable, value| pop(variable)}
|
162
|
+
end
|
163
|
+
|
164
|
+
def each_pair(array)
|
165
|
+
pair_ready = false
|
166
|
+
head = nil
|
167
|
+
array.each do |v|
|
168
|
+
if pair_ready
|
169
|
+
yield(head, v)
|
170
|
+
else
|
171
|
+
head = v
|
172
|
+
end
|
173
|
+
pair_ready = !pair_ready
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,580 @@
|
|
1
|
+
require 'dynamic_variable'
|
2
|
+
|
3
|
+
describe DynamicVariable do
|
4
|
+
subject { DynamicVariable.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
extend subject.with_module
|
8
|
+
end
|
9
|
+
|
10
|
+
### Utilities ###
|
11
|
+
|
12
|
+
def x_and_y_bindings
|
13
|
+
[[:x, 1], [:y, 2], [:y, 3], [:x, 4]]
|
14
|
+
end
|
15
|
+
|
16
|
+
def set_x_any_y_bindings
|
17
|
+
subject.bindings = x_and_y_bindings
|
18
|
+
end
|
19
|
+
|
20
|
+
def bindings_should_equal_x_and_y_bindings
|
21
|
+
subject.bindings.should == x_and_y_bindings
|
22
|
+
end
|
23
|
+
|
24
|
+
def bindings_should_be_empty
|
25
|
+
subject.bindings.should == []
|
26
|
+
end
|
27
|
+
|
28
|
+
def expect_should_raise_unbound_variable(&block)
|
29
|
+
expect do
|
30
|
+
block[]
|
31
|
+
end.should raise_error ArgumentError, "unbound variable :z"
|
32
|
+
end
|
33
|
+
|
34
|
+
### Examples ###
|
35
|
+
|
36
|
+
example "typical usage" do
|
37
|
+
DynamicVariable.new(1) do |dv|
|
38
|
+
dv.value.should == 1
|
39
|
+
dv.value = 2
|
40
|
+
dv.value.should == 2
|
41
|
+
|
42
|
+
dv.with(:another, :middle, 3) do
|
43
|
+
dv.another.should == :middle
|
44
|
+
dv.value.should == 3
|
45
|
+
|
46
|
+
dv.with(:another, :inner, 4) do
|
47
|
+
dv.another.should == :inner
|
48
|
+
dv.value.should == 4
|
49
|
+
end
|
50
|
+
|
51
|
+
dv.another.should == :middle
|
52
|
+
dv.value.should == 3
|
53
|
+
end
|
54
|
+
|
55
|
+
expect do
|
56
|
+
dv.another
|
57
|
+
end.should raise_error(ArgumentError, "unbound variable :another")
|
58
|
+
dv.value.should == 2
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
example "reflection" do
|
63
|
+
dv = DynamicVariable.new(:v, 1, :w, 2) do |dv|
|
64
|
+
dv.with(:w, 3) do
|
65
|
+
dv.with(:v, 4) do
|
66
|
+
dv.bindings.should == [[:v, 1], [:w, 2], [:w, 3], [:v, 4]]
|
67
|
+
dv.bindings(:v).should == [1, 4]
|
68
|
+
dv.bindings(:w).should == [2, 3]
|
69
|
+
|
70
|
+
dv.set_bindings(:v, [-1, -2, -3])
|
71
|
+
dv.set_bindings(:w, [-4])
|
72
|
+
dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2], [:v, -3]]
|
73
|
+
end
|
74
|
+
|
75
|
+
dv.bindings.should == [[:v, -1], [:w, -4], [:v, -2]]
|
76
|
+
end
|
77
|
+
|
78
|
+
dv.bindings.should == [[:v, -1], [:v, -2]]
|
79
|
+
end
|
80
|
+
|
81
|
+
dv.bindings.should == [[:v, -1]]
|
82
|
+
|
83
|
+
dv.bindings = []
|
84
|
+
dv.bindings.should == []
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '.new' do
|
88
|
+
context "when called" do
|
89
|
+
context "with no arguments" do
|
90
|
+
it "should have no bindings" do
|
91
|
+
DynamicVariable.new.bindings.should == []
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "with one argument" do
|
96
|
+
it "should bind :value to argument" do
|
97
|
+
DynamicVariable.new(:argument).bindings.should ==
|
98
|
+
[[:value, :argument]]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "with two arguments" do
|
103
|
+
it "should bind the first to the second" do
|
104
|
+
DynamicVariable.new(:first, :second).bindings.should ==
|
105
|
+
[[:first, :second]]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "with an odd number of arguments" do
|
110
|
+
it "should bind :value to the second to last" do
|
111
|
+
DynamicVariable.new(:first, :second, :last).bindings.should ==
|
112
|
+
[[:first, :second], [:value, :last]]
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with an even number of arguments" do
|
117
|
+
it "should bind variable, value pairs" do
|
118
|
+
DynamicVariable.new(:var1, :val1, :var2, :val2, :var3,
|
119
|
+
:val3).bindings.should ==
|
120
|
+
[[:var1, :val1], [:var2, :val2], [:var3, :val3]]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "with a block" do
|
125
|
+
it "should pass self to the block" do
|
126
|
+
dv_in_block = nil;
|
127
|
+
dv = DynamicVariable.new do |dv|
|
128
|
+
dv.should be_a(DynamicVariable)
|
129
|
+
dv.value.should == nil
|
130
|
+
dv_in_block = dv
|
131
|
+
end
|
132
|
+
dv.should == dv_in_block
|
133
|
+
expect do
|
134
|
+
dv.value
|
135
|
+
end.should raise_error(ArgumentError, "unbound variable :value")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe '#push' do
|
142
|
+
it "should return the binding pair" do
|
143
|
+
subject.push(:x, 1).should == [:x, 1]
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should add binding pairs" do
|
147
|
+
subject.push(:x, 1)
|
148
|
+
subject.push(:y, 2)
|
149
|
+
subject.push(:z, 3)
|
150
|
+
subject.push(:x, 4)
|
151
|
+
|
152
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:z, 3], [:x, 4]]
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should allow a variable to be any object" do
|
156
|
+
subject.push(1337, 1)
|
157
|
+
subject.push(nil, 2)
|
158
|
+
subject.push("", 3)
|
159
|
+
subject.push([], 4)
|
160
|
+
|
161
|
+
subject.bindings.should == [[1337, 1], [nil, 2], ["", 3], [[], 4]]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe '#pop' do
|
166
|
+
before { set_x_any_y_bindings }
|
167
|
+
|
168
|
+
it "should return the value of the variable" do
|
169
|
+
subject.pop(:x).should == 4
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should remove last occurence of variable" do
|
173
|
+
subject.pop(:x)
|
174
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:y, 3]]
|
175
|
+
|
176
|
+
subject.pop(:x)
|
177
|
+
subject.bindings.should == [[:y, 2], [:y, 3]]
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should remove last occurence leaving other variables in order" do
|
181
|
+
subject.pop(:y)
|
182
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:x, 4]]
|
183
|
+
|
184
|
+
subject.pop(:y)
|
185
|
+
subject.bindings.should == [[:x, 1], [:x, 4]]
|
186
|
+
end
|
187
|
+
|
188
|
+
context "when variable is not found" do
|
189
|
+
it "should return nil and do nothing" do
|
190
|
+
subject.pop(:z).should == nil
|
191
|
+
bindings_should_equal_x_and_y_bindings
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe '#with' do
|
197
|
+
it "should make bindings and clean up when done" do
|
198
|
+
with(:key, :value) do
|
199
|
+
subject.bindings.should == [[:key, :value]]
|
200
|
+
end
|
201
|
+
bindings_should_be_empty
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should yield self to the block" do
|
205
|
+
with do |dv|
|
206
|
+
dv.should == subject
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should return the result of its block" do
|
211
|
+
with{:result}.should == :result
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should nest nicely" do
|
215
|
+
with(:value) do
|
216
|
+
subject.bindings.should == [[:value, :value]]
|
217
|
+
with(:second_value) do
|
218
|
+
subject.bindings.should == [[:value, :value], [:value, :second_value]]
|
219
|
+
end
|
220
|
+
subject.bindings.should == [[:value, :value]]
|
221
|
+
end
|
222
|
+
bindings_should_be_empty
|
223
|
+
end
|
224
|
+
|
225
|
+
it "should be exception safe" do
|
226
|
+
expect do
|
227
|
+
with(:value) do
|
228
|
+
subject.bindings.should == [[:value, :value]]
|
229
|
+
raise
|
230
|
+
end
|
231
|
+
end.should raise_error
|
232
|
+
bindings_should_be_empty
|
233
|
+
end
|
234
|
+
|
235
|
+
context "when called" do
|
236
|
+
context "with no arguments" do
|
237
|
+
it "should bind :value to nil" do
|
238
|
+
with do
|
239
|
+
subject.bindings.should == [[:value, nil]]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
context "with one argument" do
|
245
|
+
it "should bind :value to argument" do
|
246
|
+
with(:argument) do
|
247
|
+
subject.bindings.should == [[:value, :argument]]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context "with two arguments" do
|
253
|
+
it "should bind the first to the second" do
|
254
|
+
with(:first, :second) do
|
255
|
+
subject.bindings.should == [[:first, :second]]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
context "with an odd number of arguments" do
|
261
|
+
it "should bind :value to the second to last" do
|
262
|
+
with(:first, :second, :last) do
|
263
|
+
subject.bindings.should == [[:first, :second], [:value, :last]]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
context "with an even number of arguments" do
|
269
|
+
it "should bind variable, value pairs" do
|
270
|
+
with(:var1, :val1, :var2, :val2, :var3, :val3) do
|
271
|
+
subject.bindings.should ==
|
272
|
+
[[:var1, :val1], [:var2, :val2], [:var3, :val3]]
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
context "with a variable repeated more than once" do
|
278
|
+
it "should bind the variable twice and unbind when done" do
|
279
|
+
with(:var, 1, :var, 2) do
|
280
|
+
subject.bindings.should == [[:var, 1], [:var, 2]]
|
281
|
+
end
|
282
|
+
subject.bindings.should == []
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context "without a block" do
|
287
|
+
it "should raise \"no block given\"" do
|
288
|
+
expect do
|
289
|
+
subject.with(:key, :value)
|
290
|
+
end.should raise_error LocalJumpError, /no block given/
|
291
|
+
bindings_should_be_empty
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
describe '#with_module' do
|
298
|
+
it "should return a Module with a #with method" do
|
299
|
+
mod = subject.with_module
|
300
|
+
mod.should be_a Module
|
301
|
+
methods = mod.instance_methods(false)
|
302
|
+
methods.map(&:to_sym).should == [:with]
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
describe '#[]' do
|
307
|
+
before { set_x_any_y_bindings }
|
308
|
+
|
309
|
+
it "should return the nearest binding for a variable" do
|
310
|
+
subject[:x].should == 4
|
311
|
+
subject[:y].should == 3
|
312
|
+
end
|
313
|
+
|
314
|
+
context "when there is no binding" do
|
315
|
+
it "should raise \"unbound variable\"" do
|
316
|
+
expect_should_raise_unbound_variable do
|
317
|
+
subject[:z]
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
describe '#[]=' do
|
324
|
+
before { set_x_any_y_bindings }
|
325
|
+
|
326
|
+
it "should update nearest binding for a variable" do
|
327
|
+
subject[:x] = -1
|
328
|
+
subject[:y] = -2
|
329
|
+
|
330
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, -1]]
|
331
|
+
end
|
332
|
+
|
333
|
+
it "should return the value" do
|
334
|
+
(subject[:x] = -1).should == -1
|
335
|
+
end
|
336
|
+
|
337
|
+
context "when there is no binding" do
|
338
|
+
it "should raise \"unbound variable\"" do
|
339
|
+
expect_should_raise_unbound_variable do
|
340
|
+
subject[:z] = -2
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
describe '#method_missing', " reader and writer generation" do
|
347
|
+
before { set_x_any_y_bindings }
|
348
|
+
|
349
|
+
it "should initially have no variable readers or writers defined" do
|
350
|
+
subject.methods(false).should == []
|
351
|
+
end
|
352
|
+
|
353
|
+
it "should generate variable readers as needed" do
|
354
|
+
subject.x.should == 4
|
355
|
+
subject.methods(false).map(&:to_sym).should == [:x]
|
356
|
+
end
|
357
|
+
|
358
|
+
it "should generate variable writers as needed" do
|
359
|
+
(subject.x = 4).should == 4
|
360
|
+
subject.methods(false).map(&:to_sym).should == [:x=]
|
361
|
+
end
|
362
|
+
|
363
|
+
context "when variable is unbound" do
|
364
|
+
it "should raise \"unbound variable\"" do
|
365
|
+
expect_should_raise_unbound_variable do
|
366
|
+
subject.z
|
367
|
+
end
|
368
|
+
|
369
|
+
expect_should_raise_unbound_variable do
|
370
|
+
subject.z = 3
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
it "should not generate readers or writers" do
|
375
|
+
expect do
|
376
|
+
subject.z
|
377
|
+
subject.z = 3
|
378
|
+
end.should raise_error
|
379
|
+
|
380
|
+
subject.methods(false).should == []
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
context "with to many arguments to be a writer" do
|
385
|
+
it "should raise NoMethodError" do
|
386
|
+
expect do
|
387
|
+
subject.too_many(1, 2)
|
388
|
+
end.should raise_error NoMethodError,
|
389
|
+
/undefined method `too_many' for/
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
context "with one argument but method name does not end in \"=\"" do
|
394
|
+
it "should raise NoMethodError" do
|
395
|
+
expect do
|
396
|
+
subject.not_writer(1)
|
397
|
+
end.should raise_error NoMethodError,
|
398
|
+
/undefined method `not_writer' for/
|
399
|
+
end
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
describe '#variables' do
|
404
|
+
before { set_x_any_y_bindings }
|
405
|
+
|
406
|
+
it "should return hash of all variables with their current bindings" do
|
407
|
+
subject.variables.should == {:x => 4, :y => 3}
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
describe '#variables=' do
|
412
|
+
before { set_x_any_y_bindings }
|
413
|
+
|
414
|
+
it "should update current bindings of variables" do
|
415
|
+
subject.variables = {:x => -1, :y => -2}
|
416
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, -1]]
|
417
|
+
end
|
418
|
+
|
419
|
+
it "should return the variables" do
|
420
|
+
variables = {:x => -1, :y => -2}
|
421
|
+
(subject.variables = variables).should be_equal variables
|
422
|
+
end
|
423
|
+
|
424
|
+
it "should accept assoc array as argument" do
|
425
|
+
subject.variables = [[:x, -1], [:y, -2]]
|
426
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, -1]]
|
427
|
+
end
|
428
|
+
|
429
|
+
context "when some variables are not mentioned" do
|
430
|
+
it "should retain the old bindings" do
|
431
|
+
subject.variables = {:y => -2}
|
432
|
+
subject.bindings.should == [[:x, 1], [:y, 2], [:y, -2], [:x, 4]]
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
context "when an unbound variable is mentioned" do
|
437
|
+
it "should raise \"unbound variable\"" do
|
438
|
+
expect_should_raise_unbound_variable do
|
439
|
+
subject.variables = {:z => -3}
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
it "should NOT update atomically" do
|
444
|
+
expect_should_raise_unbound_variable do
|
445
|
+
subject.variables = [[:y, -2], [:z, -3]]
|
446
|
+
end
|
447
|
+
subject.y.should == -2
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
describe '#bindings' do
|
453
|
+
before { set_x_any_y_bindings }
|
454
|
+
|
455
|
+
context "with no argument" do
|
456
|
+
it "should return an array of all variable bindings" do
|
457
|
+
bindings_should_equal_x_and_y_bindings
|
458
|
+
end
|
459
|
+
|
460
|
+
it "should not be affected by changes to the returned array" do
|
461
|
+
bindings = subject.bindings
|
462
|
+
bindings[0][1] = -1
|
463
|
+
bindings_should_equal_x_and_y_bindings
|
464
|
+
end
|
465
|
+
end
|
466
|
+
|
467
|
+
context "with variable as argument" do
|
468
|
+
it "should return array of all bindings for variable" do
|
469
|
+
subject.bindings(:x).should == [1, 4]
|
470
|
+
subject.bindings(:y).should == [2, 3]
|
471
|
+
end
|
472
|
+
|
473
|
+
context "when an unbound variable is used" do
|
474
|
+
it "should return empty array" do
|
475
|
+
subject.bindings(:z).should == []
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
shared_examples_for "bindings writer" do
|
482
|
+
it "should update all bindings" do
|
483
|
+
subject.send(bindings_writer, x_and_y_bindings)
|
484
|
+
bindings_should_equal_x_and_y_bindings
|
485
|
+
end
|
486
|
+
|
487
|
+
it "should return the argument" do
|
488
|
+
bindings = x_and_y_bindings
|
489
|
+
subject.send(bindings_writer, bindings).should be_equal bindings
|
490
|
+
end
|
491
|
+
|
492
|
+
it "should use a copy of bindings" do
|
493
|
+
bindings = x_and_y_bindings
|
494
|
+
subject.send(bindings_writer, bindings)
|
495
|
+
bindings[0][1] = -9
|
496
|
+
bindings_should_equal_x_and_y_bindings
|
497
|
+
end
|
498
|
+
|
499
|
+
context "when bindings does not respond to #map" do
|
500
|
+
it "should raise NoMethodError" do
|
501
|
+
expect do
|
502
|
+
subject.send(bindings_writer, :oops)
|
503
|
+
end.should raise_error NoMethodError
|
504
|
+
bindings_should_be_empty
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
context "when a binding is not an Array" do
|
509
|
+
it "should raise ArgumentError" do
|
510
|
+
expect do
|
511
|
+
subject.send(bindings_writer, [66])
|
512
|
+
end.should raise_error ArgumentError,
|
513
|
+
/expected \[variable, value\] pair, got/
|
514
|
+
bindings_should_be_empty
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
context "when a binding is of the wrong size" do
|
519
|
+
it "should raise ArgumentError" do
|
520
|
+
expect do
|
521
|
+
subject.send(bindings_writer, [[1, 2, 3]])
|
522
|
+
end.should raise_error ArgumentError,
|
523
|
+
/expected \[variable, value\] pair, got/
|
524
|
+
bindings_should_be_empty
|
525
|
+
end
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
describe '#bindings=' do
|
530
|
+
let(:bindings_writer) { :bindings= }
|
531
|
+
|
532
|
+
it_should_behave_like "bindings writer"
|
533
|
+
end
|
534
|
+
|
535
|
+
describe '#set_bindings' do
|
536
|
+
context "with one argument" do
|
537
|
+
let(:bindings_writer) { :set_bindings }
|
538
|
+
|
539
|
+
it_should_behave_like "bindings writer"
|
540
|
+
end
|
541
|
+
|
542
|
+
context "with two arguments" do
|
543
|
+
before { set_x_any_y_bindings }
|
544
|
+
|
545
|
+
it "should replace existing bindings for variable" do
|
546
|
+
subject.set_bindings(:x, [-1, -2])
|
547
|
+
subject.bindings.should == [[:x, -1], [:y, 2], [:y, 3], [:x, -2]]
|
548
|
+
end
|
549
|
+
|
550
|
+
it "should return the bindings array" do
|
551
|
+
bindings = [-1, -2]
|
552
|
+
subject.set_bindings(:x, bindings).should be_equal bindings
|
553
|
+
end
|
554
|
+
|
555
|
+
context "when there are fewer new bindings than existing bindings" do
|
556
|
+
it "should delete bindings from the end" do
|
557
|
+
subject.set_bindings(:x, [-1])
|
558
|
+
subject.bindings.should == [[:x, -1], [:y, 2], [:y, 3]]
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
context "when there are more new bindings than existing bindings" do
|
563
|
+
it "should add bindings to the end" do
|
564
|
+
subject.set_bindings(:x, [-1, -2, -3])
|
565
|
+
subject.bindings.should ==
|
566
|
+
[[:x, -1], [:y, 2], [:y, 3], [:x, -2], [:x, -3]]
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
context "when argument is not an array" do
|
571
|
+
it "should raise ArgumentError" do
|
572
|
+
expect do
|
573
|
+
subject.set_bindings(:x, :not_this)
|
574
|
+
end.should raise_error ArgumentError,
|
575
|
+
"expected bindings to be Array, got a Symbol"
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dynamic_variable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 0.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- William Taysom
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-09 00:00:00 +08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Occasionally a method's behavior should depend on the context in which it is called. What is happening above me on the stack? DynamicVariable helps you in these context dependent situations.
|
23
|
+
email: wtaysom@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.md
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- README.md
|
33
|
+
- Rakefile
|
34
|
+
- VERSION
|
35
|
+
- doc/readme_code/01example_at_start.rb
|
36
|
+
- doc/readme_code/02example_with_debug.rb
|
37
|
+
- doc/readme_code/03simple_with.rb
|
38
|
+
- dynamic_variable.gemspec
|
39
|
+
- lib/dynamic_variable.rb
|
40
|
+
- spec/dynamic_variable_spec.rb
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/wtaysom/Ruby-DynamicVariable
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --charset=UTF-8
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.3.7
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Provides dynamically scoped variables.
|
75
|
+
test_files:
|
76
|
+
- spec/dynamic_variable_spec.rb
|