ruote 2.3.0.1 → 2.3.0.2

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.
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
+