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