fluid 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.
@@ -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
+