openwferu-extras 0.9.12.863

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,191 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2007, John Mettraux, OpenWFE.org
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # . Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # . Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
+ # used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ #++
32
+ #
33
+
34
+ #
35
+ # "made in Japan"
36
+ #
37
+ # John Mettraux at openwfe.org
38
+ #
39
+
40
+ #
41
+ # this participant requires the twitter4r gem
42
+ #
43
+ # http://rubyforge.org/projects/twitter4r/
44
+ #
45
+ # atom-tools' license is X11/MIT
46
+ #
47
+
48
+ require 'rubygems'
49
+
50
+ begin
51
+ #gem 'twitter4r', '0.2.3'
52
+ require 'twitter'
53
+ rescue LoadError
54
+ #
55
+ # soft dependency on 'atom-tools'
56
+ #
57
+ puts
58
+ puts
59
+ puts "'twitter4r' is missing. You can install it with :"
60
+ puts
61
+ puts " [sudo] gem install -y twitter4r"
62
+ puts
63
+ puts
64
+ exit 1
65
+ end
66
+
67
+ require 'openwfe/utils'
68
+ require 'openwfe/participants/participant'
69
+
70
+
71
+ module OpenWFE
72
+ module Extras
73
+
74
+ #
75
+ # Sometimes email is a bit too heavy for notification, this participant
76
+ # emit messages via a twitter account.
77
+ #
78
+ # By default, the message emitted is the value of the field
79
+ # "twitter_message", but this behaviour can be changed by overriding
80
+ # the extract_message() method.
81
+ #
82
+ # If the extract_message doesn't find a message, the message will
83
+ # be the result of the default_message method call, of course this
84
+ # method is overridable as well.
85
+ #
86
+ class TwitterParticipant
87
+ include OpenWFE::LocalParticipant
88
+
89
+ #
90
+ # The actual twitter4r client instance.
91
+ #
92
+ attr_accessor :client
93
+
94
+ #
95
+ # Keeping the initialization params at hand (if any)
96
+ #
97
+ attr_accessor :params
98
+
99
+ #
100
+ # This participant expects a login (twitter user name) and a password.
101
+ #
102
+ # The only optional param for now is :no_ssl, which you can set to
103
+ # true if you want the connection to twitter to not use SSL.
104
+ # (seems like the Twitter SSL service is available less often
105
+ # than the plain http one).
106
+ #
107
+ def initialize (login, password, params={})
108
+
109
+ super()
110
+
111
+ Twitter::Client.configure do |conf|
112
+ conf.protocol = :http
113
+ conf.port = 80
114
+ end if params[:no_ssl] == true
115
+
116
+ @client = Twitter::Client.new(
117
+ :login => login,
118
+ :password => password)
119
+
120
+ @params = params
121
+ end
122
+
123
+ #
124
+ # The method called by the engine when a workitem for this
125
+ # participant is available.
126
+ #
127
+ def consume (workitem)
128
+
129
+ user, tmessage = extract_message workitem
130
+
131
+ tmessage = default_message(workitem) unless tmessage
132
+
133
+ begin
134
+
135
+ if user
136
+ #
137
+ # direct message
138
+ #
139
+ tuser = @client.user user.to_s
140
+ @client.message :post, tmessage, tuser
141
+ else
142
+ #
143
+ # just the classical status
144
+ #
145
+ @client.status :post, tmessage
146
+ end
147
+
148
+ rescue Exception => e
149
+
150
+ linfo do
151
+ "consume() not emitted twitter, because of " +
152
+ OpenWFE::exception_to_s(e)
153
+ end
154
+ end
155
+
156
+ reply_to_engine(workitem) if @application_context
157
+ end
158
+
159
+ protected
160
+
161
+ #
162
+ # Returns a pair : the target user (twitter login name)
163
+ # and the message (or status message if there is no target user) to
164
+ # send to Twitter.
165
+ #
166
+ # This default implementation returns a pair composed with the
167
+ # values of the field 'twitter_target' and of the field
168
+ # 'twitter_message'.
169
+ #
170
+ def extract_message (workitem)
171
+
172
+ [ workitem.attributes['twitter_target'],
173
+ workitem.attributes['twitter_message'] ]
174
+ end
175
+
176
+ #
177
+ # Returns the default message (called when the extract_message
178
+ # returned nil as the second element of its pair).
179
+ #
180
+ # This default implementation simply returns the workitem
181
+ # FlowExpressionId instance in its to_s() representation.
182
+ #
183
+ def default_message (workitem)
184
+
185
+ workitem.fei.to_s
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+
@@ -0,0 +1,448 @@
1
+ #
2
+ #--
3
+ # Copyright (c) 2007, John Mettraux, OpenWFE.org
4
+ # All rights reserved.
5
+ #
6
+ # Redistribution and use in source and binary forms, with or without
7
+ # modification, are permitted provided that the following conditions are met:
8
+ #
9
+ # . Redistributions of source code must retain the above copyright notice, this
10
+ # list of conditions and the following disclaimer.
11
+ #
12
+ # . Redistributions in binary form must reproduce the above copyright notice,
13
+ # this list of conditions and the following disclaimer in the documentation
14
+ # and/or other materials provided with the distribution.
15
+ #
16
+ # . Neither the name of the "OpenWFE" nor the names of its contributors may be
17
+ # used to endorse or promote products derived from this software without
18
+ # specific prior written permission.
19
+ #
20
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
24
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
+ # POSSIBILITY OF SUCH DAMAGE.
31
+ #++
32
+ #
33
+
34
+ #
35
+ # "made in Japan"
36
+ #
37
+ # John Mettraux at openwfe.org
38
+ #
39
+
40
+ require 'csv'
41
+
42
+ require 'openwfe/utils'
43
+ require 'openwfe/util/dollar'
44
+
45
+ include OpenWFE
46
+
47
+
48
+ module OpenWFE
49
+ module Extras
50
+
51
+ #
52
+ # A 'CsvTable' is called a 'decision table' in OpenWFEja (the initial
53
+ # Java implementation of OpenWFE).
54
+ #
55
+ # A csv table is a description of a set of rules as a CSV (comma
56
+ # separated values) file. Such a file can be edited / generated by
57
+ # a spreadsheet (Excel, Google spreadsheets, Gnumeric, ...)
58
+ #
59
+ # The following CSV file
60
+ #
61
+ # in:topic,in:region,out:team_member
62
+ # sports,europe,Alice
63
+ # sports,,Bob
64
+ # finance,america,Charly
65
+ # finance,europe,Donald
66
+ # finance,,Ernest
67
+ # politics,asia,Fujio
68
+ # politics,america,Gilbert
69
+ # politics,,Henry
70
+ # ,,Zach
71
+ #
72
+ # embodies a rule for distributing items (piece of news) labelled with a
73
+ # topic and a region to various members of a team.
74
+ # For example, all news about finance from Europe are to be routed to
75
+ # Donald.
76
+ #
77
+ # Evaluation occurs row by row. The "in out" row states which field
78
+ # is considered at input and which are to be modified if the "ins" do
79
+ # match.
80
+ #
81
+ # The default behaviour is to change the value of the "outs" if all the
82
+ # "ins" match and then terminate.
83
+ # An empty "in" cell means "matches any".
84
+ #
85
+ # Enough words, some code :
86
+ #
87
+ # table = CsvTable.new("""
88
+ # in:topic,in:region,out:team_member
89
+ # sports,europe,Alice
90
+ # sports,,Bob
91
+ # finance,america,Charly
92
+ # finance,europe,Donald
93
+ # finance,,Ernest
94
+ # politics,asia,Fujio
95
+ # politics,america,Gilbert
96
+ # politics,,Henry
97
+ # ,,Zach
98
+ # """)
99
+ #
100
+ # h = {}
101
+ # h["topic"] = "politics"
102
+ #
103
+ # table.transform(h)
104
+ #
105
+ # puts h["team_member"]
106
+ # # will yield "Henry" who takes care of all the politics stuff,
107
+ # # except for Asia and America
108
+ #
109
+ # '>', '>=', '<' and '<=' can be put in front of individual cell values :
110
+ #
111
+ # table = CsvTable.new("""
112
+ # ,
113
+ # in:fx, out:fy
114
+ # ,
115
+ # >100,a
116
+ # >=10,b
117
+ # ,c
118
+ # """)
119
+ #
120
+ # h = { 'fx' => '10' }
121
+ # table.transform(h)
122
+ #
123
+ # require 'pp'; pp h
124
+ # # will yield { 'fx' => '10', 'fy' => 'b' }
125
+ #
126
+ # Such comparisons are done after the elements are transformed to float
127
+ # numbers. By default, non-numeric arguments will get compared as Strings.
128
+ #
129
+ #
130
+ # Disclaimer : the decision / CSV table system is no replacement for
131
+ # full rule engines with forward and backward chaining, RETE implementation
132
+ # and the like...
133
+ #
134
+ #
135
+ # Options :
136
+ #
137
+ # You can put options on their own in a cell BEFORE the line containing
138
+ # "in:xxx" and "out:yyy" (ins and outs).
139
+ #
140
+ # Currently, two options are supported, "ignorecase" and "through".
141
+ #
142
+ # "ignorecase", if found by the CsvTable will make any match (in the "in"
143
+ # columns) case unsensitive.
144
+ #
145
+ # "through", will make sure that EVERY row is evaluated and potentially
146
+ # applied. The default behaviour (without "through"), is to stop the
147
+ # evaluation after applying the results of the first matching row.
148
+ #
149
+ #
150
+ # CSV Tables are available to workflows as CsvParticipant.
151
+ #
152
+ #
153
+ # See also :
154
+ #
155
+ # http://jmettraux.wordpress.com/2007/02/11/ruby-decision-tables/
156
+ # http://rubyforge.org/viewvc/trunk/openwfe-ruby/test/extras/csv_test.rb?root=openwferu&view=co
157
+ #
158
+ class CsvTable
159
+
160
+ attr_accessor \
161
+ :first_match,
162
+ :ignore_case,
163
+ :header,
164
+ :rows
165
+
166
+ #
167
+ # The constructor for CsvTable, you can pass a String, an Array
168
+ # (of arrays), a File object. The CSV parser coming with Ruby will take
169
+ # care of it and a CsvTable instance will be built.
170
+ #
171
+ def initialize (csv_data)
172
+
173
+ @first_match = true
174
+ @ignore_case = false
175
+
176
+ @header = nil
177
+ @rows = []
178
+
179
+ csv_array = to_csv_array(csv_data)
180
+
181
+ csv_array.each do |row|
182
+
183
+ next if empty_row? row
184
+
185
+ if @header
186
+ #@rows << row
187
+ @rows << row.collect { |c| c.strip if c }
188
+ else
189
+ parse_header_row(row)
190
+ end
191
+ end
192
+ end
193
+
194
+ #
195
+ # Returns the workitem massaged by the csv table
196
+ #
197
+ def transform_wi (flow_expression, workitem)
198
+
199
+ @rows.each do |row|
200
+
201
+ if matches?(row, flow_expression, workitem)
202
+ apply(row, flow_expression, workitem)
203
+ break if @first_match
204
+ end
205
+ end
206
+
207
+ return workitem
208
+ end
209
+
210
+ #
211
+ # Passes a simple Hash instance though the csv table
212
+ #
213
+ def transform (hash)
214
+ wi = InFlowWorkItem.new()
215
+ wi.attributes = hash
216
+ return transform_wi(nil, wi).attributes
217
+ end
218
+
219
+ protected
220
+
221
+ def to_csv_array (csv_data)
222
+
223
+ return csv_data if csv_data.kind_of? Array
224
+ return CSV::Reader.parse(csv_data)
225
+ end
226
+
227
+ def matches? (row, fexp, wi)
228
+
229
+ return false if empty_row? row
230
+
231
+ #puts
232
+ #puts "__row match ?"
233
+ #require 'pp'
234
+ #pp row
235
+
236
+ @header.ins.each_with_index do |in_header, icol|
237
+
238
+ in_header = resolve_in_header(in_header)
239
+
240
+ value = OpenWFE::dosub(in_header, fexp, wi)
241
+
242
+ cell = row[icol]
243
+
244
+ next if not cell
245
+
246
+ cell = cell.strip
247
+
248
+ next if cell.length < 1
249
+
250
+ cell = OpenWFE::dosub(cell, fexp, wi)
251
+
252
+ #puts "__does '#{value}' match '#{cell}' ?"
253
+
254
+ b = if cell[0, 1] == '<' or cell[0, 1] == '>'
255
+ numeric_compare(value, cell)
256
+ else
257
+ regex_compare(value, cell)
258
+ end
259
+
260
+ return false unless b
261
+ end
262
+
263
+ #puts "__row matches"
264
+
265
+ return true
266
+ end
267
+
268
+ def regex_compare (value, cell)
269
+
270
+ modifiers = 0
271
+ modifiers += Regexp::IGNORECASE if @ignore_case
272
+
273
+ rcell = Regexp.new(cell, modifiers)
274
+
275
+ rcell.match(value)
276
+ end
277
+
278
+ def numeric_compare (value, cell)
279
+
280
+ comparator = cell[0, 1]
281
+ comparator += "=" if cell[1, 1] == "="
282
+ cell = cell[comparator.length..-1]
283
+
284
+ nvalue = narrow(value)
285
+ ncell = narrow(cell)
286
+
287
+ if nvalue.is_a? String or ncell.is_a? String
288
+ value = '"' + value + '"'
289
+ cell = '"' + cell + '"'
290
+ else
291
+ value = nvalue
292
+ cell = ncell
293
+ end
294
+
295
+ s = "#{value} #{comparator} #{cell}"
296
+
297
+ #puts "...>>>#{s}<<<"
298
+
299
+ begin
300
+ return OpenWFE::eval_safely(s, 4)
301
+ rescue Exception => e
302
+ return false
303
+ end
304
+ end
305
+
306
+ def narrow (s)
307
+ begin
308
+ return Float(s)
309
+ rescue Exception => e
310
+ return s
311
+ end
312
+ end
313
+
314
+ def resolve_in_header (in_header)
315
+
316
+ in_header = "f:#{in_header}" \
317
+ if points_to_nothing? in_header
318
+
319
+ return "${#{in_header}}"
320
+ end
321
+
322
+ def apply (row, fexp, wi)
323
+
324
+ #puts "__ apply() wi.class : #{wi.class.name}"
325
+
326
+ @header.outs.each_with_index do |out_header, icol|
327
+
328
+ next unless out_header
329
+
330
+ value = row[icol]
331
+
332
+ next unless value
333
+ #next unless value.strip.length > 0
334
+ next unless value.length > 0
335
+
336
+ value = OpenWFE::dosub(value, fexp, wi)
337
+
338
+ #puts "___ value:'#{value}'"
339
+ #puts "___ value:'"+value+"'"
340
+
341
+ type, target = points_at(out_header)
342
+
343
+ #puts "___ t:'#{type}' target:'#{target}'"
344
+
345
+ if type == "v"
346
+ fexp.set_variable(target, value) if fexp
347
+ elsif type == "f"
348
+ wi.set_attribute(target, value)
349
+ elsif type == "r"
350
+ #instance_eval(value)
351
+ OpenWFE::instance_eval_safely(self, value, 3)
352
+ end
353
+ end
354
+ end
355
+
356
+ def parse_header_row (row)
357
+
358
+ row.each_with_index do |cell, icol|
359
+
360
+ next unless cell
361
+
362
+ cell = cell.strip
363
+ s = cell.downcase
364
+
365
+ if s == "ignorecase" or s == "ignore_case"
366
+ @ignore_case = true
367
+ next
368
+ end
369
+
370
+ if s == "through"
371
+ @first_match = false
372
+ next
373
+ end
374
+
375
+ if OpenWFE::starts_with(cell, "in:") or OpenWFE::starts_with(cell, "out:")
376
+ @header = Header.new unless @header
377
+ @header.add cell, icol
378
+ end
379
+ end
380
+ end
381
+
382
+ def empty_row? (row)
383
+ return true unless row
384
+ return true if (row.length == 1 and not row[0])
385
+ row.each do |cell|
386
+ return false if cell
387
+ end
388
+ return true
389
+ end
390
+
391
+ def points_to_nothing? (label)
392
+ (not points_to_variable? label) and \
393
+ (not points_to_field? label) and \
394
+ (not points_to_ruby? label)
395
+ end
396
+ def points_at (label)
397
+ v = points_to_variable? label
398
+ return "v", v if v
399
+ r = points_to_ruby? label
400
+ return "r", r if r
401
+ f = points_to_field? label
402
+ return "f", f if f
403
+ return "f", label # else
404
+ end
405
+ def points_to_variable? (label)
406
+ points_to?(label, [ "v", "var", "variable" ])
407
+ end
408
+ def points_to_field? (label)
409
+ points_to?(label, [ "f", "field" ])
410
+ end
411
+ def points_to_ruby? (label)
412
+ points_to?(label, [ "r", "ruby", "reval" ])
413
+ end
414
+ def points_to? (label, names)
415
+ i = label.index(":")
416
+ return nil unless i
417
+ s = label[0..i-1]
418
+ names.each do |name|
419
+ return label[i+1..-1] if s == name
420
+ end
421
+ return nil
422
+ end
423
+
424
+ class Header
425
+
426
+ attr_accessor :ins, :outs
427
+
428
+ def initialize
429
+ @ins = []
430
+ @outs = []
431
+ end
432
+
433
+ def add (cell, icol)
434
+ if OpenWFE::starts_with(cell, "in:")
435
+ @ins[icol] = cell[3..-1]
436
+ #puts "i added #{@ins[icol]}"
437
+ elsif OpenWFE::starts_with(cell, "out:")
438
+ @outs[icol] = cell[4..-1]
439
+ #puts "o added #{@outs[icol]}"
440
+ end
441
+ # else don't add
442
+ end
443
+ end
444
+ end
445
+
446
+ end
447
+ end
448
+