gisele 0.0.1

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,264 @@
1
+ require 'spec_helper'
2
+ module Gisele::Language
3
+ describe Grammar, "ast" do
4
+
5
+ let(:grammar){ Gisele::Language::Grammar }
6
+
7
+ def ast(text, rule, consume = true)
8
+ grammar.parse(text, :root => rule, :consume => consume).value
9
+ end
10
+
11
+ describe "the bool_expr rule" do
12
+
13
+ it 'returns expected ast on simple expressions' do
14
+ expected = [:and, [:varref, "diagKnown"], [:varref, "platLow"]]
15
+ ast("diagKnown and platLow", :bool_expr).should eq(expected)
16
+ end
17
+
18
+ it 'respects priorities' do
19
+ expected = [:or, [:and, [:varref, "diagKnown"], [:varref, "platLow"]], [:varref, "platHigh"]]
20
+ ast("diagKnown and platLow or platHigh", :bool_expr).should eq(expected)
21
+ end
22
+
23
+ it 'supports double negations' do
24
+ expected = [:not, [:not, [:varref, "diagKnown"]]]
25
+ ast("not not(diagKnown)", :bool_expr).should eq(expected)
26
+ end
27
+
28
+ end # bool_expr
29
+
30
+ describe 'the event_commalist rule' do
31
+
32
+ it 'parses singleton lists as expected' do
33
+ expr = 'Diagnosis:start'
34
+ expected = ["Diagnosis:start"]
35
+ ast(expr, :event_commalist).should eq(expected)
36
+ end
37
+
38
+ it 'parses non empty lists as expected' do
39
+ expr = 'Diagnosis:start, an_event'
40
+ expected = ["Diagnosis:start", "an_event"]
41
+ ast(expr, :event_commalist).should eq(expected)
42
+ end
43
+
44
+ end # event_commalist
45
+
46
+ describe 'the event_set rule' do
47
+
48
+ it 'parses non empty lists as expected' do
49
+ expr = '{Diagnosis:start, an_event}'
50
+ expected = [:event_set, "Diagnosis:start", "an_event"]
51
+ ast(expr, :event_set).should eq(expected)
52
+ end
53
+
54
+ end # event_set
55
+
56
+ describe "the fluent_def rule" do
57
+
58
+ it 'parses fluent definitions as expected' do
59
+ defn = "fluent diagKnown {Diagnosis:start, diagnosis}, {Treatment:end} initially false"
60
+ expected = [:fluent,
61
+ "diagKnown",
62
+ [:event_set, "Diagnosis:start", "diagnosis"],
63
+ [:event_set, "Treatment:end"],
64
+ false]
65
+ ast(defn, :fluent_def).should eq(expected)
66
+ end
67
+
68
+ it 'does not require the initial value' do
69
+ defn = "fluent diagKnown {Diagnosis:start, diagnosis}, {Treatment:end}"
70
+ expected = [:fluent,
71
+ "diagKnown",
72
+ [:event_set, "Diagnosis:start", "diagnosis"],
73
+ [:event_set, "Treatment:end"],
74
+ nil]
75
+ ast(defn, :fluent_def).should eq(expected)
76
+ end
77
+
78
+ end # fluent_def rule
79
+
80
+ describe "the trackvar_def rule" do
81
+
82
+ it 'parses tracking variable definitions as expected' do
83
+ defn = "trackvar mplus {Diagnosis:start}"
84
+ expected = [:trackvar,
85
+ "mplus",
86
+ [:event_set, "Diagnosis:start"],
87
+ [:event_set],
88
+ nil]
89
+ ast(defn, :trackvar_def).should eq(expected)
90
+ end
91
+
92
+ it 'supports obsolete events and initial value' do
93
+ defn = "trackvar mplus {Diagnosis:start}, {Treatment:end} initially true"
94
+ expected = [:trackvar,
95
+ "mplus",
96
+ [:event_set, "Diagnosis:start"],
97
+ [:event_set, "Treatment:end"],
98
+ true]
99
+ ast(defn, :trackvar_def).should eq(expected)
100
+ end
101
+
102
+ end # trackvar_def rule
103
+
104
+ describe "the task_call_statement rule" do
105
+
106
+ it 'parses as expected' do
107
+ ast("Diagnosis", :task_call_statement).should eq([:task_call, "Diagnosis"])
108
+ end
109
+
110
+ end # task_call_statement
111
+
112
+ describe "the statement_list rule" do
113
+
114
+ it 'parses a list of 2 elements' do
115
+ expr = "Task1 Task2"
116
+ expected = [[:task_call, "Task1"], [:task_call, "Task2"]]
117
+ ast(expr, :statement_list).should eq(expected)
118
+ end
119
+
120
+ it 'parses a list of 3 elements' do
121
+ expr = "Task1 Task2 Task3"
122
+ expected = [[:task_call, "Task1"], [:task_call, "Task2"], [:task_call, "Task3"]]
123
+ ast(expr, :statement_list).should eq(expected)
124
+ end
125
+
126
+ end # statement_list
127
+
128
+ describe "the par_statement rule" do
129
+
130
+ it 'parses as expected' do
131
+ expr = "par Task1 Task2 end"
132
+ expected = [:par, [:task_call, "Task1"], [:task_call, "Task2"]]
133
+ ast(expr, :par_statement).should eq(expected)
134
+ end
135
+
136
+ end # par_statement
137
+
138
+ describe "the seq_statement rule" do
139
+
140
+ it 'parses as expected' do
141
+ expr = "seq Task1 Task2 end"
142
+ expected = [:seq, [:task_call, "Task1"], [:task_call, "Task2"]]
143
+ ast(expr, :seq_statement).should eq(expected)
144
+ end
145
+
146
+ end # seq_statement
147
+
148
+ describe "the while_statement rule" do
149
+
150
+ it 'parses as expected' do
151
+ expr = "while goodCond Task1 end"
152
+ expected = \
153
+ [:while,
154
+ [:varref, "goodCond"],
155
+ [:task_call, "Task1"]]
156
+ ast(expr, :while_statement).should eq(expected)
157
+ end
158
+
159
+ it 'recognizes implicit sequences' do
160
+ expr = "while goodCond Task1 Task2 end"
161
+ expected = \
162
+ [:while,
163
+ [:varref, "goodCond"],
164
+ [:seq, [:task_call, "Task1"], [:task_call, "Task2"]]]
165
+ ast(expr, :while_statement).should eq(expected)
166
+ end
167
+
168
+ end # while_statement
169
+
170
+ describe "the else_clause rule" do
171
+
172
+ it 'parses as expected' do
173
+ expr = "else Task1 "
174
+ expected = \
175
+ [:else, [:task_call, "Task1"]]
176
+ ast(expr, :else_clause).should eq(expected)
177
+ end
178
+
179
+ end # else_clause
180
+
181
+ describe "the elsif_clause rule" do
182
+
183
+ it 'parses as expected' do
184
+ expr = "elsif goodCond Task1 "
185
+ expected = \
186
+ [:elsif, [:varref, "goodCond"], [:task_call, "Task1"]]
187
+ ast(expr, :elsif_clause).should eq(expected)
188
+ end
189
+
190
+ end # elsif_clause
191
+
192
+ describe "the if_statement rule" do
193
+
194
+ it 'parses as expected' do
195
+ expr = "if goodCond Task1 end"
196
+ expected = \
197
+ [:if, [:varref, "goodCond"], [:task_call, "Task1"]]
198
+ ast(expr, :if_statement).should eq(expected)
199
+ end
200
+
201
+ it 'supports a else clause' do
202
+ expr = "if goodCond Task1 else Task2 end"
203
+ expected = \
204
+ [:if,
205
+ [:varref, "goodCond"], [:task_call, "Task1"],
206
+ [:else, [:task_call, "Task2"]] ]
207
+ ast(expr, :if_statement).should eq(expected)
208
+ end
209
+
210
+ it 'supports elsif clauses' do
211
+ expr = "if goodCond Task1 elsif otherCond Task2 elsif stillAnother Task3 else Task4 end"
212
+ expected = \
213
+ [:if,
214
+ [:varref, "goodCond"], [:task_call, "Task1"],
215
+ [:elsif,
216
+ [:varref, "otherCond"], [:task_call, "Task2"]],
217
+ [:elsif,
218
+ [:varref, "stillAnother"], [:task_call, "Task3"]],
219
+ [:else,
220
+ [:task_call, "Task4"]] ]
221
+ ast(expr, :if_statement).should eq(expected)
222
+ end
223
+
224
+ end # if_statement
225
+
226
+ describe 'the task_refinement rule' do
227
+
228
+ it 'parses as expected' do
229
+ expr = "refinement Task1 end"
230
+ expected = [:task_call, "Task1"]
231
+ ast(expr, :task_refinement).should eq(expected)
232
+ end
233
+
234
+ end # task_refinement
235
+
236
+ describe 'the task_signature rule' do
237
+
238
+ it 'parses as expected' do
239
+ expr = "fluent diagKnown {}, {}\ntrackvar mplus {}"
240
+ expected = \
241
+ [ [:fluent, "diagKnown", [:event_set], [:event_set], nil],
242
+ [:trackvar, "mplus", [:event_set], [:event_set], nil]]
243
+ ast(expr, :task_signature).should eq(expected)
244
+ end
245
+
246
+ end # task_signature
247
+
248
+ describe 'the task_def rule' do
249
+
250
+ it 'parses as expected' do
251
+ expr = "task Task1 fluent diagKnown {}, {} refinement Task2 end end"
252
+ expected = \
253
+ [:task, "Task1",
254
+ [:signature,
255
+ [:fluent, "diagKnown", [:event_set], [:event_set], nil]],
256
+ [:refinement,
257
+ [:task_call, "Task2"]]]
258
+ ast(expr, :task_def).should eq(expected)
259
+ end
260
+
261
+ end # task_def
262
+
263
+ end
264
+ end
@@ -0,0 +1,329 @@
1
+ require 'spec_helper'
2
+ module Gisele::Language
3
+ describe Grammar do
4
+
5
+ let(:grammar){ Gisele::Language::Grammar }
6
+
7
+ def parse(text, rule, consume = true)
8
+ grammar.parse(text, :root => rule, :consume => consume)
9
+ end
10
+
11
+ ### Spacing
12
+
13
+ describe 'the spaces rule' do
14
+
15
+ it 'parses all kind of spaces' do
16
+ parse(' ', :spaces).should eq(' ')
17
+ parse("\t", :spaces).should eq("\t")
18
+ parse("\n", :spaces).should eq("\n")
19
+ parse(" \t\n", :spaces).should eq(" \t\n")
20
+ end
21
+
22
+ it 'enforces mandatory spacing' do
23
+ lambda{ parse('', :spaces) }.should raise_error(Citrus::ParseError)
24
+ end
25
+
26
+ end
27
+
28
+ describe 'the spacing rule' do
29
+
30
+ it 'parses all kind of spaces' do
31
+ parse(' ', :spacing).should eq(' ')
32
+ parse("\t", :spacing).should eq("\t")
33
+ parse("\n", :spacing).should eq("\n")
34
+ parse(" \t\n", :spacing).should eq(" \t\n")
35
+ end
36
+
37
+ it 'does not enforces mandatory spacing' do
38
+ parse('', :spacing).should eq('')
39
+ end
40
+
41
+ end
42
+
43
+ ### Literals
44
+
45
+ describe 'the boolean_literal rule' do
46
+
47
+ it 'parses booleans' do
48
+ parse('true', :boolean_literal).should eq('true')
49
+ parse('false', :boolean_literal).should eq('false')
50
+ end
51
+
52
+ it 'does not parses integers' do
53
+ lambda{ parse('0', :boolean_literal) }.should raise_error(Citrus::ParseError)
54
+ end
55
+
56
+ end
57
+
58
+ ### Names
59
+
60
+ describe 'the task_name rule' do
61
+
62
+ it 'parses correct task names' do
63
+ parse('A', :task_name).should eq('A')
64
+ parse('Diagnosis', :task_name).should eq('Diagnosis')
65
+ parse('TaskName', :task_name).should eq('TaskName')
66
+ parse('Task_Name', :task_name).should eq('Task_Name')
67
+ end
68
+
69
+ it 'raises on invalid task names' do
70
+ lambda{ parse('not_a_task_name', :task_name) }.should raise_error(Citrus::ParseError)
71
+ end
72
+
73
+ end # task_name
74
+
75
+ describe 'the variable_name rule' do
76
+
77
+ it 'parses correct variable names' do
78
+ parse('a', :variable_name).should eq('a')
79
+ parse('diagnosis', :variable_name).should eq('diagnosis')
80
+ parse('varName', :variable_name).should eq('varName')
81
+ end
82
+
83
+ it 'raises on invalid variable names' do
84
+ lambda{ parse('NotAVarName', :variable_name) }.should raise_error(Citrus::ParseError)
85
+ end
86
+
87
+ end # variable_name
88
+
89
+ describe 'the event_name rule' do
90
+
91
+ it 'parses correct event names' do
92
+ parse('a', :event_name).should eq('a')
93
+ parse('event', :event_name).should eq('event')
94
+ parse('event_name', :event_name).should eq('event_name')
95
+ end
96
+
97
+ it 'raises on invalid variable names' do
98
+ lambda{ parse('NotAnEventName', :event_name) }.should raise_error(Citrus::ParseError)
99
+ lambda{ parse('notAnEventName', :event_name) }.should raise_error(Citrus::ParseError)
100
+ end
101
+
102
+ end # event_name
103
+
104
+ ### Events
105
+
106
+ describe 'the task_start_or_end rule' do
107
+
108
+ it 'parses correct event names' do
109
+ parse('Task:start', :task_start_or_end).should eq('Task:start')
110
+ parse('Task:end', :task_start_or_end).should eq('Task:end')
111
+ end
112
+
113
+ it 'raises on simple task names' do
114
+ lambda{ parse('Task', :task_start_or_end) }.should raise_error(Citrus::ParseError)
115
+ end
116
+
117
+ end # task_start_or_end
118
+
119
+ describe 'the event rule' do
120
+
121
+ it 'parses correct events' do
122
+ parse('Task:start', :event).should eq('Task:start')
123
+ parse('Task:end', :event).should eq('Task:end')
124
+ parse('an_event', :event).should eq('an_event')
125
+ end
126
+
127
+ it 'raises on invalid event names' do
128
+ lambda{ parse('Task', :event) }.should raise_error(Citrus::ParseError)
129
+ end
130
+
131
+ end # event
132
+
133
+ describe 'the event_commalist rule' do
134
+
135
+ it 'parses a singleton list' do
136
+ parse('Task:start', :event_commalist).should eq('Task:start')
137
+ end
138
+
139
+ it 'parses multiple events' do
140
+ parse('Task:start, an_event', :event_commalist).should eq('Task:start, an_event')
141
+ end
142
+
143
+ it 'recognizes invalid events' do
144
+ lambda{ parse('Task:start, NotAnEvent', :event_commalist) }.should raise_error(Citrus::ParseError)
145
+ end
146
+
147
+ end # event_commalist
148
+
149
+ describe 'the event_set rule' do
150
+
151
+ it 'parses empty sets' do
152
+ parse('{}', :event_set).should eq('{}')
153
+ parse('{ }', :event_set).should eq('{ }')
154
+ end
155
+
156
+ it 'parses event singletons' do
157
+ parse('{Task:start}', :event_set).should eq('{Task:start}')
158
+ parse('{ Task:start }', :event_set).should eq('{ Task:start }')
159
+ end
160
+
161
+ it 'recognizes invalid events in the set' do
162
+ lambda{ parse('{Task:start, NotAnEvent}', :event_set) }.should raise_error(Citrus::ParseError)
163
+ end
164
+
165
+ end # event_set
166
+
167
+ ### Variables
168
+
169
+ describe 'the fluent_def rule' do
170
+
171
+ it 'parses valid fluent definitions' do
172
+ defn = 'fluent diagKnown {Diagnosis:start}, {Treatment:end} initially false'
173
+ parse(defn, :fluent_def).should eq(defn)
174
+ end
175
+
176
+ it 'supports a missing initial value' do
177
+ defn = 'fluent diagKnown {Diagnosis:start}, {Treatment:end}'
178
+ parse(defn, :fluent_def).should eq(defn)
179
+ end
180
+
181
+ it 'supports empty sets for events' do
182
+ defn = 'fluent diagKnown {}, {} initially true'
183
+ parse(defn, :fluent_def).should eq(defn)
184
+ end
185
+
186
+ end # fluent_def
187
+
188
+ describe 'the trackvar_def rule' do
189
+
190
+ it 'parses valid tracking variable definitions' do
191
+ defn = 'trackvar plateletLow {BloodTest:end}'
192
+ parse(defn, :trackvar_def).should eq(defn)
193
+ end
194
+
195
+ it 'supports an optional initial value' do
196
+ defn = 'trackvar plateletLow {BloodTest:end} initially false'
197
+ parse(defn, :trackvar_def).should eq(defn)
198
+ end
199
+
200
+ it 'supports optional obsolete events' do
201
+ defn = 'trackvar plateletLow {BloodTest:end}, {Chemotherapy:end}'
202
+ parse(defn, :trackvar_def).should eq(defn)
203
+ end
204
+
205
+ end # trackvar_def
206
+
207
+ describe 'the bool_expr rule' do
208
+
209
+ it 'parses single variable references' do
210
+ parse('diagKnown', :bool_expr).should eq('diagKnown')
211
+ end
212
+
213
+ it 'parses boolean literals' do
214
+ parse('true', :bool_expr).should eq('true')
215
+ parse('false', :bool_expr).should eq('false')
216
+ end
217
+
218
+ it 'parses negated expression' do
219
+ parse('not diagKnown', :bool_expr).should eq('not diagKnown')
220
+ parse('not true', :bool_expr).should eq('not true')
221
+ parse('not false', :bool_expr).should eq('not false')
222
+ end
223
+
224
+ it 'parses or expressions' do
225
+ parse('diagKnown or platLow', :bool_expr).should eq('diagKnown or platLow')
226
+ end
227
+
228
+ it 'parses and expressions' do
229
+ parse('diagKnown and platLow', :bool_expr).should eq('diagKnown and platLow')
230
+ end
231
+
232
+ it 'parses complex expressions' do
233
+ expr = 'diagKnown and (platLow or not(metastased and mplus))'
234
+ parse(expr, :bool_expr).should eq(expr)
235
+ end
236
+
237
+ end # bool_expr
238
+
239
+ ### Process statements
240
+
241
+ describe 'the par_statement rule' do
242
+
243
+ it 'parses a single parallel statement' do
244
+ expr = 'par Task1 Task2 end'
245
+ parse(expr, :par_statement).should eq(expr)
246
+ end
247
+
248
+ end # par_statement
249
+
250
+ describe 'the seq_statement rule' do
251
+
252
+ it 'parses a single sequence statement' do
253
+ expr = 'seq Task1 Task2 end'
254
+ parse(expr, :seq_statement).should eq(expr)
255
+ end
256
+
257
+ end # seq_statement
258
+
259
+ describe 'the while_statement rule' do
260
+
261
+ it 'parses a single while statement' do
262
+ expr = 'while badCond Task end'
263
+ parse(expr, :while_statement).should eq(expr)
264
+ end
265
+
266
+ end # while_statement
267
+
268
+ describe 'the if_statement rule' do
269
+
270
+ it 'parses a single if statement' do
271
+ expr = 'if goodCond Task end'
272
+ parse(expr, :if_statement).should eq(expr)
273
+ end
274
+
275
+ it 'supports an optional else' do
276
+ expr = 'if goodCond GoodTask else BadTask end'
277
+ parse(expr, :if_statement).should eq(expr)
278
+ end
279
+
280
+ it 'supports an optional elsif clauses' do
281
+ expr = 'if goodCond GoodTask elsif otherCond OtherTask elsif yetAnother BadTask end'
282
+ parse(expr, :if_statement).should eq(expr)
283
+ end
284
+
285
+ end # if_statement
286
+
287
+ describe 'the process_statement rule' do
288
+
289
+ it 'parses a simple process statement' do
290
+ expr = <<-PROCESS.strip
291
+ DoSomething
292
+ if goodCond
293
+ DoForGood
294
+ else
295
+ DoForBad
296
+ end
297
+ CleanDesk
298
+ PROCESS
299
+ parse(expr, :process_statement).should eq(expr)
300
+ end
301
+
302
+ end # process_statement
303
+
304
+ describe 'the task_def rule' do
305
+
306
+ it 'parses a simple task definition' do
307
+ taskdef = <<-TASKDEF.strip
308
+ task Process
309
+ refinement Diagnosis end
310
+ end
311
+ TASKDEF
312
+ parse(taskdef, :task_def).should eq(taskdef)
313
+ end
314
+
315
+ it 'supports optional variable definitions in the signature' do
316
+ taskdef = <<-TASKDEF.strip
317
+ task Process
318
+ fluent diagKnown {Diagnosis:start}, {} initially false
319
+ trackvar mplus {Diagnosis:end}
320
+ refinement Diagnosis end
321
+ end
322
+ TASKDEF
323
+ parse(taskdef, :task_def).should eq(taskdef)
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ module Gisele::Language
3
+ describe Parser do
4
+
5
+ fixture_files('tasks/**/*.gis').each do |file|
6
+ if file.sub_ext(".ast").exist?
7
+
8
+ it "returns the expected ast on #{file}" do
9
+ parsed = Parser.parse(file)
10
+ expected = Kernel::eval(file.sub_ext(".ast").read, TOPLEVEL_BINDING, file.sub_ext(".ast").to_s)
11
+ parsed.should eq(expected)
12
+ end
13
+
14
+ else
15
+
16
+ it "parses #{file} without error" do
17
+ parsed = Parser.parse(file)
18
+ parsed.should be_a(Array)
19
+ parsed.first.should eq(:task)
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+ describe Gisele do
3
+
4
+ it "has a version number" do
5
+ Gisele.const_defined?(:VERSION).should be_true
6
+ end
7
+
8
+ end
@@ -0,0 +1,75 @@
1
+ # Installs a rake task for debuging the announcement mail.
2
+ #
3
+ # This file installs the 'rake debug_mail' that flushes an announcement mail
4
+ # for your library on the standard output. It is automatically generated
5
+ # by Noe from your .noespec file, and should therefore be configured there,
6
+ # under the variables/rake_tasks/debug_mail entry, as illustrated below:
7
+ #
8
+ # variables:
9
+ # rake_tasks:
10
+ # debug_mail:
11
+ # rx_changelog_sections: /^#/
12
+ # nb_changelog_sections: 1
13
+ # ...
14
+ #
15
+ # If you have specific needs requiring manual intervention on this file,
16
+ # don't forget to set safe-override to false in your noe specification:
17
+ #
18
+ # template-info:
19
+ # manifest:
20
+ # tasks/debug_mail.rake:
21
+ # safe-override: false
22
+ #
23
+ # The mail template used can be found in debug_mail.txt. That file may be
24
+ # changed to tune the mail you want to send. If you do so, don't forget to
25
+ # add a manifest entry in your .noespec file to avoid overriding you
26
+ # changes. The mail template uses wlang, with parentheses for block
27
+ # delimiters.
28
+ #
29
+ # template-info:
30
+ # manifest:
31
+ # tasks/debug_mail.txt:
32
+ # safe-override: false
33
+ #
34
+ desc "Debug the release announcement mail"
35
+ task :debug_mail do
36
+ begin
37
+ require 'wlang'
38
+ rescue LoadError
39
+ abort "wlang is not available. Try 'gem install wlang'"
40
+ end
41
+ require 'yaml'
42
+
43
+ # Check that a .noespec file exists
44
+ noespec_file = File.expand_path('../../gisele.noespec', __FILE__)
45
+ unless File.exists?(noespec_file)
46
+ raise "Unable to find .noespec project file, sorry."
47
+ end
48
+
49
+ # Load it as well as variables and options
50
+ noespec = YAML::load(File.read(noespec_file))
51
+ vars = noespec['variables'] || {}
52
+
53
+ # Changes are taken from CHANGELOG
54
+ logs = Dir[File.expand_path("../../CHANGELOG.*", __FILE__)]
55
+ unless logs.size == 1
56
+ abort "Unable to find a changelog file"
57
+ end
58
+
59
+ # Load interesting changesets
60
+ changes, end_found = [], 0
61
+ File.readlines(logs.first).select{|line|
62
+ if line =~ /^# /
63
+ break if end_found >= 1
64
+ end_found += 1
65
+ end
66
+ changes << line
67
+ }
68
+ vars['changes'] = changes.join
69
+
70
+ # WLang template
71
+ template = File.expand_path('../debug_mail.txt', __FILE__)
72
+
73
+ # Let's go!
74
+ $stdout << WLang::file_instantiate(template, vars, "wlang/active-text")
75
+ end