puppet 0.13.1 → 0.13.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (44) hide show
  1. data/CHANGELOG +4 -0
  2. data/Rakefile +2 -2
  3. data/bin/puppet +29 -0
  4. data/bin/puppetd +35 -1
  5. data/conf/redhat/puppet.spec +1 -1
  6. data/lib/puppet.rb +1 -1
  7. data/lib/puppet/client/master.rb +34 -3
  8. data/lib/puppet/filetype.rb +6 -2
  9. data/lib/puppet/networkclient.rb +4 -1
  10. data/lib/puppet/parameter.rb +35 -38
  11. data/lib/puppet/parser/ast/node.rb +1 -0
  12. data/lib/puppet/parser/interpreter.rb +129 -2
  13. data/lib/puppet/parser/scope.rb +44 -6
  14. data/lib/puppet/type.rb +47 -37
  15. data/lib/puppet/type/cron.rb +25 -10
  16. data/lib/puppet/type/exec.rb +165 -71
  17. data/lib/puppet/type/nameservice.rb +2 -17
  18. data/lib/puppet/type/package.rb +31 -7
  19. data/lib/puppet/type/package/sun.rb +11 -6
  20. data/lib/puppet/type/parsedtype.rb +94 -60
  21. data/lib/puppet/type/parsedtype/host.rb +5 -12
  22. data/lib/puppet/type/parsedtype/port.rb +53 -32
  23. data/lib/puppet/type/parsedtype/sshkey.rb +8 -4
  24. data/lib/puppet/type/pfile.rb +6 -4
  25. data/lib/puppet/type/pfile/ensure.rb +1 -6
  26. data/lib/puppet/type/state.rb +34 -74
  27. data/lib/puppet/type/symlink.rb +30 -19
  28. data/lib/puppet/type/user.rb +63 -11
  29. data/lib/puppet/util.rb +54 -60
  30. data/test/client/master.rb +72 -0
  31. data/test/language/interpreter.rb +94 -0
  32. data/test/other/log.rb +8 -1
  33. data/test/puppet/utiltest.rb +101 -1
  34. data/test/test +12 -5
  35. data/test/types/cron.rb +21 -1
  36. data/test/types/exec.rb +46 -2
  37. data/test/types/group.rb +15 -3
  38. data/test/types/host.rb +43 -4
  39. data/test/types/port.rb +67 -6
  40. data/test/types/sshkey.rb +45 -4
  41. data/test/types/symlink.rb +4 -4
  42. data/test/types/type.rb +41 -3
  43. data/test/types/user.rb +23 -2
  44. metadata +3 -2
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ 0.13.2
2
+ Changed package[answerfile] to package[adminfile], and added package[responsefile]
3
+ Fixed a bunch of internal functions to behave more consistently and usefully
4
+
1
5
  0.13.1
2
6
  Fixed RPM spec files to create puppet user and group (lutter)
3
7
  Fixed crontab reading and writing (luke)
data/Rakefile CHANGED
@@ -47,7 +47,7 @@ DOWNDIR = "/export/docroots/reductivelabs.com/htdocs/downloads"
47
47
  if ENV['HOSTS']
48
48
  TESTHOSTS = ENV['HOSTS'].split(/\s+/)
49
49
  else
50
- TESTHOSTS = %w{fedora1 rh3a kirby culain openbsd1 centos1}
50
+ TESTHOSTS = %w{fedora1 rh3a culain openbsd1 centos1}
51
51
  end
52
52
  #TESTHOSTS = %w{sol10b}
53
53
 
@@ -386,8 +386,8 @@ task :hosttest do
386
386
  if $? != 0
387
387
  file = File.join("/tmp", "%stest.out" % host)
388
388
  File.open(file, "w") { |of| of.print out }
389
- puts "%s failed; output is in %s" % [host, file]
390
389
  puts out
390
+ puts "%s failed; output is in %s" % [host, file]
391
391
  end
392
392
  #sh %{ssh #{host} 'cd #{cwd}/test; sudo ./test' 2>&1}
393
393
  }
data/bin/puppet CHANGED
@@ -27,9 +27,18 @@
27
27
  # debug::
28
28
  # Enable full debugging.
29
29
  #
30
+ # extclassfile::
31
+ # Specify the location of the class file to load. Only affects the
32
+ # +--loadclasses+ option.
33
+ #
30
34
  # help::
31
35
  # Print this help message
32
36
  #
37
+ # loadclasses::
38
+ # Load any stored classes. +puppetd+ caches configured classes (usually at
39
+ # /etc/puppet/classes.txt), and setting this option causes all of those classes
40
+ # to be set in your +puppet+ manifest.
41
+ #
33
42
  # logfile::
34
43
  # Where to send messages. Choose between syslog, the console, and a log file.
35
44
  # Defaults to sending messages to the console.
@@ -65,8 +74,10 @@ end
65
74
 
66
75
  options = [
67
76
  [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
77
+ [ "--extclassfile", "-e", GetoptLong::REQUIRED_ARGUMENT ],
68
78
  [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
69
79
  [ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ],
80
+ [ "--loadclasses", GetoptLong::NO_ARGUMENT ],
70
81
  [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ],
71
82
  [ "--use-nodes", GetoptLong::NO_ARGUMENT ],
72
83
  [ "--version", "-V", GetoptLong::NO_ARGUMENT ]
@@ -82,6 +93,8 @@ verbose = false
82
93
  noop = false
83
94
  logfile = false
84
95
  parseonly = false
96
+ loadclasses = false
97
+ classfile = nil
85
98
 
86
99
  master = {
87
100
  :Local => true
@@ -108,6 +121,10 @@ begin
108
121
  verbose = true
109
122
  when "--debug"
110
123
  debug = true
124
+ when "--extclassfile"
125
+ classfile = arg
126
+ when "--loadclasses"
127
+ loadclasses = true
111
128
  when "--logdest"
112
129
  begin
113
130
  Puppet::Log.newdestination(arg)
@@ -142,6 +159,18 @@ Puppet.genmanifest
142
159
 
143
160
  master[:File] = ARGV.shift
144
161
 
162
+ if loadclasses
163
+ file = classfile || Puppet[:classfile]
164
+ if FileTest.exists?(file)
165
+ unless FileTest.readable?(file)
166
+ $stderr.puts "%s is not readable" % file
167
+ exit(63)
168
+ end
169
+
170
+ master[:Classes] = File.read(file).split(/[\s\n]/)
171
+ end
172
+ end
173
+
145
174
  begin
146
175
  server = Puppet::Server::Master.new(master)
147
176
  client = Puppet::Client::MasterClient.new(
data/bin/puppetd CHANGED
@@ -37,9 +37,24 @@
37
37
  # Send all produced logs to the central puppetmasterd system. This currently
38
38
  # results in a significant slowdown, so it is not recommended.
39
39
  #
40
+ # disable::
41
+ # Disable working on the local system. This puts a lock file in place,
42
+ # causing +puppetd+ not to work on the system until the lock file is removed.
43
+ # This is useful if you are testing a configuration and do not want the central
44
+ # configuration to override the local state until everything is tested and
45
+ # committed.
46
+ #
47
+ # +puppetd+ exits after executing this.
48
+ #
40
49
  # debug::
41
50
  # Enable full debugging.
42
51
  #
52
+ # enable::
53
+ # Enable working on the local system. This removes any lock file, causing
54
+ # +puppetd+ to start managing the local system again.
55
+ #
56
+ # +puppetd+ exits after executing this.
57
+ #
43
58
  # fqdn::
44
59
  # Set the fully-qualified domain name of the client. This is only used for
45
60
  # certificate purposes, but can be used to override the discovered hostname.
@@ -95,7 +110,9 @@ end
95
110
 
96
111
  options = [
97
112
  [ "--centrallogging", GetoptLong::NO_ARGUMENT ],
113
+ [ "--disable", GetoptLong::NO_ARGUMENT ],
98
114
  [ "--debug", "-d", GetoptLong::NO_ARGUMENT ],
115
+ [ "--enable", GetoptLong::NO_ARGUMENT ],
99
116
  [ "--fqdn", "-f", GetoptLong::REQUIRED_ARGUMENT ],
100
117
  [ "--help", "-h", GetoptLong::NO_ARGUMENT ],
101
118
  [ "--logdest", "-l", GetoptLong::REQUIRED_ARGUMENT ],
@@ -122,11 +139,18 @@ centrallogs = false
122
139
 
123
140
  setdest = false
124
141
 
142
+ enable = false
143
+ disable = false
144
+
125
145
  begin
126
146
  result.each { |opt,arg|
127
147
  case opt
128
148
  # First check to see if the argument is a valid configuration parameter;
129
149
  # if so, set it.
150
+ when "--disable"
151
+ disable = true
152
+ when "--enable"
153
+ enable = true
130
154
  when "--centrallogging"
131
155
  centrallogs = true
132
156
  when "--help"
@@ -209,6 +233,16 @@ end
209
233
  Puppet.notice "Starting Puppet client version %s" % [Puppet.version]
210
234
  client = Puppet::Client::MasterClient.new(args)
211
235
 
236
+ if enable
237
+ client.enable
238
+ elsif disable
239
+ client.disable
240
+ end
241
+
242
+ if enable or disable
243
+ exit(0)
244
+ end
245
+
212
246
  unless client.readcert
213
247
  if waitforcert
214
248
  begin
@@ -264,4 +298,4 @@ else
264
298
  Puppet.start
265
299
  end
266
300
 
267
- # $Id: puppetd 895 2006-02-09 21:15:38Z lutter $
301
+ # $Id: puppetd 908 2006-02-14 00:14:56Z luke $
@@ -4,7 +4,7 @@
4
4
 
5
5
  Summary: A network tool for managing many disparate systems
6
6
  Name: puppet
7
- Version: 0.13.1
7
+ Version: 0.13.2
8
8
  Release: 1%{?dist}
9
9
  License: GPL
10
10
  Group: System Environment/Base
data/lib/puppet.rb CHANGED
@@ -14,7 +14,7 @@ require 'puppet/util'
14
14
  #
15
15
  # it's also a place to find top-level commands like 'debug'
16
16
  module Puppet
17
- PUPPETVERSION = '0.13.1'
17
+ PUPPETVERSION = '0.13.2'
18
18
 
19
19
  def Puppet.version
20
20
  return PUPPETVERSION
@@ -1,5 +1,10 @@
1
1
  # The client for interacting with the puppetmaster config server.
2
2
  class Puppet::Client::MasterClient < Puppet::Client
3
+ Puppet.setdefaults("puppetd",
4
+ [:puppetdlockfile, "$statedir/puppetdlock",
5
+ "A lock file to temporarily stop puppetd from doing anything."]
6
+ )
7
+
3
8
  @drivername = :Master
4
9
 
5
10
  def self.facts
@@ -79,6 +84,19 @@ class Puppet::Client::MasterClient < Puppet::Client
79
84
  @cachefile
80
85
  end
81
86
 
87
+ # Disable running the configuration.
88
+ def disable
89
+ Puppet.notice "Disabling puppetd"
90
+ unless FileTest.exists? File.dirname(Puppet[:puppetdlockfile])
91
+ Puppet.recmkdir(File.dirname(Puppet[:puppetdlockfile]))
92
+ end
93
+ begin
94
+ File.open(Puppet[:puppetdlockfile], "w") { |f| f.puts ""; f.flush }
95
+ rescue => detail
96
+ raise Puppet::Error, "Could not lock puppetd: %s" % detail
97
+ end
98
+ end
99
+
82
100
  # Initialize and load storage
83
101
  def dostorage
84
102
  begin
@@ -96,6 +114,14 @@ class Puppet::Client::MasterClient < Puppet::Client
96
114
  end
97
115
  end
98
116
 
117
+ # Enable running again.
118
+ def enable
119
+ Puppet.notice "Enabling puppetd"
120
+ if FileTest.exists? Puppet[:puppetdlockfile]
121
+ File.unlink(Puppet[:puppetdlockfile])
122
+ end
123
+ end
124
+
99
125
  # Check whether our configuration is up to date
100
126
  def fresh?
101
127
  unless defined? @configstamp
@@ -227,8 +253,13 @@ class Puppet::Client::MasterClient < Puppet::Client
227
253
 
228
254
  # The code that actually runs the configuration.
229
255
  def run
230
- self.getconfig
231
- self.apply
256
+ if FileTest.exists? Puppet[:puppetdlockfile]
257
+ Puppet.notice "%s exists; skipping configuration run" %
258
+ Puppet[:puppetdlockfile]
259
+ else
260
+ self.getconfig
261
+ self.apply
262
+ end
232
263
  end
233
264
 
234
265
  def setclasses(ary)
@@ -243,4 +274,4 @@ class Puppet::Client::MasterClient < Puppet::Client
243
274
  end
244
275
  end
245
276
 
246
- # $Id: master.rb 872 2006-02-07 17:07:15Z luke $
277
+ # $Id: master.rb 908 2006-02-14 00:14:56Z luke $
@@ -40,7 +40,11 @@ module Puppet
40
40
  begin
41
41
  val = real_read()
42
42
  @loaded = Time.now
43
- return val.gsub(/# HEADER.*\n/,'')
43
+ if val
44
+ return val.gsub(/# HEADER.*\n/,'')
45
+ else
46
+ return ""
47
+ end
44
48
  rescue Puppet::Error => detail
45
49
  raise
46
50
  rescue => detail
@@ -204,4 +208,4 @@ module Puppet
204
208
  end
205
209
  end
206
210
 
207
- # $Id: filetype.rb 898 2006-02-13 17:13:09Z luke $
211
+ # $Id: filetype.rb 910 2006-02-15 07:20:36Z luke $
@@ -68,6 +68,9 @@ module Puppet
68
68
  Puppet.err "Could not call %s.%s: %s" %
69
69
  [namespace, method, detail.inspect]
70
70
  #raise NetworkClientError.new(detail.to_s)
71
+ if Puppet[:debug]
72
+ puts detail.backtrace
73
+ end
71
74
  raise
72
75
  end
73
76
  }
@@ -142,4 +145,4 @@ module Puppet
142
145
  end
143
146
  end
144
147
 
145
- # $Id: networkclient.rb 856 2006-01-30 16:18:24Z luke $
148
+ # $Id: networkclient.rb 914 2006-02-15 21:04:14Z luke $
@@ -11,14 +11,18 @@ module Puppet
11
11
  if block
12
12
  define_method(:default, &block)
13
13
  else
14
+ if value.nil?
15
+ raise Puppet::DevError,
16
+ "Either a default value or block must be provided"
17
+ end
14
18
  define_method(:default) do value end
15
19
  end
16
20
  end
17
21
 
18
22
  def nodefault
19
- undef_method :default
20
- #if defined_method? :default
21
- #end
23
+ if public_method_defined? :default
24
+ undef_method :default
25
+ end
22
26
  end
23
27
 
24
28
  # Store documentation for this parameter.
@@ -42,6 +46,9 @@ module Puppet
42
46
  Puppet.debug "Reraising %s" % detail
43
47
  raise
44
48
  rescue => detail
49
+ if Puppet[:debug]
50
+ puts detail.backtrace
51
+ end
45
52
  raise Puppet::DevError, "Munging failed for class %s: %s" %
46
53
  [self.name, detail]
47
54
  end
@@ -101,6 +108,9 @@ module Puppet
101
108
  rescue ArgumentError, Puppet::Error, TypeError
102
109
  raise
103
110
  rescue => detail
111
+ if Puppet[:debug]
112
+ puts detail.backtrace
113
+ end
104
114
  raise Puppet::DevError,
105
115
  "Validate method failed for class %s: %s" %
106
116
  [self.name, detail]
@@ -142,7 +152,13 @@ module Puppet
142
152
  @aliasvalues ||= {}
143
153
 
144
154
  #[@aliasvalues.keys, @parametervalues.keys].flatten
145
- @parametervalues.dup
155
+ if @parametervalues.is_a? Array
156
+ return @parametervalues.dup
157
+ elsif @parametervalues.is_a? Hash
158
+ return @parametervalues.keys
159
+ else
160
+ return []
161
+ end
146
162
  end
147
163
  end
148
164
 
@@ -159,36 +175,6 @@ module Puppet
159
175
 
160
176
  attr_accessor :parent
161
177
 
162
- # This doesn't work, because the instance_eval doesn't bind the inner block
163
- # only the outer one.
164
- # def munge(value)
165
- # if munger = self.class.munger
166
- # return @parent.instance_eval {
167
- # munger.call(value)
168
- # }
169
- # else
170
- # return value
171
- # end
172
- # end
173
- #
174
- # def validate(value)
175
- # if validater = self.class.validater
176
- # return @parent.instance_eval {
177
- # validater.call(value)
178
- # }
179
- # end
180
- # end
181
-
182
- #def default
183
- # default = self.class.default
184
- # if default.is_a?(Proc)
185
- # val = self.instance_eval(&default)
186
- # return val
187
- # else
188
- # return default
189
- # end
190
- #end
191
-
192
178
  def devfail(msg)
193
179
  self.fail(Puppet::DevError, msg)
194
180
  end
@@ -273,16 +259,17 @@ module Puppet
273
259
 
274
260
  # Verify that the passed value is valid.
275
261
  validate do |value|
276
- if self.class.values.empty?
262
+ values = self.class.values
263
+ if values.empty?
277
264
  # This parameter isn't using defined values to do its work.
278
265
  return
279
266
  end
280
267
  unless value.is_a?(Symbol)
281
268
  value = value.to_s.intern
282
269
  end
283
- unless self.class.values.include?(value) or self.class.alias(value)
270
+ unless values.include?(value) or self.class.alias(value)
284
271
  self.fail "Invalid '%s' value '%s'. Valid values are '%s'" %
285
- [self.class.name, value, self.class.values.join(", ")]
272
+ [self.class.name, value, values.join(", ")]
286
273
  end
287
274
  end
288
275
 
@@ -290,7 +277,17 @@ module Puppet
290
277
  # it possible to call for states, too.
291
278
  def value
292
279
  if self.is_a?(Puppet::State)
293
- return self.should
280
+ # We should return the 'is' value if there's not 'should' value.
281
+ # This might be bad, though, because the 'should' method
282
+ # knows whether to return an array or not and that info is
283
+ # not exposed, and the 'is' value could be a symbol. I can't
284
+ # seem to create a test in which this is a problem, but that doesn't
285
+ # mean it's not one.
286
+ if self.should
287
+ return self.should
288
+ else
289
+ return self.is
290
+ end
294
291
  else
295
292
  if defined? @value
296
293
  return @value
@@ -48,6 +48,7 @@ class Puppet::Parser::AST
48
48
  # Evaluate our parent class.
49
49
  def evalparent(scope)
50
50
  if @parentclass
51
+ Puppet.warning "evaluating parent %s" % @parentclass
51
52
  # This is pretty messed up. I don't know if this will
52
53
  # work in the long term, but we need to evaluate the node
53
54
  # in our own scope, even though our parent node has
@@ -10,6 +10,32 @@ require 'puppet/parser/scope'
10
10
  module Puppet
11
11
  module Parser
12
12
  class Interpreter
13
+ Puppet.setdefaults("ldap",
14
+ [:ldapnodes, false,
15
+ "Whether to search for node configurations in LDAP."],
16
+ [:ldapserver, "ldap",
17
+ "The LDAP server. Only used if ``ldapnodes`` is enabled."],
18
+ [:ldapport, 389,
19
+ "The LDAP port. Only used if ``ldapnodes`` is enabled."],
20
+ [:ldapstring, "(&(objectclass=puppetClient)(cn=%s))",
21
+ "The search string used to find an LDAP node."],
22
+ [:ldapattrs, "puppetclass",
23
+ "The LDAP attributes to use to define Puppet classes. Values
24
+ should be comma-separated."],
25
+ [:ldapparentattr, "parentnode",
26
+ "The attribute to use to define the parent node."],
27
+ [:ldapuser, "",
28
+ "The user to use to connect to LDAP. Must be specified as a
29
+ full DN."],
30
+ [:ldappassword, "",
31
+ "The password to use to connect to LDAP."],
32
+ [:ldapbase, "",
33
+ "The search base for LDAP searches. It's impossible to provide
34
+ a meaningful default here, although the LDAP libraries might
35
+ have one already set. Generally, it should be the 'ou=Hosts'
36
+ branch under your main directory."]
37
+ )
38
+
13
39
  attr_accessor :ast, :filetimeout
14
40
  # just shorten the constant path a bit, using what amounts to an alias
15
41
  AST = Puppet::Parser::AST
@@ -31,6 +57,20 @@ module Puppet
31
57
  @usenodes = true
32
58
  end
33
59
 
60
+ @nodesources = hash[:NodeSources] || [:file]
61
+
62
+ @nodesources.each { |source|
63
+ method = "setup_%s" % source.to_s
64
+ if respond_to? method
65
+ begin
66
+ self.send(method)
67
+ rescue => detail
68
+ raise Puppet::Error,
69
+ "Could not set up node source %s" % source
70
+ end
71
+ end
72
+ }
73
+
34
74
  # Set it to either the value or nil. This is currently only used
35
75
  # by the cfengine module.
36
76
  @classes = hash[:Classes] || []
@@ -41,6 +81,77 @@ module Puppet
41
81
  evaluate
42
82
  end
43
83
 
84
+ # Connect to the LDAP Server
85
+ def setup_ldap
86
+ begin
87
+ require 'ldap'
88
+ rescue LoadError
89
+ @ldap = nil
90
+ end
91
+ begin
92
+ @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport])
93
+ @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
94
+ @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword])
95
+ rescue => detail
96
+ raise Puppet::Error, "Could not connect to LDAP: %s" % detail
97
+ end
98
+ end
99
+
100
+ # Find the ldap node and extra the info, returning just
101
+ # the critical data.
102
+ def nodesearch_ldap(node)
103
+ unless defined? @ldap
104
+ ldapconnect()
105
+ end
106
+
107
+ filter = Puppet[:ldapstring]
108
+ attrs = Puppet[:ldapattrs].split("\s*,\s*")
109
+ sattrs = attrs.dup
110
+ pattr = nil
111
+ if pattr = Puppet[:ldapparentattr]
112
+ if pattr == ""
113
+ pattr = nil
114
+ else
115
+ sattrs << pattr
116
+ end
117
+ end
118
+
119
+ if filter =~ /%s/
120
+ filter = filter.gsub(/%s/, node)
121
+ end
122
+
123
+ parent = nil
124
+ classes = []
125
+
126
+ found = false
127
+ # We're always doing a sub here; oh well.
128
+ @ldap.search(Puppet[:ldapbase], 2, filter, sattrs) do |entry|
129
+ found = true
130
+ if pattr
131
+ if values = entry.vals(pattr)
132
+ if values.length > 1
133
+ raise Puppet::Error,
134
+ "Node %s has more than one parent: %s" %
135
+ [node, values.inspect]
136
+ end
137
+ unless values.empty?
138
+ parent = values.shift
139
+ end
140
+ end
141
+ end
142
+
143
+ attrs.each { |attr|
144
+ if values = entry.vals(attr)
145
+ classes += values
146
+ end
147
+ }
148
+ end
149
+
150
+ classes.flatten!
151
+
152
+ return parent, classes
153
+ end
154
+
44
155
  def parsedate
45
156
  parsefiles()
46
157
  @parsedate
@@ -67,8 +178,24 @@ module Puppet
67
178
  "Cannot evaluate nodes with a nil client"
68
179
  end
69
180
 
181
+ classes = nil
182
+ parent = nil
183
+ # At this point, stop at the first source that defines
184
+ # the node
185
+ @nodesources.each do |source|
186
+ method = "nodesearch_%s" % source
187
+ if self.respond_to? method
188
+ parent, classes = self.send(method, client)
189
+ end
190
+
191
+ if classes
192
+ Puppet.info "Found %s in %s" % [client, source]
193
+ break
194
+ end
195
+ end
196
+
70
197
  # We've already evaluated the AST, in this case
71
- return @scope.evalnode(names, facts)
198
+ return @scope.evalnode(names, facts, classes, parent)
72
199
  else
73
200
  # We've already evaluated the AST, in this case
74
201
  @scope = Puppet::Parser::Scope.new() # no parent scope
@@ -164,4 +291,4 @@ module Puppet
164
291
  end
165
292
  end
166
293
 
167
- # $Id: interpreter.rb 858 2006-01-30 19:22:25Z luke $
294
+ # $Id: interpreter.rb 915 2006-02-15 21:56:54Z luke $