ruote 2.2.0 → 2.3.0

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