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/lib/ruote/fei.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
@@ -25,6 +25,7 @@
25
25
  require 'digest/md5'
26
26
 
27
27
  require 'ruote/version'
28
+ require 'ruote/extract'
28
29
  require 'ruote/workitem'
29
30
  require 'ruote/util/misc'
30
31
  require 'ruote/util/hashdot'
@@ -32,63 +33,6 @@ require 'ruote/util/hashdot'
32
33
 
33
34
  module Ruote
34
35
 
35
- # A shortcut for
36
- #
37
- # Ruote::FlowExpressionId.to_storage_id(fei)
38
- #
39
- def self.to_storage_id(fei)
40
-
41
- Ruote::FlowExpressionId.to_storage_id(fei)
42
- end
43
-
44
- # A shorter shortcut for
45
- #
46
- # Ruote::FlowExpressionId.to_storage_id(fei)
47
- #
48
- def self.sid(fei)
49
-
50
- Ruote::FlowExpressionId.to_storage_id(fei)
51
- end
52
-
53
- # A shortcut for
54
- #
55
- # Ruote::FlowExpressionId.is_a_fei?(o)
56
- #
57
- def self.is_a_fei?(o)
58
-
59
- Ruote::FlowExpressionId.is_a_fei?(o)
60
- end
61
-
62
- # Will do its best to return a wfid (String) or a fei (Hash instance)
63
- # extract from the given o argument.
64
- #
65
- def self.extract_id(o)
66
-
67
- return o if o.is_a?(String) and o.index('!').nil? # wfid
68
-
69
- Ruote::FlowExpressionId.extract_h(o)
70
- end
71
-
72
- # Given something, tries to return the fei (Ruote::FlowExpressionId) in it.
73
- #
74
- def self.extract_fei(o)
75
-
76
- Ruote::FlowExpressionId.extract(o)
77
- end
78
-
79
- # Given an object, will return the wfid (workflow instance id) nested into
80
- # it (or nil if it can't find or doesn't know how to find).
81
- #
82
- # The wfid is a String instance.
83
- #
84
- def self.extract_wfid(o)
85
-
86
- return o.strip == '' ? nil : o if o.is_a?(String)
87
- return o.wfid if o.respond_to?(:wfid)
88
- return o['wfid'] || o.fetch('fei', {})['wfid'] if o.respond_to?(:[])
89
- nil
90
- end
91
-
92
36
  # This function is used to generate the subids. Each flow
93
37
  # expression receives such an id (it's useful for cursors, loops and
94
38
  # forgotten branches).
@@ -162,11 +106,13 @@ module Ruote
162
106
 
163
107
  def self.to_storage_id(hfei)
164
108
 
165
- hfei.respond_to?(:to_storage_id) ?
166
- hfei.to_storage_id :
167
- "#{hfei['expid']}!#{hfei['subid'] || hfei['sub_wfid']}!#{hfei['wfid']}"
168
-
169
109
  # TODO : for 2.1.13, remove the subid || sub_wfid trick
110
+
111
+ if hfei.respond_to?(:to_storage_id)
112
+ hfei.to_storage_id
113
+ else
114
+ "#{hfei['expid']}!#{hfei['subid'] || hfei['sub_wfid']}!#{hfei['wfid']}"
115
+ end
170
116
  end
171
117
 
172
118
  # Turns the result of to_storage_id back to a FlowExpressionId instance.
@@ -217,16 +163,6 @@ module Ruote
217
163
 
218
164
  alias eql? ==
219
165
 
220
- SUBS = %w[ subid sub_wfid ]
221
- IDS = %w[ engine_id expid wfid ]
222
-
223
- # Returns true if the h is a representation of a FlowExpressionId instance.
224
- #
225
- def self.is_a_fei?(h)
226
-
227
- h.respond_to?(:keys) && (h.keys - SUBS).sort == IDS
228
- end
229
-
230
166
  # Returns child_id... For an expid of '0_1_4', this will be 4.
231
167
  #
232
168
  def self.child_id(h)
@@ -285,7 +221,8 @@ module Ruote
285
221
  'engine_id' => ss[-4] || 'engine',
286
222
  'expid' => ss[-3],
287
223
  'subid' => ss[-2],
288
- 'wfid' => ss[-1] }
224
+ 'wfid' => ss[-1]
225
+ }
289
226
  end
290
227
 
291
228
  raise ArgumentError.new(
@@ -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
@@ -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
@@ -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
@@ -33,12 +33,14 @@ module Ruote
33
33
  #
34
34
  # NOTE:
35
35
  #
36
- # this default history is worthless when there are multiple workers.
36
+ # This default history is worthless when there are multiple workers.
37
37
  # It only keeps track of the msgs processed by the worker in the same
38
38
  # context. Msgs processed by other workers (in different Ruby runtimes) are
39
39
  # not seen (they are tracked by the DefaultHistory next to those workers).
40
40
  #
41
41
  # By default, this history keeps track of the latest 1'000 msgs.
42
+ # This can be changed by passing a 'history_max_size' option to the storage
43
+ # when initializing ruote ('history_max_size' => 0) is acceptable.
42
44
  #
43
45
  class DefaultHistory
44
46
 
@@ -52,6 +54,8 @@ module Ruote
52
54
  @context = context
53
55
  @options = options
54
56
 
57
+ @max_size = context['history_max_size'] || DEFAULT_MAX_SIZE
58
+
55
59
  @history = []
56
60
  end
57
61
 
@@ -122,14 +126,24 @@ module Ruote
122
126
  #
123
127
  def on_msg(msg)
124
128
 
129
+ return if @max_size < 1
130
+
125
131
  msg = Ruote.fulldup(msg)
126
132
  msg['seen_at'] = Ruote.now_to_utc_s
127
133
 
128
134
  @history << msg
129
135
 
130
- while (@history.size > (@options[:max_size] || DEFAULT_MAX_SIZE)) do
136
+ while (@history.size > @max_size) do
131
137
  @history.shift
132
138
  end
139
+
140
+ rescue => e
141
+
142
+ $stderr.puts '>' + '-' * 79
143
+ $stderr.puts "#{self.class} issue, skipping"
144
+ $stderr.puts e.inspect
145
+ $stderr.puts e.backtrace[0, 2]
146
+ $stderr.puts '<' + '-' * 79
133
147
  end
134
148
  end
135
149
  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
@@ -77,29 +77,6 @@ class Ruote::WaitLogger
77
77
  ].join('!')
78
78
  end
79
79
 
80
- def insp(o, opts={})
81
-
82
- case o
83
- when nil
84
- 'nil'
85
- when Hash
86
- trim = opts[:trim] || []
87
- '{' +
88
- o.reject { |k, v|
89
- v.nil? && trim.include?(k.to_s)
90
- }.collect { |k, v|
91
- "#{k}: #{insp(v)}"
92
- }.join(', ') +
93
- '}'
94
- when Array
95
- '[' + o.collect { |e| insp(e) }.join(', ') + ']'
96
- when String
97
- o.match(/\s/) ? o.inspect : o
98
- else
99
- o.inspect
100
- end
101
- end
102
-
103
80
  def radial_tree(msg)
104
81
 
105
82
  _, t = Ruote::Exp::DefineExpression.reorganize(msg['tree'])
@@ -117,10 +94,13 @@ class Ruote::WaitLogger
117
94
 
118
95
  @count = (@count + 1) % 10
119
96
 
120
- ei = [
121
- self.object_id.to_s[-2..-1],
122
- Thread.current['worker_name'].to_s[0, 2]
123
- ].join(':')
97
+ wo = Ruote.current_worker
98
+ woi = [ wo.object_id.to_s[-2..-1] ]
99
+ if wo.class != Ruote::Worker || wo.name != 'worker'
100
+ woi << wo.class.name.split('::').last[0, 2]
101
+ woi << wo.name[0, 2]
102
+ end
103
+ woi = woi.join(':')
124
104
 
125
105
  fei = msg['fei']
126
106
  depth = fei ? fei['expid'].split('_').size : 0
@@ -210,7 +190,7 @@ class Ruote::WaitLogger
210
190
  #
211
191
  # display backtraces
212
192
 
213
- rst = insp(
193
+ rst = Ruote.insp(
214
194
  rest.reject { |k, v| %w[ error msg ].include?(k) },
215
195
  :trim => %[ updated_tree ])[1..-2]
216
196
 
@@ -225,7 +205,7 @@ class Ruote::WaitLogger
225
205
 
226
206
  color(
227
207
  @color,
228
- "#{@count} #{tm} #{ei} #{' ' * depth}#{act} * #{i} #{rst}",
208
+ "#{@count} #{tm} #{woi} #{' ' * depth}#{act} * #{i} #{rst}",
229
209
  true
230
210
  ) +
231
211
  "\n" +
@@ -244,11 +224,11 @@ class Ruote::WaitLogger
244
224
  ''
245
225
  end
246
226
 
247
- rest = insp(rest, :trim => %[ updated_tree ])[1..-2]
227
+ rest = Ruote.insp(rest, :trim => %[ updated_tree ])[1..-2]
248
228
 
249
229
  color(
250
230
  @color,
251
- "#{@count} #{tm} #{ei} #{' ' * depth}#{act} * #{i} #{pa}#{rest}",
231
+ "#{@count} #{tm} #{woi} #{' ' * depth}#{act} * #{i} #{pa}#{rest}",
252
232
  true)
253
233
  end
254
234
 
@@ -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
@@ -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
@@ -38,7 +38,7 @@ module Ruote
38
38
  #
39
39
  # The logic behind Ruote::Dashboard#wait_for is implemented here.
40
40
  #
41
- # This logger keeps track of the last 147 events. This number can
41
+ # This logger keeps track of the last 56 events. This number can
42
42
  # be tweaked via the 'wait_logger_max' storage option
43
43
  # (http://ruote.rubyforge.org/configuration.html)
44
44
  #
@@ -51,7 +51,7 @@ module Ruote
51
51
  # === options (storage initialization options)
52
52
  #
53
53
  # wait_logger_max(Integer)::
54
- # defaults to 147, max number of recent records to keep track of
54
+ # defaults to 77, max number of recent records to keep track of
55
55
  # wait_logger_timeout(Integer)::
56
56
  # defaults to 60 (seconds), #wait_for times out after how many seconds?
57
57
  #
@@ -84,7 +84,7 @@ module Ruote
84
84
  @color = 33
85
85
  @noisy = false
86
86
 
87
- @log_max = context['wait_logger_max'] || 147
87
+ @log_max = context['wait_logger_max'] || 77
88
88
  @timeout = context['wait_logger_timeout'] || 60 # in seconds
89
89
 
90
90
  @check_mutex = Mutex.new
@@ -178,8 +178,12 @@ module Ruote
178
178
  while @waiting.any? and msg = @seen.shift
179
179
 
180
180
  @waiting.delete_if do |thread, interests|
181
- thread['__result__'] = msg if matches(interests, msg)
182
- (interests.size < 1)
181
+ if matches(interests, msg)
182
+ thread['__result__'] = msg
183
+ true
184
+ else
185
+ false
186
+ end
183
187
  end
184
188
  end
185
189
  end
@@ -267,7 +271,11 @@ module Ruote
267
271
  interests.delete(interest) if satisfied
268
272
  end
269
273
 
270
- (interests.size < 1)
274
+ if interests.include?(:or_error)
275
+ (interests.size < 2)
276
+ else
277
+ (interests.size < 1)
278
+ end
271
279
  end
272
280
  end
273
281
  end
@@ -0,0 +1,123 @@
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
+ # Gathering methods for merging workitems.
30
+ #++
31
+
32
+ # Merge workitem 'source' into workitem 'target'.
33
+ #
34
+ # If type is 'override', the source will prevail and be returned.
35
+ #
36
+ # If type is 'mix', the source fields will be merged into the target fields.
37
+ #
38
+ # If type is 'isolate', the source fields will be placed in a separte field
39
+ # in the target workitem. The name of this field is the child_id of the
40
+ # source workitem (a string from '0' to '99999' and beyond)
41
+ #
42
+ # The 'concat' type merges hashes and concats arrays. The 'union' type
43
+ # behaves much like 'concat', but it makes sure to remove duplicates.
44
+ #
45
+ # Warning: 'union' will remove duplicates that were present _before_ the
46
+ # merge.
47
+ #
48
+ def self.merge_workitem(index, target, source, merge_type)
49
+
50
+ if merge_type == 'override'
51
+
52
+ return source
53
+ end
54
+
55
+ if target == nil
56
+
57
+ case merge_type
58
+
59
+ when 'stack'
60
+
61
+ source['fields'] = { 'stack' => [ source['fields'] ] }
62
+
63
+ when 'isolate'
64
+
65
+ source['fields'] = { (index || 0).to_s => source['fields'] }
66
+
67
+ #when 'mix'
68
+ # do nothing
69
+ #when 'union', 'concat'
70
+ # do nothing
71
+ end
72
+
73
+ return source
74
+ end
75
+
76
+ # else, regular merge
77
+
78
+ case merge_type
79
+
80
+ when 'mix'
81
+
82
+ target['fields'].merge!(source['fields'])
83
+
84
+ when 'stack'
85
+
86
+ target['fields']['stack'] << source['fields']
87
+
88
+ when 'isolate'
89
+
90
+ index ||= target['fields'].keys.select { |k| k.match(/^\d+$/) }.size
91
+ target['fields'][index.to_s] = source['fields']
92
+
93
+ when 'union', 'concat', 'deep'
94
+
95
+ source['fields'].each do |k, sv|
96
+
97
+ tv = target['fields'][k]
98
+
99
+ if sv.is_a?(Array) and tv.is_a?(Array)
100
+ tv.concat(sv)
101
+ tv.uniq! if merge_type == 'union'
102
+ elsif sv.is_a?(Hash) and tv.is_a?(Hash)
103
+ merge_type == 'deep' ? deep_merge!(tv, sv) : tv.merge!(sv)
104
+ else
105
+ target['fields'][k] = sv
106
+ end
107
+ end
108
+ end
109
+
110
+ target
111
+ end
112
+
113
+ # Inspired by the one found in ActiveSupport, though not strictly
114
+ # equivalent.
115
+ #
116
+ def self.deep_merge!(target, source)
117
+
118
+ target.merge!(source) do |k, o, n|
119
+ o.is_a?(Hash) && n.is_a?(Hash) ? deep_merge!(o, n) : n
120
+ end
121
+ end
122
+ end
123
+