fluid 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ Version 1.0.0
2
+ * Converted to Rubyforge+gem distribution.
3
+ * Removed binding of I/O-related globals.
@@ -0,0 +1,34 @@
1
+ This software is Copyright (C) 2007 by Brian Marick <marick@exampler.com>. It is licensed according to "the Ruby license". Specifically:
2
+
3
+ You can redistribute it and/or modify it under either the terms of the GNU General Public License, version 2, <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html> or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) make other distribution arrangements with the author.
21
+
22
+ 3. You may modify and include part of the software into any other
23
+ software (possibly commercial).
24
+
25
+ 4. Text or files supplied as input to or produced as output from
26
+ the software do not automatically fall under the copyright of the
27
+ software, but belong to whomever generated them, and may be sold
28
+ commercially, and may be aggregated with this software.
29
+
30
+ 5. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
31
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
32
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
33
+ PURPOSE.
34
+
@@ -0,0 +1,13 @@
1
+ History.txt
2
+ LICENSE.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ Rakefile.hoe
7
+ examples/a-bit-more-clever-fluid-tracing.rb
8
+ examples/globals.rb
9
+ examples/simple-fluid-tracing.rb
10
+ homepage/index.html
11
+ lib/fluid.rb
12
+ setup.rb
13
+ test/fluid-tests.rb
@@ -0,0 +1,126 @@
1
+ Class Fluid provides dynamically scoped ("fluid") variables modeled
2
+ after those of Common Lisp. It also gives you a convenient way to
3
+ reversibly change the values of globals.
4
+
5
+ == An Alternative to Globals
6
+
7
+ Here's an incredibly simple example:
8
+
9
+ require 's4t-utils/fluid'
10
+
11
+ Fluid.let(:var, 1) {
12
+ puts Fluid.var # prints 1
13
+ }
14
+ puts Fluid.var # error - Fluid.var has disappeared at this point.
15
+
16
+ Fluid.var is a "pseudovariable" that only exists within the block. What's
17
+ interesting about fluid variables is what "within the block" means.
18
+ Here's another example:
19
+
20
+ require 's4t-utils/fluid'
21
+
22
+ def putter
23
+ puts Fluid.var
24
+ end
25
+
26
+ Fluid.let(:var, 1) {
27
+ putter
28
+ }
29
+ putter
30
+
31
+ The first call of +putter+ will successfully print 1. The second
32
+ will fail because Fluid.var doesn't exist any more.
33
+
34
+ Fluid variables are useful in relatively few situations. They're good when you have
35
+ some number of interconnected objects that need to share some
36
+ value. You could pass the value around, but then intermediate methods
37
+ that didn't care about it would have to pass it along to methods
38
+ that did. You could use a global, but then the value would be visible
39
+ to objects that ought not to be able to see it. Fluid variables let you
40
+ "scope" the value to all methods called (directly or indirectly) from
41
+ an entry point to the collection of connected objects.
42
+
43
+ This is especially handy when you want to change the value of the
44
+ variable according to the depth of processing, as when you want to
45
+ increase the indentation level of some sort of tracing output.
46
+ Like this:
47
+
48
+ fact(5)
49
+ fact(4)
50
+ fact(3)
51
+ fact(2)
52
+ fact(1)
53
+ fact(1) => 1
54
+ fact(2) => 2
55
+ fact(3) => 6
56
+ fact(4) => 24
57
+ fact(5) => 120
58
+
59
+ One implementation of that would look like the following implementation
60
+ of fact. It uses two utility methods that reduce clutter.
61
+
62
+ def fact(n)
63
+ trace "fact(#{n})" # trace() produces output with appropriate indentation.
64
+ result =
65
+ if n <= 1
66
+ n
67
+ else
68
+ deeper { n * fact(n-1) } # deeper() executes block with increased indentation.
69
+ end
70
+ trace "fact(#{n}) => #{result}"
71
+ result
72
+ end
73
+
74
+
75
+ Here's the support code.
76
+
77
+ Fluid.defvar(:indent_prefix, '') # Initial value of indentation.
78
+
79
+ def trace(text)
80
+ puts Fluid.indent_prefix + text # Use the value even though it wasn't passed in.
81
+ end
82
+
83
+ def deeper # "Rebind" the value of the variable for the scope of a block.
84
+ Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
85
+ yield
86
+ end
87
+ end
88
+
89
+ For an even less intrusive example, see the examples directory.
90
+
91
+ Here's a final example that shows the syntax and behavior of
92
+ Fluid.let. See also the more formal description at Fluid.let.
93
+
94
+ Fluid.let([:var1, 1],
95
+ [:var2, 2]) {
96
+ puts(Fluid.var1) # prints 1
97
+ puts(Fluid.var2) # prints 2
98
+ Fluid.let([:var2, "new 2"],
99
+ [:var3, "new 3"]) {
100
+ puts(Fluid.var1) # prints 1, as above.
101
+ puts(Fluid.var2) # prints "new 2"
102
+ puts(Fluid.var3) # prints "new 3"
103
+ }
104
+ puts(Fluid.var1) # prints 1
105
+ puts(Fluid.var2) # Back to printing 2
106
+ puts(Fluid.var3) # error - that variable no longer exists.
107
+ }
108
+
109
+
110
+ == Globals
111
+
112
+ If you wish, you can also use Fluid.let to temporarily change the value of
113
+ globals. For example, the global #, is used as an implicit argument to
114
+ Array#join. You could do the following:
115
+
116
+ Fluid.let("$,", "-") do
117
+ puts [1, 2, 3].join #=> "1-2-3"
118
+ end
119
+
120
+ puts [1, 2, 3].join #=> "123"
121
+
122
+ The advantage of this over just setting $, and then resetting it is that
123
+ Fluid takes care of dealing with exceptions.
124
+
125
+ You're not allowed to rebind all globals. The I/O globals ($stdout, etc.)
126
+ have special-case behavior that's either impossible or not safe to cope with.
@@ -0,0 +1,35 @@
1
+ # This is a Rakefile that would live in the root folder for any package
2
+ # that follows the Scripting for Testers conventions. You should not need
3
+ # to edit it.
4
+
5
+ $started_from_rakefile=true
6
+
7
+ require 'pathname'
8
+ PACKAGE_ROOT = Dir.pwd
9
+ $:.unshift("#{PACKAGE_ROOT}/lib")
10
+
11
+ # The tests require my S4TUtils to run, but the library itself
12
+ # doesn't require it, so it's not listed as a dependency in the gem.
13
+ require 's4t-utils/load-path-auto-adjuster'
14
+
15
+ require 's4t-utils'
16
+ include S4tUtils
17
+
18
+ # The following two lines are used to tell generic Rake tasks about
19
+ # this particular project. If you installed using the s4t-utils
20
+ # installation tool, they should have already been set correctly.
21
+
22
+ MyFileSystemName='fluid' # No ".rb" extension.
23
+ MyModuleName='Fluid'
24
+
25
+
26
+ # These are the Ruby files 'rake rdoc' searchs for documentation.
27
+ # The following includes all ruby files except for third-party
28
+ # libraries. Change at will.
29
+
30
+ MyRdocFiles = FileList.new("lib/fluid.rb",
31
+ "lib/fluid/**/*.rb").exclude("**/third-party/**")
32
+
33
+
34
+ require 's4t-utils/rakefile-common'
35
+
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-07-03.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+ require 'hoe'
7
+ require 'lib/fluid'
8
+
9
+
10
+ Hoe.new("fluid", Fluid::Version) do |p|
11
+ p.rubyforge_name = "fluid"
12
+ p.changes = "See History.txt"
13
+ p.author = "Brian Marick"
14
+ p.description = "Fluid (dynamically scoped) variables"
15
+ p.summary = p.description
16
+ p.email = "marick@exampler.com"
17
+ p.extra_deps = []
18
+ p.test_globs = "test/**/*-tests.rb"
19
+ p.rdoc_pattern = %r{README.txt|lib/}
20
+ p.url = "http://fluid.rubyforge.org"
21
+ p.remote_rdoc_dir = 'rdoc'
22
+ end
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-07-22.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+ # This is a slightly cleverer implementation of logging with
7
+ # varying indentation than the one in simple-fluid-tracing.rb.
8
+
9
+ require 'fluid'
10
+
11
+ def trace(text)
12
+ puts Fluid.indent_prefix + text
13
+ end
14
+
15
+ # Trace all calls to _methodname_ in class _klass_. By default, printing
16
+ # is in this format:
17
+ # message(arg, arg, ...)
18
+ # If _show_receiver_ is true, this format is used:
19
+ # receiver.message(arg, arg, ...)
20
+ def indenting_trace(methodname, klass = self.class, show_receiver = false)
21
+ methodname = methodname.to_s # ensure string.
22
+ original = '_untraced_'+methodname
23
+ klass.send(:alias_method, original, methodname)
24
+
25
+ klass.send(:define_method, methodname) do | *actual_args |
26
+ Fluid.defvar(:indent_prefix, '') # only has effect the first time
27
+ Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
28
+ arglist_description = "(#{actual_args.collect {|a| a.inspect }.join(', ')})"
29
+ invocation = methodname + arglist_description
30
+ invocation = "#{self.inspect}.#{invocation}" if show_receiver
31
+ trace invocation
32
+ result = self.send(original, *actual_args)
33
+ trace invocation + ' => ' + result.inspect
34
+ result
35
+ end
36
+ end
37
+ end
38
+
39
+ class Fixnum
40
+ def times(other) # Use this to show alternate printing.
41
+ self * other
42
+ end
43
+ end
44
+
45
+ def fact(n)
46
+ if (n <= 1)
47
+ 1
48
+ else
49
+ n.times(fact(n-1))
50
+ end
51
+ end
52
+
53
+ puts "factorial of 5 is #{fact(5).to_s}."
54
+
55
+ indenting_trace :fact
56
+ indenting_trace :times, Fixnum, :show_receiver
57
+
58
+ puts "factorial of 5 is #{fact(5).to_s}."
59
+
60
+
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-07-22.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+ # This example shows using Fluid with global variables. Essentially,
7
+ # it's a slightly more compact way of changing a global within a
8
+ # begin... ensure... end loop that makes sure to undo the change.
9
+
10
+ require 'fluid'
11
+
12
+ Fluid.let("$,", "-") do
13
+ puts [1, 2, 3].join #=> "1-2-3"
14
+ end
15
+
16
+ puts [1, 2, 3].join #=> "123"
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Brian Marick on 2007-07-22.
4
+ # Copyright (c) 2007. All rights reserved.
5
+
6
+ # This example shows how a set of functions (in this case, recursive
7
+ # calls of the factorial function) can control indentation of log
8
+ # output without having to pass around a variable containing the
9
+ # depth of the log.
10
+
11
+ require 'fluid'
12
+
13
+ Fluid.defvar(:indent_prefix, '')
14
+
15
+ def trace(text)
16
+ puts Fluid.indent_prefix + text
17
+ end
18
+
19
+ def deeper
20
+ Fluid.let([:indent_prefix, Fluid.indent_prefix + ' ']) do
21
+ yield
22
+ end
23
+ end
24
+
25
+ def fact(n)
26
+ trace "fact(#{n})"
27
+ result =
28
+ if n <= 1
29
+ n
30
+ else
31
+ deeper { n * fact(n-1) }
32
+ end
33
+ trace "fact(#{n}) => #{result}"
34
+ result
35
+ end
36
+
37
+
38
+ puts fact(5)
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <head>
4
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
+ <title>s4t-utils</title>
6
+
7
+ </head>
8
+ <body>
9
+ <h1>Fluid (dynamically scoped) variables</h1>
10
+ <p></p>
11
+ <p><a href="http://fluid.rubyforge.org/rdoc/" title="Fluid.rb documentation">Documentation</a> (rdoc)</p>
12
+ <p><a href="" title="RubyForge: Fluid: Project Filelist">Download</a></p>
13
+
14
+
15
+ </body>
@@ -0,0 +1,389 @@
1
+ # Fluid variables for Ruby.
2
+ # Originally 2004/03/08 20:16:13
3
+ # Gemified 2007/07
4
+
5
+ # Fluid (dynamically scoped) variables for Ruby. See README.txt[link:files/README_txt.html].
6
+ class Fluid
7
+ Version="1.0.0"
8
+ ### Environment ###
9
+
10
+ # An environment holds variable->value bindings. Each
11
+ # variable name is an entry in a hash table. It is
12
+ # associated with a stack of values. Environments are
13
+ # manipulated by objects of class Var.
14
+ class Environment < Hash # :nodoc:
15
+ def create(var_name) self[var_name] = []; end
16
+ def destroy(var_name) delete(var_name); end
17
+ def has?(var_name) has_key?(var_name); end
18
+ def unbound?(var_name) self[var_name] == []; end
19
+ def set(var_name, value) self[var_name][-1] = value; end
20
+ def get(var_name) self[var_name][-1]; end
21
+ def push_binding(var_name, var_value) self[var_name].push(var_value); end
22
+ def pop_binding(var_name) self[var_name].pop; end
23
+ end
24
+
25
+ # All fluid variables are managed by one environment.
26
+ @@environment = Environment.new
27
+
28
+
29
+ ### Var ###
30
+
31
+ # There are different kinds of variables. They use the environment
32
+ # differently.
33
+ class Var # :nodoc:
34
+ attr_reader :name, # Canonicalize all names to symbols.
35
+ :original_name # Retain original name for error messages.
36
+
37
+ # Factory method that returns subclasses of Var.
38
+ def Var.build(original_name, environment, value_destructor=nil)
39
+ klass = (global?(original_name)) ? GlobalVar : FluidVar
40
+ klass.build(original_name, environment, value_destructor)
41
+ end
42
+
43
+ def Var.global?(string_or_symbol)
44
+ ?$ == string_or_symbol.to_s[0]
45
+ end
46
+
47
+ def initialize(original_name, environment, value_destructor)
48
+ @original_name = original_name
49
+ @name = Var.ensure_symbol(original_name)
50
+ @environment = environment
51
+ @value_destructor = value_destructor
52
+ end
53
+
54
+ # These two methods are the ones used to manipulate the
55
+ # environment. Note subclasses.
56
+ def push_binding(value)
57
+ create unless @environment.has?(name)
58
+ @environment.push_binding(name, value)
59
+ end
60
+
61
+ def pop_binding
62
+ previous = @environment.pop_binding(name)
63
+ apply_value_destructor(previous) if @value_destructor
64
+ destroy if @environment.unbound?(name)
65
+ end
66
+
67
+ protected
68
+ def create
69
+ # I would prefer acceptability of the name to be checked in the
70
+ # class.build method, before other work is done. However, some
71
+ # acceptability is most cleanly checked when the variable is
72
+ # created, so I decided to do all checking here.
73
+ assert_acceptable_name
74
+ @environment.create(name)
75
+ end
76
+
77
+ def destroy
78
+ @environment.destroy(name)
79
+ end
80
+
81
+ def apply_value_destructor(value)
82
+ if @value_destructor.respond_to? :call
83
+ @value_destructor.call(value)
84
+ else
85
+ value.send(@value_destructor)
86
+ end
87
+ end
88
+
89
+ def assert_acceptable_name
90
+ subclass_responsibility
91
+ end
92
+
93
+ def Var.ensure_symbol(original_name)
94
+ original_name.to_s.intern
95
+ end
96
+ end
97
+
98
+ ### FluidVar ###
99
+
100
+ # FluidVars are those accessed via "Fluid.varname". This subclass
101
+ # contains the code for adding those getters and setters.
102
+ class FluidVar < Var # :nodoc:
103
+
104
+ def FluidVar.build(*args)
105
+ new(*args) # No subclasses to build specially.
106
+ end
107
+
108
+ def create
109
+ super
110
+ Fluid.create_getter(name)
111
+ Fluid.create_setter(name)
112
+ end
113
+
114
+ def destroy
115
+ super
116
+ Fluid.delete_getter(name)
117
+ Fluid.delete_setter(name)
118
+ end
119
+
120
+ def assert_acceptable_name
121
+ unless name.to_s =~ /^[a-z_]\w*$/
122
+ raise NameError, "'#{original_name}' is not a good fluid variable name. It can't be used as a method name."
123
+ end
124
+
125
+ if Fluid.methods.include?(name.to_s)
126
+ raise NameError, "'#{original_name}' cannot be a fluid variable. It's already a method of Fluid's."
127
+ end
128
+ end
129
+ end
130
+
131
+
132
+ ### GlobalVar ###
133
+
134
+ # GlobalVars are the subclass that handles binding and unbinding of
135
+ # Ruby globals.
136
+ #
137
+ class GlobalVar < Var # :nodoc:
138
+ def GlobalVar.build(original_name, environment, value_destructor)
139
+ new(original_name, environment, value_destructor)
140
+ end
141
+
142
+ # The original value of the global is, in effect, an outermost
143
+ # binding, one not created with Fluid.let. A binding has to be made
144
+ # here so that unbinding works on exit from the Fluid.let block.
145
+ def create
146
+ super
147
+ push_binding(instance_eval("#{name.to_s}"))
148
+ end
149
+
150
+ # The environment really just holds values for unbinding.
151
+ # Access to the newly-bound variable within the Fluid.let block
152
+ # is through the global itself. So it needs to be set and reset
153
+ # by these methods.
154
+ def push_binding(value)
155
+ super
156
+ set_global
157
+ end
158
+
159
+ def pop_binding
160
+ super
161
+ set_global
162
+ end
163
+
164
+ def set_global
165
+ evalme = "#{name.to_s} = @environment.get(#{name.inspect})"
166
+ # puts evalme
167
+ instance_eval evalme
168
+ end
169
+
170
+ def assert_acceptable_name
171
+ if [:$!, :$stdin, :$stdout, :$stderr].include?(name)
172
+ raise NameError,
173
+ "'#{name}' is not allowed in Fluid.let. It's too iffy to get it right."
174
+ end
175
+ end
176
+ end
177
+
178
+
179
+ # Puts the variable specifications in effect, then executes the block,
180
+ # undoes the variable specifications, and returns the block's value.
181
+ #
182
+ # The simplest form of variable specification is a variable name (symbol or string).
183
+ # In this case, the variable starts out with the value nil:
184
+ #
185
+ # Fluid.let(:will_have_value_nil) {...}
186
+ #
187
+ # You can also specify the initial value of the variable:
188
+ #
189
+ # Fluid.let(:will_have_value_1, 1) {...}
190
+ #
191
+ # Multiple variables can be specified in a single let. Each one must
192
+ # be in an array, <i>even if given no value</i>:
193
+ #
194
+ # Fluid.let([:will_have_value_1, 1],
195
+ # [:starts_as_nil]) {...}
196
+ #
197
+ #
198
+ # From the moment the block begins to execute until the moment it returns,
199
+ # getters and setters for the variables are made class methods of Fluid:
200
+ #
201
+ # Fluid.let(:var, 1) {
202
+ # Fluid.var # has value 1
203
+ # Fluid.var = 2 # change value to 2
204
+ # }
205
+ #
206
+ # References to the variable needn't be in the lexical scope of the
207
+ # Fluid.let. They can be anywhere in the program. (To be more precise:
208
+ # references can be made whenever the original Fluid.let is on the stack.)
209
+ #
210
+ # When Fluid.let's block exits, the value of the variable is no longer accessible
211
+ # through Fluid.var. An attempt to access it will raise a NameError.
212
+ # If, however, there's another Fluid.let binding the same name still on the
213
+ # stack, that version's value is back in effect. For example:
214
+ #
215
+ # Fluid.let(:var, 1) { # Fluid.var => 1
216
+ # Fluid.var = 2 # Fluid.var => 2
217
+ # Fluid.let(:var, "hello") { # Fluid.var => "hello"
218
+ # Fluid.var *= 2 # Fluid.var => "hellohello"
219
+ # } # Fluid.var => 2
220
+ # } # Fluid.var => raises NameError
221
+ #
222
+ # Variable values are undone even if the block exits with a
223
+ # throw or an exception.
224
+ #
225
+ # If a variable name begins with '$', Fluid.let realizes
226
+ # it's a global variable and gives that variable a new value.
227
+ # No getters or setters are created. The old
228
+ # value is still restored when the block exits.
229
+ #
230
+ #
231
+ #
232
+ # === Destructors
233
+ #
234
+ # There may be an argument past the initial value, the <i>value destructor</i>.
235
+ # It may be either a Proc or the name of a method. If it's the name of a method,
236
+ # it's sent as a message to the value of the second argument. Like this:
237
+ #
238
+ # Fluid.let(out, File.open("logfile", "w"), :close)
239
+ #
240
+ # (You wouldn't really do that, though, since File.open does the same
241
+ # thing for you.)
242
+ #
243
+ # If the third argument is a block, it's called with the value of the
244
+ # second argument as its single parameter.
245
+ #
246
+ # Fluid.let(:out, File.open("logfile", 'w'),
247
+ # proc {|io| io.close}) {...}
248
+
249
+ def Fluid.let(*var_specs)
250
+ unwind_list = create_dynamic_context(var_specs)
251
+
252
+ begin
253
+ return yield if block_given?
254
+ ensure
255
+ unwind_dynamic_context unwind_list
256
+ end
257
+ end
258
+
259
+ # A global declaration of a fluid variable. After executing this code:
260
+ #
261
+ # Fluid.defvar(:global, 5)
262
+ #
263
+ # Fluid.global will normally everywhere have the value 5, unless it's
264
+ # changed by assignment or Fluid.let.
265
+ #
266
+ # However, Fluid.defvar has effect only the first time it's executed.
267
+ # That is, given this sequence:
268
+ #
269
+ # Fluid.defvar(:global, 5)
270
+ # Fluid.defvar(:global, 6666666)
271
+ #
272
+ # Fluid.global has value 5. The second Fluid.defvar is ignored.
273
+ # The "a-bit-more-clever-fluid-tracing.rb" example shows how
274
+ # this can be useful.
275
+ #
276
+ # A Fluid.defvar executed while a Fluid.let block is in effect will
277
+ # have no effect:
278
+ #
279
+ # Fluid.let(:not_global, 1) {
280
+ # Fluid.defvar(:not_global, 5555) # Fluid.not_global == 1
281
+ # }
282
+ # # The defvar has had no effect, so Fluid.not_global
283
+ # # has no value after the block.
284
+ #
285
+ # Fluid.defvar can take a block as an argument:
286
+ #
287
+ # Fluid.defvar(:var) { long_and_expensive_computation }
288
+ #
289
+ # The block is only executed if its value would be assigned to the
290
+ # variable.
291
+ #
292
+ # The first argument to Fluid.defvar may not be the name of
293
+ # a global variable.
294
+
295
+ def Fluid.defvar(name, value = nil)
296
+ if Var.global?(name)
297
+ raise NameError, "Fluid.defvar of a global can never have an effect, so it's not allowed."
298
+ end
299
+
300
+ var = Var.build(name, @@environment)
301
+ unless @@environment.has?(var.name)
302
+ value = yield if block_given?
303
+ var.push_binding(value)
304
+ end
305
+ end
306
+
307
+ # Answers whether _name_ is already a fluid variable.
308
+ def Fluid.has?(name)
309
+ return true if Var.global?(name)
310
+ @@environment.has?(Var.ensure_symbol(name))
311
+ end
312
+
313
+ def Fluid.method_missing(symbol, *args) # :nodoc:
314
+ symbol = symbol.to_s.gsub(/=/, '')
315
+ raise NameError, "'#{symbol}' has not been defined with Fluid.let or Fluid.defvar."
316
+ end
317
+
318
+ private
319
+
320
+ def Fluid.create_dynamic_context(var_specs)
321
+ var_list = []
322
+ return var_list if var_specs.empty?
323
+
324
+ var_specs = [var_specs] unless var_specs[0].is_a? Array
325
+
326
+ var_specs.each { |one_spec|
327
+ name = one_spec[0]
328
+ value = one_spec[1]
329
+ value_destructor = one_spec[2]
330
+
331
+ var = Var.build(name, @@environment, value_destructor)
332
+ assert_variable_name_is_not_duplicate(var.name, var_list)
333
+ var.push_binding(value)
334
+ var_list.push(var)
335
+ }
336
+ var_list
337
+ end
338
+
339
+ def Fluid.unwind_dynamic_context(var_list)
340
+ var_list.each { | var | var.pop_binding }
341
+ end
342
+
343
+ def Fluid.create_getter(var)
344
+ evalme = %Q{
345
+ def Fluid.#{var.to_s}
346
+ @@environment.get(#{var.inspect})
347
+ end
348
+ }
349
+ # puts evalme
350
+ class_eval evalme
351
+ end
352
+
353
+ def Fluid.create_setter(var)
354
+ evalme = %Q{
355
+ def Fluid.#{var.to_s}=(value)
356
+ @@environment.set(#{var.inspect}, value)
357
+ end
358
+ }
359
+ # puts evalme
360
+ class_eval evalme
361
+ end
362
+
363
+ def Fluid.delete_fluid_method(name)
364
+ evalme = %Q{
365
+ class << Fluid
366
+ remove_method(#{name.inspect})
367
+ end
368
+ }
369
+ #puts evalme
370
+ class_eval evalme
371
+ end
372
+
373
+ def Fluid.delete_getter(var)
374
+ delete_fluid_method(var)
375
+ end
376
+ def Fluid.delete_setter(var)
377
+ delete_fluid_method("#{var}=".intern)
378
+ end
379
+
380
+
381
+ def Fluid.assert_variable_name_is_not_duplicate(name_symbol,
382
+ already_defined = [])
383
+ if already_defined.detect { | e | e.name == name_symbol }
384
+ raise NameError, "'#{name_symbol}' is defined twice in the same Fluid.let."
385
+ end
386
+ end
387
+ end
388
+
389
+