openwferu 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/examples/engine_template.rb +182 -0
  2. data/examples/openwferu.rb +6 -7
  3. data/lib/openwfe/contextual.rb +8 -6
  4. data/lib/openwfe/engine/engine.rb +49 -14
  5. data/lib/openwfe/expool/expressionpool.rb +48 -17
  6. data/lib/openwfe/expool/history.rb +64 -14
  7. data/lib/openwfe/expool/journal.rb +66 -28
  8. data/lib/openwfe/expool/journal_replay.rb +190 -48
  9. data/lib/openwfe/expool/wfidgen.rb +51 -6
  10. data/lib/openwfe/expressions/condition.rb +49 -5
  11. data/lib/openwfe/expressions/environment.rb +9 -37
  12. data/lib/openwfe/expressions/expressionmap.rb +15 -1
  13. data/lib/openwfe/expressions/fe_concurrence.rb +4 -4
  14. data/lib/openwfe/expressions/fe_cron.rb +1 -5
  15. data/lib/openwfe/expressions/fe_do.rb +96 -26
  16. data/lib/openwfe/expressions/fe_equals.rb +190 -0
  17. data/lib/openwfe/expressions/fe_fqv.rb +25 -7
  18. data/lib/openwfe/expressions/fe_if.rb +262 -0
  19. data/lib/openwfe/expressions/fe_iterator.rb +3 -4
  20. data/lib/openwfe/expressions/fe_losfor.rb +0 -1
  21. data/lib/openwfe/expressions/fe_misc.rb +4 -5
  22. data/lib/openwfe/expressions/fe_participant.rb +2 -3
  23. data/lib/openwfe/expressions/fe_raw.rb +71 -31
  24. data/lib/openwfe/expressions/fe_reserve.rb +141 -0
  25. data/lib/openwfe/expressions/fe_sequence.rb +0 -1
  26. data/lib/openwfe/expressions/fe_sleep.rb +3 -58
  27. data/lib/openwfe/expressions/fe_subprocess.rb +6 -5
  28. data/lib/openwfe/expressions/fe_value.rb +20 -121
  29. data/lib/openwfe/expressions/fe_wait.rb +79 -0
  30. data/lib/openwfe/expressions/fe_when.rb +31 -96
  31. data/lib/openwfe/expressions/flowexpression.rb +112 -19
  32. data/lib/openwfe/expressions/raw_prog.rb +8 -116
  33. data/lib/openwfe/expressions/simplerep.rb +197 -0
  34. data/lib/openwfe/expressions/time.rb +266 -0
  35. data/lib/openwfe/expressions/timeout.rb +2 -2
  36. data/lib/openwfe/flowexpressionid.rb +22 -0
  37. data/lib/openwfe/listeners/socketlisteners.rb +15 -1
  38. data/lib/openwfe/participants/participantmap.rb +12 -1
  39. data/lib/openwfe/participants/participants.rb +7 -1
  40. data/lib/openwfe/rudefinitions.rb +1 -6
  41. data/lib/openwfe/service.rb +8 -0
  42. data/lib/openwfe/util/irb.rb +86 -0
  43. data/lib/openwfe/util/ometa.rb +3 -3
  44. data/lib/openwfe/util/safe.rb +26 -34
  45. data/lib/openwfe/util/scheduler.rb +133 -76
  46. data/lib/openwfe/utils.rb +1 -1
  47. data/lib/openwfe/version.rb +2 -2
  48. data/lib/openwfe/workitem.rb +38 -0
  49. data/lib/openwfe/worklist/storeparticipant.rb +27 -2
  50. data/test/console_test.rb +15 -0
  51. data/test/flowtestbase.rb +20 -28
  52. data/test/ft_12_blockparticipant.rb +24 -0
  53. data/test/ft_14b_subprocess.rb +18 -0
  54. data/test/ft_22_history.rb +22 -1
  55. data/test/ft_23_when.rb +29 -2
  56. data/test/ft_23b_when.rb +17 -0
  57. data/test/ft_23c_wait.rb +87 -0
  58. data/test/ft_2_concurrence.rb +15 -14
  59. data/test/ft_2b_concurrence.rb +4 -4
  60. data/test/ft_32_journal.rb +29 -6
  61. data/test/ft_32b_journal.rb +76 -0
  62. data/test/ft_36_subprocids.rb +96 -0
  63. data/test/ft_37_pnames.rb +55 -0
  64. data/test/ft_38_tag.rb +128 -0
  65. data/test/ft_39_reserve.rb +119 -0
  66. data/test/ft_3_equals.rb +20 -1
  67. data/test/ft_40_defined.rb +72 -0
  68. data/test/ft_41_case.rb +124 -0
  69. data/test/ft_4_misc.rb +17 -0
  70. data/test/ft_5_time.rb +15 -20
  71. data/test/ft_7_lose.rb +2 -3
  72. data/test/ft_8_forget.rb +1 -1
  73. data/test/ft_tests.rb +9 -0
  74. data/test/hparticipant_test.rb +47 -1
  75. data/test/nut_0_irb.rb +20 -0
  76. data/test/raw_prog_test.rb +6 -0
  77. data/test/safely_test.rb +31 -41
  78. data/test/wfid_test.rb +43 -0
  79. metadata +21 -4
  80. data/lib/openwfe/expressions/fe_utils.rb +0 -151
@@ -59,7 +59,7 @@ module OpenWFE
59
59
  include MonitorMixin, OwfeServiceLocator
60
60
  include JournalReplay
61
61
 
62
- attr_reader :workdir
62
+ attr_reader :workdir, :donedir
63
63
 
64
64
  FREQ = "1m"
65
65
  #
@@ -72,16 +72,18 @@ module OpenWFE
72
72
  @buckets = {}
73
73
 
74
74
  @workdir = OpenWFE::get_work_directory + "/journal"
75
- FileUtils.makedirs(@workdir) unless File.exist?(@workdir)
75
+ @donedir = @workdir + "/done"
76
+
77
+ FileUtils.makedirs(@donedir) unless File.exist?(@donedir)
76
78
 
77
79
  get_expression_pool.add_observer(:all) do |event, *args|
78
80
  #ldebug { ":#{event} for #{args[0].to_debug_s}" }
79
81
  queue_event(event, *args)
80
82
  end
81
83
 
82
- #@thread_id = get_scheduler.schedule_every(FREQ) do
83
- # flush_buckets()
84
- #end
84
+ @thread_id = get_scheduler.schedule_every(FREQ) do
85
+ flush_buckets()
86
+ end
85
87
  end
86
88
 
87
89
  #
@@ -94,28 +96,64 @@ module OpenWFE
94
96
 
95
97
  protected
96
98
 
99
+ #
100
+ # Queues the events before a flush.
101
+ #
102
+ # If the event is a :terminate, the individual bucket will get
103
+ # flushed.
104
+ #
97
105
  def queue_event (event, *args)
106
+ synchronize do
107
+
108
+ return if event == :stop
109
+ return if event == :launch
110
+ return if event == :reschedule
98
111
 
99
- return if event == :stop
100
- return if event == :launch
112
+ wfid = args[0].parent_wfid
113
+ #puts "__#{wfid} : #{event}"
101
114
 
102
- wfid = args[0].parent_wfid
103
- #puts "__#{wfid} : #{event}"
115
+ e = serialize_event(event, *args)
104
116
 
105
- e = serialize_event(event, *args)
117
+ bucket = get_bucket(wfid)
118
+ bucket << e
106
119
 
107
- bucket = get_bucket(wfid)
108
- bucket << e
120
+ if event == :terminate
109
121
 
110
- if event == :terminate
111
- bucket.flush
112
- @buckets.delete(wfid)
122
+ bucket.flush
123
+ @buckets.delete(wfid)
124
+
125
+ if @application_context[:keep_journals] == true
126
+ #
127
+ # 'move' journal to the done/ subdir of journal/
128
+ #
129
+ FileUtils.cp(
130
+ bucket.get_path,
131
+ @donedir + "/" + bucket.get_file_name)
132
+ end
133
+
134
+ FileUtils.rm bucket.get_path
135
+ end
113
136
  end
114
137
  end
115
138
 
139
+ #
140
+ # Makes sure that all the buckets are persisted to disk
141
+ #
116
142
  def flush_buckets
143
+
144
+ count = 0
145
+
117
146
  synchronize do
147
+
148
+ @buckets.each do |k, v|
149
+ v.flush
150
+ count += 1
151
+ end
152
+ @buckets.clear
118
153
  end
154
+
155
+ linfo { "flush_buckets() flushed #{count} buckets" } \
156
+ if count > 0
119
157
  end
120
158
 
121
159
  def get_bucket (wfid)
@@ -133,7 +171,6 @@ module OpenWFE
133
171
  # events waiting to get written in the journal
134
172
  #
135
173
  class Bucket
136
- include MonitorMixin
137
174
 
138
175
  attr_reader :wfid, :events
139
176
 
@@ -145,9 +182,7 @@ module OpenWFE
145
182
  end
146
183
 
147
184
  def << (event)
148
- synchronize do
149
- @events << event
150
- end
185
+ @events << event
151
186
  end
152
187
 
153
188
  def size
@@ -156,17 +191,20 @@ module OpenWFE
156
191
  alias :length :size
157
192
 
158
193
  def flush
159
- synchronize do
160
-
161
- fn = @workdir + "/" + @wfid.to_s + ".journal"
162
-
163
- File.open(fn, "w+") do |f|
164
- @events.each do |e|
165
- f.puts e
166
- end
194
+ File.open(get_path, "w+") do |f|
195
+ @events.each do |e|
196
+ f.puts e
167
197
  end
168
- @events.clear
169
198
  end
199
+ @events.clear
200
+ end
201
+
202
+ def get_path
203
+ @workdir + "/" + get_file_name
204
+ end
205
+
206
+ def get_file_name
207
+ @wfid.to_s + ".journal"
170
208
  end
171
209
  end
172
210
 
@@ -45,77 +45,219 @@ require 'openwfe/flowexpressionid'
45
45
  module OpenWFE
46
46
 
47
47
  #
48
- # Keeping a replayable track of the events in an OpenWFEru engine
48
+ # The code decicated to replay and reconstitute journal.
49
49
  #
50
50
  module JournalReplay
51
51
 
52
- def replay (file_path)
52
+ #
53
+ # Replays a given journal file.
54
+ #
55
+ # The offset can be determined by running the analyze() method.
56
+ #
57
+ # If 'trigger_action' is set to true, the apply or reply or cancel
58
+ # action found at the given offset will be triggered.
59
+ #
60
+ def replay (file_path, offset, trigger_action=false)
53
61
 
54
- events = File.open(file_path) do |f|
55
- s = YAML.load_stream f
56
- s.documents
62
+ states = decompose(file_path)
63
+
64
+ state = nil
65
+
66
+ states.each do |s|
67
+ state = s if s.offset == offset
68
+ end
69
+
70
+ raise "cannot replay offset #{offset}" unless state
71
+
72
+ #puts "expstorage size 0 = #{get_expression_storage.size}"
73
+
74
+ state.static.each do |update|
75
+ flow_expression = update[3]
76
+ flow_expression.application_context = @application_context
77
+ get_expression_pool.update(flow_expression)
57
78
  end
58
79
 
59
- #
60
- # what to do with Sleep and When ?
80
+ get_expression_pool.reschedule
61
81
 
62
- events = events[0..-20]
82
+ #puts "expstorage size 1 = #{get_expression_storage.size}"
63
83
 
64
- participants = {}
84
+ return unless trigger_action
65
85
 
66
- seen = {}
67
- static = []
68
- events.reverse.each do |e|
86
+ state.dynamic.each do |ply|
69
87
 
70
- etype = e[0]
71
- fei = e[2]
88
+ message = ply[0]
89
+ fei = ply[2]
90
+ wi = ply[3]
72
91
 
73
- next if etype == :apply
74
- next if etype == :reply
75
- next if etype == :reply_to_parent
76
- next if seen[fei]
92
+ if wi
93
+ #
94
+ # apply, reply, reply_to_parent
95
+ #
96
+ get_expression_pool.send message, fei, wi
97
+ else
98
+ #
99
+ # cancel
100
+ #
101
+ get_expression_pool.send message, fei
102
+ end
103
+ end
104
+ end
105
+
106
+ #
107
+ # Outputs a report of the each of the main events that the journal
108
+ # traced.
109
+ #
110
+ # The output goes to the stdout.
111
+ #
112
+ # The output can be used to determine an offset number for a replay()
113
+ # of the journal.
114
+ #
115
+ def analyze (file_path)
77
116
 
78
- seen[fei] = true
117
+ states = decompose(file_path)
79
118
 
80
- next if etype == :remove
119
+ states.each do |state|
81
120
 
82
- static << e
121
+ next if state.dynamic.length < 1
83
122
 
84
- participants[fei] = true \
85
- if e[3].is_a? OpenWFE::ParticipantExpression
123
+ puts
124
+ puts state.to_s
125
+ puts
86
126
  end
127
+ end
128
+
129
+ #
130
+ # Decomposes the given file_path into a list of states
131
+ #
132
+ def decompose (file_path)
87
133
 
88
- seen = {}
89
- dynamic = []
90
- events.reverse.each do |e|
91
- etype = e[0]
92
- fei = e[2]
93
- next if etype == :update
94
- next if etype == :remove
95
- #next if etype == :reply_to_parent
96
- next if seen[fei]
97
- next unless participants[fei]
98
- seen[fei] = true
99
- dynamic << e
134
+ do_decompose(load_events(file_path), [], nil, 0)
135
+ end
136
+
137
+ protected
138
+
139
+ def do_decompose (events, result, previous_state, offset)
140
+
141
+ current_state = extract_state(events, offset)
142
+
143
+ return result unless current_state
144
+
145
+ result << current_state
146
+
147
+ do_decompose(events, result, current_state, offset + 1)
100
148
  end
101
149
 
102
- puts
103
- puts "static :"
104
- static.each do |e|
105
- puts " - #{e[0]} #{e[2].to_short_s}"
150
+ def load_events (file_path)
151
+
152
+ File.open(file_path) do |f|
153
+ s = YAML.load_stream f
154
+ s.documents
155
+ end
106
156
  end
107
157
 
108
- puts
109
- puts "participants :"
110
- participants.each do |fei, v|
111
- puts " - #{fei.to_short_s}"
158
+ def extract_state (file, offset)
159
+
160
+ events = if file.is_a?(String)
161
+ load_events(file)
162
+ else
163
+ file
164
+ end
165
+
166
+ #
167
+ # what to do with Sleep and When ?
168
+
169
+ off = -1
170
+ off = off - offset if offset
171
+
172
+ return nil if (events.size + off < 0)
173
+
174
+ events = events[0..off]
175
+
176
+ date = events[-1][1]
177
+
178
+ participants = {}
179
+
180
+ seen = {}
181
+ static = []
182
+ events.reverse.each do |e|
183
+
184
+ etype = e[0]
185
+ fei = e[2]
186
+
187
+ next if etype == :apply
188
+ next if etype == :reply
189
+ next if etype == :reply_to_parent
190
+ next if seen[fei]
191
+
192
+ seen[fei] = true
193
+
194
+ next if etype == :remove
195
+
196
+ static << e
197
+
198
+ participants[fei] = true \
199
+ if e[3].is_a? OpenWFE::ParticipantExpression
200
+ end
201
+
202
+ seen = {}
203
+ dynamic = []
204
+ events.reverse.each do |e|
205
+ etype = e[0]
206
+ fei = e[2]
207
+ next if etype == :update
208
+ next if etype == :remove
209
+ #next if etype == :reply_to_parent
210
+ next if seen[fei]
211
+ next unless participants[fei]
212
+ seen[fei] = true
213
+ dynamic << e
214
+ end
215
+
216
+ ExpoolState.new(offset, date, static, dynamic, participants)
112
217
  end
113
218
 
114
- puts
115
- puts "dynamic :"
116
- dynamic.each do |e|
117
- puts " - #{e[0]} #{e[2].to_short_s}"
219
+ class ExpoolState
220
+
221
+ attr_accessor \
222
+ :offset,
223
+ :date,
224
+ :static,
225
+ :dynamic,
226
+ :participants
227
+
228
+ def initialize (offset, date, static, dynamic, participants)
229
+
230
+ @offset = offset
231
+ @date = date
232
+ @static = static
233
+ @dynamic = dynamic
234
+ @participants = participants
235
+ end
236
+
237
+ def to_s
238
+
239
+ s = " ===== offset : #{@offset} #{@date} =====\n"
240
+
241
+ s << "\n"
242
+ s << "static :\n"
243
+ @static.each do |e|
244
+ s << " - #{e[0]} #{e[2].to_short_s}\n"
245
+ end
246
+
247
+ s << "\n"
248
+ s << "dynamic :\n"
249
+ @dynamic.each do |e|
250
+ s << " - #{e[0]} #{e[2].to_short_s}\n"
251
+ end
252
+
253
+ #s << "\n"
254
+ #s << "participants :\n"
255
+ #@participants.each do |fei, v|
256
+ # s << " - #{fei.to_short_s}\n"
257
+ #end
258
+
259
+ s
260
+ end
118
261
  end
119
- end
120
262
  end
121
263
  end
@@ -65,13 +65,22 @@ module OpenWFE
65
65
 
66
66
  load_last()
67
67
 
68
- @last_f = File.open(@last_fn, "w")
68
+ begin
69
+ @last_f = File.open(@last_fn, "w+")
70
+ rescue Exception => e
71
+ lwarn do
72
+ "new() failed to open #{@last_fn}, continuing anyway...\n"+
73
+ OpenWFE::exception_to_s(e)
74
+ end
75
+ end
69
76
  end
70
77
 
71
78
  #
72
79
  # Returns a new workflow instance id
73
80
  #
74
- def generate
81
+ # The launchitem parameter is not used by this generator.
82
+ #
83
+ def generate (launchitem=nil)
75
84
  wfid = nil
76
85
  @mutex.synchronize do
77
86
  wfid = now
@@ -115,7 +124,7 @@ module OpenWFE
115
124
  # file is closed.
116
125
  #
117
126
  def stop
118
- @last_f.close
127
+ @last_f.close if @last_f
119
128
  end
120
129
 
121
130
  protected
@@ -126,6 +135,7 @@ module OpenWFE
126
135
  end
127
136
 
128
137
  def save_last
138
+ return unless @last_f
129
139
  @last_f.pos = 0
130
140
  @last_f.puts @last
131
141
  end
@@ -135,7 +145,7 @@ module OpenWFE
135
145
 
136
146
  if File.exist?(@last_fn)
137
147
  begin
138
- s = File.open(@last_fn, "r+") do |f|
148
+ s = File.open(@last_fn, "r") do |f|
139
149
  f.readline
140
150
  end
141
151
  @last = Integer(s)
@@ -216,7 +226,40 @@ module OpenWFE
216
226
  end
217
227
 
218
228
  def self.from_time (t)
219
- to_string (t.to_f * 10 * 1000).to_i
229
+ to_string(t.to_f * 10 * 1000).to_i
230
+ end
231
+ end
232
+
233
+ #
234
+ # This wfid generator returns as wfid the value found in a given
235
+ # field of the launchitem (if any).
236
+ #
237
+ # If there is no launchitem or no field, a Kotoba wfid is returned.
238
+ #
239
+ # This generator is useful for engines that have to use workflow
240
+ # instance ids generated by other systems.
241
+ #
242
+ class FieldWfidGenerator < KotobaWfidGenerator
243
+
244
+ def initialize (service_name, application_context, field_name)
245
+
246
+ super(service_name, application_context)
247
+
248
+ @field_name = field_name
249
+ end
250
+
251
+ def generate (launchitem=nil)
252
+
253
+ return super unless launchitem
254
+
255
+ wfid = launchitem.attributes[@field_name]
256
+
257
+ return wfid.to_s if wfid
258
+
259
+ super
260
+ #
261
+ # if the field is not present in the launchitem, will
262
+ # return a Kotoba wfid
220
263
  end
221
264
  end
222
265
 
@@ -263,7 +306,9 @@ module OpenWFE
263
306
  #
264
307
  # Generates a brand new UUID
265
308
  #
266
- def generate
309
+ # The launchitem parameter is not used by this generator.
310
+ #
311
+ def generate (launchitem=nil)
267
312
  return `#{@command}`.chomp
268
313
  end
269
314
 
@@ -61,17 +61,23 @@ module OpenWFE
61
61
  #
62
62
  module ConditionMixin
63
63
 
64
+ #
65
+ # This is the method brought to expression classes including this
66
+ # mixin. Easy evaluation of a conditon expressed in an attribute.
67
+ #
64
68
  def eval_condition (attname, workitem)
65
69
 
70
+ #attname = pick_attribute(attname) \
71
+ # if attname.is_a?(Array)
72
+
66
73
  conditional = lookup_attribute(attname, workitem)
67
74
  rconditional = lookup_attribute("r"+attname.to_s, workitem)
68
75
 
69
- if rconditional and not conditional
70
- #return instance_eval(rconditional)
71
- return do_eval(rconditional)
72
- end
76
+ return do_eval(rconditional) \
77
+ if rconditional and not conditional
73
78
 
74
- return nil unless conditional
79
+ return nil \
80
+ unless conditional
75
81
 
76
82
  #ldebug { "eval_condition() 0 for '#{conditional}'" }
77
83
 
@@ -91,6 +97,44 @@ module OpenWFE
91
97
  return (result == "true" or result == true)
92
98
  end
93
99
 
100
+ #
101
+ # Returns nil if the cited attname (without or without 'r' prefix)
102
+ # is not present.
103
+ #
104
+ # Attname may be a String or a Symbol, or an Array of String or Symbol
105
+ # instances.
106
+ #
107
+ # Returns the Symbol the attribute if present.
108
+ #
109
+ def determine_condition_attribute (attname)
110
+
111
+ attname = [ attname ] unless attname.is_a?(Array)
112
+
113
+ attname.each do |aname|
114
+ aname = aname.intern if aname.is_a?(String)
115
+ return aname if has_attribute(aname)
116
+ return aname if has_attribute("r" + aname.to_s)
117
+ end
118
+
119
+ nil
120
+ end
121
+
122
+ protected
123
+
124
+ #
125
+ # If the attribute name is a list of attribute names, pick
126
+ # the first one present.
127
+ #
128
+ #def pick_attribute (attnames)
129
+ # attnames.each do |attname|
130
+ # return attname if has_attribute(attname)
131
+ # return attname if has_attribute("r" + attname.to_s)
132
+ # end
133
+ # return attnames[0]
134
+ # #
135
+ # # some kind of a default (for error messages)
136
+ #end
137
+
94
138
  private
95
139
 
96
140
  def from_xml (string)
@@ -48,7 +48,7 @@ module OpenWFE
48
48
 
49
49
  #
50
50
  # An environment is a store for variables.
51
- # It's an expression as it's storable in the expression pool.
51
+ # It's an expression thus it's storable in the expression pool.
52
52
  #
53
53
  class Environment < FlowExpression
54
54
  include Schedulable
@@ -68,19 +68,10 @@ module OpenWFE
68
68
 
69
69
  def [] (key)
70
70
 
71
- #if OpenWFE::starts_with(key, "//")
72
- # return get_expression_pool()\
73
- # .get_engine_environment()[key[2..-1]]
74
- #end
75
- #if OpenWFE::starts_with(key, "/")
76
- # return get_root_environment()[key[1..-1]]
77
- #end
78
-
79
71
  value = @variables[key]
80
72
 
81
73
  return value \
82
74
  if @variables.has_key?(key) or is_engine_environment?
83
- #if value or is_engine_environment?
84
75
 
85
76
  return get_parent[key] if @parent_id
86
77
 
@@ -88,38 +79,19 @@ module OpenWFE
88
79
  end
89
80
 
90
81
  def []= (key, value)
82
+ synchronize do
91
83
 
92
- #if OpenWFE::starts_with(key, "//")
93
- # key = key[2..-1]
94
- # get_expression_pool().fetch_engine_environment()[key] = value
95
- #elsif OpenWFE::starts_with(key, "/")
96
- # key = key[1..-1]
97
- # get_root_environment()[key] = value
98
- #else
99
- # @variables[key] = value
100
- # store_itself()
101
- # #ldebug { "[]= #{@fei.to_debug_s} : '#{key}' -> '#{value}'" }
102
- #end
103
-
104
- @variables[key] = value
105
- store_itself()
84
+ @variables[key] = value
85
+ store_itself()
86
+ end
106
87
  end
107
88
 
108
89
  def delete (key)
90
+ synchronize do
109
91
 
110
- #if OpenWFE::starts_with(key, "//")
111
- # key = key[2..-1]
112
- # get_expression_pool().fetch_engine_environment().delete(key)
113
- #elsif OpenWFE::starts_with(key, "/")
114
- # key = key[1..-1]
115
- # get_root_environment().delete(key)
116
- #else
117
- # @variables.delete(key)
118
- # store_itself()
119
- #end
120
-
121
- @variables.delete key
122
- store_itself()
92
+ @variables.delete key
93
+ store_itself()
94
+ end
123
95
  end
124
96
 
125
97
  #