ruote 2.3.0.1 → 2.3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/CHANGELOG.txt +23 -0
  2. data/CREDITS.txt +4 -0
  3. data/LICENSE.txt +1 -1
  4. data/lib/ruote.rb +2 -0
  5. data/lib/ruote/context.rb +2 -1
  6. data/lib/ruote/dashboard.rb +169 -13
  7. data/lib/ruote/dboard/mutation.rb +282 -0
  8. data/lib/ruote/dboard/process_error.rb +1 -1
  9. data/lib/ruote/dboard/process_status.rb +61 -48
  10. data/lib/ruote/engine.rb +1 -1
  11. data/lib/ruote/exp/command.rb +1 -1
  12. data/lib/ruote/exp/commanded.rb +1 -1
  13. data/lib/ruote/exp/condition.rb +2 -1
  14. data/lib/ruote/exp/fe_add_branches.rb +1 -1
  15. data/lib/ruote/exp/fe_apply.rb +1 -1
  16. data/lib/ruote/exp/fe_await.rb +97 -48
  17. data/lib/ruote/exp/fe_cancel_process.rb +1 -1
  18. data/lib/ruote/exp/fe_command.rb +2 -3
  19. data/lib/ruote/exp/fe_concurrence.rb +162 -66
  20. data/lib/ruote/exp/fe_concurrent_iterator.rb +25 -7
  21. data/lib/ruote/exp/fe_cron.rb +1 -1
  22. data/lib/ruote/exp/fe_cursor.rb +10 -11
  23. data/lib/ruote/exp/fe_define.rb +1 -1
  24. data/lib/ruote/exp/fe_echo.rb +1 -1
  25. data/lib/ruote/exp/fe_equals.rb +1 -1
  26. data/lib/ruote/exp/fe_error.rb +1 -1
  27. data/lib/ruote/exp/fe_filter.rb +1 -1
  28. data/lib/ruote/exp/fe_forget.rb +1 -1
  29. data/lib/ruote/exp/fe_given.rb +1 -1
  30. data/lib/ruote/exp/fe_if.rb +87 -7
  31. data/lib/ruote/exp/fe_inc.rb +1 -1
  32. data/lib/ruote/exp/fe_iterator.rb +1 -1
  33. data/lib/ruote/exp/fe_listen.rb +1 -1
  34. data/lib/ruote/exp/fe_lose.rb +1 -1
  35. data/lib/ruote/exp/fe_noop.rb +1 -1
  36. data/lib/ruote/exp/fe_on_error.rb +1 -1
  37. data/lib/ruote/exp/fe_once.rb +1 -1
  38. data/lib/ruote/exp/fe_participant.rb +49 -16
  39. data/lib/ruote/exp/fe_read.rb +1 -1
  40. data/lib/ruote/exp/fe_redo.rb +1 -1
  41. data/lib/ruote/exp/fe_ref.rb +1 -1
  42. data/lib/ruote/exp/fe_registerp.rb +1 -1
  43. data/lib/ruote/exp/fe_reserve.rb +1 -1
  44. data/lib/ruote/exp/fe_restore.rb +1 -7
  45. data/lib/ruote/exp/fe_save.rb +1 -1
  46. data/lib/ruote/exp/fe_sequence.rb +1 -1
  47. data/lib/ruote/exp/fe_set.rb +1 -1
  48. data/lib/ruote/exp/fe_stall.rb +1 -1
  49. data/lib/ruote/exp/fe_subprocess.rb +1 -1
  50. data/lib/ruote/exp/fe_that.rb +1 -1
  51. data/lib/ruote/exp/fe_undo.rb +1 -1
  52. data/lib/ruote/exp/fe_unregisterp.rb +1 -1
  53. data/lib/ruote/exp/fe_wait.rb +1 -1
  54. data/lib/ruote/exp/flow_expression.rb +117 -8
  55. data/lib/ruote/exp/iterator.rb +1 -1
  56. data/lib/ruote/exp/ro_attributes.rb +1 -1
  57. data/lib/ruote/exp/ro_filters.rb +1 -1
  58. data/lib/ruote/exp/ro_on_x.rb +4 -2
  59. data/lib/ruote/exp/ro_persist.rb +1 -1
  60. data/lib/ruote/exp/ro_timers.rb +1 -1
  61. data/lib/ruote/exp/ro_variables.rb +1 -1
  62. data/lib/ruote/extract.rb +125 -0
  63. data/lib/ruote/fei.rb +10 -73
  64. data/lib/ruote/id/mnemo_wfid_generator.rb +1 -1
  65. data/lib/ruote/id/wfid_generator.rb +1 -1
  66. data/lib/ruote/log/default_history.rb +17 -3
  67. data/lib/ruote/log/fancy_printing.rb +12 -32
  68. data/lib/ruote/log/storage_history.rb +1 -1
  69. data/lib/ruote/log/wait_logger.rb +15 -7
  70. data/lib/ruote/merge.rb +123 -0
  71. data/lib/ruote/observer.rb +1 -1
  72. data/lib/ruote/part/block_participant.rb +1 -1
  73. data/lib/ruote/part/code_participant.rb +1 -1
  74. data/lib/ruote/part/engine_participant.rb +1 -1
  75. data/lib/ruote/part/local_participant.rb +9 -1
  76. data/lib/ruote/part/no_op_participant.rb +1 -1
  77. data/lib/ruote/part/null_participant.rb +1 -1
  78. data/lib/ruote/part/participant.rb +1 -1
  79. data/lib/ruote/part/rev_participant.rb +1 -1
  80. data/lib/ruote/part/smtp_participant.rb +1 -1
  81. data/lib/ruote/part/storage_participant.rb +18 -1
  82. data/lib/ruote/part/template.rb +1 -1
  83. data/lib/ruote/reader.rb +1 -1
  84. data/lib/ruote/reader/json.rb +1 -1
  85. data/lib/ruote/reader/radial.rb +4 -4
  86. data/lib/ruote/reader/ruby_dsl.rb +1 -1
  87. data/lib/ruote/reader/xml.rb +1 -1
  88. data/lib/ruote/receiver/base.rb +13 -1
  89. data/lib/ruote/storage/base.rb +8 -14
  90. data/lib/ruote/storage/composite_storage.rb +1 -1
  91. data/lib/ruote/storage/fs_storage.rb +1 -1
  92. data/lib/ruote/storage/hash_storage.rb +2 -1
  93. data/lib/ruote/svc/dispatch_pool.rb +29 -18
  94. data/lib/ruote/svc/dollar_sub.rb +5 -8
  95. data/lib/ruote/svc/error_handler.rb +1 -1
  96. data/lib/ruote/svc/expression_map.rb +1 -1
  97. data/lib/ruote/svc/participant_list.rb +8 -5
  98. data/lib/ruote/svc/tracker.rb +154 -56
  99. data/lib/ruote/svc/treechecker.rb +1 -1
  100. data/lib/ruote/tree_dot.rb +1 -1
  101. data/lib/ruote/util/deep.rb +4 -2
  102. data/lib/ruote/util/filter.rb +1 -1
  103. data/lib/ruote/util/hashdot.rb +1 -1
  104. data/lib/ruote/util/look.rb +1 -1
  105. data/lib/ruote/util/lookup.rb +1 -1
  106. data/lib/ruote/util/misc.rb +51 -1
  107. data/lib/ruote/util/mpatch.rb +1 -1
  108. data/lib/ruote/util/ometa.rb +1 -1
  109. data/lib/ruote/util/subprocess.rb +1 -1
  110. data/lib/ruote/util/time.rb +3 -3
  111. data/lib/ruote/util/tree.rb +43 -4
  112. data/lib/ruote/version.rb +2 -2
  113. data/lib/ruote/worker.rb +30 -18
  114. data/lib/ruote/workitem.rb +1 -1
  115. data/ruote.gemspec +6 -2
  116. data/test/functional/base.rb +0 -1
  117. data/test/functional/concurrent_base.rb +1 -1
  118. data/test/functional/eft_14_cursor.rb +42 -52
  119. data/test/functional/eft_16_if.rb +24 -16
  120. data/test/functional/eft_18_concurrent_iterator.rb +31 -1
  121. data/test/functional/eft_6_concurrence.rb +149 -34
  122. data/test/functional/ft_10_dollar.rb +14 -30
  123. data/test/functional/ft_12_launchitem.rb +15 -0
  124. data/test/functional/ft_1_process_status.rb +62 -13
  125. data/test/functional/ft_20_storage_participant.rb +25 -0
  126. data/test/functional/ft_38_participant_more.rb +1 -1
  127. data/test/functional/ft_42_storage_copy.rb +1 -3
  128. data/test/functional/ft_43_participant_on_reply.rb +63 -5
  129. data/test/functional/ft_66_flank.rb +41 -0
  130. data/test/functional/ft_6_on_cancel.rb +9 -18
  131. data/test/functional/ft_71_retries.rb +25 -12
  132. data/test/functional/ft_79_attach.rb +138 -0
  133. data/test/functional/ft_7_tags.rb +27 -0
  134. data/test/functional/ft_80_pause_on_apply.rb +64 -0
  135. data/test/functional/ft_81_mutation.rb +417 -0
  136. data/test/functional/ft_82_await_attribute.rb +84 -0
  137. data/test/functional/ft_83_trackers.rb +79 -0
  138. data/test/functional/storage.rb +3 -4
  139. data/test/unit/ut_12_wait_logger.rb +41 -3
  140. data/test/unit/ut_15_util.rb +30 -0
  141. data/test/unit/ut_17_merge.rb +54 -53
  142. data/test/unit/ut_1_fei.rb +2 -2
  143. data/test/unit/ut_24_radial_reader.rb +7 -0
  144. data/test/unit/ut_26_deep.rb +14 -0
  145. data/test/unit/ut_5_tree.rb +38 -28
  146. metadata +206 -169
  147. data/couch_url.txt +0 -1
  148. data/lib/ruote/exp/merge.rb +0 -134
data/CHANGELOG.txt CHANGED
@@ -2,6 +2,29 @@
2
2
  = ruote - CHANGELOG.txt
3
3
 
4
4
 
5
+ == ruote - 2.3.1 not yet released
6
+
7
+
8
+ == ruote - 2.3.0.2 not yet released
9
+
10
+ - pin ruote.gemspec to ruby_parser ~>2.3 and parslet 1.4.0
11
+ - add Ruote.[de]camelize
12
+ - expose 'history_max_size' config option
13
+ - Dashboard#get_trackers
14
+ - Dashboard#remove_tracker(fei_sid_or_id, wfid)
15
+ - :await attribute
16
+ - let Ruote.deep_mutate mutate matching keys and their collection values
17
+ - stop tripping on empty tags ("")
18
+ - jump to 'x' where x in "participant 'x'" unlocked (thanks Tobias Neubert)
19
+ - unlock on_error more (on_error: "retry * 3, pass" for example)
20
+ - revise "$x" (literal dollars), now return nil instead of "$x" when missing
21
+ - _auto_remove (and _alter) for trackers (pause on apply++)
22
+ - pause on apply concept brought in
23
+ - Dashboard#add_tracker
24
+ - Dashboard#attach(fei, pdef)
25
+ - participant#on_error
26
+
27
+
5
28
  == ruote - 2.3.0.1 released 2012/09/08
6
29
 
7
30
  - pin ruote.gemspec to blankslate 2.1.2.4
data/CREDITS.txt CHANGED
@@ -13,6 +13,8 @@ Kenneth Kalmer - http://www.opensourcery.co.za
13
13
 
14
14
  == Contributors
15
15
 
16
+ Mario Camou - lots of help
17
+ Peter Brindisi - https://gitbhub.com/npj
16
18
  Gvozden Neskovic - https://github.com/ladenBrain
17
19
  Hartog C. de Mik - https://github.com/coffeeaddict
18
20
  Doug Bryant - re_dispatch bug and 'dispatched' << 'workitem'
@@ -54,6 +56,8 @@ Richard Jennings
54
56
 
55
57
  == Feedback
56
58
 
59
+ Larry Marburger - await attribute clarifications
60
+ Tobias Neubert - jump to participant 'x'
57
61
  tsdeng - https://github.com/tsdeng
58
62
  Klaus Schmidtmamn - sub_wf_name inspiration
59
63
  chaofan - https://github.com/chaofoan
data/LICENSE.txt CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- Copyright (c) 2001-2012, John Mettraux, jmettraux@gmail.com
2
+ Copyright (c) 2001-2013, John Mettraux, jmettraux@gmail.com
3
3
 
4
4
  Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  of this software and associated documentation files (the "Software"), to deal
data/lib/ruote.rb CHANGED
@@ -1,4 +1,6 @@
1
1
 
2
+ require 'ruby_parser'
3
+
2
4
  require 'ruote/util/deep'
3
5
  require 'ruote/util/lookup'
4
6
  require 'ruote/util/mpatch'
data/lib/ruote/context.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -240,6 +240,7 @@ module Ruote
240
240
 
241
241
  default_conf.merge(conf).each do |key, value|
242
242
 
243
+ key = key.to_s
243
244
  add_service(key, *value) if SERVICE_PREFIX.match(key)
244
245
  end
245
246
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2012, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2013, John Mettraux, jmettraux@gmail.com
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining a copy
5
5
  # of this software and associated documentation files (the "Software"), to deal
@@ -25,6 +25,7 @@
25
25
  require 'ruote/context'
26
26
  require 'ruote/util/ometa'
27
27
  require 'ruote/receiver/base'
28
+ require 'ruote/dboard/mutation'
28
29
  require 'ruote/dboard/process_status'
29
30
 
30
31
 
@@ -178,6 +179,48 @@ module Ruote
178
179
  wfid
179
180
  end
180
181
 
182
+ # Given a flow expression id, locates the corresponding ruote
183
+ # expression and attaches a subprocess to it.
184
+ #
185
+ # Accepts the fei as a Hash or as a FlowExpressionId instance.
186
+ #
187
+ # By default, the workitem of the expression you attach to provides
188
+ # the initial workitem for the attached branch. By using the
189
+ # :fields/:workitem or :merge_fields options, one can change that.
190
+ #
191
+ # Returns the fei of the attached [root] expression
192
+ # (as a FlowExpressionId instance).
193
+ #
194
+ def attach(fei_or_fe, definition, opts={})
195
+
196
+ fe = Ruote.extract_fexp(@context, fei_or_fe).h
197
+ fei = fe['fei']
198
+
199
+ cfei = fei.merge(
200
+ 'expid' => "#{fei['expid']}_0",
201
+ 'subid' => Ruote.generate_subid(fei.inspect))
202
+
203
+ tree = @context.reader.read(definition)
204
+ tree[0] = 'sequence'
205
+
206
+ fields = fe['applied_workitem']['fields']
207
+ if fs = opts[:fields] || opts[:workitem]
208
+ fields = fs
209
+ elsif fs = opts[:merge_fields]
210
+ fields.merge!(fs)
211
+ end
212
+
213
+ @context.storage.put_msg(
214
+ 'launch', # "apply" is OK, but "launch" stands out better
215
+ 'parent_id' => fei,
216
+ 'fei' => cfei,
217
+ 'tree' => tree,
218
+ 'workitem' => { 'fields' => fields },
219
+ 'attached' => true)
220
+
221
+ Ruote::FlowExpressionId.new(cfei)
222
+ end
223
+
181
224
  # Given a workitem or a fei, will do a cancel_expression,
182
225
  # else it's a wfid and it does a cancel_process.
183
226
  #
@@ -325,6 +368,11 @@ module Ruote
325
368
  # dashboard.re_apply(
326
369
  # fei, :fields => { 'customer' => 'bob' })
327
370
  #
371
+ # :workitem is ok too
372
+ #
373
+ # dashboard.re_apply(
374
+ # fei, :workitem => { 'fields' => { 'customer' => 'bob' } })
375
+ #
328
376
  # :merge_in_fields is used to add / override fields
329
377
  #
330
378
  # dashboard.re_apply(
@@ -338,6 +386,28 @@ module Ruote
338
386
  're_apply' => Ruote.keys_to_s(opts))
339
387
  end
340
388
 
389
+ # Returns a Mutation instance listing all the operations necessary
390
+ # to transform the current tree of the process (wfid) into
391
+ # the given definition tree (pdef).
392
+ #
393
+ # See also #apply_mutation
394
+ #
395
+ def compute_mutation(wfid, pdef)
396
+
397
+ Mutation.new(self, wfid, @context.reader.read(pdef))
398
+ end
399
+
400
+ # Computes mutation and immediately applies it...
401
+ #
402
+ # See #compute_mutation
403
+ #
404
+ # Return the mutation instance (forensic?)
405
+ #
406
+ def apply_mutation(wfid, pdef)
407
+
408
+ Mutation.new(self, wfid, @context.reader.read(pdef)).apply
409
+ end
410
+
341
411
  # This method re_apply all the leaves of a process instance. It's meant
342
412
  # to be used against stalled workflows to give them back the spark of
343
413
  # life.
@@ -676,11 +746,11 @@ module Ruote
676
746
  # def initialize(opts)
677
747
  # @name = opts['name']
678
748
  # end
679
- # def consume(workitem)
749
+ # def on_workitem
680
750
  # workitem.fields['rocket_name'] = @name
681
751
  # send_to_the_moon(workitem)
682
752
  # end
683
- # def cancel(fei, flavour)
753
+ # def on_cancel
684
754
  # # do nothing
685
755
  # end
686
756
  # end
@@ -693,11 +763,14 @@ module Ruote
693
763
  # class TotalParticipant
694
764
  # include Ruote::LocalParticipant
695
765
  #
696
- # def consume(workitem)
766
+ # def on_workitem
697
767
  # workitem['total'] = workitem.fields['items'].inject(0.0) { |t, item|
698
768
  # t + item['count'] * PricingService.lookup(item['id'])
699
769
  # }
700
- # reply_to_engine(workitem)
770
+ # reply
771
+ # end
772
+ #
773
+ # def on_cancel
701
774
  # end
702
775
  # end
703
776
  # dashboard.register_participant 'total', TotalParticipant
@@ -1077,15 +1150,72 @@ module Ruote
1077
1150
  #
1078
1151
  def on_terminate=(target)
1079
1152
 
1153
+ msg = {
1154
+ 'action' => 'launch',
1155
+ 'tree' => target.is_a?(String) ?
1156
+ [ 'define', {}, [ [ target, {}, [] ] ] ] : target,
1157
+ 'workitem' => 'replace' }
1158
+
1080
1159
  @context.tracker.add_tracker(
1081
- nil, # do not track a specific wfid
1082
- 'terminated', # react on 'error_intercepted' msgs
1083
- 'on_terminate', # the identifier
1084
- nil, # no specific condition
1085
- { 'action' => 'launch',
1086
- 'tree' => target.is_a?(String) ?
1087
- [ 'define', {}, [ [ target, {}, [] ] ] ] : target,
1088
- 'workitem' => 'replace' })
1160
+ nil, # do not track a specific wfid
1161
+ 'terminated', # react on 'error_intercepted' msgs
1162
+ 'on_terminate', # the identifier
1163
+ nil, # no specific condition
1164
+ msg) # the message that gets triggered
1165
+ end
1166
+
1167
+ # /!\ warning: advanced method.
1168
+ #
1169
+ # Adds a tracker to the ruote engine.
1170
+ #
1171
+ # === Arguments
1172
+ #
1173
+ # * wfid:
1174
+ # When nil will track any workflow execution, when set will only
1175
+ # react on msgs for the given wfid.
1176
+ # * action:
1177
+ # A string like "apply", "reply" or "receive", the action being tracked
1178
+ # May begin with a "pre_" prefix.
1179
+ # * tracker_id:
1180
+ # When nil, ruote chooses a tracker_id, else its the unique identifier
1181
+ # for the new tracker.
1182
+ # * conditions:
1183
+ # A Hash of keys pointing to arrays of expected values.
1184
+ # For example { 'tree.0' ~=> [ 'alfred', 'knuth' ] } will trigger
1185
+ # if the first element of msg['tree'] equals alfred or knuth.
1186
+ # * msg:
1187
+ # The msg to place in the msg queue if the tracker matches the msg,
1188
+ # the reaction.
1189
+ #
1190
+ # Returns the tracker_id.
1191
+ #
1192
+ def add_tracker(wfid, action, tracker_id, conditions, msg)
1193
+
1194
+ @context.tracker.add_tracker(wfid, action, tracker_id, conditions, msg)
1195
+ end
1196
+
1197
+ # /!\ warning: advanced method.
1198
+ #
1199
+ # Removes a tracker from the ruote system.
1200
+ #
1201
+ # The first arg is a FlowExpressionId, in its instance form, hash form or
1202
+ # shortened (sid) string form. It can also be any string (any tracker id).
1203
+ #
1204
+ # The second arg is optional, it's a wfid. It's useful for some storage
1205
+ # implementations (like ruote-swf) and helps determine how to grab
1206
+ # the tracker list. Most of the ruote deployments don't need that arg set.
1207
+ #
1208
+ def remove_tracker(fei_sid_or_id, wfid=nil)
1209
+
1210
+ @context.tracker.remove_tracker(fei_sid_or_id, wfid)
1211
+ end
1212
+
1213
+ # Returns a hash { tracker_id => tracker_hash } enumerating all
1214
+ # the trackers in the ruote system.
1215
+ #
1216
+ def get_trackers(wfid=nil)
1217
+
1218
+ @context.storage.get_trackers(wfid)['trackers']
1089
1219
  end
1090
1220
 
1091
1221
  # A debug helper :
@@ -1100,6 +1230,32 @@ module Ruote
1100
1230
  @context.logger.noisy = b
1101
1231
  end
1102
1232
 
1233
+ # Warning: advanced method.
1234
+ #
1235
+ # Currently only used by mutations.
1236
+ #
1237
+ def update_expression(fei, opts)
1238
+
1239
+ fei = Ruote.extract_fei(fei)
1240
+ fexp = Ruote::Exp::FlowExpression::fetch(@context, fei)
1241
+
1242
+ raise ArgumentError.new(
1243
+ "no expression found with fei #{fei.sid}"
1244
+ ) unless fexp
1245
+
1246
+ if t = opts[:tree]
1247
+ fexp.h.updated_tree = opts[:tree]
1248
+ end
1249
+
1250
+ r = @context.storage.put(fexp.h)
1251
+
1252
+ raise ArgumentError.new(
1253
+ "expression #{fei.sid} is gone"
1254
+ ) if r == true
1255
+
1256
+ return update_expression(fei, opts) unless r.nil?
1257
+ end
1258
+
1103
1259
  protected
1104
1260
 
1105
1261
  # Used by #pause and #resume.
@@ -0,0 +1,282 @@
1
+ #--
2
+ # Copyright (c) 2005-2013, John Mettraux, jmettraux@gmail.com
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #
22
+ # Made in Japan.
23
+ #++
24
+
25
+
26
+ module Ruote
27
+
28
+ #
29
+ # Gathers info about a possible mutation. The point of application (fei),
30
+ # the new tree (tree) and if it's a re_apply or an update (only changing
31
+ # the tree of the expression behind (fei)).
32
+ #
33
+ class MutationPoint
34
+
35
+ attr_reader :fei
36
+ attr_reader :tree
37
+ attr_reader :type # :re_apply or :update
38
+
39
+ def initialize(fei, tree, type)
40
+
41
+ @fei = fei
42
+ @tree = tree
43
+ @type = type
44
+ end
45
+
46
+ def to_s
47
+
48
+ s = []
49
+ s << self.class.name
50
+ s << " at: #{@fei.sid} (#{@fei.expid})"
51
+ s << " action: #{@type.inspect}"
52
+ s << " tree:"
53
+
54
+ s.concat(
55
+ Ruote::Reader.to_radial(@tree).split("\n").map { |l| " | #{l}" })
56
+
57
+ s.join("\n")
58
+ end
59
+
60
+ def apply(dboard, option=nil)
61
+
62
+ option ||= @type
63
+ option = option.to_sym
64
+
65
+ return if option != :force_update && option != @type
66
+
67
+ type = option == :force_update ? :update : @type
68
+
69
+ if type == :re_apply
70
+ dboard.re_apply(@fei, :tree => @tree)
71
+ else
72
+ dboard.update_expression(@fei, :tree => @tree)
73
+ end
74
+ end
75
+ end
76
+
77
+ #
78
+ # A set of mutation points.
79
+ #
80
+ # Initialized by Ruote::Dashboard#compute_mutation
81
+ #
82
+ class Mutation
83
+
84
+ # ProcessStatus instance (advanced stuff).
85
+ #
86
+ attr_reader :ps
87
+
88
+ attr_reader :points
89
+
90
+ def initialize(dboard, wfid, tree)
91
+
92
+ @dboard = dboard
93
+ @points = []
94
+ @ps = @dboard.ps(wfid)
95
+
96
+ walk(@ps.root_expression, Ruote.compact_tree(tree))
97
+
98
+ @points = @points.sort_by { |point| point.fei.expid }
99
+ end
100
+
101
+ def to_a
102
+
103
+ @points.collect { |pt|
104
+ { 'fei' => pt.fei, 'action' => pt.type, 'tree' => pt.tree }
105
+ }
106
+ end
107
+
108
+ def to_s
109
+
110
+ @points.collect(&:to_s).join("\n")
111
+ end
112
+
113
+ # Applies the mutation, :update points first then :re_apply points.
114
+ #
115
+ # Accepts an option, nil means apply all, :update means apply only
116
+ # update mutations points, :re_apply means apply on re_apply points,
117
+ # :force_update means apply all but turn re_apply points into update
118
+ # points.
119
+ #
120
+ def apply(option=nil)
121
+
122
+ updates, re_applies = @points.partition { |pt| pt.type == :update }
123
+ points = updates + re_applies
124
+
125
+ points.each { |pt| pt.apply(@dboard, option) }
126
+
127
+ self
128
+ end
129
+
130
+ protected
131
+
132
+ def register(point)
133
+
134
+ pt = @points.find { |p| p.fei == point.fei }
135
+
136
+ if pt && point.type == :re_apply
137
+ @points.delete(pt)
138
+ @points << point
139
+ else
140
+ @points << point
141
+ end
142
+ end
143
+
144
+ # Look for mutation points in an expression and its children.
145
+ #
146
+ def walk(fexp, tree)
147
+
148
+ ftree = Ruote.compact_tree(@ps.current_tree(fexp))
149
+
150
+ if ftree[0] != tree[0] || ftree[1] != tree[1]
151
+ #
152
+ # if there is anything different between the current tree and the
153
+ # desired tree, let's force a re-apply
154
+
155
+ register(MutationPoint.new(fexp.fei, tree, :re_apply))
156
+
157
+ elsif ftree[2] == tree[2]
158
+ #
159
+ # else, if the tree children are the same, exit, there is nothing to do
160
+
161
+ return
162
+
163
+ else
164
+
165
+ register(MutationPoint.new(fexp.fei, tree, :update))
166
+ #
167
+ # NOTE: maybe a switch for this mutation not to be added would
168
+ # be necessary...
169
+
170
+ if fexp.is_concurrent?
171
+ #
172
+ # concurrent expressions follow a different heuristic
173
+
174
+ walk_concurrence(fexp, ftree, tree)
175
+
176
+ else
177
+ #
178
+ # all other expressions are considered sequence-like
179
+
180
+ walk_sequence(fexp, ftree, tree)
181
+ end
182
+ end
183
+ end
184
+
185
+ # Look for mutation points in a concurrent expression (concurrence or
186
+ # concurrent-iterator).
187
+ #
188
+ def walk_concurrence(fexp, ftree, tree)
189
+
190
+ if ftree[2].size != tree[2].size
191
+ #
192
+ # that's lazy, but why not?
193
+ #
194
+ # we could add/apply a new child...
195
+
196
+ register(MutationPoint.new(fexp.fei, tree, :re_apply))
197
+
198
+ else
199
+ #
200
+ # if there is a least one child that replied and whose
201
+ # tree must be changes, then re-apply the whole concurrence
202
+ #
203
+ # else try to re-apply only the necessary branch (walk them)
204
+
205
+ branches = ftree[2].zip(tree[2]).each_with_object([]) { |(ft, t), a|
206
+ #
207
+ # match child expressions (if not yet replied) with current tree (ft)
208
+ # and desired tree (t)
209
+ #
210
+ cfei = fexp.children[a.size]
211
+ cexp = cfei ? @ps.fexp(cfei) : nil
212
+ a << [ cexp, ft, t ]
213
+ #
214
+ }.select { |cexp, ft, t|
215
+ #
216
+ # only keep diverging branches
217
+ #
218
+ ft != t
219
+ }
220
+
221
+ branches.each do |cexp, ft, t|
222
+
223
+ next if cexp
224
+
225
+ # there is at least one branch that replied,
226
+ # this forces re-apply for the whole concurrence
227
+
228
+ register(MutationPoint.new(fexp.fei, tree, :re_apply))
229
+ return
230
+ end
231
+
232
+ branches.each do |cexp, ft, t|
233
+ #
234
+ # we're left with divering branches that haven't yet replied,
235
+ # let's walk to register the mutation point deep into it
236
+
237
+ walk(cexp, t)
238
+ end
239
+ end
240
+ end
241
+
242
+ # Look for mutation points in any non-concurrent expression.
243
+ #
244
+ def walk_sequence(fexp, ftree, tree)
245
+
246
+ i = fexp.child_ids.first
247
+
248
+ ehead = ftree[2].take(i)
249
+ ecurrent = ftree[2][i]
250
+ etail = ftree[2].drop(i + 1)
251
+ head = tree[2].take(i)
252
+ current = tree[2][i]
253
+ tail = tree[2].drop(i + 1)
254
+
255
+ if ehead != head
256
+ #
257
+ # if the name and/or attributes of the exp are supposed to change
258
+ # then we have to reapply it
259
+ #
260
+ register(MutationPoint.new(fexp.fei, tree, :re_apply))
261
+ return
262
+ end
263
+
264
+ if ecurrent != current
265
+ #
266
+ # if the child currently applied is supposed to change, let's walk
267
+ # it down
268
+ #
269
+ walk(@ps.fexp(fexp.children.first), current)
270
+ end
271
+
272
+ #if etail != tail
273
+ # #
274
+ # # if elements are added at the end of the sequence, let's register
275
+ # # a mutation that simply changes the tree (no need to re-apply)
276
+ # #
277
+ # register(MutationPoint.new(fexp.fei, tree, :update))
278
+ #end
279
+ end
280
+ end
281
+ end
282
+