dynamic_variable 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|