ruote 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (305) hide show
  1. data/CHANGELOG.txt +166 -1
  2. data/CREDITS.txt +36 -17
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +1 -7
  5. data/Rakefile +38 -29
  6. data/TODO.txt +93 -52
  7. data/lib/ruote-fs.rb +3 -0
  8. data/lib/ruote.rb +5 -1
  9. data/lib/ruote/context.rb +140 -35
  10. data/lib/ruote/dashboard.rb +1247 -0
  11. data/lib/ruote/{engine → dboard}/process_error.rb +22 -2
  12. data/lib/ruote/dboard/process_status.rb +587 -0
  13. data/lib/ruote/engine.rb +6 -871
  14. data/lib/ruote/exp/command.rb +7 -2
  15. data/lib/ruote/exp/commanded.rb +2 -2
  16. data/lib/ruote/exp/condition.rb +38 -13
  17. data/lib/ruote/exp/fe_add_branches.rb +1 -1
  18. data/lib/ruote/exp/fe_apply.rb +1 -1
  19. data/lib/ruote/exp/fe_await.rb +357 -0
  20. data/lib/ruote/exp/fe_cancel_process.rb +17 -3
  21. data/lib/ruote/exp/fe_command.rb +8 -4
  22. data/lib/ruote/exp/fe_concurrence.rb +218 -18
  23. data/lib/ruote/exp/fe_concurrent_iterator.rb +71 -10
  24. data/lib/ruote/exp/fe_cron.rb +3 -10
  25. data/lib/ruote/exp/fe_cursor.rb +14 -4
  26. data/lib/ruote/exp/fe_define.rb +3 -1
  27. data/lib/ruote/exp/fe_echo.rb +1 -1
  28. data/lib/ruote/exp/fe_equals.rb +1 -1
  29. data/lib/ruote/exp/fe_error.rb +1 -1
  30. data/lib/ruote/exp/fe_filter.rb +163 -4
  31. data/lib/ruote/exp/fe_forget.rb +21 -4
  32. data/lib/ruote/exp/fe_given.rb +1 -1
  33. data/lib/ruote/exp/fe_if.rb +1 -1
  34. data/lib/ruote/exp/fe_inc.rb +102 -35
  35. data/lib/ruote/exp/fe_iterator.rb +47 -12
  36. data/lib/ruote/exp/fe_listen.rb +96 -11
  37. data/lib/ruote/exp/fe_lose.rb +31 -4
  38. data/lib/ruote/exp/fe_noop.rb +1 -1
  39. data/lib/ruote/exp/fe_on_error.rb +109 -0
  40. data/lib/ruote/exp/fe_once.rb +10 -19
  41. data/lib/ruote/exp/fe_participant.rb +90 -28
  42. data/lib/ruote/exp/fe_read.rb +69 -0
  43. data/lib/ruote/exp/fe_redo.rb +3 -2
  44. data/lib/ruote/exp/fe_ref.rb +57 -27
  45. data/lib/ruote/exp/fe_registerp.rb +1 -3
  46. data/lib/ruote/exp/fe_reserve.rb +1 -1
  47. data/lib/ruote/exp/fe_restore.rb +6 -6
  48. data/lib/ruote/exp/fe_save.rb +12 -19
  49. data/lib/ruote/exp/fe_sequence.rb +38 -2
  50. data/lib/ruote/exp/fe_set.rb +143 -40
  51. data/lib/ruote/exp/{fe_let.rb → fe_stall.rb} +7 -38
  52. data/lib/ruote/exp/fe_subprocess.rb +8 -2
  53. data/lib/ruote/exp/fe_that.rb +1 -1
  54. data/lib/ruote/exp/fe_undo.rb +40 -4
  55. data/lib/ruote/exp/fe_unregisterp.rb +1 -3
  56. data/lib/ruote/exp/fe_wait.rb +12 -25
  57. data/lib/ruote/exp/{flowexpression.rb → flow_expression.rb} +375 -229
  58. data/lib/ruote/exp/iterator.rb +2 -2
  59. data/lib/ruote/exp/merge.rb +78 -17
  60. data/lib/ruote/exp/ro_attributes.rb +46 -36
  61. data/lib/ruote/exp/ro_filters.rb +34 -8
  62. data/lib/ruote/exp/ro_on_x.rb +431 -0
  63. data/lib/ruote/exp/ro_persist.rb +19 -7
  64. data/lib/ruote/exp/ro_timers.rb +123 -0
  65. data/lib/ruote/exp/ro_variables.rb +90 -29
  66. data/lib/ruote/fei.rb +57 -3
  67. data/lib/ruote/fs.rb +3 -0
  68. data/lib/ruote/id/mnemo_wfid_generator.rb +30 -7
  69. data/lib/ruote/id/wfid_generator.rb +17 -38
  70. data/lib/ruote/log/default_history.rb +23 -9
  71. data/lib/ruote/log/fancy_printing.rb +265 -0
  72. data/lib/ruote/log/storage_history.rb +23 -13
  73. data/lib/ruote/log/wait_logger.rb +224 -17
  74. data/lib/ruote/observer.rb +82 -0
  75. data/lib/ruote/part/block_participant.rb +65 -28
  76. data/lib/ruote/part/code_participant.rb +81 -0
  77. data/lib/ruote/part/engine_participant.rb +7 -2
  78. data/lib/ruote/part/local_participant.rb +221 -21
  79. data/lib/ruote/part/no_op_participant.rb +1 -1
  80. data/lib/ruote/part/null_participant.rb +1 -1
  81. data/lib/ruote/part/participant.rb +50 -0
  82. data/lib/ruote/part/rev_participant.rb +178 -0
  83. data/lib/ruote/part/smtp_participant.rb +2 -2
  84. data/lib/ruote/part/storage_participant.rb +228 -60
  85. data/lib/ruote/part/template.rb +1 -1
  86. data/lib/ruote/participant.rb +2 -0
  87. data/lib/ruote/reader.rb +205 -68
  88. data/lib/ruote/reader/json.rb +49 -0
  89. data/lib/ruote/reader/radial.rb +303 -0
  90. data/lib/ruote/reader/ruby_dsl.rb +44 -9
  91. data/lib/ruote/reader/xml.rb +11 -8
  92. data/lib/ruote/receiver/base.rb +98 -45
  93. data/lib/ruote/storage/base.rb +104 -35
  94. data/lib/ruote/storage/composite_storage.rb +50 -60
  95. data/lib/ruote/storage/fs_storage.rb +25 -34
  96. data/lib/ruote/storage/hash_storage.rb +38 -36
  97. data/lib/ruote/svc/dispatch_pool.rb +104 -35
  98. data/lib/ruote/svc/dollar_sub.rb +10 -8
  99. data/lib/ruote/svc/error_handler.rb +108 -52
  100. data/lib/ruote/svc/expression_map.rb +3 -3
  101. data/lib/ruote/svc/participant_list.rb +160 -55
  102. data/lib/ruote/svc/tracker.rb +31 -31
  103. data/lib/ruote/svc/treechecker.rb +28 -16
  104. data/lib/ruote/tree_dot.rb +1 -1
  105. data/lib/ruote/util/deep.rb +143 -0
  106. data/lib/ruote/util/filter.rb +125 -18
  107. data/lib/ruote/util/hashdot.rb +15 -13
  108. data/lib/ruote/util/look.rb +1 -1
  109. data/lib/ruote/util/lookup.rb +60 -22
  110. data/lib/ruote/util/misc.rb +63 -18
  111. data/lib/ruote/util/mpatch.rb +53 -0
  112. data/lib/ruote/util/ometa.rb +1 -2
  113. data/lib/ruote/util/process_observer.rb +177 -0
  114. data/lib/ruote/util/subprocess.rb +1 -1
  115. data/lib/ruote/util/time.rb +2 -2
  116. data/lib/ruote/util/tree.rb +64 -2
  117. data/lib/ruote/version.rb +3 -2
  118. data/lib/ruote/worker.rb +421 -92
  119. data/lib/ruote/workitem.rb +157 -22
  120. data/ruote.gemspec +15 -9
  121. data/test/bm/ci.rb +0 -2
  122. data/test/bm/ici.rb +0 -2
  123. data/test/bm/load_26c.rb +0 -3
  124. data/test/bm/mega.rb +0 -2
  125. data/test/functional/base.rb +57 -43
  126. data/test/functional/concurrent_base.rb +16 -13
  127. data/test/functional/ct_0_concurrence.rb +7 -11
  128. data/test/functional/ct_1_iterator.rb +9 -11
  129. data/test/functional/ct_2_cancel.rb +28 -17
  130. data/test/functional/eft_0_flow_expression.rb +35 -0
  131. data/test/functional/eft_10_cancel_process.rb +1 -1
  132. data/test/functional/eft_11_wait.rb +13 -13
  133. data/test/functional/eft_12_listen.rb +199 -66
  134. data/test/functional/eft_13_iterator.rb +95 -29
  135. data/test/functional/eft_14_cursor.rb +74 -24
  136. data/test/functional/eft_15_loop.rb +7 -7
  137. data/test/functional/eft_16_if.rb +1 -1
  138. data/test/functional/eft_17_equals.rb +1 -1
  139. data/test/functional/eft_18_concurrent_iterator.rb +156 -68
  140. data/test/functional/eft_19_reserve.rb +15 -15
  141. data/test/functional/eft_1_echo.rb +1 -1
  142. data/test/functional/eft_20_save.rb +51 -9
  143. data/test/functional/eft_21_restore.rb +1 -1
  144. data/test/functional/eft_22_noop.rb +1 -1
  145. data/test/functional/eft_23_apply.rb +1 -1
  146. data/test/functional/eft_24_add_branches.rb +7 -8
  147. data/test/functional/eft_25_command.rb +1 -1
  148. data/test/functional/eft_26_error.rb +11 -11
  149. data/test/functional/eft_27_inc.rb +111 -67
  150. data/test/functional/eft_28_once.rb +16 -16
  151. data/test/functional/eft_29_cron.rb +9 -9
  152. data/test/functional/eft_2_sequence.rb +23 -4
  153. data/test/functional/eft_30_ref.rb +36 -24
  154. data/test/functional/eft_31_registerp.rb +24 -24
  155. data/test/functional/eft_32_lose.rb +46 -20
  156. data/test/functional/eft_34_given.rb +1 -1
  157. data/test/functional/eft_35_filter.rb +161 -7
  158. data/test/functional/eft_36_read.rb +97 -0
  159. data/test/functional/{eft_0_process_definition.rb → eft_37_process_definition.rb} +4 -4
  160. data/test/functional/eft_38_on_error.rb +195 -0
  161. data/test/functional/eft_39_stall.rb +35 -0
  162. data/test/functional/eft_3_participant.rb +77 -22
  163. data/test/functional/eft_40_await.rb +297 -0
  164. data/test/functional/eft_4_set.rb +110 -11
  165. data/test/functional/eft_5_subprocess.rb +27 -5
  166. data/test/functional/eft_6_concurrence.rb +299 -60
  167. data/test/functional/eft_7_forget.rb +24 -22
  168. data/test/functional/eft_8_undo.rb +52 -15
  169. data/test/functional/eft_9_redo.rb +18 -20
  170. data/test/functional/ft_0_worker.rb +122 -13
  171. data/test/functional/ft_10_dollar.rb +77 -16
  172. data/test/functional/ft_11_recursion.rb +9 -9
  173. data/test/functional/ft_12_launchitem.rb +7 -9
  174. data/test/functional/ft_13_variables.rb +125 -22
  175. data/test/functional/ft_14_re_apply.rb +112 -56
  176. data/test/functional/ft_15_timeout.rb +64 -33
  177. data/test/functional/ft_16_participant_params.rb +59 -6
  178. data/test/functional/ft_17_conditional.rb +68 -2
  179. data/test/functional/ft_18_kill.rb +48 -30
  180. data/test/functional/ft_19_participant_code.rb +67 -0
  181. data/test/functional/ft_1_process_status.rb +222 -150
  182. data/test/functional/ft_20_storage_participant.rb +445 -44
  183. data/test/functional/ft_21_forget.rb +21 -26
  184. data/test/functional/ft_22_process_definitions.rb +8 -6
  185. data/test/functional/ft_23_load_defs.rb +29 -5
  186. data/test/functional/ft_24_block_participant.rb +199 -20
  187. data/test/functional/ft_25_receiver.rb +98 -46
  188. data/test/functional/ft_26_participant_rtimeout.rb +34 -26
  189. data/test/functional/ft_27_var_indirection.rb +40 -5
  190. data/test/functional/ft_28_null_noop_participants.rb +5 -5
  191. data/test/functional/ft_29_part_template.rb +2 -2
  192. data/test/functional/ft_2_errors.rb +106 -74
  193. data/test/functional/ft_30_smtp_participant.rb +7 -7
  194. data/test/functional/ft_31_part_blocking.rb +11 -11
  195. data/test/functional/ft_32_scope.rb +50 -0
  196. data/test/functional/ft_33_participant_subprocess_priority.rb +3 -3
  197. data/test/functional/ft_34_cursor_rewind.rb +14 -14
  198. data/test/functional/ft_35_add_service.rb +67 -9
  199. data/test/functional/ft_36_storage_history.rb +92 -24
  200. data/test/functional/ft_37_default_history.rb +35 -23
  201. data/test/functional/ft_38_participant_more.rb +189 -32
  202. data/test/functional/ft_39_wait_for.rb +25 -25
  203. data/test/functional/ft_3_participant_registration.rb +235 -107
  204. data/test/functional/ft_40_wait_logger.rb +105 -18
  205. data/test/functional/ft_41_participants.rb +13 -12
  206. data/test/functional/ft_42_storage_copy.rb +12 -12
  207. data/test/functional/ft_43_participant_on_reply.rb +85 -11
  208. data/test/functional/ft_44_var_participant.rb +5 -5
  209. data/test/functional/ft_45_participant_accept.rb +3 -3
  210. data/test/functional/ft_46_launch_single.rb +17 -17
  211. data/test/functional/ft_47_wfids.rb +41 -0
  212. data/test/functional/ft_48_lose.rb +19 -25
  213. data/test/functional/ft_49_engine_on_error.rb +54 -70
  214. data/test/functional/ft_4_cancel.rb +84 -26
  215. data/test/functional/ft_50_engine_config.rb +4 -4
  216. data/test/functional/ft_51_misc.rb +12 -12
  217. data/test/functional/ft_52_case.rb +17 -17
  218. data/test/functional/ft_53_engine_on_terminate.rb +18 -21
  219. data/test/functional/ft_54_patterns.rb +18 -16
  220. data/test/functional/ft_55_engine_participant.rb +55 -55
  221. data/test/functional/ft_56_filter_attribute.rb +90 -52
  222. data/test/functional/ft_57_rev_participant.rb +252 -0
  223. data/test/functional/ft_58_workitem.rb +150 -0
  224. data/test/functional/ft_59_pause.rb +329 -0
  225. data/test/functional/ft_5_on_error.rb +430 -77
  226. data/test/functional/ft_60_code_participant.rb +65 -0
  227. data/test/functional/ft_61_trailing_fields.rb +34 -0
  228. data/test/functional/ft_62_exp_name_and_dollar_substitution.rb +35 -0
  229. data/test/functional/ft_63_participants_221.rb +458 -0
  230. data/test/functional/ft_64_stash.rb +41 -0
  231. data/test/functional/ft_65_timers.rb +313 -0
  232. data/test/functional/ft_66_flank.rb +133 -0
  233. data/test/functional/ft_67_radial_misc.rb +34 -0
  234. data/test/functional/ft_68_reput.rb +72 -0
  235. data/test/functional/ft_69_worker_info.rb +56 -0
  236. data/test/functional/ft_6_on_cancel.rb +189 -36
  237. data/test/functional/ft_70_take_and_discard_attributes.rb +94 -0
  238. data/test/functional/ft_71_retries.rb +144 -0
  239. data/test/functional/ft_72_on_terminate.rb +60 -0
  240. data/test/functional/ft_73_raise_msg.rb +107 -0
  241. data/test/functional/ft_74_respark.rb +106 -0
  242. data/test/functional/ft_75_context.rb +66 -0
  243. data/test/functional/ft_76_observer.rb +53 -0
  244. data/test/functional/ft_77_process_observer.rb +157 -0
  245. data/test/functional/ft_78_part_participant.rb +37 -0
  246. data/test/functional/ft_7_tags.rb +238 -50
  247. data/test/functional/ft_8_participant_consumption.rb +27 -21
  248. data/test/functional/ft_9_subprocesses.rb +48 -18
  249. data/test/functional/restart_base.rb +4 -6
  250. data/test/functional/rt_0_wait.rb +10 -10
  251. data/test/functional/rt_1_listen.rb +6 -6
  252. data/test/functional/rt_2_errors.rb +12 -12
  253. data/test/functional/rt_3_once.rb +17 -12
  254. data/test/functional/rt_4_cron.rb +17 -17
  255. data/test/functional/rt_5_timeout.rb +13 -13
  256. data/test/functional/signals.rb +103 -0
  257. data/test/functional/storage.rb +730 -0
  258. data/test/functional/storage_helper.rb +48 -35
  259. data/test/functional/test.rb +6 -2
  260. data/test/misc/idle.rb +21 -0
  261. data/test/misc/light.rb +29 -0
  262. data/test/path_helper.rb +1 -1
  263. data/test/test.rb +2 -5
  264. data/test/test_helper.rb +13 -0
  265. data/test/unit/test.rb +1 -4
  266. data/test/unit/ut_0_ruby_reader.rb +25 -9
  267. data/test/unit/ut_10_participants.rb +47 -0
  268. data/test/unit/ut_11_lookup.rb +59 -2
  269. data/test/unit/ut_12_wait_logger.rb +123 -0
  270. data/test/unit/ut_14_is_uri.rb +1 -1
  271. data/test/unit/ut_15_util.rb +1 -1
  272. data/test/unit/ut_16_reader.rb +136 -14
  273. data/test/unit/ut_17_merge.rb +155 -0
  274. data/test/unit/ut_19_part_template.rb +1 -1
  275. data/test/unit/ut_1_fei.rb +11 -2
  276. data/test/unit/ut_20_composite_storage.rb +27 -1
  277. data/test/unit/{ut_21_participant_list.rb → ut_21_svc_participant_list.rb} +2 -3
  278. data/test/unit/ut_22_filter.rb +231 -10
  279. data/test/unit/ut_23_svc_tracker.rb +48 -0
  280. data/test/unit/ut_24_radial_reader.rb +458 -0
  281. data/test/unit/ut_25_process_status.rb +143 -0
  282. data/test/unit/ut_26_deep.rb +131 -0
  283. data/test/unit/ut_2_dashboard.rb +114 -0
  284. data/test/unit/ut_3_worker.rb +54 -0
  285. data/test/unit/ut_4_expmap.rb +1 -1
  286. data/test/unit/ut_5_tree.rb +23 -23
  287. data/test/unit/ut_6_condition.rb +71 -29
  288. data/test/unit/ut_7_workitem.rb +18 -4
  289. data/test/unit/ut_8_tree_to_dot.rb +1 -1
  290. data/test/unit/ut_9_xml_reader.rb +1 -1
  291. metadata +142 -63
  292. data/jruby_issue.txt +0 -32
  293. data/lib/ruote/engine/process_status.rb +0 -403
  294. data/lib/ruote/log/pretty.rb +0 -165
  295. data/lib/ruote/log/test_logger.rb +0 -204
  296. data/lib/ruote/util/serializer.rb +0 -103
  297. data/phil.txt +0 -14
  298. data/test/functional/eft_33_let.rb +0 -31
  299. data/test/functional/ft_19_alias.rb +0 -33
  300. data/test/functional/ft_47_wfid_generator.rb +0 -54
  301. data/test/unit/storage.rb +0 -403
  302. data/test/unit/storages.rb +0 -37
  303. data/test/unit/ut_13_serializer.rb +0 -65
  304. data/test/unit/ut_18_engine.rb +0 -47
  305. data/test/unit/ut_3_wait_logger.rb +0 -39
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2012, 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
@@ -42,7 +42,7 @@ module Ruote::Exp
42
42
 
43
43
  if count = attribute(:times) || attribute(:branches)
44
44
 
45
- list = ((1..count.to_i).to_a rescue nil)
45
+ list = ((0...count.to_i).to_a rescue nil)
46
46
 
47
47
  if list
48
48
  h.times_iterator = true
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2012, 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
@@ -30,43 +30,104 @@ module Ruote::Exp
30
30
  #
31
31
  module MergeMixin
32
32
 
33
+ # Given a list of workitems and a merge_type, will merge according to
34
+ # the merge type.
33
35
  #
36
+ # The return value is the merged workitem.
37
+ #
38
+ def merge_workitems(workitems, merge_type)
39
+
40
+ workitems.inject(nil) do |t, wi|
41
+ merge_workitem(workitems.index(wi), t, wi, merge_type)
42
+ end
43
+ end
44
+
34
45
  # Merge workitem 'source' into workitem 'target'.
35
46
  #
36
47
  # If type is 'override', the source will prevail and be returned.
37
48
  #
38
- # If type is 'mix', the source fields will be merge into the target fields.
49
+ # If type is 'mix', the source fields will be merged into the target fields.
39
50
  #
40
51
  # If type is 'isolate', the source fields will be placed in a separte field
41
52
  # in the target workitem. The name of this field is the child_id of the
42
53
  # source workitem (a string from '0' to '99999' and beyond)
43
54
  #
44
- def merge_workitems(index, target, source, type)
55
+ # The 'concat' type merges hashes and concats arrays. The 'union' type
56
+ # behaves much like 'concat', but it makes sure to remove duplicates.
57
+ #
58
+ # Warning: 'union' will remove duplicates that were present _before_ the
59
+ # merge.
60
+ #
61
+ def merge_workitem(index, target, source, merge_type)
45
62
 
46
- return source if type == 'override'
63
+ return source if merge_type == 'override'
47
64
 
48
65
  if target == nil
49
- case type
66
+
67
+ case merge_type
68
+
69
+ #when 'mix'
70
+ # do nothing
71
+
72
+ when 'stack'
73
+ source['fields'] = { 'stack' => [ source['fields'] ] }
74
+
50
75
  when 'isolate'
51
76
  source['fields'] = { index.to_s => source['fields'] }
77
+
78
+ #when 'union', 'concat'
79
+ # do nothing
80
+ end
81
+
82
+ source
83
+
84
+ else
85
+
86
+ case merge_type
87
+
88
+ when 'mix'
89
+
90
+ target['fields'].merge!(source['fields'])
91
+
52
92
  when 'stack'
53
- source['fields'] = { 'stack' => [ source['fields'] ] }
93
+
94
+ target['fields']['stack'] << source['fields']
95
+ target['fields']['stack_attributes'] = compile_atts
96
+
97
+ when 'isolate'
98
+
99
+ target['fields'][index.to_s] = source['fields']
100
+
101
+ when 'union', 'concat', 'deep'
102
+
103
+ source['fields'].each do |k, sv|
104
+
105
+ tv = target['fields'][k]
106
+
107
+ if sv.is_a?(Array) and tv.is_a?(Array)
108
+ tv.concat(sv)
109
+ tv.uniq! if merge_type == 'union'
110
+ elsif sv.is_a?(Hash) and tv.is_a?(Hash)
111
+ merge_type == 'deep' ? deep_merge!(tv, sv) : tv.merge!(sv)
112
+ else
113
+ target['fields'][k] = sv
114
+ end
115
+ end
54
116
  end
117
+
118
+ target
55
119
  end
120
+ end
56
121
 
57
- return source unless target
122
+ protected
58
123
 
59
- case type
60
- when 'mix'
61
- target['fields'].merge!(source['fields'])
62
- when 'stack'
63
- target['fields']['stack'] << source['fields']
64
- target['fields']['stack_attributes'] = expand_atts
65
- else # 'isolate'
66
- target['fields'][index.to_s] = source['fields']
67
- end
124
+ # Inspired by the one found in ActiveSupport, though not strictly equivalent.
125
+ #
126
+ def deep_merge!(target, source)
68
127
 
69
- target
128
+ target.merge!(source) do |k, o, n|
129
+ o.is_a?(Hash) && n.is_a?(Hash) ? deep_merge!(o, n) : n
130
+ end
70
131
  end
71
132
  end
72
133
  end
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2012, 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
@@ -60,7 +60,7 @@ module Ruote::Exp
60
60
  elsif escape
61
61
  v
62
62
  else
63
- @context.dollar_sub.s(v, self, workitem)
63
+ dsub(v, workitem)
64
64
  end
65
65
 
66
66
  v = v.to_s if v and string
@@ -72,19 +72,11 @@ module Ruote::Exp
72
72
  # in the array list 'values'. If not, the default value is returned.
73
73
  # By default, the default value is the first element of 'values'.
74
74
  #
75
- def att(key, values, opts={})
75
+ def att(keys, values, opts={})
76
76
 
77
77
  default = opts[:default] || values.first
78
78
 
79
- val = attribute(key)
80
- val = val.to_s if val
81
-
82
- #raise(
83
- # ArgumentError.new("attribute '#{key}' missing in #{tree}")
84
- #) if opts[:mandatory] && val == nil
85
- #raise(
86
- # ArgumentError.new("attribute '#{key}' has invalid value in #{tree}")
87
- #) if opts[:enforce] && (not values.include?(val))
79
+ val = Array(keys).collect { |key| attribute(key) }.compact.first.to_s
88
80
 
89
81
  values.include?(val) ? val : default
90
82
  end
@@ -115,24 +107,8 @@ module Ruote::Exp
115
107
  #
116
108
  def compile_atts(opts={})
117
109
 
118
- attributes.keys.inject({}) { |r, k|
119
- r[k] = attribute(k, h.applied_workitem, opts)
120
- r
121
- }
122
- end
123
-
124
- # Like compile_atts, but the keys are expanded as well.
125
- #
126
- # Useful for things like
127
- #
128
- # set "f:${v:field_name}" => "${v:that_variable}"
129
- #
130
- def expand_atts(opts={})
131
-
132
- attributes.keys.inject({}) { |r, k|
133
- kk = @context.dollar_sub.s(k, self, h.applied_workitem)
134
- r[kk] = attribute(k, h.applied_workitem, opts)
135
- r
110
+ attributes.keys.each_with_object({}) { |k, r|
111
+ r[dsub(k)] = attribute(k, h.applied_workitem, opts)
136
112
  }
137
113
  end
138
114
 
@@ -154,15 +130,51 @@ module Ruote::Exp
154
130
 
155
131
  text = attributes.keys.find { |k| attributes[k] == nil }
156
132
 
157
- @context.dollar_sub.s(text.to_s, self, workitem)
133
+ dsub(text.to_s, workitem)
134
+ end
135
+
136
+ # Equivalent to #attribute_text, but will return nil if there
137
+ # is no attribute whose values is nil.
138
+ #
139
+ def att_text(workitem=h.applied_workitem)
140
+
141
+ text = attributes.keys.find { |k| attributes[k] == nil }
142
+
143
+ text ? dsub(text.to_s, workitem) : nil
158
144
  end
159
145
 
160
146
  protected
161
147
 
148
+ # dollar substitution for expressions.
149
+ #
150
+ def dsub(o, wi=h.applied_workitem)
151
+
152
+ case o
153
+ when String; @context.dollar_sub.s(o, self, wi)
154
+ when Array; o.collect { |e| dsub(e, wi) }
155
+ when Hash; o.remap { |(k, v), h| h[dsub(k, wi)] = dsub(v, wi) }
156
+ else o
157
+ end
158
+ end
159
+
160
+ # 'tos' meaning 'many "to"'
161
+ #
162
162
  def determine_tos
163
163
 
164
- [ attribute(:to_v) || attribute(:to_var) || attribute(:to_variable),
165
- attribute(:to_f) || attribute(:to_fld) || attribute(:to_field) ]
164
+ to_v = attribute(:to_v) || attribute(:to_var) || attribute(:to_variable)
165
+ to_f = attribute(:to_f) || attribute(:to_fld) || attribute(:to_field)
166
+
167
+ if to = attribute(:to)
168
+ pre, key = to.split(':')
169
+ pre, key = [ 'f', pre ] if key == nil
170
+ if pre.match(/^f/)
171
+ to_f = key
172
+ else
173
+ to_v = key
174
+ end
175
+ end
176
+
177
+ [ to_v, to_f ]
166
178
  end
167
179
 
168
180
  # Val and Value (Sense and Sensibility ?)
@@ -188,9 +200,7 @@ module Ruote::Exp
188
200
  elsif k = has_att(*flds)
189
201
 
190
202
  k = attribute(k, h.applied_workitem, att_options)
191
- h.applied_workitem['fields'][k]
192
-
193
- # TODO : what about leveraging workitem#lookup ?
203
+ Ruote.lookup(h.applied_workitem['fields'], k)
194
204
 
195
205
  else
196
206
 
@@ -1,5 +1,5 @@
1
1
  #--
2
- # Copyright (c) 2005-2011, John Mettraux, jmettraux@gmail.com
2
+ # Copyright (c) 2005-2012, 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
@@ -76,7 +76,13 @@ module Ruote::Exp
76
76
 
77
77
  workitem['fields'] =
78
78
  Ruote.filter(
79
- filter, workitem['fields'], :double_tilde => h.fields_pre_filter)
79
+ filter,
80
+ workitem['fields'],
81
+ :double_tilde =>
82
+ h.fields_pre_filter || h.applied_workitem['fields'])
83
+
84
+ workitem['fields'].delete('params')
85
+ # take and discard tend to copy it over, so let's remove it
80
86
  end
81
87
  end
82
88
 
@@ -85,21 +91,41 @@ module Ruote::Exp
85
91
  #
86
92
  # Returns nil, if there is no filter. Raises an ArgumentError if the
87
93
  # filter is not usable. Returns the instantiated participant if the
88
- # filter points to one.
94
+ # filter points to a participant filter.
89
95
  #
90
96
  def lookup_filter(workitem)
91
97
 
92
98
  f = attribute(:filter)
93
99
 
100
+ if f.nil? and workitem
101
+
102
+ reply = if t = attribute(:take)
103
+ Array(t).collect { |tt| { 'field' => tt, 'take' => true } }
104
+ elsif d = attribute(:discard)
105
+ if d == true
106
+ [ { 'field' => /.+/, 'discard' => 'all' } ]
107
+ else
108
+ Array(d).collect { |dd| { 'field' => dd, 'discard' => true } }
109
+ end
110
+ else
111
+ nil
112
+ end
113
+
114
+ f = { 'reply' => reply } if reply
115
+ end
116
+
94
117
  return nil unless f
118
+ # no filter
95
119
 
96
- 3.times { f = narrow_filter(f, workitem) }
120
+ if f.is_a?(Hash)
121
+ f['in'] = [] unless f['in'] or f['apply']
122
+ f['out'] = [] unless f['out'] or f['reply']
123
+ end
124
+ # empty ins and outs for a sucessful narrowing
97
125
 
98
- raise ArgumentError.new(
99
- "found no filter corresponding to '#{f}'"
100
- ) unless f
126
+ 3.times { f = narrow_filter(f, workitem) }
101
127
 
102
- f
128
+ f or raise ArgumentError.new("found no filter corresponding to '#{f}'")
103
129
  end
104
130
 
105
131
  # Called successively to dig for the filter (Array or Participant).
@@ -0,0 +1,431 @@
1
+ #--
2
+ # Copyright (c) 2005-2012, 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::Exp
27
+
28
+ #
29
+ # 're-opening' the FlowExpression class to add methods and classes about
30
+ # on_error, on_cancel, on_timeout, ...
31
+ #
32
+ class FlowExpression
33
+
34
+ # Given this expression and an error, deflates the error into a hash
35
+ # (serializable).
36
+ #
37
+ def deflate(err)
38
+
39
+ {
40
+ 'fei' => h.fei,
41
+ 'at' => Ruote.now_to_utc_s,
42
+ 'class' => err.class.to_s,
43
+ 'message' => err.message,
44
+ 'trace' => err.backtrace,
45
+ 'details' => err.respond_to?(:ruote_details) ? err.ruote_details : nil,
46
+ 'deviations' => err.respond_to?(:deviations) ? err.deviations : nil,
47
+ 'tree' => tree
48
+ }
49
+ end
50
+
51
+ # Returns a dummy expression. Only used by the error_handler service.
52
+ #
53
+ def self.dummy(h)
54
+
55
+ class << h; include Ruote::HashDot; end
56
+
57
+ fe = self.allocate
58
+ fe.instance_variable_set(:@h, h)
59
+
60
+ fe
61
+ end
62
+
63
+ # Looks up parent with on_error attribute and triggers it
64
+ #
65
+ def handle_on_error(msg, error)
66
+
67
+ return false if h.state == 'failing'
68
+
69
+ err = deflate(error)
70
+ oe_parent = lookup_on_error(err)
71
+
72
+ return false unless oe_parent
73
+ # no parent with on_error attribute found
74
+
75
+ handler = oe_parent.local_on_error(err)
76
+
77
+ return false if handler.to_s == ''
78
+ # empty on_error handler nullifies ancestor's on_error
79
+
80
+ workitem = msg['workitem']
81
+ workitem['fields']['__error__'] = err
82
+
83
+ immediate = if handler.is_a?(String)
84
+ !! handler.match(/^!/)
85
+ elsif handler.is_a?(Array)
86
+ !! handler.first.to_s.match(/^!/)
87
+ else
88
+ false
89
+ end
90
+
91
+ # NOTE: why not pass the handler in the msg?
92
+ # no, because of HandlerEntry (not JSON serializable)
93
+
94
+ @context.storage.put_msg(
95
+ 'fail',
96
+ 'fei' => oe_parent.h.fei,
97
+ 'workitem' => workitem,
98
+ 'immediate' => immediate)
99
+
100
+ true # yes, error is being handled.
101
+ end
102
+
103
+ protected
104
+
105
+ #
106
+ # Used by on_error when patterns are involved. Gathers much of the
107
+ # pattern logic...
108
+ #
109
+ class HandlerEntry
110
+
111
+ attr_reader :pattern, :action, :child_id
112
+
113
+ def initialize(on_error_entry)
114
+
115
+ if on_error_entry.is_a?(Hash)
116
+ on_error_entry = on_error_entry.to_a.flatten
117
+ end
118
+
119
+ @pattern, @action, @child_id = on_error_entry
120
+ @pat = Ruote.regex_or_s(@pattern) || //
121
+ end
122
+
123
+ def split(pat)
124
+
125
+ @action.split(pat)
126
+ end
127
+
128
+ def match(regex_or_err)
129
+
130
+ if regex_or_err.is_a?(Regexp)
131
+ @action.match(regex_or_err)
132
+ else
133
+ @pat.match(regex_or_err['message']) ||
134
+ @pat.match(regex_or_err['class'])
135
+ end
136
+ end
137
+
138
+ def narrow
139
+
140
+ @action.is_a?(Array) ? @action : self
141
+ end
142
+
143
+ def update_tree(tree, retries)
144
+
145
+ child = tree[2][@child_id]
146
+
147
+ if @pattern
148
+ if retries.empty?
149
+ child[1].delete(@pattern)
150
+ else
151
+ child[1][@pattern] = retries.join(', ')
152
+ end
153
+ else
154
+ key, _ = child[1].find { |k, v| v.nil? }
155
+ child[1].delete(key)
156
+ child[1][retries.join(', ')] = nil if retries.any?
157
+ end
158
+ end
159
+ end
160
+
161
+ # Given an error, returns the on_error registered for it, or nil if none.
162
+ #
163
+ def local_on_error(err)
164
+
165
+ if h.on_error.is_a?(String) or Ruote.is_tree?(h.on_error)
166
+
167
+ return h.on_error
168
+ end
169
+
170
+ if h.on_error.is_a?(Array)
171
+
172
+ # all for the 'on_error' expression
173
+ # see test/functional/eft_38_
174
+
175
+ h.on_error.each do |oe|
176
+
177
+ if (he = HandlerEntry.new(oe)).match(err)
178
+ return he.narrow
179
+ end
180
+ end
181
+ end
182
+
183
+ nil
184
+ end
185
+
186
+ # Looks up "on_error" attribute locally and in the parent expressions,
187
+ # if any.
188
+ #
189
+ def lookup_on_error(err)
190
+
191
+ if local_on_error(err)
192
+ return self
193
+ end
194
+ if par = parent
195
+ return par.lookup_on_error(err)
196
+ end
197
+
198
+ nil
199
+ end
200
+
201
+ # Called by #trigger when it encounters something like
202
+ #
203
+ # :on_error => '5m: retry, pass'
204
+ #
205
+ def schedule_retries(handler, err)
206
+
207
+ retries = handler.split(/\s*,\s*/)
208
+ after, action = retries.shift.split(/:/)
209
+
210
+ # deal with "* 3"
211
+
212
+ if m = action.match(/^ *([^ ]+) *\* *(\d+)$/)
213
+
214
+ count = m[2].to_i - 1
215
+
216
+ if count == 1
217
+ retries.unshift("#{after}: #{m[1]}")
218
+ elsif count > 1
219
+ retries.unshift("#{after}: #{m[1]} * #{count}")
220
+ end
221
+ end
222
+
223
+ # rewrite tree to remove current retry
224
+
225
+ t = Ruote.fulldup(tree)
226
+
227
+ if h.on_error.is_a?(Array)
228
+ handler.update_tree(t, retries)
229
+ else
230
+ if retries.empty?
231
+ t[1].delete('on_error')
232
+ else
233
+ t[1]['on_error'] = retries.join(', ')
234
+ end
235
+ end
236
+
237
+ update_tree(t)
238
+
239
+ # schedule current retry
240
+
241
+ after = after.strip
242
+ action = action.strip
243
+
244
+ msg = {
245
+ 'action' => 'cancel',
246
+ 'fei' => h.fei,
247
+ 'flavour' => 'retry',
248
+ 're_apply' => true }
249
+
250
+ (h.timers ||= []) <<
251
+ [ @context.storage.put_schedule('at', h.fei, after, msg), 'retry' ]
252
+
253
+ # over
254
+
255
+ persist_or_raise
256
+
257
+ rescue => e
258
+
259
+ raise Ruote::MetaError.new(__method__.to_s, e)
260
+ end
261
+
262
+ # 'on_{error|timeout|cancel|re_apply}' triggering
263
+ #
264
+ def trigger(on, workitem)
265
+
266
+ tree = tree()
267
+ err = h.applied_workitem['fields']['__error__']
268
+
269
+ handler = on == 'on_error' ? local_on_error(err) : h[on]
270
+
271
+ if on == 'on_error' && handler.to_s.match(/^!(.+)$/)
272
+ handler = $1
273
+ end
274
+
275
+ if handler.is_a?(Hash) # on_re_apply
276
+
277
+ wi = handler['workitem']
278
+ fi = handler['fields']
279
+ me = handler['merge_in_fields']
280
+
281
+ workitem = if wi == 'applied' || me
282
+ h.applied_workitem
283
+ elsif wi
284
+ wi
285
+ else
286
+ workitem
287
+ end
288
+
289
+ workitem['fields'] = fi if fi
290
+ workitem['fields'].merge!(me) if me
291
+ end
292
+
293
+ if h.trigger && t = workitem['fields']["__#{h.trigger}__"]
294
+ #
295
+ # the "second take"...
296
+
297
+ handler = t
298
+ tree = h.supplanted['original_tree']
299
+ workitem = h.supplanted['applied_workitem']
300
+ end
301
+
302
+ if on == 'on_error' && handler.respond_to?(:match) && handler.match(/:/)
303
+ return schedule_retries(handler, err)
304
+ end
305
+
306
+ new_tree = case handler
307
+ when Hash then handler['tree']
308
+ when Array then handler
309
+ when HandlerEntry then [ handler.action, {}, [] ]
310
+ else [ handler.to_s, {}, [] ]
311
+ end
312
+
313
+ handler = handler.action if handler.is_a?(HandlerEntry)
314
+ handler = handler.strip if handler.respond_to?(:strip)
315
+
316
+ if handler =~ /^can(cel|do)$/ && (on == 'on_cancel' || h.on_cancel == nil)
317
+ handler = handler == 'cancel' ? 'undo' : 'redo'
318
+ end
319
+
320
+ h.on_reply = nil if on == 'on_reply'
321
+ # preventing cascades
322
+
323
+ case handler
324
+
325
+ when 'redo', 'retry'
326
+ #
327
+ # retry with the same tree as before
328
+
329
+ new_tree = tree
330
+
331
+ when 'undo', 'pass', ''
332
+ #
333
+ # let's forget it
334
+
335
+ h.state = on == 'on_cancel' ? 'cancelled' : 'failed'
336
+
337
+ reply_to_parent(workitem); return
338
+
339
+ when 'cancel'
340
+ #
341
+ # let's trigger on the on_cancel
342
+
343
+ trigger('on_cancel', workitem); return
344
+
345
+ when 'cando'
346
+ #
347
+ # trigger on_cancel, then redo
348
+
349
+ h.on_reply = tree
350
+
351
+ trigger('on_cancel', workitem); return
352
+
353
+ when 'raise'
354
+ #
355
+ # re-raise
356
+
357
+ raise Ruote.constantize(err['class']), err['message'], err['trace']
358
+
359
+ when CommandExpression::REGEXP
360
+ #
361
+ # a command like 'jump to shark'...
362
+
363
+ hh = handler.split(' ')
364
+ command = hh.first
365
+ step = hh.last
366
+
367
+ h.state = nil
368
+ workitem['fields'][CommandMixin::F_COMMAND] = [ command, step ]
369
+
370
+ reply(workitem); return
371
+
372
+ #else
373
+ #
374
+ # if h.trigger
375
+ # #
376
+ # # do not accept participant or subprocess names
377
+ # # for "second trigger"
378
+ #
379
+ # h.state = 'failed'
380
+ # reply_to_parent(workitem)
381
+ # return
382
+ # end
383
+ #
384
+ # actually, let's not care about that, let's trust people.
385
+ end
386
+
387
+ workitem = h.applied_workitem if on == 'on_error'
388
+
389
+ #
390
+ # supplant this expression with new tree
391
+
392
+ r = try_unpersist
393
+
394
+ raise(
395
+ "failed to remove exp to supplant " +
396
+ "#{Ruote.to_storage_id(h.fei)} #{tree.first}"
397
+ ) if r.respond_to?(:keys)
398
+
399
+ if new_tree[0].match(/^!(.+)$/); new_tree[0] = $1; end
400
+ new_tree[1]['_triggered'] = on
401
+
402
+ attributes.each { |k, v|
403
+ new_tree[1][k] = v if (k.match(/^on_/) && k != on) || k == 'tag'
404
+ }
405
+ #
406
+ # let the triggered tree have the same on_ attributes as the original
407
+ # expression, so that on_cancel/on_error/on_x effects still apply
408
+ #
409
+ # 'tag' is copied as well. (so that 'left_tag' is emitted too)
410
+ #
411
+ # Should 'timeout', should other common attributes be copied as well?
412
+
413
+ @context.storage.put_msg(
414
+ 'apply',
415
+ { 'fei' => h.fei,
416
+ 'parent_id' => h.parent_id,
417
+ 'tree' => new_tree,
418
+ 'workitem' => workitem,
419
+ 'variables' => h.variables,
420
+ 'trigger' => on,
421
+ 'on_reply' => h.on_reply,
422
+ 'supplanted' => {
423
+ 'tree' => tree,
424
+ 'original_tree' => original_tree,
425
+ 'applied_workitem' => h.applied_workitem,
426
+ 'variables' => h.variables
427
+ }})
428
+ end
429
+ end
430
+ end
431
+